fix: verification response handling 🐛
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 12m34s

This commit is contained in:
2025-09-15 22:00:57 +05:00
parent 17aa65a686
commit c67b8ade10
2 changed files with 351 additions and 366 deletions

View File

@@ -1,227 +1,229 @@
"use server"; "use server";
import { signUpFormSchema } from "@/lib/schemas";
import {
backendRegister,
checkIdOrPhone,
checkTempIdOrPhone,
} from "@/queries/authentication";
import { handleApiResponse, tryCatch } from "@/utils/tryCatch";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { z } from "zod"; import { z } from "zod";
import { signUpFormSchema } from "@/lib/schemas";
import {
backendRegister,
checkIdOrPhone,
checkTempIdOrPhone,
} from "@/queries/authentication";
import { handleApiResponse, tryCatch } from "@/utils/tryCatch";
const formSchema = z.object({ const formSchema = z.object({
phoneNumber: z phoneNumber: z
.string() .string()
.regex(/^[7|9][0-9]{2}-[0-9]{4}$/, "Please enter a valid phone number"), .regex(/^[7|9][0-9]{2}-[0-9]{4}$/, "Please enter a valid phone number"),
}); });
export type FilterUserResponse = { export type FilterUserResponse = {
ok: boolean; ok: boolean;
verified: boolean; verified: boolean;
}; };
export type FilterTempUserResponse = { export type FilterTempUserResponse = {
ok: boolean; ok: boolean;
otp_verified: boolean; otp_verified: boolean;
t_verified: boolean;
}; };
export async function signin(previousState: ActionState, formData: FormData) { export async function signin(_previousState: ActionState, formData: FormData) {
const phoneNumber = formData.get("phoneNumber") as string; const phoneNumber = formData.get("phoneNumber") as string;
const result = formSchema.safeParse({ phoneNumber }); const result = formSchema.safeParse({ phoneNumber });
console.log(phoneNumber); console.log(phoneNumber);
if (!result.success) { if (!result.success) {
return { return {
message: result.error.errors[0].message, // Get the error message from Zod message: result.error.errors[0].message, // Get the error message from Zod
status: "error", status: "error",
}; };
} }
if (!phoneNumber) { if (!phoneNumber) {
return { return {
message: "Please enter a phone number", message: "Please enter a phone number",
status: "error", status: "error",
}; };
} }
const FORMATTED_MOBILE_NUMBER: string = `${phoneNumber.split("-").join("")}`; const FORMATTED_MOBILE_NUMBER: string = `${phoneNumber.split("-").join("")}`;
console.log({ FORMATTED_MOBILE_NUMBER }); console.log({ FORMATTED_MOBILE_NUMBER });
const user = await fetch( const user = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/users/filter/?mobile=${FORMATTED_MOBILE_NUMBER}`, `${process.env.SARLINK_API_BASE_URL}/api/auth/users/filter/?mobile=${FORMATTED_MOBILE_NUMBER}`,
{ {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}, },
); );
const userData = (await user.json()) as FilterUserResponse; const userData = (await user.json()) as FilterUserResponse;
console.log({ userData }); console.log({ userData });
if (!userData.ok) { if (!userData.ok) {
redirect(`/auth/signup?phone_number=${phoneNumber}`); redirect(`/auth/signup?phone_number=${phoneNumber}`);
} }
if (!userData.verified) { if (!userData.verified) {
return { return {
message: message:
"Your account is on pending verification. Please wait for a response from admin or contact shihaam.", "Your account is on pending verification. Please wait for a response from admin or contact shihaam.",
status: "error", status: "error",
}; };
} }
const sendOTPResponse = await fetch( const sendOTPResponse = await fetch(
`${process.env.SARLINK_API_BASE_URL}/auth/mobile/`, `${process.env.SARLINK_API_BASE_URL}/auth/mobile/`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
mobile: FORMATTED_MOBILE_NUMBER, mobile: FORMATTED_MOBILE_NUMBER,
}), }),
}, },
); );
const otpResponse = await sendOTPResponse.json(); const otpResponse = await sendOTPResponse.json();
console.log("otpResponse", otpResponse); console.log("otpResponse", otpResponse);
redirect(`/auth/verify-otp?phone_number=${FORMATTED_MOBILE_NUMBER}`); redirect(`/auth/verify-otp?phone_number=${FORMATTED_MOBILE_NUMBER}`);
} }
export type ActionState = { export type ActionState = {
status?: string; status?: string;
payload?: FormData; payload?: FormData;
errors?: z.typeToFlattenedError< errors?: z.typeToFlattenedError<
{ {
id_card: string; id_card: string;
phone_number: string; phone_number: string;
name: string; name: string;
atoll_id: string; atoll_id: string;
island_id: string; island_id: string;
address: string; address: string;
dob: Date; dob: Date;
terms: string; terms: string;
policy: string; policy: string;
accNo: string; accNo: string;
}, },
string string
>; >;
db_error?: string; db_error?: string;
error?: string; error?: string;
}; };
export async function signup(_actionState: ActionState, formData: FormData) { export async function signup(_actionState: ActionState, formData: FormData) {
const data = Object.fromEntries(formData.entries()); const data = Object.fromEntries(formData.entries());
const parsedData = signUpFormSchema.safeParse(data); const parsedData = signUpFormSchema.safeParse(data);
// get phone number from /signup?phone_number=999-1231 // get phone number from /signup?phone_number=999-1231
console.log("DATA ON SERVER SIDE", data); console.log("DATA ON SERVER SIDE", data);
if (!parsedData.success) { if (!parsedData.success) {
return { return {
message: "Invalid form data", message: "Invalid form data",
payload: formData, payload: formData,
errors: parsedData.error.flatten(), errors: parsedData.error.flatten(),
}; };
} }
const age = const age =
new Date().getFullYear() - new Date(parsedData.data.dob).getFullYear(); new Date().getFullYear() - new Date(parsedData.data.dob).getFullYear();
if (age < 18) { if (age < 18) {
return { return {
message: "You must be at least 18 years old to register.", message: "You must be at least 18 years old to register.",
payload: formData, payload: formData,
db_error: "dob", db_error: "dob",
}; };
} }
const idCardExists = await checkIdOrPhone({ const idCardExists = await checkIdOrPhone({
id_card: parsedData.data.id_card, id_card: parsedData.data.id_card,
}); });
if (idCardExists.ok) { if (idCardExists.ok) {
return { return {
message: "ID card already exists.", message: "ID card already exists.",
payload: formData, payload: formData,
db_error: "id_card", db_error: "id_card",
}; };
} }
const phoneNumberExists = await checkIdOrPhone({ const phoneNumberExists = await checkIdOrPhone({
phone_number: parsedData.data.phone_number, phone_number: parsedData.data.phone_number,
}); });
const tempPhoneNumberExists = await checkTempIdOrPhone({ const tempPhoneNumberExists = await checkTempIdOrPhone({
phone_number: parsedData.data.phone_number, phone_number: parsedData.data.phone_number,
}); });
if (phoneNumberExists.ok || tempPhoneNumberExists.ok) { if (phoneNumberExists.ok || tempPhoneNumberExists.ok) {
return { return {
message: "Phone number already exists.", message: "Phone number already exists.",
payload: formData, payload: formData,
db_error: "phone_number", db_error: "phone_number",
}; };
} }
const [signupError, signupResponse] = await tryCatch( const [signupError, signupResponse] = await tryCatch(
backendRegister({ backendRegister({
payload: { payload: {
firstname: parsedData.data.name.split(" ")[0], firstname: parsedData.data.name.split(" ")[0],
lastname: parsedData.data.name.split(" ").slice(1).join(" "), lastname: parsedData.data.name.split(" ").slice(1).join(" "),
username: parsedData.data.phone_number, username: parsedData.data.phone_number,
address: parsedData.data.address, address: parsedData.data.address,
id_card: parsedData.data.id_card, id_card: parsedData.data.id_card,
dob: new Date(parsedData.data.dob).toISOString().split("T")[0], dob: new Date(parsedData.data.dob).toISOString().split("T")[0],
mobile: parsedData.data.phone_number, mobile: parsedData.data.phone_number,
island: Number.parseInt(parsedData.data.island_id), island: Number.parseInt(parsedData.data.island_id),
atoll: Number.parseInt(parsedData.data.atoll_id), atoll: Number.parseInt(parsedData.data.atoll_id),
acc_no: parsedData.data.accNo, acc_no: parsedData.data.accNo,
terms_accepted: parsedData.data.terms, terms_accepted: parsedData.data.terms,
policy_accepted: parsedData.data.policy, policy_accepted: parsedData.data.policy,
}, },
}), }),
); );
if (signupError) { if (signupError) {
return { return {
message: signupError.message, message: signupError.message,
payload: formData, payload: formData,
db_error: "phone_number", db_error: "phone_number",
}; };
} }
console.log("SIGNUP RESPONSE", signupResponse); console.log("SIGNUP RESPONSE", signupResponse);
redirect( redirect(
`/auth/verify-otp-registration?phone_number=${encodeURIComponent(signupResponse.t_username)}`, `/auth/verify-otp-registration?phone_number=${encodeURIComponent(signupResponse.t_username)}`,
); );
return { message: "User created successfully", error: "success" }; return { message: "User created successfully", error: "success" };
} }
export const sendOtp = async (phoneNumber: string, code: string) => { export const sendOtp = async (phoneNumber: string, code: string) => {
// Implement sending OTP code via SMS // Implement sending OTP code via SMS
console.log("Send OTP server fn", phoneNumber, code); console.log("Send OTP server fn", phoneNumber, code);
const respose = await fetch(`${process.env.SMS_API_BASE_URL}/api/sms`, { const respose = await fetch(`${process.env.SMS_API_BASE_URL}/api/sms`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${process.env.SMS_API_KEY}`, Authorization: `Bearer ${process.env.SMS_API_KEY}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
check_delivery: false, check_delivery: false,
number: phoneNumber, number: phoneNumber,
message: `Your OTP code is ${code}`, message: `Your OTP code is ${code}`,
}), }),
}); });
const data = await respose.json(); const data = await respose.json();
console.log(data); console.log(data);
return data; return data;
}; };
export async function backendMobileLogin({ mobile }: { mobile: string }) { export async function backendMobileLogin({ mobile }: { mobile: string }) {
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/auth/mobile/`, `${process.env.SARLINK_API_BASE_URL}/auth/mobile/`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
mobile, mobile,
}), }),
}, },
); );
return handleApiResponse<{ detail: string }>(response, "backendMobileLogin"); return handleApiResponse<{ detail: string }>(response, "backendMobileLogin");
} }

