change project structure

This commit is contained in:
vergiLgood1 2025-03-12 16:13:57 +07:00
parent 0dc1717704
commit bf84395efe
102 changed files with 213 additions and 582 deletions

View File

@ -1,4 +1,31 @@
# Update these with your Supabase details from your project settings > API
# https://app.supabase.com/project/_/settings/api
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# Supabase Production URL
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_SECRET=
NEXT_PUBLIC_SUPABASE_STORAGE_URL=
# Supabase Local URL
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
# Supabase Service Role Secret Key
SERVICE_ROLE_SECRET=
# RESEND_API_KEY_TES=
RESEND_API_KEY=
SEND_EMAIL_HOOK_SECRET=
# db connection string
# Connect to Supabase via connection pooling with Supavisor.
DATABASE_URL=
# Direct connection to the database. Used for migrations.
DIRECT_URL=
DENO_ENV=
SIGAP_MAPBOX_ACCESS_TOKEN=

View File

@ -1,40 +0,0 @@
"use server";
import { createClient } from "@/utils/supabase/server";
import { encodedRedirect } from "@/utils/utils";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
export const forgotPasswordAction = async (formData: FormData) => {
const email = formData.get("email")?.toString();
const supabase = await createClient();
const origin = (await headers()).get("origin");
const callbackUrl = formData.get("callbackUrl")?.toString();
if (!email) {
return encodedRedirect("error", "/forgot-password", "Email is required");
}
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${origin}/auth/callback?redirect_to=/protected/reset-password`,
});
if (error) {
console.error(error.message);
return encodedRedirect(
"error",
"/forgot-password",
"Could not reset password",
);
}
if (callbackUrl) {
return redirect(callbackUrl);
}
return encodedRedirect(
"success",
"/forgot-password",
"Check your email for a link to reset your password.",
);
};

View File

@ -1,43 +0,0 @@
"use server";
import { encodedRedirect } from "@/utils/utils";
import { createClient } from "@/utils/supabase/server";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
export const resetPasswordAction = async (formData: FormData) => {
const supabase = await createClient();
const password = formData.get("password") as string;
const confirmPassword = formData.get("confirmPassword") as string;
if (!password || !confirmPassword) {
encodedRedirect(
"error",
"/protected/reset-password",
"Password and confirm password are required"
);
}
if (password !== confirmPassword) {
encodedRedirect(
"error",
"/protected/reset-password",
"Passwords do not match"
);
}
const { error } = await supabase.auth.updateUser({
password: password,
});
if (error) {
encodedRedirect(
"error",
"/protected/reset-password",
"Password update failed"
);
}
encodedRedirect("success", "/protected/reset-password", "Password updated");
};

View File

@ -1,100 +0,0 @@
// import { createClient } from "@/utils/supabase/server";
// export async function sendContactEmail(formData: {
// name: string;
// email: string;
// phone: string;
// typeMessage: string;
// message: string;
// }) {
// try {
// // Initialize Supabase
// const supabase = await createClient();
// const { resend } = useResend();
// // Get message type label
// const messageTypeLabel =
// typeMessageMap.get(formData.typeMessage) || "Unknown";
// // Save to Supabase
// const { data: contactData, error: contactError } = await supabase
// .from("contact_messages")
// .insert([
// {
// name: formData.name,
// email: formData.email,
// phone: formData.phone,
// message_type: formData.typeMessage,
// message_type_label: messageTypeLabel,
// message: formData.message,
// status: "new",
// },
// ])
// .select();
// if (contactError) {
// console.error("Error saving contact message to Supabase:", contactError);
// return {
// success: false,
// error: "Failed to save your message. Please try again later.",
// };
// }
// // Render admin email template
// const adminEmailHtml = await render(
// AdminNotification({
// name: formData.name,
// email: formData.email,
// phone: formData.phone,
// messageType: messageTypeLabel,
// message: formData.message,
// })
// );
// // Send email to admin
// const { data: emailData, error: emailError } = await resend.emails.send({
// from: "Contact Form <contact@backspacex.tech>",
// to: ["xdamazon17@gmail.com"],
// subject: `New Contact Form Submission: ${messageTypeLabel}`,
// html: adminEmailHtml,
// });
// if (emailError) {
// console.error("Error sending email via Resend:", emailError);
// // Note: We don't return error here since the data is already saved to Supabase
// }
// const userEmailHtml = await render(
// UserConfirmation({
// name: formData.name,
// messageType: messageTypeLabel,
// message: formData.message,
// })
// );
// // Send confirmation email to user
// const { data: confirmationData, error: confirmationError } =
// await resend.emails.send({
// from: "Your Company <support@backspacex.tech>",
// to: [formData.email],
// subject: "Thank you for contacting us",
// html: userEmailHtml,
// });
// if (confirmationError) {
// console.error("Error sending confirmation email:", confirmationError);
// // Note: We don't return error here either
// }
// return {
// success: true,
// message: "Your message has been sent successfully!",
// };
// } catch (error) {
// console.error("Unexpected error in sendContactEmail:", error);
// return {
// success: false,
// error: "An unexpected error occurred. Please try again later.",
// };
// }
// }

View File

@ -1,37 +0,0 @@
import { createClient } from "@/utils/supabase/server";
export const checkSession = async () => {
const supabase = await createClient();
try {
const {
data: { session },
error,
} = await supabase.auth.getSession();
if (error) {
return {
success: false,
error: error.message,
};
}
if (session) {
return {
success: true,
session,
redirectTo: "/dashboard",
};
}
return {
success: false,
message: "No active session",
};
} catch (error) {
return {
success: false,
error: "An unexpected error occurred",
};
}
};

View File

@ -1,56 +0,0 @@
"use server";
import { createClient } from "@/utils/supabase/server";
import { encodedRedirect } from "@/utils/utils";
import { redirect } from "next/navigation";
import { checkSession } from "./session";
export const signInAction = async (formData: FormData) => {
const supabase = await createClient();
const email = formData.get("email") as string;
const encodeEmail = encodeURIComponent(email);
try {
// First, check for existing session
const {
data: { session },
} = await supabase.auth.getSession();
// If there's an active session and the email matches
if (session?.user?.email === email) {
return {
success: true,
message: "You are already signed in",
redirectTo: "/dashboard",
};
}
// If no active session or different email, proceed with OTP
const { data, error } = await supabase.auth.signInWithOtp({
email,
options: {
shouldCreateUser: false,
},
});
if (error) {
return {
success: false,
error: error.message,
redirectTo: `/verify-otp?email=${encodeEmail}`,
};
}
return {
success: true,
message: "OTP has been sent to your email",
redirectTo: `/verify-otp?email=${encodeEmail}`,
};
} catch (error) {
return {
success: false,
error: "An unexpected error occurred",
redirectTo: "/sign-in",
};
}
};

View File

@ -1,10 +0,0 @@
"use server";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export const signOutAction = async () => {
const supabase = await createClient();
await supabase.auth.signOut();
return redirect("/sign-in");
};

View File

@ -1,39 +0,0 @@
"use server";
import { createClient } from "@/utils/supabase/server";
import { encodedRedirect } from "@/utils/utils";
import { headers } from "next/headers";
export const signUpAction = async (formData: FormData) => {
const email = formData.get("email")?.toString();
const password = formData.get("password")?.toString();
const supabase = await createClient();
const origin = (await headers()).get("origin");
if (!email || !password) {
return encodedRedirect(
"error",
"/sign-up",
"Email and password are required"
);
}
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
},
});
if (error) {
console.error(error.code + " " + error.message);
return encodedRedirect("error", "/sign-up", error.message);
} else {
return encodedRedirect(
"success",
"/sign-up",
"Thanks for signing up! Please check your email for a verification link."
);
}
};

View File

@ -1,30 +0,0 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export const verifyOtpAction = async (formData: FormData) => {
const email = formData.get("email") as string;
const token = formData.get("token") as string;
const supabase = await createClient();
console.log("email", email);
console.log("token", token);
if (!email || !token) {
redirect("/error?message=Email and OTP are required");
}
const {
data: { session },
error,
} = await supabase.auth.verifyOtp({
email,
token,
type: "email",
});
if (error) {
return redirect(`/verify-otp?error=${encodeURIComponent(error.message)}`);
}
return redirect("/dashboard?message=OTP verified successfully");
};

View File

@ -2,9 +2,9 @@
import * as React from "react";
import { NavMain } from "@/app/(protected)/(admin)/_components/admin/navigations/nav-main";
import { NavReports } from "@/app/(protected)/(admin)/_components/admin/navigations/nav-report";
import { NavUser } from "@/app/(protected)/(admin)/_components/admin/navigations/nav-user";
import { NavMain } from "@/app/(pages)/(admin)/_components/navigations/nav-main";
import { NavReports } from "@/app/(pages)/(admin)/_components/navigations/nav-report";
import { NavUser } from "@/app/(pages)/(admin)/_components/navigations/nav-user";
import {
Sidebar,
@ -15,10 +15,10 @@ import {
} from "@/app/_components/ui/sidebar";
import { NavPreMain } from "./navigations/nav-pre-main";
import { navData } from "@/prisma/data/nav";
import { TeamSwitcher } from "../../../../_components/team-switcher";
import { TeamSwitcher } from "../../../_components/team-switcher";
import { Profile, User } from "@/src/models/users/users.model";
import { getCurrentUser } from "@/app/(protected)/(admin)/dashboard/user-management/action";
import { Profile, User } from "@/src/entities/models/users/users.model";
import { getCurrentUser } from "@/app/(pages)/(admin)/dashboard/user-management/action";
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const [user, setUser] = React.useState<User | null>(null);

View File

@ -19,7 +19,7 @@ import {
import type * as TablerIcons from "@tabler/icons-react";
import { useNavigations } from "@/app/_hooks/use-navigations";
import { formatUrl } from "@/utils/utils";
import { formatUrl } from "@/app/_utils/utils";
interface SubSubItem {
title: string;

View File

@ -24,8 +24,8 @@ import {
useSidebar,
} from "@/app/_components/ui/sidebar";
import { IconLogout, IconSettings, IconSparkles } from "@tabler/icons-react";
import type { User } from "@/src/models/users/users.model";
import { signOut } from "@/app/(auth-pages)/action";
import type { User } from "@/src/entities/models/users/users.model";
import { signOut } from "@/app/(pages)/(auth)/action";
import { SettingsDialog } from "../settings/setting-dialog";
export function NavUser({ user }: { user: User | null }) {

View File

@ -4,7 +4,7 @@ import { Card, CardContent } from "@/app/_components/ui/card";
import { ScrollArea } from "@/app/_components/ui/scroll-area";
import { Separator } from "@/app/_components/ui/separator";
import { Upload } from "lucide-react";
import { Badge } from "../../../../../_components/ui/badge";
import { Badge } from "../../../../_components/ui/badge";
import {
IconBrandGoogleAnalytics,
IconCsv,

View File

@ -10,7 +10,7 @@ import {
getNotificationPreferences,
saveNotificationPreferences,
applyNotificationPreferences,
} from "@/utils/notification-cookies-manager";
} from "@/app/_utils/cookies/notification-cookies-manager";
import { toast } from "sonner";
export default function NotificationsSetting() {

View File

@ -5,8 +5,8 @@ import { ChevronDown } from "lucide-react";
import { Switch } from "@/app/_components/ui/switch";
import { Separator } from "@/app/_components/ui/separator";
import { ScrollArea } from "@/app/_components/ui/scroll-area";
import { ThemeSwitcher } from "../../../../../_components/theme-switcher";
import DropdownSwitcher from "../../../../../_components/custom-dropdown-switcher";
import { ThemeSwitcher } from "../../../../_components/theme-switcher";
import DropdownSwitcher from "../../../../_components/custom-dropdown-switcher";
import {
type CookiePreferences,
defaultCookiePreferences,
@ -21,7 +21,7 @@ import {
getAutoTimezonePreference,
saveAutoTimezonePreference,
applyCookiePreferences,
} from "@/utils/cookies-manager";
} from "@/app/_utils/cookies/cookies-manager";
import { toast } from "sonner";
import { initialTimezones, TimezoneType } from "@/prisma/data/timezones";
import { languages, LanguageType } from "@/prisma/data/languages";

View File

@ -2,7 +2,7 @@
import type React from "react";
import type { User } from "@/src/models/users/users.model";
import type { User } from "@/src/entities/models/users/users.model";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@ -31,7 +31,7 @@ import { ScrollArea } from "@/app/_components/ui/scroll-area";
import {
updateUser,
uploadAvatar,
} from "@/app/(protected)/(admin)/dashboard/user-management/action";
} from "@/app/(pages)/(admin)/dashboard/user-management/action";
const profileFormSchema = z.object({
username: z.string().nullable().optional(),

View File

@ -1,6 +1,6 @@
"use client";
import type { User } from "@/src/models/users/users.model";
import type { User } from "@/src/entities/models/users/users.model";
import { Button } from "@/app/_components/ui/button";
import { Separator } from "@/app/_components/ui/separator";

View File

@ -1,7 +1,7 @@
"use client";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
import {
Dialog,
DialogContent,
@ -28,7 +28,7 @@ import {
IconUsers,
IconWorld,
} from "@tabler/icons-react";
import type { User } from "@/src/models/users/users.model";
import type { User } from "@/src/entities/models/users/users.model";
import { ProfileSettings } from "./profile-settings";
import { DialogTitle } from "@radix-ui/react-dialog";
import { useState } from "react";

View File

@ -1,5 +1,5 @@
import { DateTimePicker2 } from "@/app/_components/ui/date-picker";
import { createClient } from "@/utils/supabase/server";
import { createClient } from "@/app/_utils/supabase/server";
import { redirect } from "next/navigation";
export default async function DashboardPage() {

View File

@ -10,7 +10,7 @@ import {
import { Button } from "@/app/_components/ui/button";
import { Input } from "@/app/_components/ui/input";
import { Checkbox } from "@/app/_components/ui/checkbox";
import { createUser } from "@/app/(protected)/(admin)/dashboard/user-management/action";
import { createUser } from "@/app/(pages)/(admin)/dashboard/user-management/action";
import { toast } from "sonner";
import { Mail, Lock, Loader2, X } from "lucide-react";
import { useMutation } from "@tanstack/react-query";

View File

@ -14,7 +14,7 @@ import { Label } from "@/app/_components/ui/label";
import { Input } from "@/app/_components/ui/input";
import { Textarea } from "@/app/_components/ui/textarea";
import { useMutation } from "@tanstack/react-query";
import { inviteUser } from "@/app/(protected)/(admin)/dashboard/user-management/action";
import { inviteUser } from "@/app/(pages)/(admin)/dashboard/user-management/action";
import { toast } from "sonner";
interface InviteUserDialogProps {

View File

@ -6,7 +6,7 @@ import { useState, useRef } from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import type { User } from "@/src/models/users/users.model";
import type { User } from "@/src/entities/models/users/users.model";
import {
Form,
@ -27,7 +27,7 @@ import { Textarea } from "@/app/_components/ui/textarea";
import { Button } from "@/app/_components/ui/button";
import { Label } from "@/app/_components/ui/label";
import { ImageIcon, Loader2 } from "lucide-react";
import { createClient } from "@/utils/supabase/client";
import { createClient } from "@/app/_utils/supabase/client";
// Profile update form schema
const profileFormSchema = z.object({

View File

@ -38,7 +38,7 @@ import {
sendMagicLink,
sendPasswordRecovery,
unbanUser,
} from "@/app/(protected)/(admin)/dashboard/user-management/action";
} from "@/app/(pages)/(admin)/dashboard/user-management/action";
import { format } from "date-fns";
interface UserDetailSheetProps {

View File

@ -1,20 +1,15 @@
import type React from "react"
import { useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import type * as z from "zod"
import { Loader2 } from "lucide-react"
import { UpdateUserParamsSchema, type User, UserSchema } from "@/src/models/users/users.model"
import { UpdateUserParamsSchema, type User } from "@/src/entities/models/users/users.model"
// UI Components
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/app/_components/ui/sheet"
import {
Form,
} from "@/app/_components/ui/form"
import { Form } from "@/app/_components/ui/form"
import { Button } from "@/app/_components/ui/button"
import { FormSection } from "@/app/_components/form-section"
@ -22,8 +17,6 @@ import { FormFieldWrapper } from "@/app/_components/form-wrapper"
import { useMutation } from "@tanstack/react-query"
import { updateUser } from "../action"
import { toast } from "sonner"
import { DateTimePicker2 } from "@/app/_components/ui/date-picker"
type UserProfileFormValues = z.infer<typeof UpdateUserParamsSchema>
@ -31,33 +24,33 @@ interface UserProfileSheetProps {
open: boolean
onOpenChange: (open: boolean) => void
userData?: User
onUserUpdated: () => void
}
export function UserProfileSheet({ open, onOpenChange, userData }: UserProfileSheetProps) {
const [isSaving, setIsSaving] = useState(false)
export function UserProfileSheet({ open, onOpenChange, userData, onUserUpdated }: UserProfileSheetProps) {
// Initialize form with user data
const form = useForm<UserProfileFormValues>({
resolver: zodResolver(UpdateUserParamsSchema),
defaultValues: {
email: userData?.email || "",
password_hash: userData?.password_hash || "",
email: userData?.email || undefined,
encrypted_password: userData?.encrypted_password || undefined,
role: (userData?.role as "user" | "staff" | "admin") || "user",
phone: userData?.phone || "",
phone: userData?.phone || undefined,
invited_at: userData?.invited_at || undefined,
confirmed_at: userData?.confirmed_at || undefined,
recovery_sent_at: userData?.recovery_sent_at || undefined,
// recovery_sent_at: userData?.recovery_sent_at || undefined,
last_sign_in_at: userData?.last_sign_in_at || undefined,
created_at: userData?.created_at || undefined,
updated_at: userData?.updated_at || undefined,
is_anonymous: userData?.is_anonymous || false,
profile: {
id: userData?.profile?.id || "",
user_id: userData?.profile?.user_id || "",
avatar: userData?.profile?.avatar || "",
username: userData?.profile?.username || "",
first_name: userData?.profile?.first_name || "",
last_name: userData?.profile?.last_name || "",
bio: userData?.profile?.bio || "",
id: userData?.profile?.id || undefined,
user_id: userData?.profile?.user_id || undefined,
avatar: userData?.profile?.avatar || undefined,
username: userData?.profile?.username || undefined,
first_name: userData?.profile?.first_name || undefined,
last_name: userData?.profile?.last_name || undefined,
bio: userData?.profile?.bio || undefined,
address: userData?.profile?.address || {
street: "",
city: "",
@ -74,27 +67,26 @@ export function UserProfileSheet({ open, onOpenChange, userData }: UserProfileSh
mutationKey: ["updateUser"],
mutationFn: (data: UserProfileFormValues) => {
if (!userData?.id) {
throw new Error("User ID is required");
throw new Error("User ID is required")
}
return updateUser(userData.id, data);
return updateUser(userData.id, data)
},
onError: (error) => {
toast("Failed to update user");
toast("Failed to update user")
onOpenChange(false)
},
onSuccess: () => {
toast("User updated");
toast("User updated")
onUserUpdated()
onOpenChange(false)
},
})
async function onSubmit(data: UserProfileFormValues) {
try {
setIsSaving(true)
await updateUserMutation(data)
onOpenChange(false)
} catch (error) {
console.error("Error saving user profile:", error)
} finally {
setIsSaving(false)
}
}
@ -113,7 +105,6 @@ export function UserProfileSheet({ open, onOpenChange, userData }: UserProfileSh
title="User Information"
description="Update the user information below. Fields marked with an asterisk (*) are required."
>
<FormFieldWrapper
name="email"
label="Email"
@ -149,19 +140,13 @@ export function UserProfileSheet({ open, onOpenChange, userData }: UserProfileSh
booleanType="select"
/>
<FormFieldWrapper
name="password_hash"
label="Password Hash"
name="encrypted_password_hash"
label="Encrypted_password Hash"
type="string"
control={form.control}
placeholder="Password Hash"
/>
<FormFieldWrapper
name="invited_at"
label="Invited At"
type="date"
control={form.control}
isDate={true}
placeholder="Encrypted_password Hash"
/>
<FormFieldWrapper name="invited_at" label="Invited At" type="date" control={form.control} isDate={true} />
<FormFieldWrapper
name="confirmed_at"
label="Confirmed At"
@ -183,21 +168,8 @@ export function UserProfileSheet({ open, onOpenChange, userData }: UserProfileSh
control={form.control}
isDate={true}
/>
<FormFieldWrapper
name="created_at"
label="Created At"
type="date"
control={form.control}
isDate={true}
/>
<FormFieldWrapper
name="updated_at"
label="Updated At"
type="date"
control={form.control}
isDate={true}
/>
<FormFieldWrapper name="created_at" label="Created At" type="date" control={form.control} isDate={true} />
<FormFieldWrapper name="updated_at" label="Updated At" type="date" control={form.control} isDate={true} />
</FormSection>
{/* Profile Information Section */}
@ -288,11 +260,17 @@ export function UserProfileSheet({ open, onOpenChange, userData }: UserProfileSh
{/* Action Buttons */}
<div className="flex justify-end space-x-4">
<Button type="button" variant="outline" size="xs" onClick={() => onOpenChange(false)} disabled={isPending}>
<Button
type="button"
variant="outline"
size="xs"
onClick={() => !isPending && onOpenChange(false)}
disabled={isPending}
>
Cancel
</Button>
<Button size="xs" type="submit" disabled={isPending}>
{isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{isPending && <Loader2 className="mr-1 h-4 w-4 animate-spin" />}
{isPending ? "Saving..." : "Save"}
</Button>
</div>

View File

@ -26,8 +26,8 @@ import {
DropdownMenuCheckboxItem,
} from "@/app/_components/ui/dropdown-menu";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { fetchUsers } from "@/app/(protected)/(admin)/dashboard/user-management/action";
import type { User } from "@/src/models/users/users.model";
import { fetchUsers } from "@/app/(pages)/(admin)/dashboard/user-management/action";
import type { User } from "@/src/entities/models/users/users.model";
import { DataTable } from "./data-table";
import { InviteUserDialog } from "./invite-user";
import { AddUserDialog } from "./add-user-dialog";
@ -668,7 +668,7 @@ export default function UserManagement() {
open={isUpdateOpen}
onOpenChange={setIsUpdateOpen}
userData={updateUser}
onUserUpdated={() => refetch()}
/>
)}
</div>

View File

@ -3,8 +3,8 @@
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent } from "@/app/_components/ui/card";
import { Users, UserCheck, UserX } from "lucide-react";
import { fetchUsers } from "@/app/(protected)/(admin)/dashboard/user-management/action";
import { User } from "@/src/models/users/users.model";
import { fetchUsers } from "@/app/(pages)/(admin)/dashboard/user-management/action";
import { User } from "@/src/entities/models/users/users.model";
import { useNavigations } from "@/app/_hooks/use-navigations";
import { useEffect, useState } from "react";
import { toast } from "sonner";

View File

@ -1,15 +1,15 @@
"use server";
import db from "@/lib/db";
import db from "@/prisma/db";
import {
CreateUserParams,
InviteUserParams,
UpdateUserParams,
User,
UserResponse,
} from "@/src/models/users/users.model";
import { createClient } from "@/utils/supabase/server";
import { createAdminClient } from "@/utils/supabase/admin";
} from "@/src/entities/models/users/users.model";
import { createClient } from "@/app/_utils/supabase/server";
import { createAdminClient } from "@/app/_utils/supabase/admin";
// Initialize Supabase client with admin key
@ -84,7 +84,7 @@ export async function createUser(
const { data, error } = await supabase.auth.admin.createUser({
email: params.email,
password: params.password,
password: params.encrypted_password,
phone: params.phone,
email_confirm: params.email_confirm,
});
@ -163,8 +163,8 @@ export async function updateUser(
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
email: params.email,
email_confirm: params.email_confirmed_at,
password: params.password_hash ?? undefined,
password_hash: params.password_hash ?? undefined,
password: params.encrypted_password ?? undefined,
password_hash: params.encrypted_password ?? undefined,
phone: params.phone,
phone_confirm: params.phone_confirmed_at,
role: params.role,
@ -196,13 +196,13 @@ export async function updateUser(
},
data: {
role: params.role || user.role,
invited_at: params.invited_at || user.role,
confirmed_at: params.confirmed_at || user.role,
recovery_sent_at: params.recovery_sent_at || user.role,
last_sign_in_at: params.last_sign_in_at || user.role,
invited_at: params.invited_at || user.invited_at,
confirmed_at: params.confirmed_at || user.confirmed_at,
// recovery_sent_at: params.recovery_sent_at || user.recovery_sent_at,
last_sign_in_at: params.last_sign_in_at || user.last_sign_in_at,
is_anonymous: params.is_anonymous || user.is_anonymous,
created_at: params.created_at || user.role,
updated_at: params.updated_at || user.role,
created_at: params.created_at || user.created_at,
updated_at: params.updated_at || user.updated_at,
profile: {
update: {
avatar: params.profile?.avatar || user.profile?.avatar,

View File

@ -1,5 +1,5 @@
import UserManagement from "@/app/(protected)/(admin)/dashboard/user-management/_components/user-management";
import { UserStats } from "@/app/(protected)/(admin)/dashboard/user-management/_components/user-stats";
import UserManagement from "@/app/(pages)/(admin)/dashboard/user-management/_components/user-management";
import { UserStats } from "@/app/(pages)/(admin)/dashboard/user-management/_components/user-stats";
export default function UsersPage() {
return (

View File

@ -26,8 +26,8 @@ import { ThemeSwitcher } from "@/app/_components/theme-switcher";
import { Separator } from "@/app/_components/ui/separator";
import { InboxDrawer } from "@/app/_components/inbox-drawer";
import FloatingActionSearchBar from "@/app/_components/floating-action-search-bar";
import { AppSidebar } from "@/app/(protected)/(admin)/_components/admin/app-sidebar";
import { createClient } from "@/utils/supabase/server";
import { AppSidebar } from "@/app/(pages)/(admin)/_components/app-sidebar";
import { createClient } from "@/app/_utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Layout({

View File

@ -3,11 +3,11 @@
import type React from "react";
import { Lock } from "lucide-react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { SubmitButton } from "../submit-button";
import { Button } from "../../../_components/ui/button";
import { Input } from "../../../_components/ui/input";
import { SubmitButton } from "../../../_components/submit-button";
import Link from "next/link";
import { FormField } from "../form-field";
import { FormField } from "../../../_components/form-field";
import { useSignInForm } from "@/src/controller/auth/sign-in-controller";
export function SignInForm({

View File

@ -23,7 +23,7 @@ import {
CardHeader,
CardTitle,
} from "@/app/_components/ui/card";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
import { useVerifyOtpForm } from "@/src/controller/auth/verify-otp.controller";
interface VerifyOtpFormProps extends React.HTMLAttributes<HTMLDivElement> {}

View File

@ -1,12 +1,12 @@
// src/app/(auth-pages)/actions.ts
// src/app/(auth)/actions.ts
"use server";
import db from "@/lib/db";
import { SignInFormData } from "@/src/models/auth/sign-in.model";
import { VerifyOtpFormData } from "@/src/models/auth/verify-otp.model";
import { User } from "@/src/models/users/users.model";
import db from "@/prisma/db";
import { SignInFormData } from "@/src/entities/models/auth/sign-in.model";
import { VerifyOtpFormData } from "@/src/entities/models/auth/verify-otp.model";
import { User } from "@/src/entities/models/users/users.model";
import { authRepository } from "@/src/repositories/authentication.repository";
import { createClient } from "@/utils/supabase/server";
import { createClient } from "@/app/_utils/supabase/server";
import { redirect } from "next/navigation";
export async function signIn(

View File

@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
import { checkSession } from "./_actions/session";
import { createClient } from "@/utils/supabase/client";
import { createClient } from "@/app/_utils/supabase/client";
export default async function Layout({
children,

View File

@ -1,4 +1,4 @@
import { SignInForm } from "@/app/_components/auth/signin-form";
import { SignInForm } from "@/app/(pages)/(auth)/_components/signin-form";
import { Message } from "@/app/_components/form-message";
import { Button } from "@/app/_components/ui/button";
import { GalleryVerticalEnd, Globe } from "lucide-react";

View File

@ -1,4 +1,4 @@
import { VerifyOtpForm } from "@/app/_components/auth/verify-otp-form";
import { VerifyOtpForm } from "@/app/(pages)/(auth)/_components/verify-otp-form";
import { GalleryVerticalEnd } from "lucide-react";
export default async function VerifyOtpPage() {

View File

@ -1,13 +1,7 @@
import DeployButton from "@/app/_components/deploy-button";
import { EnvVarWarning } from "@/app/_components/env-var-warning";
import HeaderAuth from "@/app/_components/header-auth";
import { ThemeSwitcher } from "@/app/_components/theme-switcher";
import { hasEnvVars } from "@/utils/supabase/check-env-vars";
import { Geist } from "next/font/google";
import { ThemeProvider } from "next-themes";
import Link from "next/link";
import "./globals.css";
import ReactQueryProvider from "@/providers/react-query-provider";
import "@/app/_styles/globals.css";
import ReactQueryProvider from "@/app/_lib/react-query-provider";
import { Toaster } from "@/app/_components/ui/sonner";
const defaultUrl = process.env.VERCEL_URL

View File

@ -1,7 +1,7 @@
import Hero from "@/app/_components/hero";
import ConnectSupabaseSteps from "@/app/_components/tutorial/connect-supabase-steps";
import SignUpUserSteps from "@/app/_components/tutorial/sign-up-user-steps";
import { hasEnvVars } from "@/utils/supabase/check-env-vars";
import { hasEnvVars } from "@/app/_utils/supabase/check-env-vars";
export default async function Home() {
return (

View File

@ -1,48 +0,0 @@
import FetchDataSteps from "@/app/_components/tutorial/fetch-data-steps";
import db from "@/lib/db";
import { createClient } from "@/utils/supabase/server";
import { InfoIcon } from "lucide-react";
import { redirect } from "next/navigation";
export default async function ProtectedPage() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/sign-in");
}
const userDetail = await db.users.findUnique({
where: {
id: user.id,
},
});
return (
<div className="flex-1 w-full flex flex-col gap-12">
<div className="w-full">
<div className="bg-accent text-sm p-3 px-5 rounded-md text-foreground flex gap-3 items-center">
<InfoIcon size="16" strokeWidth={2} />
This is a protected page that you can only see as an authenticated
user
</div>
</div>
<div className="flex gap-2 items-start">
<h2 className="font-bold text-2xl mb-4">Your user details</h2>
<pre className="text-xs font-mono p-3 rounded border overflow-auto">
{JSON.stringify(userDetail, null, 2)}
</pre>
<pre className="text-xs font-mono p-3 rounded border overflow-auto">
{JSON.stringify(user, null, 2)}
</pre>
</div>
<div>
<h2 className="font-bold text-2xl mb-4">Next steps</h2>
<FetchDataSteps />
</div>
</div>
);
}

View File

@ -18,7 +18,7 @@ import {
} from "@/app/_components/ui/dropdown-menu";
import { Badge } from "@/app/_components/ui/badge";
import { Input } from "@/app/_components/ui/input";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
type Option<T> = {
value: T;

View File

@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight, Clock } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { useVirtualizer } from "@tanstack/react-virtual"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/app/_components/ui/select"
import { Input } from "@/app/_components/ui/input"
import { Button } from "@/app/_components/ui/button"

View File

@ -1,7 +1,7 @@
"use client"
import { format } from "date-fns"
import { CalendarIcon, ChevronLeft, ChevronRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
// UI Components
import { FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription } from "@/app/_components/ui/form"

View File

@ -1,10 +1,10 @@
import { hasEnvVars } from "@/utils/supabase/check-env-vars";
import { hasEnvVars } from "@/app/_utils/supabase/check-env-vars";
import Link from "next/link";
import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
import { createClient } from "@/utils/supabase/server";
import { signOutAction } from "@/app/(auth-pages)/_actions/sign-out";
import { createClient } from "@/app/_utils/supabase/server";
import { signOutAction } from "@/app/(pages)/(auth)/_actions/sign-out";
export default async function AuthButton() {
const supabase = await createClient();

View File

@ -3,7 +3,7 @@
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
import { buttonVariants } from "@/app/_components/ui/button";
const AlertDialog = AlertDialogPrimitive.Root;

View File

@ -3,7 +3,7 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,

View File

@ -1,7 +1,7 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",

View File

@ -2,7 +2,7 @@ import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Breadcrumb = React.forwardRef<
HTMLElement,

View File

@ -2,7 +2,7 @@ import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",

View File

@ -4,7 +4,7 @@ import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
import { buttonVariants } from "@/app/_components/ui/button";
export type CalendarProps = React.ComponentProps<typeof DayPicker>;

View File

@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Card = React.forwardRef<
HTMLDivElement,

View File

@ -4,7 +4,7 @@ import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,

View File

@ -4,7 +4,7 @@ import * as React from "react"
import { format, getMonth, getYear, setMonth, setYear, setHours, setMinutes, setSeconds } from "date-fns"
import { Calendar as CalendarIcon, Clock } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
import { Button } from "@/app/_components/ui/button"
import { Calendar } from "@/app/_components/ui/calendar"
import {

View File

@ -4,7 +4,7 @@ import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
const Dialog = DialogPrimitive.Root;

View File

@ -3,7 +3,7 @@
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Drawer = ({
shouldScaleBackground = true,

View File

@ -4,7 +4,7 @@ import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root;

View File

@ -12,7 +12,7 @@ import {
useFormContext,
} from "react-hook-form";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
import { Label } from "@/app/_components/ui/label";
const Form = FormProvider;

View File

@ -4,7 +4,7 @@ import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { Dot } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,

View File

@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {

View File

@ -4,7 +4,7 @@ import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"

View File

@ -3,7 +3,7 @@
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Popover = PopoverPrimitive.Root

View File

@ -3,7 +3,7 @@
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,

View File

@ -4,7 +4,7 @@ import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Select = SelectPrimitive.Root

View File

@ -3,7 +3,7 @@
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,

View File

@ -5,7 +5,7 @@ import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Sheet = SheetPrimitive.Root

View File

@ -6,7 +6,7 @@ import { VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react";
import { useIsMobile } from "@/app/_hooks/use-mobile";
import { cn } from "@/lib/utils";
import { cn } from "@/app/_lib/utils";
import { Button } from "@/app/_components/ui/button";
import { Input } from "@/app/_components/ui/input";
import { Separator } from "@/app/_components/ui/separator";

View File

@ -1,4 +1,4 @@
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
function Skeleton({
className,

View File

@ -3,7 +3,7 @@
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,

View File

@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Table = React.forwardRef<
HTMLTableElement,

View File

@ -3,7 +3,7 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Tabs = TabsPrimitive.Root

View File

@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,

View File

@ -3,7 +3,7 @@
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
import { cn } from "@/app/_lib/utils"
const TooltipProvider = TooltipPrimitive.Provider

View File

@ -1,4 +1,4 @@
import { createClient } from "@/utils/supabase/server";
import { createClient } from "@/app/_utils/supabase/server";
import { NextResponse } from "next/server";
export async function GET(request: Request) {

View File

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 283 KiB

View File

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 283 KiB

View File

@ -1,5 +1,5 @@
import { type NextRequest } from "next/server";
import { updateSession } from "@/utils/supabase/middleware";
import { updateSession } from "@/app/_utils/supabase/middleware";
export async function middleware(request: NextRequest) {
return await updateSession(request);

View File

@ -7,11 +7,11 @@ import {
defaultSignInValues,
SignInFormData,
signInSchema,
} from "@/src/models/auth/sign-in.model";
} from "@/src/entities/models/auth/sign-in.model";
import { useState, type FormEvent, type ChangeEvent } from "react";
import { toast } from "sonner";
import { z } from "zod";
import { signIn } from "@/app/(auth-pages)/action";
import { signIn } from "@/app/(pages)/(auth)/action";
type SignInFormErrors = Partial<Record<keyof SignInFormData, string>>;

View File

@ -5,12 +5,12 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { verifyOtp } from "@/app/(auth-pages)/action";
import { verifyOtp } from "@/app/(pages)/(auth)/action";
import {
defaultVerifyOtpValues,
VerifyOtpFormData,
verifyOtpSchema,
} from "@/src/models/auth/verify-otp.model";
} from "@/src/entities/models/auth/verify-otp.model";
import { useNavigations } from "@/app/_hooks/use-navigations";
import { toast } from "sonner";

View File

@ -0,0 +1,17 @@
export class AuthenticationError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}
export class UnauthenticatedError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}
export class UnauthorizedError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}

View File

@ -0,0 +1,18 @@
export class DatabaseOperationError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}
export class NotFoundError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}
export class InputParseError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}

View File

@ -40,7 +40,7 @@ export const UserSchema = z.object({
role: z.string().optional(),
email: z.string().email().optional(),
email_confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
password_hash: z.string().nullable().optional(),
encrypted_password: z.string().nullable().optional(),
invited_at: z.union([z.string(), z.date()]).nullable().optional(),
phone: z.string().nullable().optional(),
confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
@ -84,7 +84,7 @@ export type Profile = z.infer<typeof ProfileSchema>;
export const CreateUserParamsSchema = z.object({
email: z.string().email(),
password: z.string(),
encrypted_password: z.string(),
phone: z.string().optional(),
user_metadata: z.record(z.any()).optional(),
email_confirm: z.boolean().optional(),
@ -95,13 +95,13 @@ export type CreateUserParams = z.infer<typeof CreateUserParamsSchema>;
export const UpdateUserParamsSchema = z.object({
email: z.string().email().optional(),
email_confirmed_at: z.boolean().optional(),
password_hash: z.string().optional(),
encrypted_password: z.string().optional(),
role: z.enum(["user", "staff", "admin"]).optional(),
phone: z.string().optional(),
phone_confirmed_at: z.boolean().optional(),
invited_at: z.union([z.string(), z.date()]).optional(),
confirmed_at: z.union([z.string(), z.date()]).optional(),
recovery_sent_at: z.union([z.string(), z.date()]).optional(),
// recovery_sent_at: z.union([z.string(), z.date()]).optional(),
last_sign_in_at: z.union([z.string(), z.date()]).optional(),
created_at: z.union([z.string(), z.date()]).optional(),
updated_at: z.union([z.string(), z.date()]).optional(),

Some files were not shown because too many files have changed in this diff Show More