Skip to content

Commit f446a1e

Browse files
author
Lasim
committed
style(all): update email templates and frontend components for consistency
1 parent 0d0a63f commit f446a1e

File tree

7 files changed

+74
-214
lines changed

7 files changed

+74
-214
lines changed

services/backend/src/email/templates/layouts/base.pug

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,18 @@ html(lang="en")
7979
margin: 0.6666667em 0;
8080
}
8181
a {
82-
color: #2563eb;
82+
color: #0f766e;
8383
text-decoration: underline;
8484
}
85+
a:hover {
86+
color: #115e59;
87+
}
8588

8689
/* Buttons */
8790
.button {
8891
display: inline-block;
8992
padding: 14px 28px;
90-
background-color: #2563eb;
93+
background-color: #0f766e;
9194
color: #ffffff !important;
9295
text-decoration: none;
9396
border-radius: 6px;
@@ -96,14 +99,20 @@ html(lang="en")
9699
text-align: center;
97100
}
98101
.button:hover {
99-
background-color: #1d4ed8;
102+
background-color: #115e59;
100103
}
101104
.button-secondary {
102105
background-color: #6b7280;
103106
}
104107
.button-secondary:hover {
105108
background-color: #4b5563;
106109
}
110+
.button-destructive {
111+
background-color: #dc2626;
112+
}
113+
.button-destructive:hover {
114+
background-color: #b91c1c;
115+
}
107116

108117
/* Footer - now table-based */
109118

services/backend/src/email/templates/password-changed.pug

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ block content
4242
li Contact our support team if you need assistance
4343

4444
.text-center(style="margin-top: 16px;")
45-
a.button(href="#{loginUrl || 'https://app.deploystack.com/login'}" style="background-color: #dc2626; color: white;") Secure My Account
45+
a.button.button-destructive(href="#{loginUrl || 'https://app.deploystack.com/login'}") Secure My Account
4646

4747
p
4848
| For your security, we recommend:

services/backend/src/routes/auth/forgotPassword.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default async function forgotPasswordRoute(fastify: FastifyInstance) {
5757
fastify.log.info(`Password reset requested for email: ${email}`);
5858

5959
// Send reset email (always returns success for security)
60-
const result = await PasswordResetService.sendResetEmail(email);
60+
const result = await PasswordResetService.sendResetEmail(email, fastify.log);
6161

6262
if (!result.success && result.error) {
6363
// Only log actual errors, not security responses

services/frontend/src/services/userService.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,22 @@ export class UserService {
158158
return await response.json();
159159
}
160160

161-
throw new Error(`Login failed with status: ${response.status}`);
161+
// Parse error response to get the actual error message
162+
let errorMessage = `Login failed with status: ${response.status}`;
163+
try {
164+
const errorData = await response.json();
165+
if (errorData && errorData.error) {
166+
errorMessage = errorData.error;
167+
}
168+
} catch (parseError) {
169+
// If we can't parse the response, use the default message
170+
console.warn('Could not parse error response:', parseError);
171+
}
172+
173+
// Create an error object with status property for proper handling
174+
const error = new Error(errorMessage) as Error & { status: number };
175+
error.status = response.status;
176+
throw error;
162177
} catch (error) {
163178
console.error('Login error:', error);
164179
throw error;

services/frontend/src/views/ForgotPassword.vue

Lines changed: 16 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,16 @@ import { toTypedSchema } from '@vee-validate/zod'
44
import * as z from 'zod'
55
import { ref } from 'vue'
66
import { useRouter } from 'vue-router'
7-
import { Mail, AlertTriangle, CheckCircle } from 'lucide-vue-next'
7+
import { Mail } from 'lucide-vue-next'
88
import { useI18n } from 'vue-i18n'
9+
import { toast } from 'vue-sonner'
910
import { UserService } from '@/services/userService'
1011
1112
import {
1213
Card,
1314
CardContent,
1415
} from '@/components/ui/card'
1516
16-
import {
17-
Alert,
18-
AlertDescription,
19-
AlertTitle,
20-
} from '@/components/ui/alert'
21-
2217
import { Button } from '@/components/ui/button'
2318
import {
2419
FormControl,
@@ -31,8 +26,6 @@ import { Input } from '@/components/ui/input'
3126
3227
const router = useRouter()
3328
const isLoading = ref(false)
34-
const errorMessage = ref('')
35-
const showSuccess = ref(false)
3629
const { t } = useI18n()
3730
3831
// Define validation schema using Zod
@@ -52,79 +45,27 @@ const form = useForm({
5245
},
5346
})
5447
55-
// Clear error when user starts typing
56-
const clearError = () => {
57-
errorMessage.value = ''
58-
}
59-
60-
interface ForgotPasswordError {
61-
name?: string;
62-
message?: string;
63-
status?: number;
64-
}
6548
66-
interface PotentialError {
67-
name?: unknown;
68-
message?: unknown;
69-
status?: unknown;
70-
}
71-
72-
// Handle different types of errors
73-
const handleError = (error: ForgotPasswordError) => {
74-
if (error.name === 'TypeError' && error.message && error.message.includes('fetch')) {
75-
// Network error - backend is down
76-
errorMessage.value = t('forgotPassword.errors.networkError')
77-
} else if (error.message === 'SERVICE_UNAVAILABLE') {
78-
// Service unavailable
79-
errorMessage.value = t('forgotPassword.errors.serviceUnavailable')
80-
} else if (error.status && error.status >= 500) {
81-
// Server error
82-
errorMessage.value = t('forgotPassword.errors.serverError')
83-
} else if (error.name === 'AbortError') {
84-
// Request timeout
85-
errorMessage.value = t('forgotPassword.errors.networkError')
86-
} else {
87-
// Unknown error
88-
errorMessage.value = t('forgotPassword.errors.unknownError')
89-
}
90-
}
9149
9250
const onSubmit = form.handleSubmit(async (values) => {
9351
isLoading.value = true
94-
errorMessage.value = ''
9552
9653
try {
9754
await UserService.requestPasswordReset(values.email)
9855
99-
// Always show success message for security (don't reveal if email exists)
100-
showSuccess.value = true
101-
102-
} catch (e) {
103-
console.error('Password reset request error:', e);
104-
const errorToHandle: ForgotPasswordError = { message: t('forgotPassword.errors.unknownError') };
105-
const potentialError = e as PotentialError;
106-
107-
if (typeof potentialError.name === 'string') {
108-
errorToHandle.name = potentialError.name;
109-
}
110-
if (typeof potentialError.message === 'string') {
111-
errorToHandle.message = potentialError.message;
112-
}
113-
if (typeof potentialError.status === 'number') {
114-
errorToHandle.status = potentialError.status;
115-
}
116-
117-
// If it's a standard Error instance, prefer its properties
118-
if (e instanceof Error) {
119-
errorToHandle.name = e.name;
120-
errorToHandle.message = e.message;
121-
}
122-
123-
// Ensure message is always set if not already by previous checks
124-
if (!errorToHandle.message) {
125-
errorToHandle.message = t('forgotPassword.errors.unknownError');
126-
}
127-
handleError(errorToHandle);
56+
// Show success toast and redirect to login
57+
toast.success('Check your mailbox to reset password')
58+
59+
// Redirect to login page
60+
router.push('/login')
61+
62+
} catch (error) {
63+
console.error('Password reset request error:', error)
64+
65+
// Show error toast
66+
toast.error(t('forgotPassword.errors.title'), {
67+
description: t('forgotPassword.errors.unknownError')
68+
})
12869
} finally {
12970
isLoading.value = false
13071
}
@@ -152,28 +93,7 @@ const navigateToLogin = () => {
15293
</div>
15394

15495
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
155-
<!-- Success Alert -->
156-
<Alert v-if="showSuccess" class="mb-6">
157-
<CheckCircle class="h-4 w-4" />
158-
<AlertTitle>{{ $t('forgotPassword.success.title') }}</AlertTitle>
159-
<AlertDescription>
160-
<div class="space-y-2">
161-
<p>{{ $t('forgotPassword.success.message') }}</p>
162-
<p class="text-sm">{{ $t('forgotPassword.success.instruction') }}</p>
163-
</div>
164-
</AlertDescription>
165-
</Alert>
166-
167-
<!-- Error Alert -->
168-
<Alert v-if="errorMessage" variant="destructive" class="mb-6">
169-
<AlertTriangle class="h-4 w-4" />
170-
<AlertTitle>{{ $t('forgotPassword.errors.title') }}</AlertTitle>
171-
<AlertDescription>
172-
{{ errorMessage }}
173-
</AlertDescription>
174-
</Alert>
175-
176-
<Card v-if="!showSuccess">
96+
<Card>
17797
<CardContent class="pt-6">
17898
<form @submit="onSubmit" class="space-y-6">
17999
<FormField v-slot="{ componentField }" name="email">
@@ -188,7 +108,6 @@ const navigateToLogin = () => {
188108
v-bind="componentField"
189109
class="pl-10"
190110
autocomplete="email"
191-
@input="clearError"
192111
/>
193112
</div>
194113
</FormControl>

services/frontend/src/views/Login.vue

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,14 @@ const handleError = (error: LoginError) => {
8484
// Network error - backend is down
8585
errorMessage.value = t('login.errors.networkError')
8686
} else if (error.status && (error.status === 400 || error.status === 401)) {
87-
// Bad Request or Unauthorized - invalid credentials
88-
errorMessage.value = t('login.errors.invalidCredentials')
87+
// Bad Request or Unauthorized - use backend error message if available, fallback to translation
88+
if (error.message && error.message !== `Login failed with status: ${error.status}`) {
89+
// Use the actual backend error message
90+
errorMessage.value = error.message
91+
} else {
92+
// Fallback to translation
93+
errorMessage.value = t('login.errors.invalidCredentials')
94+
}
8995
} else if (error.status && error.status >= 500) {
9096
// Server error
9197
errorMessage.value = t('login.errors.serverError')
@@ -126,6 +132,10 @@ const onSubmit = form.handleSubmit(async (values) => {
126132
if (e instanceof Error) {
127133
errorToHandle.name = e.name;
128134
errorToHandle.message = e.message;
135+
// Check if the error has a status property (from our updated UserService)
136+
if ('status' in e && typeof (e as any).status === 'number') {
137+
errorToHandle.status = (e as any).status;
138+
}
129139
}
130140
131141
// Ensure message is always set if not already by previous checks

0 commit comments

Comments
 (0)