Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/auth/reset-pass-steps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './user-email';
export * from './verify-otp';
export * from './register-user';
60 changes: 60 additions & 0 deletions components/auth/reset-pass-steps/register-user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { AuthInput } from '@/components/ui/auth-input';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { useStepper } from '@/components/ui/stepper';
import { handlePasswordConfirmation } from '@/lib/api/auth/reset-password';
import { useEmailStore } from '@/lib/stores/email-store';
import { useActionState, useEffect } from 'react';
import { toast } from 'sonner';

const RegisterUser = () => {
const [state, formAction, isPending] = useActionState(
handlePasswordConfirmation,
null
);
const stepper = useStepper();
const { email } = useEmailStore();

useEffect(() => {
if (state?.success) {
toast.success(state.message);
stepper.nextStep();
}
}, [state, stepper]);

return stepper.hasCompletedAllSteps ? (
<div className="flex flex-col items-center w-full">
<h2 className="text-2xl font-bold">Registration Complete</h2>
<p className="text-sm text-muted-foreground">
You can now log in with your credentials :)
</p>
</div>
) : (
<form action={formAction} className="space-y-4 mx-auto">
<AuthInput
name="newPassword"
label="New Password"
type="password"
error={state?.errors?.password}
defaultValue={state?.inputs?.password}
required
/>
<AuthInput
name="confirmPassword"
label="Confirm Password"
type="password"
error={state?.errors?.confirmPassword}
defaultValue={state?.inputs?.confirmPassword}
required
/>
{state?.errors?.general && (
<p className="text-sm text-destructive">{state.errors.general}</p>
)}
<Button type="submit" disabled={isPending} className="w-full">
{isPending ? <Spinner className="size-4" /> : 'Finish Signup'}
</Button>
</form>
);
};

export { RegisterUser };
46 changes: 46 additions & 0 deletions components/auth/reset-pass-steps/user-email.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

import { AuthInput } from '@/components/ui/auth-input';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { useStepper } from '@/components/ui/stepper';
import { handleEmailStep } from '@/lib/api/auth/reset-password';
import { useEmailStore } from '@/lib/stores/email-store';
import { useActionState, useEffect } from 'react';
import { toast } from 'sonner';

const UserEmail = () => {
const stepper = useStepper();
const [state, formAction, isPending] = useActionState(handleEmailStep, null);
const { setEmail } = useEmailStore();

useEffect(() => {
if (state?.success) {
toast.success(state.message);
stepper.nextStep();
}
}, [state, stepper]);

return (
<form action={formAction} className="space-y-4 mx-auto">
<AuthInput
name="email"
label="Email"
type="text"
placeholder="Enter your email"
error={state?.errors?.email}
defaultValue={state?.inputs?.email}
required
onChange={(e) => setEmail(e.target.value)}
/>
{state?.errors?.general && (
<p className="text-sm text-destructive">{state.errors.general}</p>
)}
<Button type="submit" disabled={isPending} className="w-full">
{isPending ? <Spinner className="size-4" /> : 'Send OTP'}
</Button>
</form>
);
};

export { UserEmail };
49 changes: 49 additions & 0 deletions components/auth/reset-pass-steps/verify-otp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useActionState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from '@/components/ui/input-otp';
import { handleOtpVerification } from '@/lib/api/auth/reset-password';
import { useStepper } from '@/components/ui/stepper';
import { Spinner } from '@/components/ui/spinner';
import { toast } from 'sonner';

const VerifyOtp = () => {
const [state, formAction, isPending] = useActionState(
handleOtpVerification,
null
);
const stepper = useStepper();

useEffect(() => {
if (state?.success) {
toast.success(state.message);
stepper.nextStep();
}
}, [state, stepper]);

return (
<form action={formAction} className="space-y-4 flex flex-col items-center">
<InputOTP defaultValue={state?.inputs?.otp} maxLength={6} name="otp">
<InputOTPGroup>
{[...Array(6)].map((_, i) => (
<InputOTPSlot key={i} index={i} />
))}
</InputOTPGroup>
</InputOTP>
{state?.errors?.otp && (
<p className="text-sm text-destructive mt-2">{state.errors.otp}</p>
)}
{state?.errors?.general && (
<p className="text-sm text-destructive">{state.errors.general}</p>
)}
<Button type="submit" disabled={isPending} className="w-full">
{isPending ? <Spinner className="size-4" /> : 'Verify OTP'}
</Button>
</form>
);
};

