Skip to content

Commit 3b1ecbc

Browse files
committed
feat(auth): Implement OTP verification and refactor auth UI
Major improvements to authentication flow and user experience: - OTP Verification: Implemented complete email verification flow using OTP. - Schema Updates: Added isEmailVerified field to User schema and extended UserPreferences with alertFrequency, platforms, and notificationChannels. - Auth UI Refactor: Completely rebuilt AuthUI using react-hook-form and zod for robust validation. - UX Enhancements: - Added sonner for toast notifications. - Implemented Typewriter animation for dynamic quotes. - Improved form states (loading, error handling) and responsive design. - Cleanup: Removed legacy verify-email-form component and optimized imports.
1 parent 82d8e3c commit 3b1ecbc

File tree

84 files changed

+2941
-4497
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+2941
-4497
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { VerifyEmailForm } from "@/components/core/auth/verify-email-form";
2+
import { Suspense } from "react";
3+
4+
export default function VerifyEmailPage() {
5+
return (
6+
<div className="flex min-h-screen items-center justify-center p-4">
7+
<div className="w-full max-w-md">
8+
<Suspense fallback={<div>Loading...</div>}>
9+
<VerifyEmailForm />
10+
</Suspense>
11+
</div>
12+
</div>
13+
);
14+
}

client/web/components/core/auth/auth-ui.tsx

