diff --git a/.env.sample b/.env.sample index 13499984a..1dc52e0d7 100644 --- a/.env.sample +++ b/.env.sample @@ -2,4 +2,5 @@ API_URL=https://your-teams-backend-url.com GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= AUTH_SECRET= -NEXTAUTH_URL=https://your-application-deployment \ No newline at end of file +NEXTAUTH_URL=https://your-application-deployment +NODE_ENV="development" // change for deployment \ No newline at end of file diff --git a/src/utils/createOrg.ts b/src/actions/createOrg.ts similarity index 87% rename from src/utils/createOrg.ts rename to src/actions/createOrg.ts index d2cc23280..ee02d67c2 100644 --- a/src/utils/createOrg.ts +++ b/src/actions/createOrg.ts @@ -3,6 +3,7 @@ import axios from "axios"; import * as z from "zod"; +import { auth } from "~/lib/auth"; import { organizationSchema } from "~/schemas"; import { getApiUrl } from "./getApiUrl"; @@ -10,9 +11,10 @@ const team = process.env.TEAM; export const createOrg = async ( values: z.infer, - token: string, + token?: string, ) => { const apiUrl = await getApiUrl(); + const session = await auth(); const validatedFields = organizationSchema.safeParse(values); if (!validatedFields.success) { @@ -26,7 +28,7 @@ export const createOrg = async ( validatedFields.data, { headers: { - Authorization: `Bearer ${token}`, + Authorization: `Bearer ${session?.access_token || token}`, }, }, ); diff --git a/src/utils/getApiUrl.ts b/src/actions/getApiUrl.ts similarity index 100% rename from src/utils/getApiUrl.ts rename to src/actions/getApiUrl.ts diff --git a/src/actions/googleAuth.ts b/src/actions/googleAuth.ts new file mode 100644 index 000000000..ac7a8ad0b --- /dev/null +++ b/src/actions/googleAuth.ts @@ -0,0 +1,29 @@ +"use server"; + +import axios from "axios"; + +import { Profile, User } from "~/types"; + +interface AuthResponse { + data: User; + access_token: string; +} + +const apiUrl = process.env.API_URL; + +const googleAuth = async (profile: Profile): Promise => { + try { + const response = await axios.post(`${apiUrl}/api/v1/auth/google`, { + id_token: profile.id_token, + }); + + return { + data: response.data.user, + access_token: response.data.access_token, + }; + } catch { + throw new Error("Authentication failed"); + } +}; + +export { googleAuth }; diff --git a/src/actions/login.ts b/src/actions/login.ts new file mode 100644 index 000000000..45d81505a --- /dev/null +++ b/src/actions/login.ts @@ -0,0 +1,69 @@ +"use server"; + +import axios from "axios"; +import { cookies } from "next/headers"; +import * as z from "zod"; + +import { LoginSchema } from "~/schemas"; + +const apiUrl = process.env.API_URL; + +export const loginUser = async (values: z.infer) => { + const cookie = cookies(); + const validatedFields = LoginSchema.safeParse(values); + if (!validatedFields.success) { + return { + error: "Login Failed. Please check your email and password.", + }; + } + const { email, password } = validatedFields.data; + const payload = { email, password }; + try { + const response = await axios.post(`${apiUrl}/api/v1/auth/login`, payload); + const access_token = response.data.access_token; + + cookie.set("access_token", access_token, { + maxAge: 60 * 60 * 24 * 1, + httpOnly: true, + path: "/", + priority: "high", + }); + return { + status: response.status, + }; + } catch (error) { + return axios.isAxiosError(error) && error.response + ? { + error: error.response.data.message || "Login failed.", + status: error.response.status, + } + : { + error: "An unexpected error occurred.", + }; + } +}; + +export const nextlogin = async (values: z.infer) => { + const validatedFields = LoginSchema.safeParse(values); + if (!validatedFields.success) { + return { + error: "Login Failed. Please check your email and password.", + }; + } + const { email, password } = validatedFields.data; + const payload = { email, password }; + try { + const response = await axios.post(`${apiUrl}/api/v1/auth/login`, payload); + + return response.data; + } catch (error) { + return axios.isAxiosError(error) && error.response + ? { + error: error.response.data.message || "Login failed.", + status: error.response.status, + } + : { + error: "An unexpected error occurred.", + }; + } +}; diff --git a/src/utils/registerAuth.ts b/src/actions/register.ts similarity index 73% rename from src/utils/registerAuth.ts rename to src/actions/register.ts index 6c04aeab2..c7f24e21d 100644 --- a/src/utils/registerAuth.ts +++ b/src/actions/register.ts @@ -1,14 +1,14 @@ "use server"; import axios from "axios"; -import { cookies } from "next/headers"; +// import { cookies } from "next/headers"; import * as z from "zod"; import { OtpSchema, RegisterSchema } from "~/schemas"; const apiUrl = process.env.API_URL; -export const registerAuth = async (values: z.infer) => { - const cookie = cookies(); + +export const registerUser = async (values: z.infer) => { const validatedFields = RegisterSchema.safeParse(values); if (!validatedFields.success) { return { @@ -20,17 +20,11 @@ export const registerAuth = async (values: z.infer) => { `${apiUrl}/api/v1/auth/register`, validatedFields.data, ); - const access_token = - response.data.access_token ?? response.data.data.access_token; - - cookie.set("access_token", access_token, { - maxAge: 60 * 60 * 24 * 1, - httpOnly: true, - path: "/", - priority: "high", - }); - return response?.data?.user; + return { + status: response.status, + data: response.data, + }; } catch (error) { return axios.isAxiosError(error) && error.response ? { @@ -44,10 +38,10 @@ export const registerAuth = async (values: z.infer) => { }; export const verifyOtp = async (values: z.infer) => { - const otp = values.otp; const token = values.token; + const email = values.email; - const payload = { otp: Number(otp), token: token }; + const payload = { token, email }; try { const response = await axios.post( diff --git a/src/utils/useVersionSync.ts b/src/actions/useVersionSync.ts similarity index 100% rename from src/utils/useVersionSync.ts rename to src/actions/useVersionSync.ts diff --git a/src/app/(auth-routes)/login/page.tsx b/src/app/(auth-routes)/login/page.tsx index 24a5fe7f9..d80f9a1d1 100644 --- a/src/app/(auth-routes)/login/page.tsx +++ b/src/app/(auth-routes)/login/page.tsx @@ -9,6 +9,8 @@ import { useEffect, useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import * as z from "zod"; +import { getApiUrl } from "~/actions/getApiUrl"; +import { loginUser } from "~/actions/login"; import CustomButton from "~/components/common/common-button/common-button"; import { Input } from "~/components/common/input"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; @@ -24,8 +26,6 @@ import { import { useToast } from "~/components/ui/use-toast"; import { cn } from "~/lib/utils"; import { LoginSchema } from "~/schemas"; -import { getApiUrl } from "~/utils/getApiUrl"; -import { loginAuth } from "~/utils/loginAuth"; const Login = () => { const router = useRouter(); @@ -66,7 +66,7 @@ const Login = () => { const onSubmit = async (values: z.infer) => { startTransition(async () => { - await loginAuth(values).then(async (data) => { + await loginUser(values).then(async (data) => { const { email, password } = values; if (data) { diff --git a/src/app/(auth-routes)/register/organisation/page.tsx b/src/app/(auth-routes)/register/organisation/page.tsx index 24d098ce7..731fccda4 100644 --- a/src/app/(auth-routes)/register/organisation/page.tsx +++ b/src/app/(auth-routes)/register/organisation/page.tsx @@ -8,6 +8,7 @@ import { useTransition } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; +import { createOrg } from "~/actions/createOrg"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; import { Button } from "~/components/ui/button"; import { @@ -28,7 +29,6 @@ import { } from "~/components/ui/select"; import { useToast } from "~/components/ui/use-toast"; import { organizationSchema } from "~/schemas"; -import { createOrg } from "~/utils/createOrg"; function Organisation() { const router = useRouter(); @@ -57,7 +57,7 @@ function Organisation() { const onSubmit = async (values: z.infer) => { const token = sessionStorage.getItem("temp_token"); startTransition(async () => { - await createOrg(values, token || "").then(async (data) => { + await createOrg(values, token ?? "").then(async (data) => { if (data.status === 201) { router.push("/login"); } diff --git a/src/app/(auth-routes)/register/page.tsx b/src/app/(auth-routes)/register/page.tsx index 0fb092428..9eeff9ce0 100644 --- a/src/app/(auth-routes)/register/page.tsx +++ b/src/app/(auth-routes)/register/page.tsx @@ -9,6 +9,8 @@ import { useEffect, useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import * as z from "zod"; +import { getApiUrl } from "~/actions/getApiUrl"; +import { registerUser } from "~/actions/register"; import CustomButton from "~/components/common/common-button/common-button"; import { Input } from "~/components/common/input"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; @@ -23,8 +25,6 @@ import { import { useToast } from "~/components/ui/use-toast"; import { cn } from "~/lib/utils"; import { RegisterSchema } from "~/schemas"; -import { getApiUrl } from "~/utils/getApiUrl"; -import { registerAuth } from "~/utils/registerAuth"; const Register = () => { const router = useRouter(); @@ -66,9 +66,9 @@ const Register = () => { const onSubmit = async (values: z.infer) => { startTransition(async () => { - await registerAuth(values).then(async (data) => { + await registerUser(values).then(async (data) => { if (data) { - sessionStorage.setItem("temp_token", data.access_token); + sessionStorage.setItem("temp_token", data.data); router.push("/register/organisation"); } diff --git a/src/app/(landing-routes)/(home)/pricing/page.tsx b/src/app/(landing-routes)/(home)/pricing/page.tsx index 25ff9d46c..a9bd3a032 100644 --- a/src/app/(landing-routes)/(home)/pricing/page.tsx +++ b/src/app/(landing-routes)/(home)/pricing/page.tsx @@ -5,12 +5,12 @@ import Image from "next/image"; import Link from "next/link"; import { useEffect, useState } from "react"; +import { getApiUrl } from "~/actions/getApiUrl"; import FaqAccordion from "~/components/layouts/accordion/FaqsAccordion"; import Heading from "~/components/layouts/heading"; import PricingCardSkeleton from "~/components/skeleton/pricingcardskeleton"; import { Button } from "~/components/ui/button"; import { faqData } from "~/constants/faqsdata"; -import { getApiUrl } from "~/utils/getApiUrl"; interface BillingPlan { id: string; diff --git a/src/app/(landing-routes)/career/page.tsx b/src/app/(landing-routes)/career/page.tsx index cc5e5db8d..18e18e92b 100644 --- a/src/app/(landing-routes)/career/page.tsx +++ b/src/app/(landing-routes)/career/page.tsx @@ -4,13 +4,13 @@ import axios from "axios"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; +import { getApiUrl } from "~/actions/getApiUrl"; import CareerCardParent from "~/components/common/CareerCard"; import { Job } from "~/components/common/CareerCard/Jobs"; import Heading from "~/components/layouts/heading"; import Pagination from "~/components/layouts/pagination/Pagination"; import JobSkeleton from "~/components/skeleton/jobskeleton"; import { useToast } from "~/components/ui/use-toast"; -import { getApiUrl } from "~/utils/getApiUrl"; import Nojobs from "./Nojobs"; export default function Career() { diff --git a/src/app/(landing-routes)/squeeze/index.tsx b/src/app/(landing-routes)/squeeze/index.tsx index c401cde06..7cf76b46a 100644 --- a/src/app/(landing-routes)/squeeze/index.tsx +++ b/src/app/(landing-routes)/squeeze/index.tsx @@ -6,8 +6,8 @@ import { useRouter } from "next/navigation"; import React, { useEffect, useState } from "react"; import { z, ZodError } from "zod"; +import { getApiUrl } from "~/actions/getApiUrl"; import { useToast } from "~/components/ui/use-toast"; -import { getApiUrl } from "~/utils/getApiUrl"; const validationSchema = z.object({ first_name: z.string().min(1, "First name is required"), diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/notification/page.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/notification/page.tsx index 38f43c39f..e1e1d263f 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/notification/page.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/notification/page.tsx @@ -5,10 +5,10 @@ import { Check, ChevronLeft } from "lucide-react"; import { getSession } from "next-auth/react"; import { useState } from "react"; +import { getApiUrl } from "~/actions/getApiUrl"; import CustomButton from "~/components/common/common-button/common-button"; import NotificationSettingSavedModal from "~/components/common/modals/notification-settings-saved"; import { useToast } from "~/components/ui/use-toast"; -import { getApiUrl } from "~/utils/getApiUrl"; import { useNotificationStore } from "./_action/notification-store"; import NotificationHeader from "./_components/header"; import { NotificationSwitchBox } from "./_components/notification-switch-box"; diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/export.ts b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/export.ts index f4b799677..521f32983 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/export.ts +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/export.ts @@ -1,6 +1,6 @@ import axios from "axios"; -import { getApiUrl } from "~/utils/getApiUrl"; +import { getApiUrl } from "~/actions/getApiUrl"; export const exportMembersEndpoint = async (format: string = "csv") => { const apiUrl = await getApiUrl(); diff --git a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx index df7b0c3ca..66eb20c31 100644 --- a/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx +++ b/src/app/dashboard/(admin)/admin/(settings)/settings/organization/roles-and-permissions/page.tsx @@ -2,11 +2,11 @@ import { useEffect, useState } from "react"; +import { getApiUrl } from "~/actions/getApiUrl"; import CustomButton from "~/components/common/common-button/common-button"; import RoleCreationModal from "~/components/common/modals/role-creation"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; import { useToast } from "~/components/ui/use-toast"; -import { getApiUrl } from "~/utils/getApiUrl"; type Role = { id: number; diff --git a/src/app/dashboard/(user-dashboard)/settings/organization/members/action/member.ts b/src/app/dashboard/(user-dashboard)/settings/organization/members/action/member.ts index 0fa09eee5..bd613b121 100644 --- a/src/app/dashboard/(user-dashboard)/settings/organization/members/action/member.ts +++ b/src/app/dashboard/(user-dashboard)/settings/organization/members/action/member.ts @@ -1,8 +1,8 @@ import axios from "axios"; import { useEffect, useState } from "react"; +import { getApiUrl } from "~/actions/getApiUrl"; import { useToast } from "~/components/ui/use-toast"; -import { getApiUrl } from "~/utils/getApiUrl"; const useApiUrl = () => { const [apiUrl, setApiUrl] = useState(""); diff --git a/src/components/layouts/navbar/index.tsx b/src/components/layouts/navbar/index.tsx index 700181016..a5abdfe70 100644 --- a/src/components/layouts/navbar/index.tsx +++ b/src/components/layouts/navbar/index.tsx @@ -5,10 +5,10 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useState } from "react"; +import useVersionSync from "~/actions/useVersionSync"; import UserCard from "~/components/card/user-card"; import Logo from "~/components/common/logo"; import { cn } from "~/lib/utils"; -import useVersionSync from "~/utils/useVersionSync"; import { NAV_LINKS } from "./links"; import MobileNav from "./mobile-navbar"; diff --git a/src/config/auth.config.ts b/src/config/auth.config.ts index c97694d1f..19ae48b2d 100644 --- a/src/config/auth.config.ts +++ b/src/config/auth.config.ts @@ -2,12 +2,13 @@ import { NextAuthConfig, Session } from "next-auth"; import { JWT } from "next-auth/jwt"; import Credentials from "next-auth/providers/credentials"; import Google from "next-auth/providers/google"; -import { cookies } from "next/headers"; +import { googleAuth } from "~/actions/googleAuth"; +import { nextlogin } from "~/actions/login"; import { LoginSchema } from "~/schemas"; -import { ApiResponse, CustomJWT, CustomSession } from "~/types"; -import { googleAuth } from "~/utils/googleAuth"; -import { loginAuth } from "~/utils/loginAuth"; +import { CustomJWT } from "~/types"; + +const isDevelopment = process.env.NODE_ENV === "development"; export default { providers: [ @@ -26,84 +27,78 @@ export default { async authorize(credentials) { const validatedFields = LoginSchema.safeParse(credentials); if (!validatedFields.success) { - throw new Error("Invalid credentials"); + return; } - const { email, password, rememberMe } = validatedFields.data; - try { - const response = await loginAuth({ email, password, rememberMe }); - if (!response) { - throw new Error("User authentication failed"); - } + const { email, password, rememberMe } = validatedFields.data; + const response = await nextlogin({ email, password, rememberMe }); - return { - ...response, - } as CustomJWT; - } catch { - throw new Error("Something went wrong"); + if (!response) { + return; } + + const user = { + ...response.data.user, + }; + + return user; }, }), ], + session: { + strategy: "jwt", + }, + debug: isDevelopment, callbacks: { async signIn({ account, profile, user }) { if (account?.provider === "google" && profile?.email) { - return profile.email.endsWith("@gmail.com"); + return true; } - return !!user; }, - async jwt({ token, user, account }) { - if (account?.provider === "google" && account?.id_token) { - try { - const response: ApiResponse = (await googleAuth( - account.id_token ?? "", - )) as ApiResponse; + async jwt({ token, user, account, profile }) { + if (account?.provider !== "google") { + return { + ...token, + ...user, + } as CustomJWT; + } - if (!response) { - throw new Error("User authentication failed"); - } + if (!account?.id_token) { + return token; + } - return { - ...token, - ...response, - } as CustomJWT; - } catch { - throw new Error("Something went wrong"); - } + const response = await googleAuth({ id_token: account?.id_token }); + + if (!response.data) { + token = { + email: profile?.email, + name: profile?.given_name, + picture: profile?.picture, + } as CustomJWT; + return token; } + token = response.data as CustomJWT; + token.picture = profile?.picture; + token.access_token = response.access_token; + + user = response.data as CustomJWT; - return { ...token, ...user } as CustomJWT; + return token; }, - async session({ - session, - token, - }: { - session: Session; - token: JWT; - }): Promise { + async session({ session, token }: { session: Session; token: JWT }) { const customToken = token as CustomJWT; - const authToken = cookies().get("access_token")?.value; - session.user = { - id: customToken.id, - first_name: - customToken.first_name || - customToken.name?.split(" ")[0] || - customToken.fullname?.split(" ")[0] || - "", - last_name: - customToken.last_name || - customToken.name?.split(" ").slice(1).join(" ") || - customToken.fullname?.split(" ").slice(1).join(" ") || - "", - email: customToken.email, - image: customToken.picture || customToken.avatar_url || "", - role: customToken.role, + id: customToken.id as string, + first_name: customToken.first_name as string, + last_name: customToken.last_name as string, + image: token.picture || customToken.avatar_url || "", + role: customToken.role as string, + email: token.email as string, }; - session.access_token = authToken; - return session as CustomSession; + session.access_token = customToken.access_token; + return session; }, }, pages: { diff --git a/src/lib/auth.react.ts b/src/lib/auth.react.ts deleted file mode 100644 index f041eb0a8..000000000 --- a/src/lib/auth.react.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { DefaultSession } from "next-auth"; -import { getSession as getAuthSession } from "next-auth/react"; - -export { signIn, signOut } from "next-auth/react"; - -interface Session extends DefaultSession { - user?: DefaultSession["user"] & { - id: string; - }; -} - -export const getSession: () => Promise = async () => { - const session = await getAuthSession(); - return session as Session | null; -}; diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 25fd2d96d..d6c8721ca 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -35,7 +35,7 @@ export const LoginSchema = z.object({ message: "Invalid email address", }), password: passwordSchema, - rememberMe: z.boolean().default(false), + rememberMe: z.boolean().default(false).optional(), }); export const organizationSchema = z.object({ @@ -66,7 +66,6 @@ export const organizationSchema = z.object({ }); export const OtpSchema = z.object({ - otp: z.string().optional(), token: z.string().optional(), email: z.string().email().optional(), }); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index ff31cd2d1..f46df2785 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -2,9 +2,6 @@ import { Session, type DefaultSession } from "next-auth"; import { JWT } from "next-auth/jwt"; export interface CustomJWT extends JWT { - status?: string; - status_code?: number; - message?: string; id?: string; email?: string; picture?: string; @@ -14,7 +11,6 @@ export interface CustomJWT extends JWT { last_name?: string; fullname?: string; access_token?: string; - expires_in?: string; } export interface CustomSession extends Session { user: { @@ -27,6 +23,7 @@ export interface CustomSession extends Session { role: string; }; expires: DefaultSession["expires"]; + access_token?: string; } export interface User { @@ -50,3 +47,7 @@ export interface ApiResponse { message: string; data: ApiResponseData; } + +export interface Profile { + id_token: string; +} diff --git a/src/utils/.gitkeep b/src/utils/.gitkeep new file mode 100644 index 000000000..c693f138c --- /dev/null +++ b/src/utils/.gitkeep @@ -0,0 +1 @@ +keep \ No newline at end of file diff --git a/src/utils/googleAuth.ts b/src/utils/googleAuth.ts deleted file mode 100644 index 5b659e369..000000000 --- a/src/utils/googleAuth.ts +++ /dev/null @@ -1,35 +0,0 @@ -"use server"; - -import axios from "axios"; -import { cookies } from "next/headers"; - -const apiUrl = process.env.API_URL; - -export const googleAuth = async (id_token: string) => { - try { - const response = await axios.post(`${apiUrl}/api/v1/auth/google`, { - id_token: id_token, - }); - - const access_token = - response.data.access_token ?? response.data.data.access_token; - const cookie = cookies(); - cookie.set("access_token", access_token, { - maxAge: 60 * 60 * 24 * 1, - httpOnly: true, - path: "/", - priority: "high", - }); - - return response.data.user; - } catch (error) { - return axios.isAxiosError(error) && error.response - ? { - error: error.response.data.message || "Authentication failed", - status: error.response.status, - } - : { - error: "An unexpected error occurred", - }; - } -}; diff --git a/src/utils/loginAuth.ts b/src/utils/loginAuth.ts deleted file mode 100644 index 183545569..000000000 --- a/src/utils/loginAuth.ts +++ /dev/null @@ -1,44 +0,0 @@ -"use server"; - -import axios from "axios"; -import { cookies } from "next/headers"; -import * as z from "zod"; - -import { LoginSchema } from "~/schemas"; - -const apiUrl = process.env.API_URL; - -export const loginAuth = async (values: z.infer) => { - const cookie = cookies(); - const validatedFields = LoginSchema.safeParse(values); - if (!validatedFields.success) { - return { - error: "Login Failed. Please check your email and password.", - }; - } - const { email, password } = validatedFields.data; - const payload = { email, password }; - try { - const response = await axios.post(`${apiUrl}/api/v1/auth/login`, payload); - const access_token = - response.data.access_token ?? response.data.data.access_token; - - cookie.set("access_token", access_token, { - maxAge: 60 * 60 * 24 * 1, - httpOnly: true, - path: "/", - priority: "high", - }); - - return response?.data?.user; - } catch (error) { - return axios.isAxiosError(error) && error.response - ? { - error: error.response.data.message || "Authentication failed", - status: error.response.status, - } - : { - error: "An unexpected error occurred", - }; - } -};