From aa52dd0ca46c3cf7118ce0c3f3dffb53ae4747c5 Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Fri, 7 Mar 2025 00:22:44 +0700 Subject: [PATCH] resolve RLS issue for upload avatars images --- sigap-website/.vscode/settings.json | 2 +- .../app/(auth-pages)/_actions/session.ts | 2 +- .../app/(auth-pages)/_actions/sign-in.ts | 10 +++--- .../app/(auth-pages)/_actions/verify-otp.ts | 2 +- sigap-website/app/(auth-pages)/layout.tsx | 14 +++++--- .../(protected)/(admin)/dashboard/page.tsx | 1 + .../dashboard/user-management/action.ts | 32 +++++++++---------- .../app/(protected)/(admin)/layout.tsx | 11 +++++++ .../admin/navigations/nav-main.tsx | 17 +--------- .../admin/settings/setting-dialog.tsx | 5 +-- .../admin/users/user-management.tsx | 4 ++- .../_components/admin/users/user-stats.tsx | 24 +++++++++++--- .../app/_components/inbox-drawer.tsx | 12 +++---- .../app/_components/theme-switcher.tsx | 4 +-- sigap-website/app/_hooks/use-navigations.ts | 6 ++-- sigap-website/app/layout.tsx | 28 ++++++++-------- sigap-website/prisma/schema.prisma | 2 +- .../repositories/authentication.repository.ts | 2 +- sigap-website/utils/utils.ts | 21 ++++++++++++ 19 files changed, 119 insertions(+), 80 deletions(-) diff --git a/sigap-website/.vscode/settings.json b/sigap-website/.vscode/settings.json index 8bb69fd..86953b0 100644 --- a/sigap-website/.vscode/settings.json +++ b/sigap-website/.vscode/settings.json @@ -1,3 +1,3 @@ { - "files.autoSave": "off" + "files.autoSave": "afterDelay" } diff --git a/sigap-website/app/(auth-pages)/_actions/session.ts b/sigap-website/app/(auth-pages)/_actions/session.ts index a742b1d..5e2026e 100644 --- a/sigap-website/app/(auth-pages)/_actions/session.ts +++ b/sigap-website/app/(auth-pages)/_actions/session.ts @@ -20,7 +20,7 @@ export const checkSession = async () => { return { success: true, session, - redirectTo: "/protected/dashboard", // or your preferred authenticated route + redirectTo: "/dashboard", }; } diff --git a/sigap-website/app/(auth-pages)/_actions/sign-in.ts b/sigap-website/app/(auth-pages)/_actions/sign-in.ts index 5c6ea79..dbdf00e 100644 --- a/sigap-website/app/(auth-pages)/_actions/sign-in.ts +++ b/sigap-website/app/(auth-pages)/_actions/sign-in.ts @@ -12,14 +12,16 @@ export const signInAction = async (formData: FormData) => { try { // First, check for existing session - const { session, error: sessionError } = await checkSession(); + const { + data: { session }, + } = await supabase.auth.getSession(); // If there's an active session and the email matches - if (session && session.user.email === email) { + if (session?.user?.email === email) { return { success: true, - message: "Already logged in", - redirectTo: "/protected/dashboard", // or wherever you want to redirect logged in users + message: "You are already signed in", + redirectTo: "/dashboard", }; } diff --git a/sigap-website/app/(auth-pages)/_actions/verify-otp.ts b/sigap-website/app/(auth-pages)/_actions/verify-otp.ts index dee6f71..c512e33 100644 --- a/sigap-website/app/(auth-pages)/_actions/verify-otp.ts +++ b/sigap-website/app/(auth-pages)/_actions/verify-otp.ts @@ -26,5 +26,5 @@ export const verifyOtpAction = async (formData: FormData) => { return redirect(`/verify-otp?error=${encodeURIComponent(error.message)}`); } - return redirect("/protected/dashboard?message=OTP verified successfully"); + return redirect("/dashboard?message=OTP verified successfully"); }; diff --git a/sigap-website/app/(auth-pages)/layout.tsx b/sigap-website/app/(auth-pages)/layout.tsx index 7102549..61b4de1 100644 --- a/sigap-website/app/(auth-pages)/layout.tsx +++ b/sigap-website/app/(auth-pages)/layout.tsx @@ -1,17 +1,21 @@ import { redirect } from "next/navigation"; import { checkSession } from "./_actions/session"; +import { createClient } from "@/utils/supabase/client"; export default async function Layout({ children, }: { children: React.ReactNode; }) { - const sessionResult = await checkSession(); + // const supabase = createClient(); - // If there's an active session, redirect to dashboard - if (sessionResult.success && sessionResult.redirectTo) { - redirect(sessionResult.redirectTo); - } + // const { + // data: { session }, + // } = await supabase.auth.getSession(); + + // if (!session) { + // return redirect("/sign-in"); + // } return
{children}
; } diff --git a/sigap-website/app/(protected)/(admin)/dashboard/page.tsx b/sigap-website/app/(protected)/(admin)/dashboard/page.tsx index 615f75b..afc58de 100644 --- a/sigap-website/app/(protected)/(admin)/dashboard/page.tsx +++ b/sigap-website/app/(protected)/(admin)/dashboard/page.tsx @@ -11,6 +11,7 @@ export default async function DashboardPage() { if (!user) { return redirect("/sign-in"); } + return ( <>
diff --git a/sigap-website/app/(protected)/(admin)/dashboard/user-management/action.ts b/sigap-website/app/(protected)/(admin)/dashboard/user-management/action.ts index aacbbff..1f5a6d5 100644 --- a/sigap-website/app/(protected)/(admin)/dashboard/user-management/action.ts +++ b/sigap-website/app/(protected)/(admin)/dashboard/user-management/action.ts @@ -9,7 +9,6 @@ import { UserResponse, } from "@/src/models/users/users.model"; import { createClient } from "@/utils/supabase/server"; -import { createClient as createClientSide } from "@/utils/supabase/client"; import { createAdminClient } from "@/utils/supabase/admin"; // Initialize Supabase client with admin key @@ -103,30 +102,34 @@ export async function createUser( export async function uploadAvatar(userId: string, email: string, file: File) { try { - const supabase = createClientSide(); - - // Pastikan mendapatkan session untuk autentikasi - const { data: session } = await supabase.auth.getSession(); - if (!session) throw new Error("User is not authenticated"); - - const baseUrl = `${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_URL}/avatars`; + const supabase = await createClient(); const fileExt = file.name.split(".").pop(); const emailName = email.split("@")[0]; const fileName = `AVR-${emailName}.${fileExt}`; - const filePath = `${baseUrl}/${fileName}`; + // Change this line - store directly in the user's folder + const filePath = `${userId}/${fileName}`; + + // Upload the avatar to Supabase storage const { error: uploadError } = await supabase.storage .from("avatars") - .upload(fileName, file, { - upsert: false, + .upload(filePath, file, { + upsert: true, contentType: file.type, }); if (uploadError) { + console.error("Error uploading avatar:", uploadError); throw uploadError; } + // Get the public URL + const { + data: { publicUrl }, + } = supabase.storage.from("avatars").getPublicUrl(filePath); + + // Update user profile with the new avatar URL await db.users.update({ where: { id: userId, @@ -134,16 +137,12 @@ export async function uploadAvatar(userId: string, email: string, file: File) { data: { profile: { update: { - avatar: filePath, + avatar: publicUrl, }, }, }, }); - const { - data: { publicUrl }, - } = supabase.storage.from("avatars").getPublicUrl(fileName); - return publicUrl; } catch (error) { console.error("Error uploading avatar:", error); @@ -151,6 +150,7 @@ export async function uploadAvatar(userId: string, email: string, file: File) { } } + // Update an existing user export async function updateUser( userId: string, diff --git a/sigap-website/app/(protected)/(admin)/layout.tsx b/sigap-website/app/(protected)/(admin)/layout.tsx index 5216332..f20db60 100644 --- a/sigap-website/app/(protected)/(admin)/layout.tsx +++ b/sigap-website/app/(protected)/(admin)/layout.tsx @@ -27,12 +27,23 @@ 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/_components/admin/app-sidebar"; +import { createClient } from "@/utils/supabase/server"; +import { redirect } from "next/navigation"; export default async function Layout({ children, }: { children: React.ReactNode; }) { + const supabase = await createClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return redirect("/sign-in"); + } + return ( <> diff --git a/sigap-website/app/_components/admin/navigations/nav-main.tsx b/sigap-website/app/_components/admin/navigations/nav-main.tsx index 6e92404..af4652d 100644 --- a/sigap-website/app/_components/admin/navigations/nav-main.tsx +++ b/sigap-website/app/_components/admin/navigations/nav-main.tsx @@ -19,6 +19,7 @@ import { import type * as TablerIcons from "@tabler/icons-react"; import { useNavigations } from "@/app/_hooks/use-navigations"; +import { formatUrl } from "@/utils/utils"; interface SubSubItem { title: string; @@ -40,22 +41,6 @@ interface NavItem { subItems?: SubItem[]; } -// Helper function to ensure URLs are properly formatted -function formatUrl(url: string): string { - // If URL starts with a slash, it's already absolute - if (url.startsWith("/")) { - return url; - } - - // Otherwise, ensure it's properly formatted relative to root - // Remove any potential duplicated '/dashboard' prefixes - if (url.startsWith("dashboard/")) { - return "/" + url; - } - - return "/" + url; -} - function SubSubItemComponent({ item }: { item: SubSubItem }) { const router = useNavigations(); const formattedUrl = formatUrl(item.url); diff --git a/sigap-website/app/_components/admin/settings/setting-dialog.tsx b/sigap-website/app/_components/admin/settings/setting-dialog.tsx index ce8a9ba..9ffb380 100644 --- a/sigap-website/app/_components/admin/settings/setting-dialog.tsx +++ b/sigap-website/app/_components/admin/settings/setting-dialog.tsx @@ -1,6 +1,6 @@ "use client"; -import * as React from "react"; + import { cn } from "@/lib/utils"; import { Dialog, @@ -27,6 +27,7 @@ import { import type { User } from "@/src/models/users/users.model"; import { ProfileSettings } from "./profile-settings"; import { DialogTitle } from "@radix-ui/react-dialog"; +import { useState } from "react"; interface SettingsDialogProps { user: User | null; @@ -55,7 +56,7 @@ export function SettingsDialog({ open, onOpenChange, }: SettingsDialogProps) { - const [selectedTab, setSelectedTab] = React.useState(defaultTab); + const [selectedTab, setSelectedTab] = useState(defaultTab); // Get user display name const preferredName = user?.profile?.first_name || ""; diff --git a/sigap-website/app/_components/admin/users/user-management.tsx b/sigap-website/app/_components/admin/users/user-management.tsx index 5b3dfe0..5d9a148 100644 --- a/sigap-website/app/_components/admin/users/user-management.tsx +++ b/sigap-website/app/_components/admin/users/user-management.tsx @@ -40,6 +40,7 @@ import { AddUserDialog } from "./add-user-dialog"; import { UserDetailsSheet } from "./sheet"; import { Avatar } from "@radix-ui/react-avatar"; import Image from "next/image"; +import { useNavigations } from "@/app/_hooks/use-navigations"; export default function UserManagement() { const [searchQuery, setSearchQuery] = useState(""); @@ -65,11 +66,12 @@ export default function UserManagement() { // Use React Query to fetch users const [users, setUsers] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const { isLoading, setIsLoading } = useNavigations(); useEffect(() => { const fetchData = async () => { try { + setIsLoading(true); const fetchedUsers = await fetchUsers(); setUsers(fetchedUsers); } catch (error) { diff --git a/sigap-website/app/_components/admin/users/user-stats.tsx b/sigap-website/app/_components/admin/users/user-stats.tsx index 3f0b4d5..a07241d 100644 --- a/sigap-website/app/_components/admin/users/user-stats.tsx +++ b/sigap-website/app/_components/admin/users/user-stats.tsx @@ -5,6 +5,9 @@ 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 { useNavigations } from "@/app/_hooks/use-navigations"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; function calculateUserStats(users: User[]) { const totalUsers = users.length; @@ -25,10 +28,23 @@ function calculateUserStats(users: User[]) { } export function UserStats() { - const { data: users = [], isLoading } = useQuery({ - queryKey: ["users"], - queryFn: fetchUsers, - }); + const { isLoading, setIsLoading } = useNavigations(); + const [users, setUsers] = useState([]); + + useEffect(() => { + const fetchUserData = async () => { + try { + setIsLoading(true); + const fetchedUsers = await fetchUsers(); + setUsers(fetchedUsers); + } catch (error) { + toast.error("Failed to fetch users"); + } finally { + setIsLoading(false); + } + }; + fetchUserData(); + }, [setIsLoading]); const stats = calculateUserStats(users); diff --git a/sigap-website/app/_components/inbox-drawer.tsx b/sigap-website/app/_components/inbox-drawer.tsx index 20ee2de..ef62448 100644 --- a/sigap-website/app/_components/inbox-drawer.tsx +++ b/sigap-website/app/_components/inbox-drawer.tsx @@ -145,9 +145,9 @@ const InboxDrawerComponent: React.FC = ({ @@ -171,7 +169,7 @@ const InboxDrawerComponent: React.FC = ({