diff --git a/.gitignore b/.gitignore
index 5ef6a52..10853e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,9 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# Swagger Files
+.yaml
+.yml
+swagger-output.json
+
diff --git a/README.md b/README.md
index 13cbc49..741e11f 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-GP Frontend
\ No newline at end of file
+# GP Frontend
diff --git a/app/(pages)/(admins)/admin-dashboard/clinics/page.tsx b/app/(pages)/(admins)/admin-dashboard/clinics/page.tsx
new file mode 100644
index 0000000..def1c4d
--- /dev/null
+++ b/app/(pages)/(admins)/admin-dashboard/clinics/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { ClinicListContainer } from "@/features/admins/admin";
+import { Shield } from "lucide-react";
+import { navigationItems } from "@/constants/AdminNavigationItems";
+
+export default function ClinicsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/admin-dashboard/doctors/page.tsx b/app/(pages)/(admins)/admin-dashboard/doctors/page.tsx
new file mode 100644
index 0000000..abf0dd1
--- /dev/null
+++ b/app/(pages)/(admins)/admin-dashboard/doctors/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { Users } from "lucide-react";
+import { navigationItems } from "@/constants/AdminNavigationItems";
+import { DoctorListContainer } from "@/features/admins/admin/components/doctors/DoctorListContainer";
+
+export default function AdminDashboardPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/admin-dashboard/nurses/page.tsx b/app/(pages)/(admins)/admin-dashboard/nurses/page.tsx
new file mode 100644
index 0000000..2cd2de8
--- /dev/null
+++ b/app/(pages)/(admins)/admin-dashboard/nurses/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { Users } from "lucide-react";
+import { navigationItems } from "@/constants/AdminNavigationItems";
+import { NurseListContainer } from "@/features/admins/admin/components/nurses/NurseListContainer";
+
+export default function AdminNursesPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/admin-dashboard/page.tsx b/app/(pages)/(admins)/admin-dashboard/page.tsx
new file mode 100644
index 0000000..04ba462
--- /dev/null
+++ b/app/(pages)/(admins)/admin-dashboard/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { Users } from "lucide-react";
+import { navigationItems } from "@/constants/AdminNavigationItems";
+import { DashboardContainer } from "@/features/admins/admin/components/dashboard/DashboardContainer";
+
+export default function AdminDashboardPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/superadmin-dashboard/admins/page.tsx b/app/(pages)/(admins)/superadmin-dashboard/admins/page.tsx
new file mode 100644
index 0000000..71446ee
--- /dev/null
+++ b/app/(pages)/(admins)/superadmin-dashboard/admins/page.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { AdminListContainer } from "@/features/admins/super-admin";
+import { Users} from "lucide-react";
+import { navigationItems } from "@/constants/superAdminNavigationItems";
+
+export default function AdminsPage() {
+
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/superadmin-dashboard/clinics/page.tsx b/app/(pages)/(admins)/superadmin-dashboard/clinics/page.tsx
new file mode 100644
index 0000000..9c63f1a
--- /dev/null
+++ b/app/(pages)/(admins)/superadmin-dashboard/clinics/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { ClinicListContainer } from "@/features/admins/super-admin";
+import { Users } from "lucide-react";
+import { navigationItems } from "@/constants/superAdminNavigationItems";
+
+export default function ClinicsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/superadmin-dashboard/doctors/page.tsx b/app/(pages)/(admins)/superadmin-dashboard/doctors/page.tsx
new file mode 100644
index 0000000..545b7f6
--- /dev/null
+++ b/app/(pages)/(admins)/superadmin-dashboard/doctors/page.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { DoctorListContainer } from "@/features/admins/super-admin";
+import { Users } from "lucide-react";
+import { navigationItems } from "@/constants/superAdminNavigationItems";
+
+export default function DoctorsPage() {
+
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/superadmin-dashboard/nurses/page.tsx b/app/(pages)/(admins)/superadmin-dashboard/nurses/page.tsx
new file mode 100644
index 0000000..5f2a0e4
--- /dev/null
+++ b/app/(pages)/(admins)/superadmin-dashboard/nurses/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { Users } from "lucide-react";
+import { navigationItems } from "@/constants/superAdminNavigationItems";
+import { NurseListContainer } from "@/features/admins/super-admin/components/nurse/NurseListContainer";
+
+export default function SuperAdminNursesPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(admins)/superadmin-dashboard/page.tsx b/app/(pages)/(admins)/superadmin-dashboard/page.tsx
new file mode 100644
index 0000000..a0b90ec
--- /dev/null
+++ b/app/(pages)/(admins)/superadmin-dashboard/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { Sidebar } from "@/components/layout/Sidebar";
+import { Users } from "lucide-react";
+import { navigationItems } from "@/constants/superAdminNavigationItems";
+import { DashboardContainer } from "@/features/admins/super-admin/components/dashboard/DashboardContainer";
+
+export default function SuperAdminDashboardPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(auth)/complete-profile/page.tsx b/app/(pages)/(auth)/complete-profile/page.tsx
new file mode 100644
index 0000000..587a1b5
--- /dev/null
+++ b/app/(pages)/(auth)/complete-profile/page.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import { CompleteProfileContainer } from "@/features/auth/complete-profile/components/CompleteProfileContainer";
+import { CompleteProfilePresentational } from "@/features/auth/complete-profile/components/CompleteProfilePresentational";
+import { useEffect, useState } from "react";
+import { useAuthFlowStore } from "@/stores/useAuthFlowStore";
+import { useAuthSync } from "@/hooks/useAuthSync";
+import { getCurrentUser } from "@/features/dashboards/dashboard/api/dashboard.api";
+
+export default function CompleteProfilePage() {
+ const [isLoading, setIsLoading] = useState(true);
+ const signupMethod = useAuthFlowStore((state) => state.signupMethod);
+ const { user, updateAuthState } = useAuthSync();
+
+ useEffect(() => {
+ const initializeUser = async () => {
+ const cookies = document.cookie;
+ const hasAuthCookie =
+ cookies.includes("Authorization=") || cookies.includes("RefreshToken=");
+ const hasUserStateCookie = cookies.includes("UserState=");
+
+ if (hasAuthCookie && !hasUserStateCookie) {
+ try {
+ const userData = await getCurrentUser();
+ updateAuthState(userData);
+
+ setTimeout(() => {
+ window.location.reload();
+ }, 100);
+ return;
+ } catch (error) {
+ console.error("Failed:", error);
+ }
+ }
+
+ if (!user || !user.email) {
+ try {
+ const userData = await getCurrentUser();
+ updateAuthState(userData);
+
+ setTimeout(() => {
+ const cookies = document.cookie.split(";");
+ const userStateCookie = cookies.find((c) =>
+ c.trim().startsWith("UserState="),
+ );
+ console.log("šŖ UserState cookie:", userStateCookie);
+ }, 50);
+ } catch (error) {
+ console.error("Failed:", error);
+ }
+ }
+ setIsLoading(false);
+ };
+
+ initializeUser();
+ }, [user, updateAuthState]);
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ const requirePhone = signupMethod === "google" || !user?.phone;
+
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/(auth)/forgot-password/page.tsx b/app/(pages)/(auth)/forgot-password/page.tsx
new file mode 100644
index 0000000..6fe8758
--- /dev/null
+++ b/app/(pages)/(auth)/forgot-password/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import {
+ ForgotPasswordContainer,
+ ForgotPasswordPresentational,
+} from "@/features/auth/password";
+
+export default function ForgotPasswordPage() {
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/(auth)/google-callback/page.tsx b/app/(pages)/(auth)/google-callback/page.tsx
new file mode 100644
index 0000000..46a04da
--- /dev/null
+++ b/app/(pages)/(auth)/google-callback/page.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+import { getCurrentUser } from "@/features/dashboards/dashboard/api/dashboard.api";
+import { useAuthSync } from "@/hooks/useAuthSync";
+
+export default function GoogleCallbackPage() {
+ const router = useRouter();
+ const { updateAuthState } = useAuthSync();
+
+ useEffect(() => {
+ const handleGoogleCallback = async () => {
+ try {
+ const userData = await getCurrentUser();
+ updateAuthState(userData);
+
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ window.location.href = "/complete-profile";
+ } catch (error) {
+ console.error("Failed:", error);
+ router.push("/login");
+ }
+ };
+
+ handleGoogleCallback();
+ }, [router, updateAuthState]);
+
+ return (
+
+
+
+
Completing Google Sign In...
+
Please wait...
+
+
+ );
+}
diff --git a/app/(pages)/(auth)/login/page.tsx b/app/(pages)/(auth)/login/page.tsx
new file mode 100644
index 0000000..8430d17
--- /dev/null
+++ b/app/(pages)/(auth)/login/page.tsx
@@ -0,0 +1,35 @@
+"use client";
+
+import { useEffect } from "react";
+import { LoginContainer } from "@/features/auth/login/components/LoginContainer";
+import { LoginPresentational } from "@/features/auth/login/components/LoginPresentational";
+import { useUserStore } from "@/stores/useUserStore";
+
+export default function LoginPage() {
+
+ const clearUser = useUserStore((state) => state.clearUser);
+
+ useEffect(() => {
+ const userStorage = localStorage.getItem('user-storage');
+ if (userStorage) {
+ try {
+ const parsed = JSON.parse(userStorage);
+ if (parsed?.state?.user) {
+ localStorage.removeItem('user-storage');
+ document.cookie = "UserState=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ clearUser();
+ }
+ } catch (error) {
+ console.error("Error parsing user storage:", error);
+ }
+ }
+ }, [clearUser]);
+
+ return (
+ <>
+
+ {(props) => }
+
+ >
+ );
+}
diff --git a/app/(pages)/(auth)/register/page.tsx b/app/(pages)/(auth)/register/page.tsx
new file mode 100644
index 0000000..410c0ff
--- /dev/null
+++ b/app/(pages)/(auth)/register/page.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { RegisterContainer } from "@/features/auth/register/components/RegisterContainer";
+import { RegisterPresentational } from "@/features/auth/register/components/RegisterPresentational";
+
+export default function RegisterPage() {
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/(auth)/reset-password/page.tsx b/app/(pages)/(auth)/reset-password/page.tsx
new file mode 100644
index 0000000..ea28cd2
--- /dev/null
+++ b/app/(pages)/(auth)/reset-password/page.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { useSearchParams } from "next/navigation";
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+import {
+ ResetPasswordContainer,
+ ResetPasswordPresentational,
+} from "@/features/auth/password";
+import { useTranslations } from "next-intl";
+import { toast } from "@/hooks/useToast";
+
+export default function ResetPasswordPage() {
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const t = useTranslations("");
+ const token = searchParams.get("token");
+
+ useEffect(() => {
+ if (!token) {
+ toast({
+ variant: "destructive",
+ title: t("error"),
+ description: t("invalidResetToken"),
+ });
+ router.push("/login");
+ }
+ }, [token, router, t]);
+
+ if (!token) {
+ return null;
+ }
+
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/(auth)/verify-email/page.tsx b/app/(pages)/(auth)/verify-email/page.tsx
new file mode 100644
index 0000000..5d87bd6
--- /dev/null
+++ b/app/(pages)/(auth)/verify-email/page.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { VerifyEmailContainer } from "@/features/auth/verify-email/components/VerifyEmailContainer";
+import { VerifyEmailPresentational } from "@/features/auth/verify-email/components/VerifyEmailPresentational";
+
+export default function VerifyEmailPage() {
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/dashboard/appointments/[appointmentId]/online/lobby/page.tsx b/app/(pages)/(dashboards)/dashboard/appointments/[appointmentId]/online/lobby/page.tsx
new file mode 100644
index 0000000..abab63d
--- /dev/null
+++ b/app/(pages)/(dashboards)/dashboard/appointments/[appointmentId]/online/lobby/page.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { useParams, useSearchParams } from "next/navigation";
+import { PatientDashboardLayout } from "@/features/dashboards/dashboard";
+import {
+ OnlineLobbyContainer,
+ OnlineLobbyPresentational,
+} from "@/features/appointment-session";
+
+export default function PatientOnlineLobbyPage() {
+ const params = useParams<{ appointmentId: string }>();
+ const searchParams = useSearchParams();
+ const startAt = searchParams.get("startAt");
+ const endAt = searchParams.get("endAt");
+
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/dashboard/appointments/[appointmentId]/online/room/page.tsx b/app/(pages)/(dashboards)/dashboard/appointments/[appointmentId]/online/room/page.tsx
new file mode 100644
index 0000000..2edae37
--- /dev/null
+++ b/app/(pages)/(dashboards)/dashboard/appointments/[appointmentId]/online/room/page.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { useParams, useSearchParams } from "next/navigation";
+import { PatientDashboardLayout } from "@/features/dashboards/dashboard";
+import {
+ OnlineRoomContainer,
+ OnlineRoomPresentational,
+} from "@/features/appointment-session";
+
+const parseBoolean = (value: string | null, fallback: boolean) => {
+ if (value === null) return fallback;
+ return value === "true";
+};
+
+export default function PatientOnlineRoomPage() {
+ const params = useParams<{ appointmentId: string }>();
+ const searchParams = useSearchParams();
+
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/dashboard/appointments/page.tsx b/app/(pages)/(dashboards)/dashboard/appointments/page.tsx
new file mode 100644
index 0000000..042902d
--- /dev/null
+++ b/app/(pages)/(dashboards)/dashboard/appointments/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import {
+ AppointmentsContainer,
+ AppointmentsPresentational,
+ PatientDashboardLayout,
+} from "@/features/dashboards/dashboard";
+
+export default function AppointmentsPage() {
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/dashboard/medical-history/page.tsx b/app/(pages)/(dashboards)/dashboard/medical-history/page.tsx
new file mode 100644
index 0000000..6250d1b
--- /dev/null
+++ b/app/(pages)/(dashboards)/dashboard/medical-history/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import {
+ MedicalHistoryContainer,
+ MedicalHistoryPresentational,
+ PatientDashboardLayout,
+} from "@/features/dashboards/dashboard";
+
+export default function MedicalHistoryPage() {
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/dashboard/page.tsx b/app/(pages)/(dashboards)/dashboard/page.tsx
new file mode 100644
index 0000000..5c40588
--- /dev/null
+++ b/app/(pages)/(dashboards)/dashboard/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import {
+ DashboardContainer,
+ DashboardPresentational,
+ PatientDashboardLayout,
+} from "@/features/dashboards/dashboard";
+
+export default function DashboardPage() {
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/announcements/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/announcements/page.tsx
new file mode 100644
index 0000000..32dfae8
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/announcements/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import {
+ DoctorDashboardContainer,
+ DoctorAnnouncementsTab,
+} from "@/features/dashboards/doctor-dashboard";
+
+export default function AnnouncementsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/offline-session/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/offline-session/page.tsx
new file mode 100644
index 0000000..3b2840a
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/offline-session/page.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { useParams, useSearchParams } from "next/navigation";
+import { DoctorDashboardContainer } from "@/features/dashboards/doctor-dashboard";
+import {
+ OfflineSessionContainer,
+ OfflineSessionPresentational,
+} from "@/features/appointment-session";
+
+export default function DoctorOfflineSessionPage() {
+ const params = useParams<{ appointmentId: string }>();
+ const searchParams = useSearchParams();
+ const startAt = searchParams.get("startAt");
+
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/online/lobby/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/online/lobby/page.tsx
new file mode 100644
index 0000000..1565b09
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/online/lobby/page.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { useParams, useSearchParams } from "next/navigation";
+import { DoctorDashboardContainer } from "@/features/dashboards/doctor-dashboard";
+import {
+ OnlineLobbyContainer,
+ OnlineLobbyPresentational,
+} from "@/features/appointment-session";
+
+export default function DoctorOnlineLobbyPage() {
+ const params = useParams<{ appointmentId: string }>();
+ const searchParams = useSearchParams();
+ const startAt = searchParams.get("startAt");
+ const endAt = searchParams.get("endAt");
+
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/online/room/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/online/room/page.tsx
new file mode 100644
index 0000000..e753f08
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/online/room/page.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { useParams, useSearchParams } from "next/navigation";
+import { DoctorDashboardContainer } from "@/features/dashboards/doctor-dashboard";
+import {
+ OnlineRoomContainer,
+ OnlineRoomPresentational,
+} from "@/features/appointment-session";
+
+const parseBoolean = (value: string | null, fallback: boolean) => {
+ if (value === null) return fallback;
+ return value === "true";
+};
+
+export default function DoctorOnlineRoomPage() {
+ const params = useParams<{ appointmentId: string }>();
+ const searchParams = useSearchParams();
+
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/soap-review/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/soap-review/page.tsx
new file mode 100644
index 0000000..4e18793
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/appointments/[appointmentId]/soap-review/page.tsx
@@ -0,0 +1,20 @@
+"use client";
+
+import { useParams } from "next/navigation";
+import { DoctorDashboardContainer } from "@/features/dashboards/doctor-dashboard";
+import {
+ SoapReviewContainer,
+ SoapReviewPresentational,
+} from "@/features/appointment-session";
+
+export default function DoctorSoapReviewPage() {
+ const params = useParams<{ appointmentId: string }>();
+
+ return (
+
+
+ {(props) => }
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/appointments/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/appointments/page.tsx
new file mode 100644
index 0000000..7c363dc
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/appointments/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import {
+ DoctorDashboardContainer,
+ AppointmentsTab,
+} from "@/features/dashboards/doctor-dashboard";
+
+export default function AppointmentsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/clinics/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/clinics/page.tsx
new file mode 100644
index 0000000..f947cde
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/clinics/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import {
+ DoctorDashboardContainer,
+ ClinicsTab,
+ useClinics,
+} from "@/features/dashboards/doctor-dashboard";
+
+export default function ClinicsPage() {
+ const { data: clinicsData, isLoading } = useClinics();
+
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/nurses/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/nurses/page.tsx
new file mode 100644
index 0000000..40a7746
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/nurses/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import {
+ DoctorDashboardContainer,
+ NursesTab,
+} from "@/features/dashboards/doctor-dashboard";
+
+export default function NursesPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/page.tsx
new file mode 100644
index 0000000..edc4b53
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/page.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import {
+ DoctorDashboardContainer,
+ DashboardStats,
+} from "@/features/dashboards/doctor-dashboard";
+
+export default function DoctorDashboardPage() {
+ const t = useTranslations("doctorDashboard");
+
+ return (
+
+
+
+
{t("dashboard")}
+
+
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/schedule/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/schedule/page.tsx
new file mode 100644
index 0000000..eb33fca
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/schedule/page.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import {
+ DoctorDashboardContainer,
+ ScheduleTab,
+ useClinics,
+} from "@/features/dashboards/doctor-dashboard";
+
+export default function SchedulePage() {
+ const { data: clinicsData } = useClinics();
+
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/doctor-dashboard/vacation/page.tsx b/app/(pages)/(dashboards)/doctor-dashboard/vacation/page.tsx
new file mode 100644
index 0000000..261b5e7
--- /dev/null
+++ b/app/(pages)/(dashboards)/doctor-dashboard/vacation/page.tsx
@@ -0,0 +1,18 @@
+"use client";
+
+import {
+ DoctorDashboardContainer,
+ VacationTab,
+ useClinics,
+} from "@/features/dashboards/doctor-dashboard";
+
+export default function VacationPage() {
+ const { data: clinicsData } = useClinics();
+ const clinics = clinicsData?.data || [];
+
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/nurse-dashboard/applications/page.tsx b/app/(pages)/(dashboards)/nurse-dashboard/applications/page.tsx
new file mode 100644
index 0000000..1438ce9
--- /dev/null
+++ b/app/(pages)/(dashboards)/nurse-dashboard/applications/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import {
+ NurseDashboardContainer,
+ ApplicationsTab,
+} from "@/features/dashboards/nurse-dashboard";
+
+export default function ApplicationsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/nurse-dashboard/appointments/page.tsx b/app/(pages)/(dashboards)/nurse-dashboard/appointments/page.tsx
new file mode 100644
index 0000000..78047b4
--- /dev/null
+++ b/app/(pages)/(dashboards)/nurse-dashboard/appointments/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import {
+ NurseDashboardContainer,
+ NurseAppointmentsTab,
+} from "@/features/dashboards/nurse-dashboard";
+
+export default function AppointmentsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/nurse-dashboard/page.tsx b/app/(pages)/(dashboards)/nurse-dashboard/page.tsx
new file mode 100644
index 0000000..77a5855
--- /dev/null
+++ b/app/(pages)/(dashboards)/nurse-dashboard/page.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import {
+ NurseDashboardContainer,
+ NurseDashboardStats,
+} from "@/features/dashboards/nurse-dashboard";
+
+export default function NurseDashboardPage() {
+ const t = useTranslations("nurseDashboard");
+
+ return (
+
+
+
+ {t("dashboard")}
+
+
+
+
+
+ );
+}
diff --git a/app/(pages)/(dashboards)/nurse-dashboard/schedule/page.tsx b/app/(pages)/(dashboards)/nurse-dashboard/schedule/page.tsx
new file mode 100644
index 0000000..6065165
--- /dev/null
+++ b/app/(pages)/(dashboards)/nurse-dashboard/schedule/page.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import {
+ NurseDashboardContainer,
+ ScheduleTab,
+} from "@/features/dashboards/nurse-dashboard";
+
+export default function SchedulePage() {
+ return (
+
+
+
+ );
+}
diff --git a/app/(pages)/announcements/page.tsx b/app/(pages)/announcements/page.tsx
new file mode 100644
index 0000000..b9f82fc
--- /dev/null
+++ b/app/(pages)/announcements/page.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { AnnouncementsContainer } from "@/features/join/nurse/components/announcements/AnnouncementsContainer";
+import { AnnouncementsPresentational } from "@/features/join/nurse/components/announcements/AnnouncementsPresentational";
+
+export default function AnnouncementsPage() {
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/clinics/page.tsx b/app/(pages)/clinics/page.tsx
new file mode 100644
index 0000000..0c6f3ed
--- /dev/null
+++ b/app/(pages)/clinics/page.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { parseAsStringEnum, useQueryState } from "nuqs";
+import {
+ ClinicsSidebar,
+ DoctorsView,
+ ClinicsView,
+ DoctorsFilters,
+ ClinicsFilters,
+} from "@/features/clinics";
+
+type View = "doctors" | "clinics";
+
+export default function ClinicsPage() {
+ const [view] = useQueryState(
+ "view",
+ parseAsStringEnum(["doctors", "clinics"]).withDefault("doctors"),
+ );
+
+ return (
+
+
+
+
+
+ {view === "doctors" ? : }
+
+
+
+ );
+}
diff --git a/app/(pages)/clinics/schedule/page.tsx b/app/(pages)/clinics/schedule/page.tsx
new file mode 100644
index 0000000..370f64b
--- /dev/null
+++ b/app/(pages)/clinics/schedule/page.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { DoctorSchedule } from "@/features/clinics";
+
+export default function SchedulePage() {
+ return ;
+}
diff --git a/app/(pages)/join/doctor/page.tsx b/app/(pages)/join/doctor/page.tsx
new file mode 100644
index 0000000..1be81c2
--- /dev/null
+++ b/app/(pages)/join/doctor/page.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { DoctorJoinContainer } from "@/features/join/doctor/components/DoctorJoinContainer";
+import { DoctorJoinPresentational } from "@/features/join/doctor/components/DoctorJoinPresentational";
+
+export default function DoctorJoinPage() {
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/join/nurse/page.tsx b/app/(pages)/join/nurse/page.tsx
new file mode 100644
index 0000000..106d31f
--- /dev/null
+++ b/app/(pages)/join/nurse/page.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { NurseJoinContainer } from "@/features/join/nurse/components/NurseJoinContainer";
+import { NurseJoinPresentational } from "@/features/join/nurse/components/NurseJoinPresentational";
+
+export default function NurseJoinPage() {
+ return (
+
+ {(props) => }
+
+ );
+}
diff --git a/app/(pages)/settings/change-password/page.tsx b/app/(pages)/settings/change-password/page.tsx
new file mode 100644
index 0000000..100aa89
--- /dev/null
+++ b/app/(pages)/settings/change-password/page.tsx
@@ -0,0 +1,5 @@
+import { ChangePasswordContainer } from "@/features/settings";
+
+export default function ChangePasswordPage() {
+ return ;
+}
diff --git a/app/(pages)/settings/general/page.tsx b/app/(pages)/settings/general/page.tsx
new file mode 100644
index 0000000..c3edea6
--- /dev/null
+++ b/app/(pages)/settings/general/page.tsx
@@ -0,0 +1,5 @@
+import { GeneralSettingsContainer } from "@/features/settings";
+
+export default function GeneralSettingsPage() {
+ return ;
+}
diff --git a/app/(pages)/settings/layout.tsx b/app/(pages)/settings/layout.tsx
new file mode 100644
index 0000000..a6dc417
--- /dev/null
+++ b/app/(pages)/settings/layout.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { ReactNode } from "react";
+import { Sidebar } from "@/components/layout/Sidebar";
+import { settingsNavigationItems } from "@/constants/SettingsNavigationItems";
+import { Settings as SettingsIcon } from "lucide-react";
+
+interface SettingsLayoutProps {
+ children: ReactNode;
+}
+
+export default function SettingsLayout({ children }: SettingsLayoutProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/app/(pages)/settings/page.tsx b/app/(pages)/settings/page.tsx
new file mode 100644
index 0000000..13bc5f6
--- /dev/null
+++ b/app/(pages)/settings/page.tsx
@@ -0,0 +1,5 @@
+import { redirect } from "next/navigation";
+
+export default function SettingsPage() {
+ redirect("/settings/general");
+}
diff --git a/app/(pages)/settings/profile/page.tsx b/app/(pages)/settings/profile/page.tsx
new file mode 100644
index 0000000..c22d209
--- /dev/null
+++ b/app/(pages)/settings/profile/page.tsx
@@ -0,0 +1,5 @@
+import { UpdateProfileContainer } from "@/features/settings";
+
+export default function ProfilePage() {
+ return ;
+}
diff --git a/app/api/auth/google-callback/route.ts b/app/api/auth/google-callback/route.ts
new file mode 100644
index 0000000..bc8d8e4
--- /dev/null
+++ b/app/api/auth/google-callback/route.ts
@@ -0,0 +1,74 @@
+import { NextRequest, NextResponse } from "next/server";
+import { cookies } from "next/headers";
+
+export async function GET(request: NextRequest) {
+
+ try {
+ const cookieStore = await cookies();
+
+ const authToken = cookieStore.get("Authorization")?.value;
+ const refreshToken = cookieStore.get("RefreshToken")?.value;
+
+ if (!authToken && !refreshToken) {
+ console.error("No auth tokens found in callback");
+ return NextResponse.redirect(
+ new URL("/login?error=auth_failed", request.url)
+ );
+ }
+
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL;
+ const baseUrl = apiUrl?.replace(/\/api$/, "") || "http://localhost:3000";
+ const userDataUrl = `${baseUrl}/auth/google/userData`;
+
+ let userState = {
+ isVerified: true,
+ hasCompletedProfile: false,
+ };
+
+ try {
+ const userResponse = await fetch(userDataUrl, {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ ...(refreshToken ? { Cookie: `RefreshToken=${refreshToken}` } : {}),
+ },
+ credentials: "include",
+ });
+
+ if (userResponse.ok) {
+ const responseData = await userResponse.json();
+ const userData = responseData.data;
+
+ userState = {
+ isVerified: userData.isVerified !== false,
+ hasCompletedProfile: userData.hasCompletedProfile || false,
+ };
+
+ } else {
+ const errorText = await userResponse.text();
+ console.warn("Error response:", errorText);
+ }
+ } catch (error) {
+ console.error("Error fetching:", error);
+ }
+ const redirectUrl = userState.hasCompletedProfile
+ ? "/dashboard"
+ : "/complete-profile";
+
+ const response = NextResponse.redirect(new URL(redirectUrl, request.url));
+
+ response.cookies.set("UserState", JSON.stringify(userState), {
+ httpOnly: false,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "lax",
+ path: "/",
+ maxAge: 60 * 60 * 24 * 7,
+ });
+
+ return response;
+ } catch (error) {
+ console.error("Google OAuth callback error:", error);
+ return NextResponse.redirect(
+ new URL("/login?error=callback_failed", request.url)
+ );
+ }
+}
diff --git a/app/api/auth/refresh/route.ts b/app/api/auth/refresh/route.ts
new file mode 100644
index 0000000..a50970f
--- /dev/null
+++ b/app/api/auth/refresh/route.ts
@@ -0,0 +1,198 @@
+import { NextRequest, NextResponse } from "next/server";
+
+const API_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:3000";
+
+export async function POST(request: NextRequest) {
+ try {
+ const authCookie = request.cookies.get("Authorization");
+ const refreshCookie = request.cookies.get("RefreshToken");
+
+ if (!refreshCookie) {
+ console.error(
+ "ā [Refresh Route] No refresh token found in request cookies",
+ );
+ const response = NextResponse.json(
+ {
+ messageEn: "No refresh token found",
+ messageAr: "ŁŁ
ŁŲŖŁ
Ų§ŁŲ¹Ų«ŁŲ± Ų¹ŁŁ Ų±Ł
Ų² Ų§ŁŲŖŲŲÆŁŲ«",
+ },
+ { status: 401 },
+ );
+
+ response.cookies.set("Authorization", "", { maxAge: 0, path: "/" });
+ response.cookies.set("RefreshToken", "", { maxAge: 0, path: "/" });
+ response.cookies.set("UserState", "", { maxAge: 0, path: "/" });
+ response.cookies.set("AuthFailure", "true", { maxAge: 60, path: "/" });
+
+ return response;
+ }
+
+ const cookieHeader = authCookie?.value
+ ? `Authorization=${authCookie.value}; RefreshToken=${refreshCookie.value}`
+ : `RefreshToken=${refreshCookie.value}`;
+
+ const response = await fetch(`${API_URL}/auth/refresh`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Cookie: cookieHeader,
+ },
+ credentials: "include",
+ });
+
+ let data;
+ try {
+ data = await response.json();
+ } catch (parseError) {
+ console.error(
+ "ā [Refresh Route] Failed to parse backend response",
+ parseError,
+ );
+
+ if (response.status === 401) {
+ const errorResponse = NextResponse.json(
+ {
+ messageEn: "Invalid refresh token",
+ messageAr: "Ų±Ł
Ų² ŲŖŲŲÆŁŲ« ŲŗŁŲ± ŲµŲ§ŁŲ",
+ },
+ { status: 401 },
+ );
+ errorResponse.cookies.set("Authorization", "", {
+ maxAge: 0,
+ path: "/",
+ });
+ errorResponse.cookies.set("RefreshToken", "", { maxAge: 0, path: "/" });
+ errorResponse.cookies.set("UserState", "", { maxAge: 0, path: "/" });
+ errorResponse.cookies.set("AuthFailure", "true", {
+ maxAge: 60,
+ path: "/",
+ });
+ return errorResponse;
+ }
+
+ return NextResponse.json(
+ {
+ messageEn: "Failed to process refresh response",
+ messageAr: "ŁŲ“Ł Ł
Ų¹Ų§ŁŲ¬Ų© Ų§Ų³ŲŖŲ¬Ų§ŲØŲ© Ų§ŁŲŖŲŲÆŁŲ«",
+ },
+ { status: 500 },
+ );
+ }
+
+ if (!response.ok) {
+ console.error(
+ `ā [Refresh Route] Backend refresh failed with status: ${response.status}`,
+ data,
+ );
+
+ const errorResponse = NextResponse.json(data, {
+ status: response.status,
+ });
+
+ if (response.status === 401) {
+ console.error(
+ "š [Refresh Route] Unauthorized - clearing all auth cookies",
+ );
+ errorResponse.cookies.set("Authorization", "", {
+ maxAge: 0,
+ path: "/",
+ });
+ errorResponse.cookies.set("RefreshToken", "", { maxAge: 0, path: "/" });
+ errorResponse.cookies.set("UserState", "", { maxAge: 0, path: "/" });
+ errorResponse.cookies.set("AuthFailure", "true", {
+ maxAge: 60,
+ path: "/",
+ });
+ } else {
+ console.warn(
+ "ā ļø [Refresh Route] Backend error (not 401) - keeping cookies, might retry later",
+ );
+ }
+
+ return errorResponse;
+ }
+
+ const nextResponse = NextResponse.json(data, { status: 200 });
+
+ const setCookieHeaders = response.headers.getSetCookie();
+ if (setCookieHeaders && setCookieHeaders.length > 0) {
+ setCookieHeaders.forEach((cookie, index) => {
+ nextResponse.headers.append("Set-Cookie", cookie);
+
+ const cookieMatch = cookie.match(/^([^=]+)=([^;]+)/);
+ if (cookieMatch) {
+ const [, name, value] = cookieMatch;
+
+ const maxAgeMatch = cookie.match(/Max-Age=(\d+)/);
+ const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1]) : undefined;
+
+ nextResponse.cookies.set(name, value, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "lax",
+ path: "/",
+ maxAge: maxAge,
+ });
+ }
+ });
+ } else {
+ console.warn(
+ "ā ļø [Refresh Route] No Set-Cookie headers from backend - cookies won't be updated!",
+ );
+ console.warn(
+ "ā ļø [Refresh Route] Backend might not be setting cookies in response",
+ );
+ }
+
+ if (data?.data?.user) {
+ const user = data.data.user;
+ const userState = {
+ isVerified: user.isVerified,
+ hasCompletedProfile: user.hasCompletedProfile,
+ role: user.role,
+ };
+
+ nextResponse.cookies.set("UserState", JSON.stringify(userState), {
+ httpOnly: false,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "lax",
+ path: "/",
+ maxAge: 60 * 60 * 24 * 7,
+ });
+
+ nextResponse.cookies.set("AuthFailure", "", { maxAge: 0, path: "/" });
+ }
+
+ return nextResponse;
+ } catch (error) {
+ console.error("ā [Refresh Route] Unexpected error:", error);
+
+ const isNetworkError =
+ error instanceof TypeError && error.message.includes("fetch");
+
+ if (isNetworkError) {
+ console.error("š [Refresh Route] Network error - backend might be down");
+ return NextResponse.json(
+ {
+ messageEn: "Unable to connect to authentication server",
+ messageAr: "ŲŗŁŲ± ŁŲ§ŲÆŲ± Ų¹ŁŁ Ų§ŁŲ§ŲŖŲµŲ§Ł ŲØŲ®Ų§ŲÆŁ
Ų§ŁŁ
ŲµŲ§ŲÆŁŲ©",
+ },
+ { status: 503 },
+ );
+ }
+
+ const errorResponse = NextResponse.json(
+ {
+ messageEn: "Failed to refresh token",
+ messageAr: "ŁŲ“Ł ŲŖŲŲÆŁŲ« Ų§ŁŲ±Ł
Ų²",
+ },
+ { status: 500 },
+ );
+
+ console.warn(
+ "ā ļø [Refresh Route] Unexpected error - keeping cookies for potential retry",
+ );
+
+ return errorResponse;
+ }
+}
diff --git a/app/favicon.ico b/app/favicon.ico
index 718d6fe..ab9bab8 100644
Binary files a/app/favicon.ico and b/app/favicon.ico differ
diff --git a/app/globals.css b/app/globals.css
index a316cee..911b8cd 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -3,115 +3,257 @@
@custom-variant dark (&:is(.dark *));
+:root {
+ --background: oklch(0.9730 0.0133 286.1503);
+ --foreground: oklch(0.3015 0.0572 282.4176);
+ --card: oklch(1.0000 0 0);
+ --card-foreground: oklch(0.3015 0.0572 282.4176);
+ --popover: oklch(1.0000 0 0);
+ --popover-foreground: oklch(0.3015 0.0572 282.4176);
+ --primary: oklch(0.5417 0.1790 288.0332);
+ --primary-foreground: oklch(1.0000 0 0);
+ --secondary: oklch(0.9174 0.0435 292.6901);
+ --secondary-foreground: oklch(0.4143 0.1039 288.1742);
+ --muted: oklch(0.9580 0.0133 286.1454);
+ --muted-foreground: oklch(0.5426 0.0465 284.7435);
+ --accent: oklch(0.9221 0.0373 262.1410);
+ --accent-foreground: oklch(0.3015 0.0572 282.4176);
+ --destructive: oklch(0.6861 0.2061 14.9941);
+ --destructive-foreground: oklch(1.0000 0 0);
+ --border: oklch(0.9115 0.0216 285.9625);
+ --input: oklch(0.9115 0.0216 285.9625);
+ --ring: oklch(0.5417 0.1790 288.0332);
+ --chart-1: oklch(0.5417 0.1790 288.0332);
+ --chart-2: oklch(0.7042 0.1602 288.9880);
+ --chart-3: oklch(0.5679 0.2113 276.7065);
+ --chart-4: oklch(0.6356 0.1922 281.8054);
+ --chart-5: oklch(0.4509 0.1758 279.3838);
+ --sidebar: oklch(0.9580 0.0133 286.1454);
+ --sidebar-foreground: oklch(0.3015 0.0572 282.4176);
+ --sidebar-primary: oklch(0.5417 0.1790 288.0332);
+ --sidebar-primary-foreground: oklch(1.0000 0 0);
+ --sidebar-accent: oklch(0.9221 0.0373 262.1410);
+ --sidebar-accent-foreground: oklch(0.3015 0.0572 282.4176);
+ --sidebar-border: oklch(0.9115 0.0216 285.9625);
+ --sidebar-ring: oklch(0.5417 0.1790 288.0332);
+ --font-sans: Inter, sans-serif;
+ --font-serif: Georgia, serif;
+ --font-mono: JetBrains Mono, monospace;
+ --radius: 0.5rem;
+ --shadow-x: 0px;
+ --shadow-y: 4px;
+ --shadow-blur: 10px;
+ --shadow-spread: 0px;
+ --shadow-opacity: 0.12;
+ --shadow-color: hsl(240 30% 25%);
+ --shadow-2xs: 0px 4px 10px 0px hsl(240 30% 25% / 0.06);
+ --shadow-xs: 0px 4px 10px 0px hsl(240 30% 25% / 0.06);
+ --shadow-sm: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12);
+ --shadow: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12);
+ --shadow-md: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px hsl(240 30% 25% / 0.12);
+ --shadow-lg: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px hsl(240 30% 25% / 0.12);
+ --shadow-xl: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px hsl(240 30% 25% / 0.12);
+ --shadow-2xl: 0px 4px 10px 0px hsl(240 30% 25% / 0.30);
+ --tracking-normal: 0em;
+ --spacing: 0.25rem;
+}
+
+.dark {
+ --background: oklch(0.1743 0.0227 283.7998);
+ --foreground: oklch(0.9185 0.0257 285.8834);
+ --card: oklch(0.2284 0.0384 282.9324);
+ --card-foreground: oklch(0.9185 0.0257 285.8834);
+ --popover: oklch(0.2284 0.0384 282.9324);
+ --popover-foreground: oklch(0.9185 0.0257 285.8834);
+ --primary: oklch(0.7162 0.1597 290.3962);
+ --primary-foreground: oklch(0.1743 0.0227 283.7998);
+ --secondary: oklch(0.3139 0.0736 283.4591);
+ --secondary-foreground: oklch(0.8367 0.0849 285.9111);
+ --muted: oklch(0.2710 0.0621 281.4377);
+ --muted-foreground: oklch(0.7166 0.0462 285.1741);
+ --accent: oklch(0.3354 0.0828 280.9705);
+ --accent-foreground: oklch(0.9185 0.0257 285.8834);
+ --destructive: oklch(0.6861 0.2061 14.9941);
+ --destructive-foreground: oklch(1.0000 0 0);
+ --border: oklch(0.3261 0.0597 282.5832);
+ --input: oklch(0.3261 0.0597 282.5832);
+ --ring: oklch(0.7162 0.1597 290.3962);
+ --chart-1: oklch(0.7162 0.1597 290.3962);
+ --chart-2: oklch(0.6382 0.1047 274.9117);
+ --chart-3: oklch(0.7482 0.1235 244.7492);
+ --chart-4: oklch(0.7124 0.0977 186.6761);
+ --chart-5: oklch(0.7546 0.1831 346.8124);
+ --sidebar: oklch(0.2284 0.0384 282.9324);
+ --sidebar-foreground: oklch(0.9185 0.0257 285.8834);
+ --sidebar-primary: oklch(0.7162 0.1597 290.3962);
+ --sidebar-primary-foreground: oklch(0.1743 0.0227 283.7998);
+ --sidebar-accent: oklch(0.3354 0.0828 280.9705);
+ --sidebar-accent-foreground: oklch(0.9185 0.0257 285.8834);
+ --sidebar-border: oklch(0.3261 0.0597 282.5832);
+ --sidebar-ring: oklch(0.7162 0.1597 290.3962);
+ --font-sans: Inter, sans-serif;
+ --font-serif: Georgia, serif;
+ --font-mono: JetBrains Mono, monospace;
+ --radius: 0.5rem;
+ --shadow-x: 0px;
+ --shadow-y: 4px;
+ --shadow-blur: 10px;
+ --shadow-spread: 0px;
+ --shadow-opacity: 0.12;
+ --shadow-color: hsl(240 30% 25%);
+ --shadow-2xs: 0px 4px 10px 0px hsl(240 30% 25% / 0.06);
+ --shadow-xs: 0px 4px 10px 0px hsl(240 30% 25% / 0.06);
+ --shadow-sm: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12);
+ --shadow: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12);
+ --shadow-md: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px hsl(240 30% 25% / 0.12);
+ --shadow-lg: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px hsl(240 30% 25% / 0.12);
+ --shadow-xl: 0px 4px 10px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px hsl(240 30% 25% / 0.12);
+ --shadow-2xl: 0px 4px 10px 0px hsl(240 30% 25% / 0.30);
+}
+
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
- --color-sidebar-ring: var(--sidebar-ring);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar: var(--sidebar);
- --color-chart-5: var(--chart-5);
- --color-chart-4: var(--chart-4);
- --color-chart-3: var(--chart-3);
- --color-chart-2: var(--chart-2);
- --color-chart-1: var(--chart-1);
- --color-ring: var(--ring);
- --color-input: var(--input);
- --color-border: var(--border);
- --color-destructive: var(--destructive);
- --color-accent-foreground: var(--accent-foreground);
- --color-accent: var(--accent);
- --color-muted-foreground: var(--muted-foreground);
- --color-muted: var(--muted);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-secondary: var(--secondary);
- --color-primary-foreground: var(--primary-foreground);
- --color-primary: var(--primary);
- --color-popover-foreground: var(--popover-foreground);
- --color-popover: var(--popover);
- --color-card-foreground: var(--card-foreground);
--color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+
+ --font-sans: var(--font-sans);
+ --font-mono: var(--font-mono);
+ --font-serif: var(--font-serif);
+
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
+
+ --shadow-2xs: var(--shadow-2xs);
+ --shadow-xs: var(--shadow-xs);
+ --shadow-sm: var(--shadow-sm);
+ --shadow: var(--shadow);
+ --shadow-md: var(--shadow-md);
+ --shadow-lg: var(--shadow-lg);
+ --shadow-xl: var(--shadow-xl);
+ --shadow-2xl: var(--shadow-2xl);
}
-:root {
- --radius: 0.65rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.141 0.005 285.823);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.141 0.005 285.823);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.141 0.005 285.823);
- --primary: oklch(0.488 0.243 264.376);
- --primary-foreground: oklch(0.97 0.014 254.604);
- --secondary: oklch(0.967 0.001 286.375);
- --secondary-foreground: oklch(0.21 0.006 285.885);
- --muted: oklch(0.967 0.001 286.375);
- --muted-foreground: oklch(0.552 0.016 285.938);
- --accent: oklch(0.967 0.001 286.375);
- --accent-foreground: oklch(0.21 0.006 285.885);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.92 0.004 286.32);
- --input: oklch(0.92 0.004 286.32);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.809 0.105 251.813);
- --chart-2: oklch(0.623 0.214 259.815);
- --chart-3: oklch(0.546 0.245 262.881);
- --chart-4: oklch(0.488 0.243 264.376);
- --chart-5: oklch(0.424 0.199 265.638);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.141 0.005 285.823);
- --sidebar-primary: oklch(0.546 0.245 262.881);
- --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
- --sidebar-accent: oklch(0.967 0.001 286.375);
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
- --sidebar-border: oklch(0.92 0.004 286.32);
- --sidebar-ring: oklch(0.708 0 0);
+input[type="time"]::-webkit-calendar-picker-indicator {
+ display: none;
}
-.dark {
- --background: oklch(0.141 0.005 285.823);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.21 0.006 285.885);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.21 0.006 285.885);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.488 0.243 264.376);
- --primary-foreground: oklch(0.97 0.014 254.604);
- --secondary: oklch(0.274 0.006 286.033);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.274 0.006 286.033);
- --muted-foreground: oklch(0.705 0.015 286.067);
- --accent: oklch(0.274 0.006 286.033);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.809 0.105 251.813);
- --chart-2: oklch(0.623 0.214 259.815);
- --chart-3: oklch(0.546 0.245 262.881);
- --chart-4: oklch(0.488 0.243 264.376);
- --chart-5: oklch(0.424 0.199 265.638);
- --sidebar: oklch(0.21 0.006 285.885);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.623 0.214 259.815);
- --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
- --sidebar-accent: oklch(0.274 0.006 286.033);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.439 0 0);
+input[type="time"]::-webkit-inner-spin-button {
+ display: none;
+}
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+input[type="number"] {
+ -moz-appearance: textfield;
+ appearance: textfield;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
}
+::-webkit-scrollbar-thumb {
+ background: oklch(0.5417 0.1790 288.0332 / 0.25);
+ border-radius: 100px;
+ border: 2px solid transparent;
+ background-clip: padding-box;
+ transition: background 0.2s ease;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: oklch(0.5417 0.1790 288.0332 / 0.55);
+ background-clip: padding-box;
+}
+
+::-webkit-scrollbar-thumb:active {
+ background: oklch(0.5417 0.1790 288.0332 / 0.80);
+ background-clip: padding-box;
+}
+
+::-webkit-scrollbar-corner {
+ background: transparent;
+}
+
+.dark ::-webkit-scrollbar-thumb {
+ background: oklch(0.7162 0.1597 290.3962 / 0.25);
+ background-clip: padding-box;
+}
+
+.dark ::-webkit-scrollbar-thumb:hover {
+ background: oklch(0.7162 0.1597 290.3962 / 0.55);
+ background-clip: padding-box;
+}
+
+.dark ::-webkit-scrollbar-thumb:active {
+ background: oklch(0.7162 0.1597 290.3962 / 0.80);
+ background-clip: padding-box;
+}
+
+* {
+ scrollbar-width: thin;
+ scrollbar-color: oklch(0.5417 0.1790 288.0332 / 0.30) transparent;
+}
+
+.dark * {
+ scrollbar-color: oklch(0.7162 0.1597 290.3962 / 0.30) transparent;
+}
+
+.scrollbar-hidden {
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+.scrollbar-hidden::-webkit-scrollbar {
+ display: none;
+}
+
+.scrollbar-thick::-webkit-scrollbar {
+ width: 12px;
+ height: 12px;
+}
+
+.scrollbar-overlay {
+ overflow: overlay;
+}
@layer base {
* {
diff --git a/app/layout.tsx b/app/layout.tsx
index 5629056..779db01 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -3,8 +3,15 @@ import { Geist, Geist_Mono } from "next/font/google";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import { cookies } from "next/headers";
+import { NuqsAdapter } from "nuqs/adapters/next/app";
import { ThemeProvider } from "@/contexts/ThemeProvider";
import { LanguageProvider } from "@/contexts/LanguageProvider";
+import { UiPreferencesProvider } from "@/contexts/UiPreferencesProvider";
+import { SocketProvider } from "@/contexts/SocketProvider";
+import { QueryProvider } from "@/contexts/QueryProvider";
+import { Toaster } from "@/components/ui/toaster";
+import { Navbar, Footer } from "@/components/layout";
+import { InteractiveBackgroundLayer } from "@/components/ui/InteractiveBackgroundLayer";
import "./globals.css";
const geistSans = Geist({
@@ -18,8 +25,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "EMR Blockchain System",
- description: "Blockchain-based Electronic Medical Records System",
+ title: "HoloCura",
+ description: "One Platform. Complete Care.",
};
export default async function RootLayout({
@@ -40,13 +47,27 @@ export default async function RootLayout({
-
-
-
- {children}
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+
+