From 39a0af5e0f0a6797982acbe97b29a9363829cd44 Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Thu, 27 Feb 2025 00:58:32 +0700 Subject: [PATCH] add users table --- sigap-website/.vscode/settings.json | 6 +- sigap-website/actions/auth/contact-us.ts | 107 ---- sigap-website/actions/auth/forgot-password.ts | 38 -- sigap-website/actions/auth/reset-password.ts | 41 -- sigap-website/actions/auth/session.ts | 39 -- sigap-website/actions/auth/sign-in.ts | 53 -- sigap-website/actions/auth/sign-out.ts | 10 - sigap-website/actions/auth/sign-up-action.ts | 38 -- sigap-website/actions/auth/verify-otp.ts | 32 - sigap-website/actions/dashboard/nav-items.ts | 211 ------- .../_components}/auth/contact-us.tsx | 18 +- .../_components}/auth/email-recovery.tsx | 12 +- .../_components}/auth/login-form-2.tsx | 10 +- .../_components}/auth/login-form.tsx | 6 +- .../_components}/auth/otp-form.tsx | 12 +- .../_components}/auth/verify-otp-form.tsx | 12 +- sigap-website/app/(auth-pages)/actions.ts | 336 ++++++++++ .../app/(auth-pages)/contact-us/page.tsx | 4 +- .../app/(auth-pages)/email-recovery/page.tsx | 4 +- .../app/(auth-pages)/forgot-password/page.tsx | 12 +- sigap-website/app/(auth-pages)/layout.tsx | 3 +- .../app/(auth-pages)/sign-in/page.tsx | 12 +- .../app/(auth-pages)/sign-up/page.tsx | 8 +- .../app/(auth-pages)/verify-otp/page.tsx | 2 +- .../_components}/action-search-bar.tsx | 124 ++-- .../_components}/deploy-button.tsx | 0 .../_components}/dynamic-icon.tsx | 0 .../email-templates/admin-notification.tsx | 0 .../email-templates/user-confirmation.tsx | 0 .../email-templates/verify-identity.tsx | 0 .../_components}/env-var-warning.tsx | 0 .../floating-action-search-bar.tsx | 2 +- .../_components}/form-field.tsx | 0 .../_components}/form-message.tsx | 0 .../_components}/header-auth.tsx | 0 .../{components => app/_components}/hero.tsx | 0 .../_components}/inbox-drawer.tsx | 43 +- .../_components}/logo/next-logo.tsx | 0 .../_components}/logo/sigap-logo.tsx | 0 .../_components}/logo/supabase-logo.tsx | 0 .../_components}/state-screen.tsx | 0 .../_components}/submit-button.tsx | 2 +- .../_components}/team-switcher.tsx | 24 +- .../_components}/theme-switcher.tsx | 6 +- .../_components}/tutorial/code-block.tsx | 0 .../tutorial/connect-supabase-steps.tsx | 0 .../tutorial/fetch-data-steps.tsx | 0 .../tutorial/sign-up-user-steps.tsx | 0 .../_components}/tutorial/tutorial-step.tsx | 0 .../_components}/typography/inline-code.tsx | 0 .../_components}/ui/avatar.tsx | 0 .../_components}/ui/badge.tsx | 0 .../_components}/ui/breadcrumb.tsx | 0 .../_components}/ui/button.tsx | 0 .../_components}/ui/card.tsx | 0 .../_components}/ui/checkbox.tsx | 0 .../_components}/ui/collapsible.tsx | 0 .../_components}/ui/drawer.tsx | 0 .../_components}/ui/dropdown-menu.tsx | 0 .../_components}/ui/form.tsx | 2 +- .../_components}/ui/input-otp.tsx | 0 .../_components}/ui/input.tsx | 0 .../_components}/ui/label.tsx | 0 sigap-website/app/_components/ui/popover.tsx | 33 + .../_components}/ui/scroll-area.tsx | 0 .../_components}/ui/select.tsx | 0 .../_components}/ui/separator.tsx | 0 .../_components}/ui/sheet.tsx | 0 .../_components}/ui/sidebar.tsx | 296 ++++----- .../_components}/ui/skeleton.tsx | 0 .../_components}/ui/switch.tsx | 0 sigap-website/app/_components/ui/table.tsx | 120 ++++ .../_components}/ui/textarea.tsx | 0 .../_components}/ui/toast.tsx | 0 .../_components}/ui/toaster.tsx | 4 +- .../_components}/ui/tooltip.tsx | 0 .../{hooks => app/_hooks}/use-debounce.ts | 0 .../{hooks => app/_hooks}/use-mobile.tsx | 0 .../{hooks => app/_hooks}/use-navigations.ts | 0 .../{hooks => app/_hooks}/use-resend.ts | 0 .../{hooks => app/_hooks}/use-supabase.ts | 0 .../{hooks => app/_hooks}/use-toast.ts | 2 +- sigap-website/app/layout.tsx | 13 +- sigap-website/app/page.tsx | 6 +- .../(admin-pages)/_components/app-sidebar.tsx | 37 ++ .../_components}/map/mapbox-view.tsx | 0 .../_components/navigations}/nav-main.tsx | 6 +- .../_components/navigations/nav-pre-main.tsx | 54 ++ .../_components/navigations}/nav-report.tsx | 8 +- .../_components/navigations}/nav-user.tsx | 10 +- .../_components/users/edit-user.tsx | 191 ++++++ .../_components/users/user-stats.tsx | 45 ++ .../_components/users/users-table.tsx | 585 ++++++++++++++++++ .../app/protected/(admin-pages)/actions.ts | 0 .../crime-management/crime-overview/page.tsx | 19 +- .../(admin-pages)/dashboard/page.tsx | 28 +- .../app/protected/(admin-pages)/layout.tsx | 19 +- .../user-management/users/page.tsx | 27 + sigap-website/app/protected/page.tsx | 2 +- .../app/protected/reset-password/page.tsx | 11 +- sigap-website/data/crime-category.ts | 231 +++++++ .../app-sidebar.tsx => data/nav.ts} | 139 +++-- .../hooks/use-contact-us-form.ts | 9 +- .../hooks/use-nav-items.ts | 0 .../hooks/use-navigation-item.ts | 4 +- .../infrastructure => }/hooks/use-signin.ts | 13 +- sigap-website/lib/db.ts | 2 +- sigap-website/package-lock.json | 83 +++ sigap-website/package.json | 3 + sigap-website/prisma/schema.prisma | 87 +-- sigap-website/prisma/seed.ts | 540 ++++++++-------- sigap-website/shadcn-ui.json | 4 + .../repositories/contact-us.repository.ts | 2 +- .../repositories/nav-items.repository.ts | 2 +- .../navigation-item.repository.ts | 2 +- .../repositories/signin.repository.ts | 5 +- .../users.repository.interface.ts | 9 + .../authentication.service.interface.ts | 12 + .../crash-reporter.service.interface.ts | 3 + .../instrumentation.service.interface.ts | 11 + .../transaction-manager.service.interface.ts | 8 + .../auth/request-magic-link.use-case.ts.ts | 22 + .../usecases/auth/request-otp.use-case.ts | 28 + .../usecases/auth/sign-in.use-case.ts | 30 + .../usecases/auth/sign-out.use-case.ts | 0 .../usecases/auth/sign-up.use-case.ts | 0 .../usecases/auth/verify-otp.use-case.ts | 23 + .../usecases/auth/verify-session.use-case.ts | 33 + .../usecases/contact-us.usecase.ts | 2 +- .../usecases/nav-items.usecase.ts | 2 +- .../usecases/navigation-item.usecase.ts | 2 +- .../usecases/signin.usecases.ts | 9 +- .../src/applications/entities/errors/auth.ts | 17 - .../entities/models/user.model.ts | 72 --- .../src/entities/errors/auth.error.ts | 17 + .../errors/common.error.ts} | 0 .../entities/models/contact-us.model.ts | 0 .../src/entities/models/cookies.model.ts | 15 + .../entities/models/crime-category.model.ts | 11 + .../entities/models/nav-items.model.ts | 0 .../entities/models/navigation-item.model.ts | 0 .../src/entities/models/session.model.ts | 9 + .../models/transaction.interface.model.ts | 3 + .../src/entities/models/users.model.ts | 95 +++ .../contact-us.repository.impl.ts | 7 +- .../repositories/nav-items.repository.impl.ts | 4 +- .../navigation-item.repository.impl.ts | 4 +- .../repositories/signin.repository.impl.ts | 7 +- .../repositories/users.repository.ts | 0 .../services/authentication.service.ts | 147 +++++ .../validators/contact-us.validator.ts | 2 +- .../validators/signin.validator.ts | 4 +- .../auth/auth-callback.controller.ts | 28 + .../auth/request-magic-link.controller.ts | 55 ++ .../auth/request-otp.controller.ts | 45 ++ .../controllers/auth/sign-in.controller.ts | 36 ++ .../controllers/auth/sign-out.controller.ts | 0 .../controllers/auth/sign-up.controller.ts | 0 .../controllers/auth/verify-otp.controller.ts | 38 ++ 159 files changed, 3181 insertions(+), 1500 deletions(-) delete mode 100644 sigap-website/actions/auth/contact-us.ts delete mode 100644 sigap-website/actions/auth/forgot-password.ts delete mode 100644 sigap-website/actions/auth/reset-password.ts delete mode 100644 sigap-website/actions/auth/session.ts delete mode 100644 sigap-website/actions/auth/sign-in.ts delete mode 100644 sigap-website/actions/auth/sign-out.ts delete mode 100644 sigap-website/actions/auth/sign-up-action.ts delete mode 100644 sigap-website/actions/auth/verify-otp.ts delete mode 100644 sigap-website/actions/dashboard/nav-items.ts rename sigap-website/{components => app/(auth-pages)/_components}/auth/contact-us.tsx (91%) rename sigap-website/{components => app/(auth-pages)/_components}/auth/email-recovery.tsx (91%) rename sigap-website/{components => app/(auth-pages)/_components}/auth/login-form-2.tsx (93%) rename sigap-website/{components => app/(auth-pages)/_components}/auth/login-form.tsx (96%) rename sigap-website/{components => app/(auth-pages)/_components}/auth/otp-form.tsx (92%) rename sigap-website/{components => app/(auth-pages)/_components}/auth/verify-otp-form.tsx (93%) create mode 100644 sigap-website/app/(auth-pages)/actions.ts rename sigap-website/{components => app/_components}/action-search-bar.tsx (74%) rename sigap-website/{components => app/_components}/deploy-button.tsx (100%) rename sigap-website/{components => app/_components}/dynamic-icon.tsx (100%) rename sigap-website/{components => app/_components}/email-templates/admin-notification.tsx (100%) rename sigap-website/{components => app/_components}/email-templates/user-confirmation.tsx (100%) rename sigap-website/{components => app/_components}/email-templates/verify-identity.tsx (100%) rename sigap-website/{components => app/_components}/env-var-warning.tsx (100%) rename sigap-website/{components => app/_components}/floating-action-search-bar.tsx (96%) rename sigap-website/{components => app/_components}/form-field.tsx (100%) rename sigap-website/{components => app/_components}/form-message.tsx (100%) rename sigap-website/{components => app/_components}/header-auth.tsx (100%) rename sigap-website/{components => app/_components}/hero.tsx (100%) rename sigap-website/{components => app/_components}/inbox-drawer.tsx (91%) rename sigap-website/{components => app/_components}/logo/next-logo.tsx (100%) rename sigap-website/{components => app/_components}/logo/sigap-logo.tsx (100%) rename sigap-website/{components => app/_components}/logo/supabase-logo.tsx (100%) rename sigap-website/{components => app/_components}/state-screen.tsx (100%) rename sigap-website/{components => app/_components}/submit-button.tsx (89%) rename sigap-website/{components => app/_components}/team-switcher.tsx (87%) rename sigap-website/{components => app/_components}/theme-switcher.tsx (92%) rename sigap-website/{components => app/_components}/tutorial/code-block.tsx (100%) rename sigap-website/{components => app/_components}/tutorial/connect-supabase-steps.tsx (100%) rename sigap-website/{components => app/_components}/tutorial/fetch-data-steps.tsx (100%) rename sigap-website/{components => app/_components}/tutorial/sign-up-user-steps.tsx (100%) rename sigap-website/{components => app/_components}/tutorial/tutorial-step.tsx (100%) rename sigap-website/{components => app/_components}/typography/inline-code.tsx (100%) rename sigap-website/{components => app/_components}/ui/avatar.tsx (100%) rename sigap-website/{components => app/_components}/ui/badge.tsx (100%) rename sigap-website/{components => app/_components}/ui/breadcrumb.tsx (100%) rename sigap-website/{components => app/_components}/ui/button.tsx (100%) rename sigap-website/{components => app/_components}/ui/card.tsx (100%) rename sigap-website/{components => app/_components}/ui/checkbox.tsx (100%) rename sigap-website/{components => app/_components}/ui/collapsible.tsx (100%) rename sigap-website/{components => app/_components}/ui/drawer.tsx (100%) rename sigap-website/{components => app/_components}/ui/dropdown-menu.tsx (100%) rename sigap-website/{components => app/_components}/ui/form.tsx (98%) rename sigap-website/{components => app/_components}/ui/input-otp.tsx (100%) rename sigap-website/{components => app/_components}/ui/input.tsx (100%) rename sigap-website/{components => app/_components}/ui/label.tsx (100%) create mode 100644 sigap-website/app/_components/ui/popover.tsx rename sigap-website/{components => app/_components}/ui/scroll-area.tsx (100%) rename sigap-website/{components => app/_components}/ui/select.tsx (100%) rename sigap-website/{components => app/_components}/ui/separator.tsx (100%) rename sigap-website/{components => app/_components}/ui/sheet.tsx (100%) rename sigap-website/{components => app/_components}/ui/sidebar.tsx (82%) rename sigap-website/{components => app/_components}/ui/skeleton.tsx (100%) rename sigap-website/{components => app/_components}/ui/switch.tsx (100%) create mode 100644 sigap-website/app/_components/ui/table.tsx rename sigap-website/{components => app/_components}/ui/textarea.tsx (100%) rename sigap-website/{components => app/_components}/ui/toast.tsx (100%) rename sigap-website/{components => app/_components}/ui/toaster.tsx (89%) rename sigap-website/{components => app/_components}/ui/tooltip.tsx (100%) rename sigap-website/{hooks => app/_hooks}/use-debounce.ts (100%) rename sigap-website/{hooks => app/_hooks}/use-mobile.tsx (100%) rename sigap-website/{hooks => app/_hooks}/use-navigations.ts (100%) rename sigap-website/{hooks => app/_hooks}/use-resend.ts (100%) rename sigap-website/{hooks => app/_hooks}/use-supabase.ts (100%) rename sigap-website/{hooks => app/_hooks}/use-toast.ts (99%) create mode 100644 sigap-website/app/protected/(admin-pages)/_components/app-sidebar.tsx rename sigap-website/{components => app/protected/(admin-pages)/_components}/map/mapbox-view.tsx (100%) rename sigap-website/{components => app/protected/(admin-pages)/_components/navigations}/nav-main.tsx (96%) create mode 100644 sigap-website/app/protected/(admin-pages)/_components/navigations/nav-pre-main.tsx rename sigap-website/{components => app/protected/(admin-pages)/_components/navigations}/nav-report.tsx (95%) rename sigap-website/{components => app/protected/(admin-pages)/_components/navigations}/nav-user.tsx (95%) create mode 100644 sigap-website/app/protected/(admin-pages)/_components/users/edit-user.tsx create mode 100644 sigap-website/app/protected/(admin-pages)/_components/users/user-stats.tsx create mode 100644 sigap-website/app/protected/(admin-pages)/_components/users/users-table.tsx create mode 100644 sigap-website/app/protected/(admin-pages)/actions.ts create mode 100644 sigap-website/app/protected/(admin-pages)/user-management/users/page.tsx create mode 100644 sigap-website/data/crime-category.ts rename sigap-website/{components/app-sidebar.tsx => data/nav.ts} (75%) rename sigap-website/{src/infrastructure => }/hooks/use-contact-us-form.ts (89%) rename sigap-website/{src/infrastructure => }/hooks/use-nav-items.ts (100%) rename sigap-website/{src/infrastructure => }/hooks/use-navigation-item.ts (89%) rename sigap-website/{src/infrastructure => }/hooks/use-signin.ts (84%) create mode 100644 sigap-website/shadcn-ui.json rename sigap-website/src/{applications => application}/repositories/contact-us.repository.ts (78%) rename sigap-website/src/{applications => application}/repositories/nav-items.repository.ts (89%) rename sigap-website/src/{applications => application}/repositories/navigation-item.repository.ts (90%) rename sigap-website/src/{applications => application}/repositories/signin.repository.ts (56%) create mode 100644 sigap-website/src/application/repositories/users.repository.interface.ts create mode 100644 sigap-website/src/application/services/authentication.service.interface.ts create mode 100644 sigap-website/src/application/services/crash-reporter.service.interface.ts create mode 100644 sigap-website/src/application/services/instrumentation.service.interface.ts create mode 100644 sigap-website/src/application/services/transaction-manager.service.interface.ts create mode 100644 sigap-website/src/application/usecases/auth/request-magic-link.use-case.ts.ts create mode 100644 sigap-website/src/application/usecases/auth/request-otp.use-case.ts create mode 100644 sigap-website/src/application/usecases/auth/sign-in.use-case.ts create mode 100644 sigap-website/src/application/usecases/auth/sign-out.use-case.ts create mode 100644 sigap-website/src/application/usecases/auth/sign-up.use-case.ts create mode 100644 sigap-website/src/application/usecases/auth/verify-otp.use-case.ts create mode 100644 sigap-website/src/application/usecases/auth/verify-session.use-case.ts rename sigap-website/src/{applications => application}/usecases/contact-us.usecase.ts (88%) rename sigap-website/src/{applications => application}/usecases/nav-items.usecase.ts (94%) rename sigap-website/src/{applications => application}/usecases/navigation-item.usecase.ts (97%) rename sigap-website/src/{applications => application}/usecases/signin.usecases.ts (55%) delete mode 100644 sigap-website/src/applications/entities/errors/auth.ts delete mode 100644 sigap-website/src/applications/entities/models/user.model.ts create mode 100644 sigap-website/src/entities/errors/auth.error.ts rename sigap-website/src/{applications/entities/errors/common.ts => entities/errors/common.error.ts} (100%) rename sigap-website/src/{applications => }/entities/models/contact-us.model.ts (100%) create mode 100644 sigap-website/src/entities/models/cookies.model.ts create mode 100644 sigap-website/src/entities/models/crime-category.model.ts rename sigap-website/src/{applications => }/entities/models/nav-items.model.ts (100%) rename sigap-website/src/{applications => }/entities/models/navigation-item.model.ts (100%) create mode 100644 sigap-website/src/entities/models/session.model.ts create mode 100644 sigap-website/src/entities/models/transaction.interface.model.ts create mode 100644 sigap-website/src/entities/models/users.model.ts create mode 100644 sigap-website/src/infrastructure/repositories/users.repository.ts create mode 100644 sigap-website/src/infrastructure/services/authentication.service.ts create mode 100644 sigap-website/src/interface/controllers/auth/auth-callback.controller.ts create mode 100644 sigap-website/src/interface/controllers/auth/request-magic-link.controller.ts create mode 100644 sigap-website/src/interface/controllers/auth/request-otp.controller.ts create mode 100644 sigap-website/src/interface/controllers/auth/sign-in.controller.ts create mode 100644 sigap-website/src/interface/controllers/auth/sign-out.controller.ts create mode 100644 sigap-website/src/interface/controllers/auth/sign-up.controller.ts create mode 100644 sigap-website/src/interface/controllers/auth/verify-otp.controller.ts diff --git a/sigap-website/.vscode/settings.json b/sigap-website/.vscode/settings.json index af62c23..0dccffe 100644 --- a/sigap-website/.vscode/settings.json +++ b/sigap-website/.vscode/settings.json @@ -1,7 +1,5 @@ { - "deno.enablePaths": [ - "supabase/functions" - ], + "deno.enablePaths": ["supabase/functions"], "deno.lint": true, "deno.unstable": [ "bare-node-builtins", @@ -19,6 +17,6 @@ "net" ], "[typescript]": { - "editor.defaultFormatter": "denoland.vscode-deno" + "editor.defaultFormatter": "esbenp.prettier-vscode" } } diff --git a/sigap-website/actions/auth/contact-us.ts b/sigap-website/actions/auth/contact-us.ts deleted file mode 100644 index 000e2f0..0000000 --- a/sigap-website/actions/auth/contact-us.ts +++ /dev/null @@ -1,107 +0,0 @@ -"use server"; - -import AdminNotification from "@/components/email-templates/admin-notification"; -import { render } from "@react-email/components"; -import UserConfirmation from "@/components/email-templates/user-confirmation"; -import { createClient } from "@/utils/supabase/server"; -import { useResend } from "@/hooks/use-resend"; -import { typeMessageMap } from "@/src/applications/entities/models/contact-us.model"; - -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 ", - 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 ", - 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.", - }; - } -} diff --git a/sigap-website/actions/auth/forgot-password.ts b/sigap-website/actions/auth/forgot-password.ts deleted file mode 100644 index bceba0b..0000000 --- a/sigap-website/actions/auth/forgot-password.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { encodedRedirect } from "@/utils/utils"; -import { createClient } from "@/utils/supabase/server"; -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." - ); -}; diff --git a/sigap-website/actions/auth/reset-password.ts b/sigap-website/actions/auth/reset-password.ts deleted file mode 100644 index b38bb95..0000000 --- a/sigap-website/actions/auth/reset-password.ts +++ /dev/null @@ -1,41 +0,0 @@ -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"); -}; diff --git a/sigap-website/actions/auth/session.ts b/sigap-website/actions/auth/session.ts deleted file mode 100644 index 15e1f75..0000000 --- a/sigap-website/actions/auth/session.ts +++ /dev/null @@ -1,39 +0,0 @@ -"use server"; - -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: "/protected/dashboard", // or your preferred authenticated route - }; - } - - return { - success: false, - message: "No active session", - }; - } catch (error) { - return { - success: false, - error: "An unexpected error occurred", - }; - } -}; diff --git a/sigap-website/actions/auth/sign-in.ts b/sigap-website/actions/auth/sign-in.ts deleted file mode 100644 index e0644d2..0000000 --- a/sigap-website/actions/auth/sign-in.ts +++ /dev/null @@ -1,53 +0,0 @@ -"use server"; - -import { createClient } from "@/utils/supabase/server"; - -export const signInAction = async (formData: { email: string }) => { - const supabase = await createClient(); - const encodeEmail = encodeURIComponent(formData.email); - - try { - // First, check for existing session - const { - data: { session }, - error: sessionError, - } = await supabase.auth.getSession(); - - // If there's an active session and the email matches - if (session && session.user.email === formData.email) { - return { - success: true, - message: "Already logged in", - redirectTo: "/protected/dashboard", // or wherever you want to redirect logged in users - }; - } - - // If no active session or different email, proceed with OTP - const { data, error } = await supabase.auth.signInWithOtp({ - email: formData.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", - }; - } -}; \ No newline at end of file diff --git a/sigap-website/actions/auth/sign-out.ts b/sigap-website/actions/auth/sign-out.ts deleted file mode 100644 index 8d50d8c..0000000 --- a/sigap-website/actions/auth/sign-out.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { encodedRedirect } from "@/utils/utils"; -import { createClient } from "@/utils/supabase/server"; -import { headers } from "next/headers"; -import { redirect } from "next/navigation"; - -export const signOutAction = async () => { - const supabase = await createClient(); - await supabase.auth.signOut(); - return redirect("/sign-in"); -}; diff --git a/sigap-website/actions/auth/sign-up-action.ts b/sigap-website/actions/auth/sign-up-action.ts deleted file mode 100644 index 5d279e4..0000000 --- a/sigap-website/actions/auth/sign-up-action.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { encodedRedirect } from "@/utils/utils"; -import { createClient } from "@/utils/supabase/server"; -import { headers } from "next/headers"; -import { redirect } from "next/navigation"; - -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.", - ); - } -}; \ No newline at end of file diff --git a/sigap-website/actions/auth/verify-otp.ts b/sigap-website/actions/auth/verify-otp.ts deleted file mode 100644 index 6f3ee91..0000000 --- a/sigap-website/actions/auth/verify-otp.ts +++ /dev/null @@ -1,32 +0,0 @@ -"use server"; -import { createClient } from "@/utils/supabase/server"; -import { encodedRedirect } from "@/utils/utils"; -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("/protected/dashboard?message=OTP verified successfully"); -}; diff --git a/sigap-website/actions/dashboard/nav-items.ts b/sigap-website/actions/dashboard/nav-items.ts deleted file mode 100644 index b12037a..0000000 --- a/sigap-website/actions/dashboard/nav-items.ts +++ /dev/null @@ -1,211 +0,0 @@ -"use server"; - -import { - NavItems, - navItemsDeleteSchema, - NavItemsInsert, - navItemsInsertSchema, - navItemsUpdateSchema, -} from "@/src/applications/entities/models/nav-items.model"; -import { NavItemsRepository } from "@/src/applications/repositories/nav-items.repository"; -import { NavItemsRepositoryImpl } from "@/src/infrastructure/repositories/nav-items.repository.impl"; -import { revalidatePath } from "next/cache"; -import { z } from "zod"; - -// Initialize repository -const navItemsRepo: NavItemsRepository = new NavItemsRepositoryImpl(); - -/** - * Get all navigation items - */ -export async function getNavItems() { - return await navItemsRepo.getNavItems(); -} - -/** - * Create a new navigation item - */ -export async function createNavItem( - formData: FormData | Record -) { - try { - const data = - formData instanceof FormData - ? Object.fromEntries(formData.entries()) - : formData; - - // Parse and validate input data - const validatedData = navItemsInsertSchema.parse(data); - - // Call repository method (assuming it exists) - const result = await navItemsRepo.createNavItems(validatedData); - - // Revalidate cache if successful - if (result.success) { - revalidatePath("/admin/navigation"); - } - - return result; - } catch (error) { - if (error instanceof z.ZodError) { - return { - success: false, - errors: error.errors.reduce( - (acc, curr) => { - acc[curr.path.join(".")] = curr.message; - return acc; - }, - {} as Record - ), - message: "Validation failed", - }; - } - - return { - success: false, - error: "Failed to create navigation item", - message: - error instanceof Error ? error.message : "Unknown error occurred", - }; - } -} - -/** - * Update an existing navigation item - */ -export async function updateNavItem( - id: string, - formData: FormData | Record -) { - try { - const data = - formData instanceof FormData - ? Object.fromEntries(formData.entries()) - : formData; - - // Parse and validate input data - const validatedData = navItemsUpdateSchema.parse(data); - - // Call repository method (assuming it exists) - const result = await navItemsRepo.updateNavItems(id, validatedData); - - // Revalidate cache if successful - if (result.success) { - revalidatePath("/admin/navigation"); - } - - return result; - } catch (error) { - if (error instanceof z.ZodError) { - return { - success: false, - errors: error.errors.reduce( - (acc, curr) => { - acc[curr.path.join(".")] = curr.message; - return acc; - }, - {} as Record - ), - message: "Validation failed", - }; - } - - return { - success: false, - error: "Failed to update navigation item", - message: - error instanceof Error ? error.message : "Unknown error occurred", - }; - } -} - -/** - * Delete a navigation item - */ -export async function deleteNavItem(id: string) { - try { - // Parse and validate input - const validatedData = navItemsDeleteSchema.parse({ id }); - - // Call repository method (assuming it exists) - const result = await navItemsRepo.deleteNavItems(validatedData.id); - - // Revalidate cache if successful - if (result.success) { - revalidatePath("/admin/navigation"); - } - - return result; - } catch (error) { - if (error instanceof z.ZodError) { - return { - success: false, - errors: error.errors.reduce( - (acc, curr) => { - acc[curr.path.join(".")] = curr.message; - return acc; - }, - {} as Record - ), - message: "Validation failed", - }; - } - - return { - success: false, - error: "Failed to delete navigation item", - message: - error instanceof Error ? error.message : "Unknown error occurred", - }; - } -} - -// /** -// * Toggle active status of a navigation item -// */ -// export async function toggleNavItemActive(id: string) { -// try { -// // Call repository method (assuming it exists) -// const result = await navItemsRepo.toggleActive(id); - -// // Revalidate cache if successful -// if (result.success) { -// revalidatePath("/admin/navigation"); -// } - -// return result; -// } catch (error) { -// return { -// success: false, -// error: "Failed to toggle navigation item status", -// message: -// error instanceof Error ? error.message : "Unknown error occurred", -// }; -// } -// } - -// /** -// * Update navigation items order -// */ -// export async function updateNavItemsOrder( -// items: { id: string; order_seq: number }[] -// ) { -// try { -// // Call repository method (assuming it exists) -// const result = await navItemsRepo.updateOrder(items); - -// // Revalidate cache if successful -// if (result.success) { -// revalidatePath("/admin/navigation"); -// } - -// return result; -// } catch (error) { -// return { -// success: false, -// error: "Failed to update navigation order", -// message: -// error instanceof Error ? error.message : "Unknown error occurred", -// }; -// } -// } diff --git a/sigap-website/components/auth/contact-us.tsx b/sigap-website/app/(auth-pages)/_components/auth/contact-us.tsx similarity index 91% rename from sigap-website/components/auth/contact-us.tsx rename to sigap-website/app/(auth-pages)/_components/auth/contact-us.tsx index bab5b26..123201c 100644 --- a/sigap-website/components/auth/contact-us.tsx +++ b/sigap-website/app/(auth-pages)/_components/auth/contact-us.tsx @@ -8,23 +8,23 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; +} from "@/app/_components/ui/card"; +import { Input } from "@/app/_components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select"; -import { Textarea } from "../ui/textarea"; -import { SubmitButton } from "../submit-button"; +} from "@/app/_components/ui/select"; +import { Textarea } from "../../../_components/ui/textarea"; +import { SubmitButton } from "../../../_components/submit-button"; import Link from "next/link"; import { TValidator } from "@/utils/validator"; -import { useContactForm } from "@/src/infrastructure/hooks/use-contact-us-form"; -import { FormField } from "../form-field"; -import { typeMessage } from "@/src/applications/entities/models/contact-us.model"; -import { Form } from "../ui/form"; +import { FormField } from "../../../_components/form-field"; +import { typeMessage } from "@/src/entities/models/contact-us.model"; +import { Form } from "../../../_components/ui/form"; +import { useContactForm } from "@/hooks/use-contact-us-form"; export function ContactUsForm() { const { diff --git a/sigap-website/components/auth/email-recovery.tsx b/sigap-website/app/(auth-pages)/_components/auth/email-recovery.tsx similarity index 91% rename from sigap-website/components/auth/email-recovery.tsx rename to sigap-website/app/(auth-pages)/_components/auth/email-recovery.tsx index 13a7305..f87b6f2 100644 --- a/sigap-website/components/auth/email-recovery.tsx +++ b/sigap-website/app/(auth-pages)/_components/auth/email-recovery.tsx @@ -5,8 +5,8 @@ import { useRouter } from "next/router"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { toast } from "@/hooks/use-toast"; -import { Button } from "@/components/ui/button"; +import { toast } from "@/app/_hooks/use-toast"; +import { Button } from "@/app/_components/ui/button"; import { Form, FormControl, @@ -15,8 +15,8 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; +} from "@/app/_components/ui/form"; +import { Input } from "@/app/_components/ui/input"; import { Card, CardContent, @@ -24,9 +24,9 @@ import { CardFooter, CardHeader, CardTitle, -} from "@/components/ui/card"; -import { SubmitButton } from "@/components/submit-button"; +} from "@/app/_components/ui/card"; import Link from "next/link"; +import { SubmitButton } from "@/app/_components/submit-button"; const FormSchema = z.object({ email: z.string().email({ diff --git a/sigap-website/components/auth/login-form-2.tsx b/sigap-website/app/(auth-pages)/_components/auth/login-form-2.tsx similarity index 93% rename from sigap-website/components/auth/login-form-2.tsx rename to sigap-website/app/(auth-pages)/_components/auth/login-form-2.tsx index 226e8bc..10e075f 100644 --- a/sigap-website/components/auth/login-form-2.tsx +++ b/sigap-website/app/(auth-pages)/_components/auth/login-form-2.tsx @@ -1,14 +1,14 @@ "use client"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; +import { Button } from "@/app/_components/ui/button"; +import { Input } from "@/app/_components/ui/input"; import { Github, Lock } from "lucide-react"; import Link from "next/link"; -import { SubmitButton } from "../submit-button"; +import { SubmitButton } from "../../../_components/submit-button"; -import { FormField } from "../form-field"; -import { useSignInForm } from "@/src/infrastructure/hooks/use-signin"; +import { FormField } from "../../../_components/form-field"; +import { useSignInForm } from "@/hooks/use-signin"; export function LoginForm2({ className, diff --git a/sigap-website/components/auth/login-form.tsx b/sigap-website/app/(auth-pages)/_components/auth/login-form.tsx similarity index 96% rename from sigap-website/components/auth/login-form.tsx rename to sigap-website/app/(auth-pages)/_components/auth/login-form.tsx index de88eeb..6503c58 100644 --- a/sigap-website/components/auth/login-form.tsx +++ b/sigap-website/app/(auth-pages)/_components/auth/login-form.tsx @@ -1,13 +1,13 @@ // import { cn } from "@/lib/utils"; -// import { Button } from "@/components/ui/button"; +// import { Button } from "@/app/_components/ui/button"; // import { // Card, // CardContent, // CardDescription, // CardHeader, // CardTitle, -// } from "@/components/ui/card"; -// import { Input } from "@/components/ui/input"; +// } from "@/app/_components/ui/card"; +// import { Input } from "@/app/_components/ui/input"; // import { Label } from "@/components/ui/label"; // import { SubmitButton } from "../submit-button"; // import { signInAction } from "@/actions/auth/sign-in"; diff --git a/sigap-website/components/auth/otp-form.tsx b/sigap-website/app/(auth-pages)/_components/auth/otp-form.tsx similarity index 92% rename from sigap-website/components/auth/otp-form.tsx rename to sigap-website/app/(auth-pages)/_components/auth/otp-form.tsx index 23737b8..24fc59f 100644 --- a/sigap-website/components/auth/otp-form.tsx +++ b/sigap-website/app/(auth-pages)/_components/auth/otp-form.tsx @@ -4,8 +4,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { toast } from "@/hooks/use-toast"; -import { Button } from "@/components/ui/button"; +import { toast } from "@/app/_hooks/use-toast"; +import { Button } from "@/app/_components/ui/button"; import { Form, FormControl, @@ -14,20 +14,20 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form"; +} from "@/app/_components/ui/form"; import { InputOTP, InputOTPGroup, InputOTPSlot, -} from "@/components/ui/input-otp"; -import { SubmitButton } from "../submit-button"; +} from "@/app/_components/ui/input-otp"; +import { SubmitButton } from "../../../_components/submit-button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "../ui/card"; +} from "../../../_components/ui/card"; import { cn } from "@/lib/utils"; const FormSchema = z.object({ diff --git a/sigap-website/components/auth/verify-otp-form.tsx b/sigap-website/app/(auth-pages)/_components/auth/verify-otp-form.tsx similarity index 93% rename from sigap-website/components/auth/verify-otp-form.tsx rename to sigap-website/app/(auth-pages)/_components/auth/verify-otp-form.tsx index 03818f3..6c607d3 100644 --- a/sigap-website/components/auth/verify-otp-form.tsx +++ b/sigap-website/app/(auth-pages)/_components/auth/verify-otp-form.tsx @@ -3,7 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { toast } from "@/hooks/use-toast"; +import { toast } from "@/app/_hooks/use-toast"; import { Form, FormControl, @@ -11,24 +11,24 @@ import { FormField, FormItem, FormMessage, -} from "@/components/ui/form"; +} from "@/app/_components/ui/form"; import { InputOTP, InputOTPGroup, InputOTPSlot, -} from "@/components/ui/input-otp"; -import { SubmitButton } from "../submit-button"; +} from "@/app/_components/ui/input-otp"; +import { SubmitButton } from "../../../_components/submit-button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "../ui/card"; +} from "../../../_components/ui/card"; import { cn } from "@/lib/utils"; import { verifyOtpAction } from "@/actions/auth/verify-otp"; import { useSearchParams } from "next/navigation"; -import { Input } from "../ui/input"; +import { Input } from "../../../_components/ui/input"; const FormSchema = z.object({ token: z.string().min(6, { diff --git a/sigap-website/app/(auth-pages)/actions.ts b/sigap-website/app/(auth-pages)/actions.ts new file mode 100644 index 0000000..3ecb5f0 --- /dev/null +++ b/sigap-website/app/(auth-pages)/actions.ts @@ -0,0 +1,336 @@ +"use server"; + +import { createClient } from "@/utils/supabase/server"; +import { encodedRedirect } from "@/utils/utils"; +import { headers } from "next/headers"; +import { redirect } from "next/navigation"; +import AdminNotification from "../_components/email-templates/admin-notification"; +import UserConfirmation from "../_components/email-templates/user-confirmation"; +import { render } from "@react-email/components"; +import { useResend } from "../_hooks/use-resend"; +import { typeMessageMap } from "@/src/entities/models/contact-us.model"; + +export const signInAction = async (formData: { email: string }) => { + const supabase = await createClient(); + const encodeEmail = encodeURIComponent(formData.email); + + try { + // First, check for existing session + const { + data: { session }, + error: sessionError, + } = await supabase.auth.getSession(); + + // If there's an active session and the email matches + if (session && session.user.email === formData.email) { + return { + success: true, + message: "Already logged in", + redirectTo: "/protected/dashboard", // or wherever you want to redirect logged in users + }; + } + + // If no active session or different email, proceed with OTP + const { data, error } = await supabase.auth.signInWithOtp({ + email: formData.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", + }; + } +}; + +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.", + ); + } + }; + + +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: "/protected/dashboard", // or your preferred authenticated route + }; + } + + return { + success: false, + message: "No active session", + }; + } catch (error) { + return { + success: false, + error: "An unexpected error occurred", + }; + } + }; + + 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." + ); + }; + + 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"); + }; + + 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("/protected/dashboard?message=OTP verified successfully"); + }; + + 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 ", + 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 ", + 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.", + }; + } + } + + export const signOutAction = async () => { + const supabase = await createClient(); + await supabase.auth.signOut(); + return redirect("/sign-in"); + }; \ No newline at end of file diff --git a/sigap-website/app/(auth-pages)/contact-us/page.tsx b/sigap-website/app/(auth-pages)/contact-us/page.tsx index e4373b7..74a13d0 100644 --- a/sigap-website/app/(auth-pages)/contact-us/page.tsx +++ b/sigap-website/app/(auth-pages)/contact-us/page.tsx @@ -1,5 +1,5 @@ -import { ContactUsForm } from "@/components/auth/contact-us"; -import { Button } from "@/components/ui/button"; +import { ContactUsForm } from "@/app/(auth-pages)/_components/auth/contact-us"; +import { Button } from "@/app/_components/ui/button"; import { GalleryVerticalEnd, Globe } from "lucide-react"; export default async function ContactAdminPage() { diff --git a/sigap-website/app/(auth-pages)/email-recovery/page.tsx b/sigap-website/app/(auth-pages)/email-recovery/page.tsx index de906de..e11b1e0 100644 --- a/sigap-website/app/(auth-pages)/email-recovery/page.tsx +++ b/sigap-website/app/(auth-pages)/email-recovery/page.tsx @@ -1,6 +1,4 @@ -import RecoveryEmailForm from "@/components/auth/email-recovery"; -import { VerifyOtpForm } from "@/components/auth/verify-otp-form"; - +import RecoveryEmailForm from "@/app/(auth-pages)/_components/auth/email-recovery"; import { GalleryVerticalEnd } from "lucide-react"; export default async function VerifyOtpPage() { diff --git a/sigap-website/app/(auth-pages)/forgot-password/page.tsx b/sigap-website/app/(auth-pages)/forgot-password/page.tsx index dca53ed..2c8bd5d 100644 --- a/sigap-website/app/(auth-pages)/forgot-password/page.tsx +++ b/sigap-website/app/(auth-pages)/forgot-password/page.tsx @@ -1,10 +1,12 @@ -import { FormMessage, Message } from "@/components/form-message"; -import { SubmitButton } from "@/components/submit-button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; + import Link from "next/link"; import { SmtpMessage } from "../smtp-message"; -import { forgotPasswordAction } from "@/actions/auth/forgot-password"; + +import { Input } from "@/app/_components/ui/input"; +import { Label } from "@/app/_components/ui/label"; +import { SubmitButton } from "@/app/_components/submit-button"; +import { FormMessage, Message } from "@/app/_components/form-message"; +import { forgotPasswordAction } from "../actions"; export default async function ForgotPassword(props: { searchParams: Promise; diff --git a/sigap-website/app/(auth-pages)/layout.tsx b/sigap-website/app/(auth-pages)/layout.tsx index ccb5d43..66c2056 100644 --- a/sigap-website/app/(auth-pages)/layout.tsx +++ b/sigap-website/app/(auth-pages)/layout.tsx @@ -1,5 +1,6 @@ -import { checkSession } from "@/actions/auth/session"; + import { redirect } from "next/navigation"; +import { checkSession } from "./actions"; export default async function Layout({ children, diff --git a/sigap-website/app/(auth-pages)/sign-in/page.tsx b/sigap-website/app/(auth-pages)/sign-in/page.tsx index e1a84c6..94a64f4 100644 --- a/sigap-website/app/(auth-pages)/sign-in/page.tsx +++ b/sigap-website/app/(auth-pages)/sign-in/page.tsx @@ -1,11 +1,7 @@ -import { FormMessage, Message } from "@/components/form-message"; -import { SubmitButton } from "@/components/submit-button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Button } from "@/components/ui/button"; -import { GalleryVerticalEnd, Book, Globe } from "lucide-react"; -import Link from "next/link"; -import { LoginForm2 } from "@/components/auth/login-form-2"; +import { Message } from "@/app/_components/form-message"; +import { Button } from "@/app/_components/ui/button"; +import { GalleryVerticalEnd, Globe } from "lucide-react"; +import { LoginForm2 } from "@/app/(auth-pages)/_components/auth/login-form-2"; export default async function Login(props: { searchParams: Promise }) { const searchParams = await props.searchParams; diff --git a/sigap-website/app/(auth-pages)/sign-up/page.tsx b/sigap-website/app/(auth-pages)/sign-up/page.tsx index 9b8c18c..81b6e43 100644 --- a/sigap-website/app/(auth-pages)/sign-up/page.tsx +++ b/sigap-website/app/(auth-pages)/sign-up/page.tsx @@ -1,7 +1,7 @@ -import { FormMessage, Message } from "@/components/form-message"; -import { SubmitButton } from "@/components/submit-button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import { FormMessage, Message } from "@/app/_components/form-message"; +import { SubmitButton } from "@/app/_components/submit-button"; +import { Input } from "@/app/_components/ui/input"; +import { Label } from "@/app/_components/ui/label"; import Link from "next/link"; import { SmtpMessage } from "../smtp-message"; import { signUpAction } from "@/actions/auth/sign-up-action"; diff --git a/sigap-website/app/(auth-pages)/verify-otp/page.tsx b/sigap-website/app/(auth-pages)/verify-otp/page.tsx index 6f34cb1..3bcf83b 100644 --- a/sigap-website/app/(auth-pages)/verify-otp/page.tsx +++ b/sigap-website/app/(auth-pages)/verify-otp/page.tsx @@ -1,4 +1,4 @@ -import { VerifyOtpForm } from "@/components/auth/verify-otp-form"; +import { VerifyOtpForm } from "@/app/(auth-pages)/_components/auth/verify-otp-form"; import { GalleryVerticalEnd } from "lucide-react"; diff --git a/sigap-website/components/action-search-bar.tsx b/sigap-website/app/_components/action-search-bar.tsx similarity index 74% rename from sigap-website/components/action-search-bar.tsx rename to sigap-website/app/_components/action-search-bar.tsx index f5724e6..1c58883 100644 --- a/sigap-website/components/action-search-bar.tsx +++ b/sigap-website/app/_components/action-search-bar.tsx @@ -1,22 +1,35 @@ -"use client" +"use client"; -import React, { useState, useEffect, forwardRef, useImperativeHandle } from "react" -import { Input } from "@/components/ui/input" -import { motion, AnimatePresence } from "framer-motion" -import { Search, Send, BarChart2, Globe, Video, PlaneTakeoff, AudioLines } from "lucide-react" -import useDebounce from "@/hooks/use-debounce" +import React, { + useState, + useEffect, + forwardRef, + useImperativeHandle, +} from "react"; +import { Input } from "@/app/_components/ui/input"; +import { motion, AnimatePresence } from "framer-motion"; +import { + Search, + Send, + BarChart2, + Globe, + Video, + PlaneTakeoff, + AudioLines, +} from "lucide-react"; +import useDebounce from "@/app/_hooks/use-debounce"; interface Action { - id: string - label: string - icon: React.ReactNode - description?: string - short?: string - end?: string + id: string; + label: string; + icon: React.ReactNode; + description?: string; + short?: string; + end?: string; } interface SearchResult { - actions: Action[] + actions: Action[]; } const allActions = [ @@ -60,49 +73,49 @@ const allActions = [ short: "", end: "Command", }, -] +]; interface ActionSearchBarProps { - actions?: Action[] - autoFocus?: boolean - isFloating?: boolean + actions?: Action[]; + autoFocus?: boolean; + isFloating?: boolean; } const ActionSearchBar = forwardRef( ({ actions = allActions, autoFocus = false, isFloating = false }, ref) => { - const [query, setQuery] = useState("") - const [result, setResult] = useState(null) - const [isFocused, setIsFocused] = useState(autoFocus) - const [selectedAction, setSelectedAction] = useState(null) - const debouncedQuery = useDebounce(query, 200) - const inputRef = React.useRef(null) + const [query, setQuery] = useState(""); + const [result, setResult] = useState(null); + const [isFocused, setIsFocused] = useState(autoFocus); + const [selectedAction, setSelectedAction] = useState(null); + const debouncedQuery = useDebounce(query, 200); + const inputRef = React.useRef(null); - useImperativeHandle(ref, () => inputRef.current as HTMLInputElement) + useImperativeHandle(ref, () => inputRef.current as HTMLInputElement); useEffect(() => { if (autoFocus && inputRef.current) { - inputRef.current.focus() + inputRef.current.focus(); } - }, [autoFocus]) + }, [autoFocus]); useEffect(() => { if (!debouncedQuery) { - setResult({ actions: allActions }) - return + setResult({ actions: allActions }); + return; } - const normalizedQuery = debouncedQuery.toLowerCase().trim() + const normalizedQuery = debouncedQuery.toLowerCase().trim(); const filteredActions = allActions.filter((action) => { - const searchableText = action.label.toLowerCase() - return searchableText.includes(normalizedQuery) - }) + const searchableText = action.label.toLowerCase(); + return searchableText.includes(normalizedQuery); + }); - setResult({ actions: filteredActions }) - }, [debouncedQuery]) + setResult({ actions: filteredActions }); + }, [debouncedQuery]); const handleInputChange = (e: React.ChangeEvent) => { - setQuery(e.target.value) - } + setQuery(e.target.value); + }; const container = { hidden: { opacity: 0, height: 0 }, @@ -128,7 +141,7 @@ const ActionSearchBar = forwardRef( }, }, }, - } + }; const item = { hidden: { opacity: 0, y: 20 }, @@ -146,10 +159,12 @@ const ActionSearchBar = forwardRef( duration: 0.2, }, }, - } + }; return ( -
+
( value={query} onChange={handleInputChange} onFocus={() => setIsFocused(true)} - onBlur={() => !isFloating && setTimeout(() => setIsFocused(false), 200)} + onBlur={() => + !isFloating && setTimeout(() => setIsFocused(false), 200) + } className="pl-3 pr-9 py-1.5 h-9 text-sm rounded-lg focus-visible:ring-offset-0" />
@@ -209,13 +226,21 @@ const ActionSearchBar = forwardRef(
{action.icon} - {action.label} - {action.description} + + {action.label} + + + {action.description} +
- {action.short} - {action.end} + + {action.short} + + + {action.end} +
))} @@ -230,11 +255,10 @@ const ActionSearchBar = forwardRef( )}
- ) - }, -) + ); + } +); -ActionSearchBar.displayName = "ActionSearchBar" - -export default ActionSearchBar +ActionSearchBar.displayName = "ActionSearchBar"; +export default ActionSearchBar; diff --git a/sigap-website/components/deploy-button.tsx b/sigap-website/app/_components/deploy-button.tsx similarity index 100% rename from sigap-website/components/deploy-button.tsx rename to sigap-website/app/_components/deploy-button.tsx diff --git a/sigap-website/components/dynamic-icon.tsx b/sigap-website/app/_components/dynamic-icon.tsx similarity index 100% rename from sigap-website/components/dynamic-icon.tsx rename to sigap-website/app/_components/dynamic-icon.tsx diff --git a/sigap-website/components/email-templates/admin-notification.tsx b/sigap-website/app/_components/email-templates/admin-notification.tsx similarity index 100% rename from sigap-website/components/email-templates/admin-notification.tsx rename to sigap-website/app/_components/email-templates/admin-notification.tsx diff --git a/sigap-website/components/email-templates/user-confirmation.tsx b/sigap-website/app/_components/email-templates/user-confirmation.tsx similarity index 100% rename from sigap-website/components/email-templates/user-confirmation.tsx rename to sigap-website/app/_components/email-templates/user-confirmation.tsx diff --git a/sigap-website/components/email-templates/verify-identity.tsx b/sigap-website/app/_components/email-templates/verify-identity.tsx similarity index 100% rename from sigap-website/components/email-templates/verify-identity.tsx rename to sigap-website/app/_components/email-templates/verify-identity.tsx diff --git a/sigap-website/components/env-var-warning.tsx b/sigap-website/app/_components/env-var-warning.tsx similarity index 100% rename from sigap-website/components/env-var-warning.tsx rename to sigap-website/app/_components/env-var-warning.tsx diff --git a/sigap-website/components/floating-action-search-bar.tsx b/sigap-website/app/_components/floating-action-search-bar.tsx similarity index 96% rename from sigap-website/components/floating-action-search-bar.tsx rename to sigap-website/app/_components/floating-action-search-bar.tsx index 8923364..ec87e10 100644 --- a/sigap-website/components/floating-action-search-bar.tsx +++ b/sigap-website/app/_components/floating-action-search-bar.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; -import ActionSearchBar from "@/components/action-search-bar"; +import ActionSearchBar from "@/app/_components/action-search-bar"; export default function FloatingActionSearchBar() { const [isOpen, setIsOpen] = useState(false); diff --git a/sigap-website/components/form-field.tsx b/sigap-website/app/_components/form-field.tsx similarity index 100% rename from sigap-website/components/form-field.tsx rename to sigap-website/app/_components/form-field.tsx diff --git a/sigap-website/components/form-message.tsx b/sigap-website/app/_components/form-message.tsx similarity index 100% rename from sigap-website/components/form-message.tsx rename to sigap-website/app/_components/form-message.tsx diff --git a/sigap-website/components/header-auth.tsx b/sigap-website/app/_components/header-auth.tsx similarity index 100% rename from sigap-website/components/header-auth.tsx rename to sigap-website/app/_components/header-auth.tsx diff --git a/sigap-website/components/hero.tsx b/sigap-website/app/_components/hero.tsx similarity index 100% rename from sigap-website/components/hero.tsx rename to sigap-website/app/_components/hero.tsx diff --git a/sigap-website/components/inbox-drawer.tsx b/sigap-website/app/_components/inbox-drawer.tsx similarity index 91% rename from sigap-website/components/inbox-drawer.tsx rename to sigap-website/app/_components/inbox-drawer.tsx index c5f06b0..20ee2de 100644 --- a/sigap-website/components/inbox-drawer.tsx +++ b/sigap-website/app/_components/inbox-drawer.tsx @@ -2,18 +2,22 @@ import * as React from "react"; import { Inbox, Search, ArrowLeft } from "lucide-react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/app/_components/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, -} from "@/components/ui/sheet"; -import { Input } from "@/components/ui/input"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Badge } from "@/components/ui/badge"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +} from "@/app/_components/ui/sheet"; +import { Input } from "@/app/_components/ui/input"; +import { ScrollArea } from "@/app/_components/ui/scroll-area"; +import { Badge } from "@/app/_components/ui/badge"; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/app/_components/ui/avatar"; interface InboxDrawerProps { showTitle?: boolean; @@ -95,7 +99,6 @@ const sampleMails: MailMessage[] = [ date: "4 days ago", read: true, }, - ]; const InboxDrawerComponent: React.FC = ({ @@ -183,19 +186,19 @@ const InboxDrawerComponent: React.FC = ({
{/* {showAvatar && ( */} - - - - {selectedMessage.name - .split(" ") - .map((n) => n[0]) - .join("") - .toUpperCase()} - - + + + + {selectedMessage.name + .split(" ") + .map((n) => n[0]) + .join("") + .toUpperCase()} + + {/* )} */}

{selectedMessage.name}

diff --git a/sigap-website/components/logo/next-logo.tsx b/sigap-website/app/_components/logo/next-logo.tsx similarity index 100% rename from sigap-website/components/logo/next-logo.tsx rename to sigap-website/app/_components/logo/next-logo.tsx diff --git a/sigap-website/components/logo/sigap-logo.tsx b/sigap-website/app/_components/logo/sigap-logo.tsx similarity index 100% rename from sigap-website/components/logo/sigap-logo.tsx rename to sigap-website/app/_components/logo/sigap-logo.tsx diff --git a/sigap-website/components/logo/supabase-logo.tsx b/sigap-website/app/_components/logo/supabase-logo.tsx similarity index 100% rename from sigap-website/components/logo/supabase-logo.tsx rename to sigap-website/app/_components/logo/supabase-logo.tsx diff --git a/sigap-website/components/state-screen.tsx b/sigap-website/app/_components/state-screen.tsx similarity index 100% rename from sigap-website/components/state-screen.tsx rename to sigap-website/app/_components/state-screen.tsx diff --git a/sigap-website/components/submit-button.tsx b/sigap-website/app/_components/submit-button.tsx similarity index 89% rename from sigap-website/components/submit-button.tsx rename to sigap-website/app/_components/submit-button.tsx index c1cd9f8..7b6ac04 100644 --- a/sigap-website/components/submit-button.tsx +++ b/sigap-website/app/_components/submit-button.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/app/_components/ui/button"; import { type ComponentProps } from "react"; import { useFormStatus } from "react-dom"; diff --git a/sigap-website/components/team-switcher.tsx b/sigap-website/app/_components/team-switcher.tsx similarity index 87% rename from sigap-website/components/team-switcher.tsx rename to sigap-website/app/_components/team-switcher.tsx index 2808e0a..f36bf24 100644 --- a/sigap-website/components/team-switcher.tsx +++ b/sigap-website/app/_components/team-switcher.tsx @@ -1,7 +1,7 @@ -"use client" +"use client"; -import * as React from "react" -import { ChevronsUpDown, Plus } from "lucide-react" +import * as React from "react"; +import { ChevronsUpDown, Plus } from "lucide-react"; import { DropdownMenu, @@ -11,25 +11,25 @@ import { DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" +} from "@/app/_components/ui/dropdown-menu"; import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar, -} from "@/components/ui/sidebar" +} from "@/app/_components/ui/sidebar"; export function TeamSwitcher({ teams, }: { teams: { - name: string - logo: React.ElementType - plan: string - }[] + name: string; + logo: React.ElementType; + plan: string; + }[]; }) { - const { isMobile } = useSidebar() - const [activeTeam, setActiveTeam] = React.useState(teams[0]) + const { isMobile } = useSidebar(); + const [activeTeam, setActiveTeam] = React.useState(teams[0]); return ( @@ -85,5 +85,5 @@ export function TeamSwitcher({ - ) + ); } diff --git a/sigap-website/components/theme-switcher.tsx b/sigap-website/app/_components/theme-switcher.tsx similarity index 92% rename from sigap-website/components/theme-switcher.tsx rename to sigap-website/app/_components/theme-switcher.tsx index ea2cf7b..e38839b 100644 --- a/sigap-website/components/theme-switcher.tsx +++ b/sigap-website/app/_components/theme-switcher.tsx @@ -2,14 +2,14 @@ import React from "react"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/app/_components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +} from "@/app/_components/ui/dropdown-menu"; import { Laptop, Moon, Sun, type LucideIcon } from "lucide-react"; import { useTheme } from "next-themes"; import { useEffect, useState, useMemo } from "react"; @@ -78,7 +78,7 @@ const ThemeSwitcherComponent = ({ className="flex gap-2" value={option.value} > - {/* */} + {option.label} ))} diff --git a/sigap-website/components/tutorial/code-block.tsx b/sigap-website/app/_components/tutorial/code-block.tsx similarity index 100% rename from sigap-website/components/tutorial/code-block.tsx rename to sigap-website/app/_components/tutorial/code-block.tsx diff --git a/sigap-website/components/tutorial/connect-supabase-steps.tsx b/sigap-website/app/_components/tutorial/connect-supabase-steps.tsx similarity index 100% rename from sigap-website/components/tutorial/connect-supabase-steps.tsx rename to sigap-website/app/_components/tutorial/connect-supabase-steps.tsx diff --git a/sigap-website/components/tutorial/fetch-data-steps.tsx b/sigap-website/app/_components/tutorial/fetch-data-steps.tsx similarity index 100% rename from sigap-website/components/tutorial/fetch-data-steps.tsx rename to sigap-website/app/_components/tutorial/fetch-data-steps.tsx diff --git a/sigap-website/components/tutorial/sign-up-user-steps.tsx b/sigap-website/app/_components/tutorial/sign-up-user-steps.tsx similarity index 100% rename from sigap-website/components/tutorial/sign-up-user-steps.tsx rename to sigap-website/app/_components/tutorial/sign-up-user-steps.tsx diff --git a/sigap-website/components/tutorial/tutorial-step.tsx b/sigap-website/app/_components/tutorial/tutorial-step.tsx similarity index 100% rename from sigap-website/components/tutorial/tutorial-step.tsx rename to sigap-website/app/_components/tutorial/tutorial-step.tsx diff --git a/sigap-website/components/typography/inline-code.tsx b/sigap-website/app/_components/typography/inline-code.tsx similarity index 100% rename from sigap-website/components/typography/inline-code.tsx rename to sigap-website/app/_components/typography/inline-code.tsx diff --git a/sigap-website/components/ui/avatar.tsx b/sigap-website/app/_components/ui/avatar.tsx similarity index 100% rename from sigap-website/components/ui/avatar.tsx rename to sigap-website/app/_components/ui/avatar.tsx diff --git a/sigap-website/components/ui/badge.tsx b/sigap-website/app/_components/ui/badge.tsx similarity index 100% rename from sigap-website/components/ui/badge.tsx rename to sigap-website/app/_components/ui/badge.tsx diff --git a/sigap-website/components/ui/breadcrumb.tsx b/sigap-website/app/_components/ui/breadcrumb.tsx similarity index 100% rename from sigap-website/components/ui/breadcrumb.tsx rename to sigap-website/app/_components/ui/breadcrumb.tsx diff --git a/sigap-website/components/ui/button.tsx b/sigap-website/app/_components/ui/button.tsx similarity index 100% rename from sigap-website/components/ui/button.tsx rename to sigap-website/app/_components/ui/button.tsx diff --git a/sigap-website/components/ui/card.tsx b/sigap-website/app/_components/ui/card.tsx similarity index 100% rename from sigap-website/components/ui/card.tsx rename to sigap-website/app/_components/ui/card.tsx diff --git a/sigap-website/components/ui/checkbox.tsx b/sigap-website/app/_components/ui/checkbox.tsx similarity index 100% rename from sigap-website/components/ui/checkbox.tsx rename to sigap-website/app/_components/ui/checkbox.tsx diff --git a/sigap-website/components/ui/collapsible.tsx b/sigap-website/app/_components/ui/collapsible.tsx similarity index 100% rename from sigap-website/components/ui/collapsible.tsx rename to sigap-website/app/_components/ui/collapsible.tsx diff --git a/sigap-website/components/ui/drawer.tsx b/sigap-website/app/_components/ui/drawer.tsx similarity index 100% rename from sigap-website/components/ui/drawer.tsx rename to sigap-website/app/_components/ui/drawer.tsx diff --git a/sigap-website/components/ui/dropdown-menu.tsx b/sigap-website/app/_components/ui/dropdown-menu.tsx similarity index 100% rename from sigap-website/components/ui/dropdown-menu.tsx rename to sigap-website/app/_components/ui/dropdown-menu.tsx diff --git a/sigap-website/components/ui/form.tsx b/sigap-website/app/_components/ui/form.tsx similarity index 98% rename from sigap-website/components/ui/form.tsx rename to sigap-website/app/_components/ui/form.tsx index b6daa65..4bfffdd 100644 --- a/sigap-website/components/ui/form.tsx +++ b/sigap-website/app/_components/ui/form.tsx @@ -13,7 +13,7 @@ import { } from "react-hook-form" import { cn } from "@/lib/utils" -import { Label } from "@/components/ui/label" +import { Label } from "@/app/_components/ui/label" const Form = FormProvider diff --git a/sigap-website/components/ui/input-otp.tsx b/sigap-website/app/_components/ui/input-otp.tsx similarity index 100% rename from sigap-website/components/ui/input-otp.tsx rename to sigap-website/app/_components/ui/input-otp.tsx diff --git a/sigap-website/components/ui/input.tsx b/sigap-website/app/_components/ui/input.tsx similarity index 100% rename from sigap-website/components/ui/input.tsx rename to sigap-website/app/_components/ui/input.tsx diff --git a/sigap-website/components/ui/label.tsx b/sigap-website/app/_components/ui/label.tsx similarity index 100% rename from sigap-website/components/ui/label.tsx rename to sigap-website/app/_components/ui/label.tsx diff --git a/sigap-website/app/_components/ui/popover.tsx b/sigap-website/app/_components/ui/popover.tsx new file mode 100644 index 0000000..29c7bd2 --- /dev/null +++ b/sigap-website/app/_components/ui/popover.tsx @@ -0,0 +1,33 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/sigap-website/components/ui/scroll-area.tsx b/sigap-website/app/_components/ui/scroll-area.tsx similarity index 100% rename from sigap-website/components/ui/scroll-area.tsx rename to sigap-website/app/_components/ui/scroll-area.tsx diff --git a/sigap-website/components/ui/select.tsx b/sigap-website/app/_components/ui/select.tsx similarity index 100% rename from sigap-website/components/ui/select.tsx rename to sigap-website/app/_components/ui/select.tsx diff --git a/sigap-website/components/ui/separator.tsx b/sigap-website/app/_components/ui/separator.tsx similarity index 100% rename from sigap-website/components/ui/separator.tsx rename to sigap-website/app/_components/ui/separator.tsx diff --git a/sigap-website/components/ui/sheet.tsx b/sigap-website/app/_components/ui/sheet.tsx similarity index 100% rename from sigap-website/components/ui/sheet.tsx rename to sigap-website/app/_components/ui/sheet.tsx diff --git a/sigap-website/components/ui/sidebar.tsx b/sigap-website/app/_components/ui/sidebar.tsx similarity index 82% rename from sigap-website/components/ui/sidebar.tsx rename to sigap-website/app/_components/ui/sidebar.tsx index 5e9f37b..5bd92a5 100644 --- a/sigap-website/components/ui/sidebar.tsx +++ b/sigap-website/app/_components/ui/sidebar.tsx @@ -1,58 +1,58 @@ -"use client" +"use client"; -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { VariantProps, cva } from "class-variance-authority" -import { PanelLeft } from "lucide-react" +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { VariantProps, cva } from "class-variance-authority"; +import { PanelLeft } from "lucide-react"; -import { useIsMobile } from "@/hooks/use-mobile" -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Separator } from "@/components/ui/separator" -import { Sheet, SheetContent } from "@/components/ui/sheet" -import { Skeleton } from "@/components/ui/skeleton" +import { useIsMobile } from "@/app/_hooks/use-mobile"; +import { cn } from "@/lib/utils"; +import { Button } from "@/app/_components/ui/button"; +import { Input } from "@/app/_components/ui/input"; +import { Separator } from "@/app/_components/ui/separator"; +import { Sheet, SheetContent } from "@/app/_components/ui/sheet"; +import { Skeleton } from "@/app/_components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from "@/components/ui/tooltip" +} from "@/app/_components/ui/tooltip"; -const SIDEBAR_COOKIE_NAME = "sidebar_state" -const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" -const SIDEBAR_WIDTH_MOBILE = "18rem" -const SIDEBAR_WIDTH_ICON = "3rem" -const SIDEBAR_KEYBOARD_SHORTCUT = "b" +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "18rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; type SidebarContext = { - state: "expanded" | "collapsed" - open: boolean - setOpen: (open: boolean) => void - openMobile: boolean - setOpenMobile: (open: boolean) => void - isMobile: boolean - toggleSidebar: () => void -} + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; -const SidebarContext = React.createContext(null) +const SidebarContext = React.createContext(null); function useSidebar() { - const context = React.useContext(SidebarContext) + const context = React.useContext(SidebarContext); if (!context) { - throw new Error("useSidebar must be used within a SidebarProvider.") + throw new Error("useSidebar must be used within a SidebarProvider."); } - return context + return context; } const SidebarProvider = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { - defaultOpen?: boolean - open?: boolean - onOpenChange?: (open: boolean) => void + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; } >( ( @@ -67,34 +67,34 @@ const SidebarProvider = React.forwardRef< }, ref ) => { - const isMobile = useIsMobile() - const [openMobile, setOpenMobile] = React.useState(false) + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); // This is the internal state of the sidebar. // We use openProp and setOpenProp for control from outside the component. - const [_open, _setOpen] = React.useState(defaultOpen) - const open = openProp ?? _open + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; const setOpen = React.useCallback( (value: boolean | ((value: boolean) => boolean)) => { - const openState = typeof value === "function" ? value(open) : value + const openState = typeof value === "function" ? value(open) : value; if (setOpenProp) { - setOpenProp(openState) + setOpenProp(openState); } else { - _setOpen(openState) + _setOpen(openState); } // This sets the cookie to keep the sidebar state. - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; }, [setOpenProp, open] - ) + ); // Helper to toggle the sidebar. const toggleSidebar = React.useCallback(() => { return isMobile ? setOpenMobile((open) => !open) - : setOpen((open) => !open) - }, [isMobile, setOpen, setOpenMobile]) + : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); // Adds a keyboard shortcut to toggle the sidebar. React.useEffect(() => { @@ -103,18 +103,18 @@ const SidebarProvider = React.forwardRef< event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey) ) { - event.preventDefault() - toggleSidebar() + event.preventDefault(); + toggleSidebar(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [toggleSidebar]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); // We add a state so that we can do data-state="expanded" or "collapsed". // This makes it easier to style the sidebar with Tailwind classes. - const state = open ? "expanded" : "collapsed" + const state = open ? "expanded" : "collapsed"; const contextValue = React.useMemo( () => ({ @@ -127,7 +127,7 @@ const SidebarProvider = React.forwardRef< toggleSidebar, }), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] - ) + ); return ( @@ -151,17 +151,17 @@ const SidebarProvider = React.forwardRef<
- ) + ); } -) -SidebarProvider.displayName = "SidebarProvider" +); +SidebarProvider.displayName = "SidebarProvider"; const Sidebar = React.forwardRef< HTMLDivElement, React.ComponentProps<"div"> & { - side?: "left" | "right" - variant?: "sidebar" | "floating" | "inset" - collapsible?: "offcanvas" | "icon" | "none" + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; } >( ( @@ -175,7 +175,7 @@ const Sidebar = React.forwardRef< }, ref ) => { - const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); if (collapsible === "none") { return ( @@ -189,7 +189,7 @@ const Sidebar = React.forwardRef< > {children}
- ) + ); } if (isMobile) { @@ -209,7 +209,7 @@ const Sidebar = React.forwardRef<
{children}
- ) + ); } return ( @@ -254,16 +254,16 @@ const Sidebar = React.forwardRef<
- ) + ); } -) -Sidebar.displayName = "Sidebar" +); +Sidebar.displayName = "Sidebar"; const SidebarTrigger = React.forwardRef< React.ElementRef, React.ComponentProps >(({ className, onClick, ...props }, ref) => { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return ( - ) -}) -SidebarTrigger.displayName = "SidebarTrigger" + ); +}); +SidebarTrigger.displayName = "SidebarTrigger"; const SidebarRail = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> >(({ className, ...props }, ref) => { - const { toggleSidebar } = useSidebar() + const { toggleSidebar } = useSidebar(); return (