diff --git a/actions/auth-actions.ts b/actions/auth-actions.ts
index ddccefc..564de7b 100644
--- a/actions/auth-actions.ts
+++ b/actions/auth-actions.ts
@@ -1,11 +1,9 @@
"use server";
-import { VerifyUserDetails } from "@/lib/person";
import { signUpFormSchema } from "@/lib/schemas";
-import { headers } from "next/headers";
+import { checkIdOrPhone } from "@/queries/authentication";
import { redirect } from "next/navigation";
import { z } from "zod";
-import { SendUserRejectionDetailSMS } from "./user-actions";
const formSchema = z.object({
phoneNumber: z
.string()
@@ -84,33 +82,28 @@ export async function signup(_actionState: ActionState, formData: FormData) {
};
}
- // const idCardExists = await prisma.user.findFirst({
- // where: {
- // id_card: parsedData.data.id_card,
- // },
- // });
+ const idCardExists = await checkIdOrPhone({
+ id_card: parsedData.data.id_card,
+ });
+ if (idCardExists.ok) {
+ return {
+ message: "ID card already exists.",
+ payload: formData,
+ db_error: "id_card",
+ };
+ }
- // if (idCardExists) {
- // return {
- // message: "ID card already exists.",
- // payload: formData,
- // db_error: "id_card",
- // };
- // }
+ const phoneNumberExists = await checkIdOrPhone({
+ phone_number: parsedData.data.phone_number,
+ });
- // const phoneNumberExists = await prisma.user.findFirst({
- // where: {
- // phoneNumber: parsedData.data.phone_number,
- // },
- // });
-
- // if (phoneNumberExists) {
- // return {
- // message: "Phone number already exists.",
- // payload: formData,
- // db_error: "phone_number",
- // };
- // }
+ if (phoneNumberExists.ok) {
+ return {
+ message: "Phone number already exists.",
+ payload: formData,
+ db_error: "phone_number",
+ };
+ }
// const newUser = await prisma.user.create({
// data: {
@@ -144,12 +137,12 @@ export async function signup(_actionState: ActionState, formData: FormData) {
// `,
// phoneNumber: process.env.ADMIN_PHONENUMBER ?? "",
// });
- // return {
- // message:
- // "Your account has been requested for verification. Please wait for a response from admin.",
- // payload: formData,
- // db_error: "invalidPersonValidation",
- // };
+ // return {
+ // message:
+ // "Your account has been requested for verification. Please wait for a response from admin.",
+ // payload: formData,
+ // db_error: "invalidPersonValidation",
+ // };
// if (isValidPerson) {
// await authClient.phoneNumber.sendOtp({
@@ -159,7 +152,7 @@ export async function signup(_actionState: ActionState, formData: FormData) {
// redirect(
// `/verify-otp?phone_number=${encodeURIComponent(newUser.phoneNumber)}`,
// );
- // return { message: "User created successfully" };
+ return { message: "User created successfully" };
}
export const sendOtp = async (phoneNumber: string, code: string) => {
diff --git a/app/(auth)/auth/signup/page.tsx b/app/(auth)/auth/signup/page.tsx
index 05d89b0..8cf7cdb 100644
--- a/app/(auth)/auth/signup/page.tsx
+++ b/app/(auth)/auth/signup/page.tsx
@@ -1,4 +1,6 @@
import SignUpForm from "@/components/auth/signup-form";
+import type { ApiResponse, Atoll, Island } from "@/lib/backend-types";
+import { getAtolls } from "@/queries/islands";
import Image from "next/image";
import { redirect } from "next/navigation";
@@ -8,11 +10,10 @@ export default async function SignupPage({
searchParams: Promise<{ phone_number: string }>;
}) {
- const atolls = await getAtollsWithIslands();
- console.log(atolls.data);
const phone_number = (await searchParams).phone_number;
+ console.log({ phone_number })
if (!phone_number) {
- return redirect("/login");
+ return redirect("/auth/login");
}
@@ -26,7 +27,7 @@ export default async function SignupPage({
Pay for your devices and track your bills.
-
+
);
diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx
index 2b55887..b64c312 100644
--- a/app/(dashboard)/layout.tsx
+++ b/app/(dashboard)/layout.tsx
@@ -1,5 +1,5 @@
import { ApplicationLayout } from "@/components/auth/application-layout";
-import QueryProvider from "@/components/query-provider";
+import QueryProvider from "@/providers/query-provider";
export default function DashboardLayout({
children,
diff --git a/app/layout.tsx b/app/layout.tsx
index a3327cd..e93b425 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,4 +1,4 @@
-import { ThemeProvider } from "@/components/theme-provider";
+import { ThemeProvider } from "@/providers/theme-provider";
import { Provider } from "jotai";
import type { Metadata } from "next";
@@ -6,6 +6,10 @@ import { Barlow } from "next/font/google";
import NextTopLoader from "nextjs-toploader";
import { Toaster } from "sonner";
import "./globals.css";
+import { AuthProvider } from "@/providers/AuthProvider";
+import QueryProvider from "@/providers/query-provider";
+import { getServerSession } from "next-auth";
+import { authOptions } from "./auth";
const barlow = Barlow({
subsets: ["latin"],
weight: ["100", "300", "400", "500", "600", "700", "800", "900"],
@@ -17,27 +21,33 @@ export const metadata: Metadata = {
description: "Sarlink Portal",
};
-export default function RootLayout({
+export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
+ const session = await getServerSession(authOptions);
return (
-
-
-
-
- {children}
-
-
+
+
+
+
+
+
+ {children}
+
+
+
+
+
);
}
diff --git a/components/auth/signup-form.tsx b/components/auth/signup-form.tsx
index d438751..38b8910 100644
--- a/components/auth/signup-form.tsx
+++ b/components/auth/signup-form.tsx
@@ -18,10 +18,20 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import type { Atoll } from "@/lib/backend-types";
+import type { ApiResponse, Atoll } from "@/lib/backend-types";
+import { getAtolls } from "@/queries/islands";
+import { keepPreviousData, useQuery } from "@tanstack/react-query";
-export default function SignUpForm({ atolls }: { atolls: Atoll[] }) {
+export default function SignUpForm() {
+ const { data: atolls, isFetching } = useQuery>({
+ queryKey: ["ATOLLS"],
+ queryFn: () =>
+ getAtolls(),
+ placeholderData: keepPreviousData,
+ staleTime: 1,
+ });
+
const [atoll, setAtoll] = React.useState();
const [actionState, action, isPending] = React.useActionState(signup, {
@@ -39,7 +49,7 @@ export default function SignUpForm({ atolls }: { atolls: Atoll[] }) {
const NUMBER_WITHOUT_DASH = phoneNumberFromUrl?.split("-").join("");
- if (actionState.db_error === "invalidPersonValidation") {
+ if (actionState?.db_error === "invalidPersonValidation") {
return (
<>
{actionState.message}
@@ -117,7 +127,7 @@ export default function SignUpForm({ atolls }: { atolls: Atoll[] }) {
disabled={isPending}
onValueChange={(v) => {
console.log({ v })
- setAtoll(atolls.find((atoll) => atoll.id === Number.parseInt(v)));
+ setAtoll(atolls?.data.find((atoll) => atoll.id === Number.parseInt(v)));
}}
name="atoll_id"
value={atoll?.id?.toString() ?? ""}
@@ -128,7 +138,7 @@ export default function SignUpForm({ atolls }: { atolls: Atoll[] }) {
Atolls
- {atolls.map((atoll) => (
+ {atolls?.data.map((atoll) => (
{atoll.name}
diff --git a/lib/backend-types.ts b/lib/backend-types.ts
index 4f5f878..21fc409 100644
--- a/lib/backend-types.ts
+++ b/lib/backend-types.ts
@@ -9,7 +9,7 @@ export interface Meta {
last_page: number;
}
-export interface DataResponse {
+export interface ApiResponse {
meta: Meta;
links: Links;
data: T[];
diff --git a/middleware.ts b/middleware.ts
index f4d2e6d..51174b5 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -6,15 +6,5 @@ export default withAuth(
);
export const config = {
- // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
- matcher: [
- /*
- * Match all request paths except for the ones starting with:
- * - api (API routes)
- * - _next/static (static files)
- * - _next/image (image optimization files)
- * - favicon.ico (favicon file)
- */
- "/((?!api|_next/static|_next/image|favicon.ico|auth/|access-denied).*)",
- ],
+ matcher: ["/about/:path*", "/dashboard/:path*"],
};
diff --git a/next-auth.d.ts b/next-auth.d.ts
new file mode 100644
index 0000000..9b64aed
--- /dev/null
+++ b/next-auth.d.ts
@@ -0,0 +1,19 @@
+import NextAuth, { DefaultSession } from "next-auth";
+import { Session } from "next-auth";
+import type { User } from "./userTypes";
+declare module "next-auth" {
+ /**
+ * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
+ */
+
+ interface Session {
+ apiToken?: string;
+ name?: string | null;
+ email?: string | null;
+ image?: string | null;
+ user?: User & {
+ expiry?: string;
+ };
+ expires: ISODateString;
+ }
+}
diff --git a/providers/AuthProvider.tsx b/providers/AuthProvider.tsx
new file mode 100644
index 0000000..2a2f496
--- /dev/null
+++ b/providers/AuthProvider.tsx
@@ -0,0 +1,13 @@
+"use client"
+
+import type { Session } from "next-auth"
+import { SessionProvider } from "next-auth/react"
+
+type Props = {
+ children: React.ReactNode
+ session?: Session
+}
+
+export const AuthProvider = ({ children, session }: Props) => {
+ return {children}
+}
\ No newline at end of file
diff --git a/components/query-provider.tsx b/providers/query-provider.tsx
similarity index 100%
rename from components/query-provider.tsx
rename to providers/query-provider.tsx
diff --git a/components/theme-provider.tsx b/providers/theme-provider.tsx
similarity index 100%
rename from components/theme-provider.tsx
rename to providers/theme-provider.tsx
diff --git a/queries/authentication.ts b/queries/authentication.ts
index ac18613..2f012ad 100644
--- a/queries/authentication.ts
+++ b/queries/authentication.ts
@@ -46,3 +46,21 @@ export async function logout({ token }: { token: string }) {
// 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
}
+
+export async function checkIdOrPhone({
+ id_card,
+ phone_number,
+}: { id_card?: string; phone_number?: string }) {
+ console.log("id_card and phone_number", { id_card, phone_number });
+ const response = await fetch(
+ `${process.env.SARLINK_API_BASE_URL}/api/auth/users/filter/?id_card=${id_card}&mobile=${phone_number}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ },
+ );
+ const data = await response.json();
+ return data;
+}
diff --git a/queries/islands.ts b/queries/islands.ts
index 04ca0b9..e4dda2e 100644
--- a/queries/islands.ts
+++ b/queries/islands.ts
@@ -1,12 +1,47 @@
"use server";
+import { AxiosClient } from "@/utils/AxiosClient";
+
export async function getIslands() {
- const res = await fetch(`${process.env.SARLINK_API_BASE_URL}/islands/`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- });
- const data = await res.json();
+ const response = await AxiosClient.get("/islands/");
+ const data = response.data;
return data;
}
+
+export async function getAtolls() {
+ const response = await fetch(
+ `${process.env.SARLINK_API_BASE_URL}/api/auth/atolls/`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ },
+ );
+ const data = response.json();
+ return data;
+}
+
+export async function getAllItems({
+ limit = 10,
+ offset = 0,
+ ...otherParams
+}: {
+ limit?: number;
+ offset?: number;
+} & Record) {
+ const params = new URLSearchParams();
+ // Add default params
+ params.append("limit", limit.toString());
+ params.append("offset", offset.toString());
+
+ // Add any additional params dynamically
+ Object.entries(otherParams).map(([key, value]) => {
+ if (value !== undefined) {
+ params.append(key, String(value || ""));
+ }
+ });
+
+ const response = await AxiosClient.get(`/inventory/?${params.toString()}`);
+ return response.data;
+}
diff --git a/utils/AxiosClient.ts b/utils/AxiosClient.ts
new file mode 100644
index 0000000..fe8e196
--- /dev/null
+++ b/utils/AxiosClient.ts
@@ -0,0 +1,55 @@
+import axios, { type AxiosError } from "axios";
+import type { Session } from "next-auth";
+import { getSession } from "next-auth/react";
+import { redirect } from "next/navigation";
+
+axios.defaults.xsrfCookieName = "csrftoken";
+axios.defaults.xsrfHeaderName = "X-CSRFToken";
+
+const ApiClient = () => {
+ const instance = axios.create({
+ baseURL: process.env.SARLINK_API_BASE_URL,
+ headers: {
+ Accept: "application/json",
+ },
+ });
+
+ let lastSession: Session | null = null;
+
+ instance.interceptors.request.use(
+ async (request) => {
+ if (lastSession == null || Date.now() > Date.parse(lastSession.expires)) {
+ const session = await getSession();
+ lastSession = session;
+ }
+
+ if (lastSession) {
+ request.headers.Authorization = `Token ${lastSession.apiToken}`;
+ } else {
+ request.headers.Authorization = undefined;
+ return redirect("/auth/signin");
+ }
+
+ return request;
+ },
+ (error) => {
+ console.error("API Error: ", error);
+ throw error;
+ },
+ );
+ instance.interceptors.response.use(
+ async (response) => {
+ return response;
+ },
+ async (error: AxiosError) => {
+ if (error?.response?.status === 401) {
+ return redirect("/auth/signin");
+ }
+ return Promise.reject(error);
+ },
+ );
+
+ return instance;
+};
+
+export const AxiosClient = ApiClient();