resolve RLS issue for upload avatars images
This commit is contained in:
parent
45daf059d3
commit
aa52dd0ca4
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"files.autoSave": "off"
|
"files.autoSave": "afterDelay"
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const checkSession = async () => {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
session,
|
session,
|
||||||
redirectTo: "/protected/dashboard", // or your preferred authenticated route
|
redirectTo: "/dashboard",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,16 @@ export const signInAction = async (formData: FormData) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First, check for existing session
|
// 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 there's an active session and the email matches
|
||||||
if (session && session.user.email === email) {
|
if (session?.user?.email === email) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Already logged in",
|
message: "You are already signed in",
|
||||||
redirectTo: "/protected/dashboard", // or wherever you want to redirect logged in users
|
redirectTo: "/dashboard",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,5 +26,5 @@ export const verifyOtpAction = async (formData: FormData) => {
|
||||||
return redirect(`/verify-otp?error=${encodeURIComponent(error.message)}`);
|
return redirect(`/verify-otp?error=${encodeURIComponent(error.message)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect("/protected/dashboard?message=OTP verified successfully");
|
return redirect("/dashboard?message=OTP verified successfully");
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { checkSession } from "./_actions/session";
|
import { checkSession } from "./_actions/session";
|
||||||
|
import { createClient } from "@/utils/supabase/client";
|
||||||
|
|
||||||
export default async function Layout({
|
export default async function Layout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const sessionResult = await checkSession();
|
// const supabase = createClient();
|
||||||
|
|
||||||
// If there's an active session, redirect to dashboard
|
// const {
|
||||||
if (sessionResult.success && sessionResult.redirectTo) {
|
// data: { session },
|
||||||
redirect(sessionResult.redirectTo);
|
// } = await supabase.auth.getSession();
|
||||||
}
|
|
||||||
|
// if (!session) {
|
||||||
|
// return redirect("/sign-in");
|
||||||
|
// }
|
||||||
|
|
||||||
return <div className="max-w-full gap-12 items-start">{children}</div>;
|
return <div className="max-w-full gap-12 items-start">{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default async function DashboardPage() {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return redirect("/sign-in");
|
return redirect("/sign-in");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className="flex h-12 shrink-0 items-center justify-end border-b px-4 mb-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-8"></header>
|
<header className="flex h-12 shrink-0 items-center justify-end border-b px-4 mb-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-8"></header>
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
UserResponse,
|
UserResponse,
|
||||||
} from "@/src/models/users/users.model";
|
} from "@/src/models/users/users.model";
|
||||||
import { createClient } from "@/utils/supabase/server";
|
import { createClient } from "@/utils/supabase/server";
|
||||||
import { createClient as createClientSide } from "@/utils/supabase/client";
|
|
||||||
import { createAdminClient } from "@/utils/supabase/admin";
|
import { createAdminClient } from "@/utils/supabase/admin";
|
||||||
|
|
||||||
// Initialize Supabase client with admin key
|
// Initialize Supabase client with admin key
|
||||||
|
@ -103,30 +102,34 @@ export async function createUser(
|
||||||
|
|
||||||
export async function uploadAvatar(userId: string, email: string, file: File) {
|
export async function uploadAvatar(userId: string, email: string, file: File) {
|
||||||
try {
|
try {
|
||||||
const supabase = createClientSide();
|
const supabase = await createClient();
|
||||||
|
|
||||||
// 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 fileExt = file.name.split(".").pop();
|
const fileExt = file.name.split(".").pop();
|
||||||
const emailName = email.split("@")[0];
|
const emailName = email.split("@")[0];
|
||||||
const fileName = `AVR-${emailName}.${fileExt}`;
|
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
|
const { error: uploadError } = await supabase.storage
|
||||||
.from("avatars")
|
.from("avatars")
|
||||||
.upload(fileName, file, {
|
.upload(filePath, file, {
|
||||||
upsert: false,
|
upsert: true,
|
||||||
contentType: file.type,
|
contentType: file.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (uploadError) {
|
if (uploadError) {
|
||||||
|
console.error("Error uploading avatar:", uploadError);
|
||||||
throw 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({
|
await db.users.update({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
|
@ -134,16 +137,12 @@ export async function uploadAvatar(userId: string, email: string, file: File) {
|
||||||
data: {
|
data: {
|
||||||
profile: {
|
profile: {
|
||||||
update: {
|
update: {
|
||||||
avatar: filePath,
|
avatar: publicUrl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
|
||||||
data: { publicUrl },
|
|
||||||
} = supabase.storage.from("avatars").getPublicUrl(fileName);
|
|
||||||
|
|
||||||
return publicUrl;
|
return publicUrl;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error uploading avatar:", 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
|
// Update an existing user
|
||||||
export async function updateUser(
|
export async function updateUser(
|
||||||
userId: string,
|
userId: string,
|
||||||
|
|
|
@ -27,12 +27,23 @@ import { Separator } from "@/app/_components/ui/separator";
|
||||||
import { InboxDrawer } from "@/app/_components/inbox-drawer";
|
import { InboxDrawer } from "@/app/_components/inbox-drawer";
|
||||||
import FloatingActionSearchBar from "@/app/_components/floating-action-search-bar";
|
import FloatingActionSearchBar from "@/app/_components/floating-action-search-bar";
|
||||||
import { AppSidebar } from "@/app/_components/admin/app-sidebar";
|
import { AppSidebar } from "@/app/_components/admin/app-sidebar";
|
||||||
|
import { createClient } from "@/utils/supabase/server";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function Layout({
|
export default async function Layout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const supabase = await createClient();
|
||||||
|
const {
|
||||||
|
data: { session },
|
||||||
|
} = await supabase.auth.getSession();
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return redirect("/sign-in");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
import type * as TablerIcons from "@tabler/icons-react";
|
import type * as TablerIcons from "@tabler/icons-react";
|
||||||
|
|
||||||
import { useNavigations } from "@/app/_hooks/use-navigations";
|
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||||
|
import { formatUrl } from "@/utils/utils";
|
||||||
|
|
||||||
interface SubSubItem {
|
interface SubSubItem {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -40,22 +41,6 @@ interface NavItem {
|
||||||
subItems?: SubItem[];
|
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 }) {
|
function SubSubItemComponent({ item }: { item: SubSubItem }) {
|
||||||
const router = useNavigations();
|
const router = useNavigations();
|
||||||
const formattedUrl = formatUrl(item.url);
|
const formattedUrl = formatUrl(item.url);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
@ -27,6 +27,7 @@ import {
|
||||||
import type { User } from "@/src/models/users/users.model";
|
import type { User } from "@/src/models/users/users.model";
|
||||||
import { ProfileSettings } from "./profile-settings";
|
import { ProfileSettings } from "./profile-settings";
|
||||||
import { DialogTitle } from "@radix-ui/react-dialog";
|
import { DialogTitle } from "@radix-ui/react-dialog";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface SettingsDialogProps {
|
interface SettingsDialogProps {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
|
@ -55,7 +56,7 @@ export function SettingsDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: SettingsDialogProps) {
|
}: SettingsDialogProps) {
|
||||||
const [selectedTab, setSelectedTab] = React.useState(defaultTab);
|
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
||||||
|
|
||||||
// Get user display name
|
// Get user display name
|
||||||
const preferredName = user?.profile?.first_name || "";
|
const preferredName = user?.profile?.first_name || "";
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { AddUserDialog } from "./add-user-dialog";
|
||||||
import { UserDetailsSheet } from "./sheet";
|
import { UserDetailsSheet } from "./sheet";
|
||||||
import { Avatar } from "@radix-ui/react-avatar";
|
import { Avatar } from "@radix-ui/react-avatar";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { useNavigations } from "@/app/_hooks/use-navigations";
|
||||||
|
|
||||||
export default function UserManagement() {
|
export default function UserManagement() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
@ -65,11 +66,12 @@ export default function UserManagement() {
|
||||||
|
|
||||||
// Use React Query to fetch users
|
// Use React Query to fetch users
|
||||||
const [users, setUsers] = useState<User[]>([]);
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const { isLoading, setIsLoading } = useNavigations();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
const fetchedUsers = await fetchUsers();
|
const fetchedUsers = await fetchUsers();
|
||||||
setUsers(fetchedUsers);
|
setUsers(fetchedUsers);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -5,6 +5,9 @@ import { Card, CardContent } from "@/app/_components/ui/card";
|
||||||
import { Users, UserCheck, UserX } from "lucide-react";
|
import { Users, UserCheck, UserX } from "lucide-react";
|
||||||
import { fetchUsers } from "@/app/(protected)/(admin)/dashboard/user-management/action";
|
import { fetchUsers } from "@/app/(protected)/(admin)/dashboard/user-management/action";
|
||||||
import { User } from "@/src/models/users/users.model";
|
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[]) {
|
function calculateUserStats(users: User[]) {
|
||||||
const totalUsers = users.length;
|
const totalUsers = users.length;
|
||||||
|
@ -25,10 +28,23 @@ function calculateUserStats(users: User[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserStats() {
|
export function UserStats() {
|
||||||
const { data: users = [], isLoading } = useQuery({
|
const { isLoading, setIsLoading } = useNavigations();
|
||||||
queryKey: ["users"],
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
queryFn: fetchUsers,
|
|
||||||
});
|
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);
|
const stats = calculateUserStats(users);
|
||||||
|
|
||||||
|
|
|
@ -145,9 +145,9 @@ const InboxDrawerComponent: React.FC<InboxDrawerProps> = ({
|
||||||
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size={showTitle ? "sm" : "icon"}
|
size={showTitle ? "sm" : "icon"}
|
||||||
className={`relative ${showTitle ? "flex items-center" : ""}`}
|
className={`relative border-2 ${showTitle ? "flex items-center" : ""}`}
|
||||||
aria-label="Open inbox"
|
aria-label="Open inbox"
|
||||||
>
|
>
|
||||||
<Inbox
|
<Inbox
|
||||||
|
@ -159,10 +159,8 @@ const InboxDrawerComponent: React.FC<InboxDrawerProps> = ({
|
||||||
{unreadCount > 0 && (
|
{unreadCount > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
className="absolute -top-2 -right-2 px-1.5 py-0.5 text-xs"
|
className="absolute -top-1.5 -right-1.5 text-[10px] h-3 w-3 rounded-full p-0 flex items-center justify-center"
|
||||||
>
|
></Badge>
|
||||||
{unreadCount}
|
|
||||||
</Badge>
|
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
|
@ -171,7 +169,7 @@ const InboxDrawerComponent: React.FC<InboxDrawerProps> = ({
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<SheetHeader className="p-4 border-b">
|
<SheetHeader className="p-4 border-b">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
onClick={handleBackToList}
|
onClick={handleBackToList}
|
||||||
className="w-fit px-2"
|
className="w-fit px-2"
|
||||||
>
|
>
|
||||||
|
|
|
@ -56,9 +56,9 @@ const ThemeSwitcherComponent = ({
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size={showTitle ? "sm" : "icon"}
|
size={showTitle ? "sm" : "icon"}
|
||||||
className={showTitle ? "flex justify-center items-center" : ""}
|
className={`border-2 ${showTitle ? "flex justify-center items-center" : ""}`}
|
||||||
aria-label={`Current theme: theme`}
|
aria-label={`Current theme: theme`}
|
||||||
>
|
>
|
||||||
<currentTheme.icon
|
<currentTheme.icon
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const useNavigations = () => {
|
export const useNavigations = () => {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const [active, setActive] = useState<string>("");
|
const [active, setActive] = useState<string>("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -16,8 +16,8 @@ export const useNavigations = () => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
isLoading,
|
||||||
setLoading,
|
setIsLoading,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
active,
|
active,
|
||||||
|
|
|
@ -16,8 +16,8 @@ const defaultUrl = process.env.VERCEL_URL
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
metadataBase: new URL(defaultUrl),
|
metadataBase: new URL(defaultUrl),
|
||||||
title: "Next.js and Supabase Starter Kit",
|
title: "Sigap | Jember ",
|
||||||
description: "The fastest way to build apps with Next.js and Supabase",
|
description: "Sigap is a platform for managing your crime data.",
|
||||||
};
|
};
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
|
@ -39,10 +39,9 @@ export default function RootLayout({
|
||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<ReactQueryProvider>
|
<main className="min-h-screen flex flex-col items-center">
|
||||||
<main className="min-h-screen flex flex-col items-center">
|
<div className="flex-1 w-full gap-20 items-center">
|
||||||
<div className="flex-1 w-full gap-20 items-center">
|
{/* <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
|
||||||
{/* <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
|
|
||||||
<div className="w-full max-w-5xl flex justify-between items-center p-3 px-5 text-sm">
|
<div className="w-full max-w-5xl flex justify-between items-center p-3 px-5 text-sm">
|
||||||
<div className="flex gap-5 items-center font-semibold">
|
<div className="flex gap-5 items-center font-semibold">
|
||||||
<Link href={"/"}>
|
<Link href={"/"}>
|
||||||
|
@ -58,14 +57,14 @@ export default function RootLayout({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav> */}
|
</nav> */}
|
||||||
<div className="flex flex-col max-w-full p-0">
|
<div className="flex flex-col max-w-full p-0">
|
||||||
{children}
|
{children}
|
||||||
<Toaster theme="system" richColors position="top-right" />
|
<Toaster theme="system" richColors position="top-right" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <footer className="w-full flex items-center justify-center border-t mx-auto text-center text-xs gap-8 py-16 mt-auto">
|
{/* <footer className="w-full flex items-center justify-center border-t mx-auto text-center text-xs gap-8 py-16 mt-auto">
|
||||||
<p>
|
<p>
|
||||||
Powered by{" "}
|
Powered by{" "
|
||||||
<a
|
<a
|
||||||
href=""
|
href=""
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -76,9 +75,8 @@ export default function RootLayout({
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</footer> */}
|
</footer> */}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</ReactQueryProvider>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -132,7 +132,7 @@ model geographics {
|
||||||
model profiles {
|
model profiles {
|
||||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
user_id String @unique @db.Uuid
|
user_id String @unique @db.Uuid
|
||||||
avatar String? @db.VarChar(255)
|
avatar String? @db.VarChar(355)
|
||||||
username String? @unique @db.VarChar(255)
|
username String? @unique @db.VarChar(255)
|
||||||
first_name String? @db.VarChar(255)
|
first_name String? @db.VarChar(255)
|
||||||
last_name String? @db.VarChar(255)
|
last_name String? @db.VarChar(255)
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class AuthRepository {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
redirectTo: "/protected/dashboard"
|
redirectTo: "/dashboard",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,3 +14,24 @@ export function encodedRedirect(
|
||||||
) {
|
) {
|
||||||
return redirect(`${path}?${type}=${encodeURIComponent(message)}`);
|
return redirect(`${path}?${type}=${encodeURIComponent(message)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a URL by removing any trailing slashes.
|
||||||
|
* @param {string} url - The URL to format.
|
||||||
|
* @returns {string} The formatted URL.
|
||||||
|
*/
|
||||||
|
// Helper function to ensure URLs are properly formatted
|
||||||
|
export 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;
|
||||||
|
}
|
Loading…
Reference in New Issue