diff --git a/components/auth/reset-pass-steps/index.ts b/components/auth/reset-pass-steps/index.ts
new file mode 100644
index 0000000..96cd42a
--- /dev/null
+++ b/components/auth/reset-pass-steps/index.ts
@@ -0,0 +1,3 @@
+export * from './user-email';
+export * from './verify-otp';
+export * from './register-user';
diff --git a/components/auth/reset-pass-steps/register-user.tsx b/components/auth/reset-pass-steps/register-user.tsx
new file mode 100644
index 0000000..d4b0336
--- /dev/null
+++ b/components/auth/reset-pass-steps/register-user.tsx
@@ -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 ? (
+
+
Registration Complete
+
+ You can now log in with your credentials :)
+
+
+ ) : (
+
+ );
+};
+
+export { RegisterUser };
diff --git a/components/auth/reset-pass-steps/user-email.tsx b/components/auth/reset-pass-steps/user-email.tsx
new file mode 100644
index 0000000..b13e02a
--- /dev/null
+++ b/components/auth/reset-pass-steps/user-email.tsx
@@ -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 (
+
+ );
+};
+
+export { UserEmail };
diff --git a/components/auth/reset-pass-steps/verify-otp.tsx b/components/auth/reset-pass-steps/verify-otp.tsx
new file mode 100644
index 0000000..42864b9
--- /dev/null
+++ b/components/auth/reset-pass-steps/verify-otp.tsx
@@ -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 (
+
+ );
+};
+
+export { VerifyOtp };
diff --git a/components/auth/reset-tab.tsx b/components/auth/reset-tab.tsx
index 551c320..e0f3cd2 100644
--- a/components/auth/reset-tab.tsx
+++ b/components/auth/reset-tab.tsx
@@ -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 (
-
-
-
- {steps.map((step, index) => (
-
-
- {index + 1}
-
-
- {index < steps.length - 1 && (
-
- )}
-
- ))}
-
-
-
- {/* Fixed height container for the form content */}
-
- {activeStep === 0 && (
-
- )}
-
- {activeStep === 1 && (
-
- )}
-
- {activeStep === 2 && (
-
- )}
-
- {activeStep === steps.length && (
-
-
Password reset successfully!
-
-
- )}
-
-
+
+
+ {RESET_PASS_STEPS.map(({ step, Component }) => (
+
+
+
+ ))}
+
);
}
diff --git a/lib/api/auth/reset-password/reset-password-action.ts b/lib/api/auth/reset-password/reset-password-action.ts
index e9fab07..f8eced5 100644
--- a/lib/api/auth/reset-password/reset-password-action.ts
+++ b/lib/api/auth/reset-password/reset-password-action.ts
@@ -1,61 +1,169 @@
-import { z } from 'zod';
-import { EmailSchema, OtpSchema, NewPasswordSchema } from '@/lib/schemas/auth';
-export const steps = ['email', 'otp', 'newPassword'] as const;
-export type StepKey = (typeof steps)[number];
+'use server';
-export type FormState = {
- step: number;
- errors?: Record;
-};
+import {
+ EmailSchema,
+ NewPasswordSchema,
+ OtpSchema,
+ RegisterUserSchema,
+} from '@/lib/schemas/auth';
+import { z } from 'zod';
+import { createUnauthenticatedAxios } from '@/lib/api/axios';
+import { AxiosError } from 'axios';
-export const initialState: FormState = { step: 0 };
+interface ActionResponse {
+ success: boolean;
+ errors?: {
+ email?: string;
+ otp?: string;
+ fullName?: string;
+ username?: string;
+ password?: string;
+ confirmPassword?: string;
+ general?: string;
+ };
+ inputs?: {
+ email?: string;
+ otp?: string;
+ fullName?: string;
+ username?: string;
+ password?: string;
+ confirmPassword?: string;
+ };
+ message: string;
+}
-const EXPECTED_OTP = '123456';
+const axiosInstance = createUnauthenticatedAxios();
+axiosInstance.defaults.headers['Content-Type'] = 'multipart/form-data';
-// TODO: Implement the logic just like sign-up step
-export async function handleResetPasswordStep(
- prevState: FormState,
+// Server Actions for each step //
+export async function handleEmailStep(
+ prevState: unknown,
formData: FormData
-): Promise {
- const step = prevState.step;
- const formObj = Object.fromEntries(formData.entries());
-
+): Promise {
try {
- if (step === 0) {
- EmailSchema.parse(formObj);
- console.log('Sending OTP to:', formObj.email);
- return { step: step + 1 };
+ EmailSchema.parse(convertFormDataToRecord(formData));
+ console.log('Sending OTP to:', formData.get('email'));
+ // await axiosInstance.post('/auth/send-otp', {
+ // email: formData.get('email'),
+ // });
+ return createSuccessResponse('OTP sent successfully');
+ } catch (err) {
+ if (err instanceof z.ZodError) {
+ return handleZodError(err, formData);
+ } else if (err instanceof AxiosError && err.response) {
+ const { data } = err.response;
+ console.log(data);
+ return {
+ success: false,
+ message: 'Failed to send OTP',
+ errors: {
+ general: data.message || 'An error occurred while sending OTP',
+ },
+ };
}
+ return createErrorResponse('An unexpected error occurred');
+ }
+}
- if (step === 1) {
- OtpSchema.parse(formObj);
- const otp = formObj.otp;
- if (otp !== EXPECTED_OTP) {
- return {
- step,
- errors: { otp: ['Incorrect OTP entered'] },
- };
- }
- return { step: step + 1 };
+export async function handleOtpVerification(
+ prevState: unknown,
+ formData: FormData
+): Promise {
+ try {
+ OtpSchema.parse(convertFormDataToRecord(formData));
+ const otp = formData.get('otp');
+ // await axiosInstance.post('/auth/verify-otp', {
+ // otp,
+ // });
+ return createSuccessResponse('OTP verified successfully');
+ } catch (err) {
+ if (err instanceof z.ZodError) {
+ return handleZodError(err, formData);
+ } else if (err instanceof AxiosError && err.response) {
+ const { data } = err.response;
+ return {
+ success: false,
+ message: 'Failed to verify OTP',
+ errors: {
+ general:
+ data.message || 'Failed to verify OTP, please try again later.',
+ },
+ };
}
+ return createErrorResponse('An unexpected error occurred');
+ }
+}
- if (step === 2) {
- NewPasswordSchema.parse(formObj);
- console.log('Resetting password');
- return { step: step + 1 };
- }
- } catch (err: any) {
+export async function handlePasswordConfirmation(
+ prevState: unknown,
+ formData: FormData
+): Promise {
+ try {
+ NewPasswordSchema.parse(convertFormDataToRecord(formData));
+ await axiosInstance.post('/auth/register', {
+ password: formData.get('newPassword'),
+ 'ssh-key': '',
+ });
+ return createSuccessResponse('User registered successfully');
+ } catch (err) {
if (err instanceof z.ZodError) {
- const errors: Record = {};
- for (const issue of err.errors) {
- const key = issue.path[0] as string;
- if (!errors[key]) errors[key] = [];
- errors[key].push(issue.message);
- }
- return { step, errors };
+ return handleZodError(err, formData);
+ } else if (err instanceof AxiosError && err.response) {
+ const { data } = err.response;
+ console.log(data);
+ return {
+ success: false,
+ message: 'Failed to create user',
+ errors: {
+ general: data.message,
+ },
+ };
+ }
+ return createErrorResponse('An unexpected error occurred');
+ }
+}
+
+// Utils //
+// TODO: Make a common utils for this
+function convertFormDataToRecord(formData: FormData): Record {
+ const record: Record = {};
+ for (const [key, value] of formData.entries()) {
+ if (typeof value === 'string') {
+ record[key] = value;
}
- return { step, errors: { general: ['Unexpected error'] } };
}
+ return record;
+}
+
+function handleZodError(err: z.ZodError, formData: FormData): ActionResponse {
+ const errors: Record = {};
+ for (const issue of err.errors) {
+ const key = issue.path[0] as string;
+ errors[key] = issue.message;
+ }
+ console.log(errors);
+ return {
+ success: false,
+ errors,
+ inputs: convertFormDataToRecord(formData),
+ message: 'Validation failed',
+ };
+}
+
+function createErrorResponse(
+ message: string,
+ errors?: Record
+): ActionResponse {
+ return {
+ success: false,
+ errors: errors || { general: 'Unexpected error' },
+ message,
+ };
+}
- return { step };
+function createSuccessResponse(message: string): ActionResponse {
+ return {
+ success: true,
+ message,
+ };
}
diff --git a/lib/api/auth/sign-up/sign-up-action.ts b/lib/api/auth/sign-up/sign-up-action.ts
index 5aaab86..f39db46 100644
--- a/lib/api/auth/sign-up/sign-up-action.ts
+++ b/lib/api/auth/sign-up/sign-up-action.ts
@@ -38,9 +38,9 @@ export async function handleEmailStep(
try {
EmailSchema.parse(convertFormDataToRecord(formData));
console.log('Sending OTP to:', formData.get('email'));
- await axiosInstance.post('/auth/send-otp', {
- email: formData.get('email'),
- });
+ // await axiosInstance.post('/auth/send-otp', {
+ // email: formData.get('email'),
+ // });
return createSuccessResponse('OTP sent successfully');
} catch (err) {
if (err instanceof z.ZodError) {
@@ -67,9 +67,9 @@ export async function handleOtpVerification(
try {
OtpSchema.parse(convertFormDataToRecord(formData));
const otp = formData.get('otp');
- await axiosInstance.post('/auth/verify-otp', {
- otp,
- });
+ // await axiosInstance.post('/auth/verify-otp', {
+ // otp,
+ // });
return createSuccessResponse('OTP verified successfully');
} catch (err) {
if (err instanceof z.ZodError) {