export { VerifyOtp };
157 changes: 17 additions & 140 deletions components/auth/reset-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,147 +1,24 @@
'use client';

import { useActionState } from 'react';
import { Button } from '@/components/ui/button';
import { AuthInput } from '@/components/ui/auth-input';
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from '@/components/ui/input-otp';
import {
handleResetPasswordStep,
steps,
initialState,
} from '@/lib/api/auth/reset-password';
import React from 'react';
import { Stepper, StepperProgress, Step } from '@/components/ui/stepper';
import { UserEmail, VerifyOtp, RegisterUser } from './reset-pass-steps';

export default function ResetTab() {
const [state, formAction] = useActionState(
handleResetPasswordStep,
initialState
);
const activeStep = state.errors
? state.step
: Math.min(state.step, steps.length);

const resetSteps = () => window.location.reload();
const RESET_PASS_STEPS = [
{ step: 0, Component: UserEmail },
{ step: 1, Component: VerifyOtp },
{ step: 2, Component: RegisterUser },
];

export default function ResetTab() {
return (
<div className="flex flex-col items-center w-full">
<div className="h-16 mb-6">
<div className="flex justify-between items-center gap-12 relative">
{steps.map((step, index) => (
<div
key={step}
className="relative flex items-center justify-center"
>
<div
className={`w-10 h-10 rounded-full flex items-center justify-center border-2 font-semibold transition-all duration-300 ease-in-out transform ${
index === activeStep
? 'border-primary bg-primary text-white shadow-lg scale-110'
: index < activeStep
? 'border-primary bg-primary text-white'
: 'border-muted-foreground bg-transparent text-muted-foreground'
}`}
>
{index + 1}
</div>

{index < steps.length - 1 && (
<div
className={`absolute left-full top-1/2 w-12 h-1 transform -translate-y-1/2 transition-all duration-300 rounded-md ${
index < activeStep ? 'bg-primary' : 'bg-muted-foreground'
}`}
/>
)}
</div>
))}
</div>
</div>

{/* Fixed height container for the form content */}
<div className="min-h-[280px] max-w-xs w-full">
{activeStep === 0 && (
<form action={formAction} className="space-y-4">
<AuthInput
name="email"
label="Email"
type="text"
error={state.errors?.email?.[0]}
required
/>
{state.errors?.general && (
<p className="text-sm text-destructive">
{state.errors.general[0]}
</p>
)}
<Button type="submit" className="w-full">
Send OTP
</Button>
</form>
)}

{activeStep === 1 && (
<form
action={formAction}
className="space-y-4 flex flex-col items-center"
>
<InputOTP maxLength={6} name="otp">
<InputOTPGroup>
{[...Array(6)].map((_, i) => (
<InputOTPSlot key={i} index={i} />
))}
</InputOTPGroup>
</InputOTP>
{state.errors?.otp && (
<p className="text-sm text-destructive mt-2">
{state.errors.otp[0]}
</p>
)}
{state.errors?.general && (
<p className="text-sm text-destructive">
{state.errors.general[0]}
</p>
)}
<Button type="submit" className="w-full">
Verify OTP
</Button>
</form>
)}

{activeStep === 2 && (
<form action={formAction} className="space-y-4">
<AuthInput
name="newPassword"
label="New Password"
type="password"
error={state.errors?.newPassword?.[0]}
required
/>
<AuthInput
name="confirmPassword"
label="Confirm Password"
type="password"
error={state.errors?.confirmPassword?.[0]}
required
/>
{state.errors?.general && (
<p className="text-sm text-destructive">
{state.errors.general[0]}
</p>
)}
<Button type="submit" className="w-full">
Reset Password
</Button>
</form>
)}

{activeStep === steps.length && (
<div className="text-center space-y-4">
<h2 className="text-sm">Password reset successfully!</h2>
<Button onClick={resetSteps}>Reset</Button>
</div>
)}
</div>
</div>
<Stepper totalSteps={RESET_PASS_STEPS.length}>
<StepperProgress />
{RESET_PASS_STEPS.map(({ step, Component }) => (
<Step key={step} index={step}>
<Component />
</Step>
))}
</Stepper>
);
}
Loading