View File

@@ -1,218 +1,201 @@
"use server"; "use server";
import { import { z } from "zod";
type ActionState, import type {
type FilterTempUserResponse, ActionState,
type FilterUserResponse, FilterTempUserResponse,
backendMobileLogin, FilterUserResponse,
} from "@/actions/auth-actions"; } from "@/actions/auth-actions";
import type { TAuthUser, User } from "@/lib/types/user"; import type { TAuthUser, User } from "@/lib/types/user";
import axiosInstance from "@/utils/axiosInstance"; import axiosInstance from "@/utils/axiosInstance";
import { handleApiResponse, tryCatch } from "@/utils/tryCatch"; import { handleApiResponse } from "@/utils/tryCatch";
import { redirect } from "next/navigation";
import { z } from "zod";
export async function login({ export async function login({
password, password,
username, username,
}: { }: {
username: string; username: string;
password: string; password: string;
}): Promise<TAuthUser> { }): Promise<TAuthUser> {
const response = await axiosInstance const response = await axiosInstance
.post("/auth/login/", { .post("/auth/login/", {
username: username, username: username,
password: password, password: password,
}) })
.then((res) => { .then((res) => {
console.log(res); console.log(res);
return res.data; // Return the data from the response return res.data; // Return the data from the response
}) })
.catch((err) => { .catch((err) => {
console.log(err.response); console.log(err.response);
throw err; // Throw the error to maintain the Promise rejection throw err; // Throw the error to maintain the Promise rejection
}); });
return response; return response;
} }
export async function logout({ token }: { token: string }) { export async function logout({ token }: { token: string }) {
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/auth/logout/`, `${process.env.SARLINK_API_BASE_URL}/auth/logout/`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Token ${token}`, // Include the token for authentication Authorization: `Token ${token}`, // Include the token for authentication
}, },
}, },
); );
if (response.status !== 204) { if (response.status !== 204) {
throw new Error("Failed to log out from the backend"); throw new Error("Failed to log out from the backend");
} }
console.log("logout res in backend", response); console.log("logout res in backend", response);
// Since the API endpoint returns 204 No Content on success, we don't need to parse JSON // Since the API endpoint returns 204 No Content on success, we don't need to parse JSON
return null; // Return null to indicate a successful logout with no content return null; // Return null to indicate a successful logout with no content
} }
export async function checkIdOrPhone({ export async function checkIdOrPhone({
id_card, id_card,
phone_number, phone_number,
}: { }: {
id_card?: string; id_card?: string;
phone_number?: string; phone_number?: string;
}) { }) {
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/users/filter/?id_card=${id_card}&mobile=${phone_number}`, `${process.env.SARLINK_API_BASE_URL}/api/auth/users/filter/?id_card=${id_card}&mobile=${phone_number}`,
{ {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}, },
); );
const data = (await response.json()) as FilterUserResponse; const data = (await response.json()) as FilterUserResponse;
return data; return data;
} }
export async function checkTempIdOrPhone({ export async function checkTempIdOrPhone({
id_card, id_card,
phone_number, phone_number,
}: { }: {
id_card?: string; id_card?: string;
phone_number?: string; phone_number?: string;
}) { }) {
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/users/temp/filter/?id_card=${id_card}&mobile=${phone_number}`, `${process.env.SARLINK_API_BASE_URL}/api/auth/users/temp/filter/?id_card=${id_card}&mobile=${phone_number}`,
{ {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}, },
); );
const data = (await response.json()) as FilterTempUserResponse; const data = (await response.json()) as FilterTempUserResponse;
return data; return data;
} }
type TSignupUser = Pick< type TSignupUser = Pick<
User, User,
"username" | "address" | "mobile" | "id_card" | "dob" "username" | "address" | "mobile" | "id_card" | "dob"
> & { > & {
firstname: string; firstname: string;
lastname: string; lastname: string;
atoll: number; atoll: number;
island: number; island: number;
acc_no: string; acc_no: string;
terms_accepted: boolean; terms_accepted: boolean;
policy_accepted: boolean; policy_accepted: boolean;
}; };
export async function backendRegister({ payload }: { payload: TSignupUser }) { export async function backendRegister({ payload }: { payload: TSignupUser }) {
console.log("backendRegister payload", payload); console.log("backendRegister payload", payload);
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/register/`, `${process.env.SARLINK_API_BASE_URL}/api/auth/register/`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(payload), body: JSON.stringify(payload),
}, },
); );
console.log("backendRegister response", response); console.log("backendRegister response", response);
return handleApiResponse<{ t_username: string }>(response, "backendRegister"); return handleApiResponse<{ t_username: string }>(response, "backendRegister");
} }
const formSchema = z.object({ const formSchema = z.object({
mobile: z.string().regex(/^[79]\d{6}$/, "Please enter a valid phone number"), mobile: z.string().regex(/^[79]\d{6}$/, "Please enter a valid phone number"),
otp: z otp: z
.string() .string()
.min(6, { .min(6, {
message: "OTP is required.", message: "OTP is required.",
}) })
.max(6, { .max(6, {
message: "OTP is required.", message: "OTP is required.",
}), }),
}); });
export async function VerifyRegistrationOTP( export async function VerifyRegistrationOTP(
_actionState: ActionState, _actionState: ActionState,
formData: FormData, formData: FormData,
) { ) {
const formValues = Object.fromEntries(formData.entries()); const formValues = Object.fromEntries(formData.entries());
const result = formSchema.safeParse(formValues); const result = formSchema.safeParse(formValues);
console.log("formValues", formValues); console.log("formValues", formValues);
if (!result.success) { if (!result.success) {
return { return {
message: result.error.errors[0].message, // Get the error message from Zod message: result.error.errors[0].message, // Get the error message from Zod
status: "error", status: "error",
}; };
} }
if (formValues.otp === "") { if (formValues.otp === "") {
return { return {
message: "OTP is required.", message: "OTP is required.",
status: "error", status: "error",
}; };
} }
const { mobile, otp } = formValues; const { mobile, otp } = formValues;
const response = await fetch( const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/register/verify/`, `${process.env.SARLINK_API_BASE_URL}/api/auth/register/verify/`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
mobile: mobile, mobile: mobile,
otp: otp as string, otp: otp as string,
}), }),
}, },
); );
const data = (await response.json()) as { message: string }; const responseJson = await response.json();
console.log("responseJson", responseJson);
const data = responseJson as { message: string; verified: boolean };
const [error, userVerified] = await tryCatch( if (data.verified) {
checkIdOrPhone({ phone_number: mobile as string }), return {
); message:
"Your account has been successfully verified! You may login now.",
if (error) { status: "verify_success",
return { };
message: // const [mobileLoginError, mobileLoginResponse] = await tryCatch(
"There was an error fetching your account information. Please contact support.", // backendMobileLogin({ mobile: mobile as string }),
status: "user_check_error", // );
}; // if (mobileLoginError) {
} // return {
if (userVerified.verified) { // message: "Login Failed. Please contact support.",
return { // status: "login_error",
message: // };
"Your account has been successfully verified! You may login now.", // }
status: "verify_success", // if (mobileLoginResponse) {
}; // redirect(`/auth/verify-otp?phone_number=${mobile}`);
// const [mobileLoginError, mobileLoginResponse] = await tryCatch( // }
// backendMobileLogin({ mobile: mobile as string }), }
// ); return {
// if (mobileLoginError) { message:
// return { "Your account could not be verified. Please wait for you verification to be processed.",
// message: "Login Failed. Please contact support.", status: "verify_error",
// status: "login_error", };
// };
// }
// if (mobileLoginResponse) {
// redirect(`/auth/verify-otp?phone_number=${mobile}`);
// }
}
if (data.message === "User created successfully.") {
return {
message:
"Your account could not be verified. Please wait for you verification to be processed.",
status: "verify_error",
};
}
return {
message: data.message,
status: "otp_error",
};
} }