Lines changed: 89 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const labelVariants = cva(
9494
const Label = React.forwardRef<
9595
React.ElementRef<typeof LabelPrimitive.Root>,
9696
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
97-
VariantProps<typeof labelVariants>
97+
VariantProps<typeof labelVariants>
9898
>(({ className, ...props }, ref) => (
9999
<LabelPrimitive.Root
100100
ref={ref}
@@ -158,7 +158,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
158158
Input.displayName = "Input";
159159

160160
export interface PasswordInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
161-
label?: string;
161+
label?: string;
162162
}
163163
const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
164164
({ className, label, ...props }, ref) => {
@@ -197,23 +197,23 @@ function SignInForm() {
197197
try {
198198
setError(null);
199199
const response = await AuthService.signin(data);
200-
200+
201201
setUser(response.user);
202202
toast.success("Welcome back!", {
203203
description: `Signed in as ${response.user.email}`,
204204
});
205-
205+
206206
router.push("/dashboard");
207207
} catch (error) {
208208
console.error("Sign in error:", error);
209-
209+
210210
let errorMessage = "Failed to sign in. Please try again.";
211211
if (error instanceof AxiosError) {
212212
errorMessage = error.response?.data?.message || errorMessage;
213213
} else if (error instanceof Error) {
214214
errorMessage = error.message;
215215
}
216-
216+
217217
setError(errorMessage);
218218
toast.error("Sign in failed", {
219219
description: errorMessage,
@@ -305,23 +305,24 @@ function SignUpForm() {
305305
try {
306306
setError(null);
307307
const response = await AuthService.signup(data);
308-
309-
setUser(response.user);
308+
309+
// Don't auto-login - require email verification first
310310
toast.success("Account created successfully!", {
311-
description: `Welcome, ${response.user.name}!`,
311+
description: "Please check your email to verify your account.",
312312
});
313-
314-
router.push("/dashboard");
313+
314+
// Redirect to email verification page
315+
router.push(`/auth/verify-email?email=${encodeURIComponent(data.email)}`);
315316
} catch (error) {
316317
console.error("Sign up error:", error);
317-
318+
318319
let errorMessage = "Failed to create account. Please try again.";
319320
if (error instanceof AxiosError) {
320321
errorMessage = error.response?.data?.message || errorMessage;
321322
} else if (error instanceof Error) {
322323
errorMessage = error.message;
323324
}
324-
325+
325326
setError(errorMessage);
326327
toast.error("Sign up failed", {
327328
description: errorMessage,
@@ -420,91 +421,91 @@ function SignUpForm() {
420421
}
421422

422423
function AuthFormContainer({ isSignIn, onToggle }: { isSignIn: boolean; onToggle: () => void; }) {
423-
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
424+
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
424425

425-
const handleGoogleSignIn = () => {
426-
setIsGoogleLoading(true);
427-
AuthService.initiateGoogleOAuth();
428-
};
426+
const handleGoogleSignIn = () => {
427+
setIsGoogleLoading(true);
428+
AuthService.initiateGoogleOAuth();
429+
};
429430

430-
return (
431-
<div className="mx-auto grid w-[350px] gap-2">
432-
{isSignIn ? <SignInForm /> : <SignUpForm />}
433-
<div className="text-center text-sm">
434-
{isSignIn ? "Don't have an account?" : "Already have an account?"}{" "}
435-
<Button variant="link" className="pl-1 text-foreground" onClick={onToggle}>
436-
{isSignIn ? "Sign up" : "Sign in"}
437-
</Button>
438-
</div>
439-
<div className="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border">
440-
<span className="relative z-10 bg-background px-2 text-muted-foreground">Or continue with</span>
441-
</div>
442-
<Button
443-
variant="outline"
444-
type="button"
445-
onClick={handleGoogleSignIn}
446-
disabled={isGoogleLoading}
447-
>
448-
{isGoogleLoading ? (
449-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
450-
) : (
451-
<Image src="https://www.svgrepo.com/show/475656/google-color.svg" alt="Google icon" width={16} height={16} className="mr-2" />
452-
)}
453-
{isGoogleLoading ? "Redirecting..." : "Continue with Google"}
454-
</Button>
455-
</div>
456-
)
431+
return (
432+
<div className="mx-auto grid w-[350px] gap-2">
433+
{isSignIn ? <SignInForm /> : <SignUpForm />}
434+
<div className="text-center text-sm">
435+
{isSignIn ? "Don't have an account?" : "Already have an account?"}{" "}
436+
<Button variant="link" className="pl-1 text-foreground" onClick={onToggle}>
437+
{isSignIn ? "Sign up" : "Sign in"}
438+
</Button>
439+
</div>
440+
<div className="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border">
441+
<span className="relative z-10 bg-background px-2 text-muted-foreground">Or continue with</span>
442+
</div>
443+
<Button
444+
variant="outline"
445+
type="button"
446+
onClick={handleGoogleSignIn}
447+
disabled={isGoogleLoading}
448+
>
449+
{isGoogleLoading ? (
450+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
451+
) : (
452+
<Image src="https://www.svgrepo.com/show/475656/google-color.svg" alt="Google icon" width={16} height={16} className="mr-2" />
453+
)}
454+
{isGoogleLoading ? "Redirecting..." : "Continue with Google"}
455+
</Button>
456+
</div>
457+
)
457458
}
458459

459460
interface AuthContentProps {
460-
image?: {
461-
src: string;
462-
alt: string;
463-
};
464-
quote?: {
465-
text: string;
466-
author: string;
467-
}
461+
image?: {
462+
src: string;
463+
alt: string;
464+
};
465+
quote?: {
466+
text: string;
467+
author: string;
468+
}
468469
}
469470

470471
interface AuthUIProps {
471-
signInContent?: AuthContentProps;
472-
signUpContent?: AuthContentProps;
472+
signInContent?: AuthContentProps;
473+
signUpContent?: AuthContentProps;
473474
}
474475

475476
const defaultSignInContent = {
476-
image: {
477-
src: "https://i.ibb.co/XrkdGrrv/original-ccdd6d6195fff2386a31b684b7abdd2e-removebg-preview.png",
478-
alt: "A beautiful interior design for sign-in"
479-
},
480-
quote: {
481-
text: "Welcome Back! The journey continues.",
482-
author: "EaseMize UI"
483-
}
477+
image: {
478+
src: "https://i.ibb.co/XrkdGrrv/original-ccdd6d6195fff2386a31b684b7abdd2e-removebg-preview.png",
479+
alt: "A beautiful interior design for sign-in"
480+
},
481+
quote: {
482+
text: "Welcome Back! The journey continues.",
483+
author: "EaseMize UI"
484+
}
484485
};
485486

486487
const defaultSignUpContent = {
487-
image: {
488-
src: "https://i.ibb.co/HTZ6DPsS/original-33b8479c324a5448d6145b3cad7c51e7-removebg-preview.png",
489-
alt: "A vibrant, modern space for new beginnings"
490-
},
491-
quote: {
492-
text: "Create an account. A new chapter awaits.",
493-
author: "EaseMize UI"
494-
}
488+
image: {
489+
src: "https://i.ibb.co/HTZ6DPsS/original-33b8479c324a5448d6145b3cad7c51e7-removebg-preview.png",
490+
alt: "A vibrant, modern space for new beginnings"
491+
},
492+
quote: {
493+
text: "Create an account. A new chapter awaits.",
494+
author: "EaseMize UI"
495+
}
495496
};
496497

497498
export function AuthUI({ signInContent = {}, signUpContent = {} }: AuthUIProps) {
498499
const [isSignIn, setIsSignIn] = useState(true);
499500
const toggleForm = () => setIsSignIn((prev) => !prev);
500501

501502
const finalSignInContent = {
502-
image: { ...defaultSignInContent.image, ...signInContent.image },
503-
quote: { ...defaultSignInContent.quote, ...signInContent.quote },
503+
image: { ...defaultSignInContent.image, ...signInContent.image },
504+
quote: { ...defaultSignInContent.quote, ...signInContent.quote },
504505
};
505506
const finalSignUpContent = {
506-
image: { ...defaultSignUpContent.image, ...signUpContent.image },
507-
quote: { ...defaultSignUpContent.quote, ...signUpContent.quote },
507+
image: { ...defaultSignUpContent.image, ...signUpContent.image },
508+
quote: { ...defaultSignUpContent.quote, ...signUpContent.quote },
508509
};
509510

510511
const currentContent = isSignIn ? finalSignInContent : finalSignUpContent;
@@ -528,20 +529,20 @@ export function AuthUI({ signInContent = {}, signUpContent = {} }: AuthUIProps)
528529
>
529530

530531
<div className="absolute inset-x-0 bottom-0 h-[100px] bg-linear-to-t from-background to-transparent" />
531-
532+
532533
<div className="relative z-10 flex h-full flex-col items-center justify-end p-2 pb-6">
533-
<blockquote className="space-y-2 text-center text-foreground">
534-
<p className="text-lg font-medium">
535-
&ldquo;<Typewriter
536-
key={currentContent.quote.text}
537-
text={currentContent.quote.text}
538-
speed={60}
539-
/>&rdquo;
540-
</p>
541-
<cite className="block text-sm font-light text-muted-foreground not-italic">
542-
{currentContent.quote.author}
543-
</cite>
544-
</blockquote>
534+
<blockquote className="space-y-2 text-center text-foreground">
535+
<p className="text-lg font-medium">
536+
&ldquo;<Typewriter
537+
key={currentContent.quote.text}
538+
text={currentContent.quote.text}
539+
speed={60}
540+
/>&rdquo;
541+
</p>
542+
<cite className="block text-sm font-light text-muted-foreground not-italic">
543+
{currentContent.quote.author}
544+
</cite>
545+
</blockquote>
545546
</div>
546547
</div>
547548
</div>

client/web/components/core/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { ForgotPasswordForm, ForgotPasswordUI } from "./forgot-password-form";
44
export { AuthUI } from "./auth-ui";
55
export { ProtectedRoute } from "./protected-route";
66
export { AuthInitializer } from "./auth-initializer";
7+
export { VerifyEmailForm } from "./verify-email-form";

0 commit comments

Comments
 (0)