poop
This commit is contained in:
35
src/app/auth/confirm/route.ts
Normal file
35
src/app/auth/confirm/route.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { type EmailOtpType } from '@supabase/supabase-js'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
import { createClient } from '@/utils/supabase/server'
|
||||
|
||||
// Creating a handler to a GET request to route /auth/confirm
|
||||
export const GET = async (request: NextRequest) => {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const token_hash = searchParams.get('token_hash')
|
||||
const type = searchParams.get('type') as EmailOtpType | null
|
||||
const next = '/account'
|
||||
|
||||
// Create redirect link without the secret token
|
||||
const redirectTo = request.nextUrl.clone()
|
||||
redirectTo.pathname = next
|
||||
redirectTo.searchParams.delete('token_hash')
|
||||
redirectTo.searchParams.delete('type')
|
||||
|
||||
if (token_hash && type) {
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
type,
|
||||
token_hash,
|
||||
})
|
||||
if (!error) {
|
||||
redirectTo.searchParams.delete('next')
|
||||
return NextResponse.redirect(redirectTo)
|
||||
}
|
||||
}
|
||||
|
||||
// return the user to an error page with some instructions
|
||||
redirectTo.pathname = '/error'
|
||||
return NextResponse.redirect(redirectTo)
|
||||
}
|
4
src/app/error/page.tsx
Normal file
4
src/app/error/page.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
const ErrorPage = () => {
|
||||
return <p>Sorry, something went wrong</p>
|
||||
}
|
||||
export default ErrorPage;
|
@ -1,12 +1,25 @@
|
||||
'use server'
|
||||
import LoginForm from '@/components/auth/LoginForm';
|
||||
import Header from '@/components/defaults/Header';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
|
||||
const HomePage = async () => {
|
||||
const supabase = await createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
return (
|
||||
<main>
|
||||
<Header />
|
||||
<LoginForm />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<main>
|
||||
<Header />
|
||||
<LoginForm />
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<h1>Hello, {session.user.email}</h1>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
107
src/components/auth/AvatarDropdown.tsx
Normal file
107
src/components/auth/AvatarDropdown.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { createClient } from '@/utils/supabase/client';
|
||||
import type { Session } from '@supabase/supabase-js';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
const AvatarDropdown = () => {
|
||||
const supabase = createClient();
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Function to fetch the session
|
||||
async function fetchSession() {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
setSession(session);
|
||||
} catch (error) {
|
||||
console.error('Error fetching session:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function
|
||||
fetchSession()
|
||||
.catch((error) => {
|
||||
console.error('Error fetching session:', error);
|
||||
});
|
||||
|
||||
// Set up auth state change listener
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
||||
(_event, session) => {
|
||||
setSession(session);
|
||||
}
|
||||
);
|
||||
|
||||
// Clean up the subscription when component unmounts
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [supabase]);
|
||||
|
||||
// Handle sign out
|
||||
const handleSignOut = async () => {
|
||||
await supabase.auth.signOut();
|
||||
};
|
||||
|
||||
// Show nothing while loading
|
||||
if (loading) {
|
||||
return <div className="animate-pulse h-8 w-8 rounded-full bg-gray-300" />;
|
||||
}
|
||||
|
||||
// If no session, return empty div
|
||||
if (!session) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
// Get user details
|
||||
const pfp = session.user?.user_metadata?.avatar_url as string ??
|
||||
session.user?.user_metadata?.picture as string ??
|
||||
'/images/default_user_pfp.png';
|
||||
|
||||
const name: string = session.user?.user_metadata?.full_name as string ??
|
||||
session.user?.user_metadata?.name as string ??
|
||||
session.user?.email as string ??
|
||||
'Profile' as string;
|
||||
|
||||
// If no session, return empty div
|
||||
if (!session) return <div />;
|
||||
return (
|
||||
<div className='m-auto mt-1'>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Image
|
||||
src={pfp}
|
||||
alt="User profile"
|
||||
width={35}
|
||||
height={35}
|
||||
className='rounded-full border-2 border-white m-auto mr-1 md:mr-2
|
||||
max-w-[25px] md:max-w-[35px]'
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>{name}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Button onClick={handleSignOut} className='w-full text-left'>
|
||||
Sign Out
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default AvatarDropdown;
|
@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
|
||||
import { login, signup } from '@/server/actions/auth';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@ -15,15 +14,17 @@ import {
|
||||
FormMessage,
|
||||
FormItem,
|
||||
} from '@/components/ui/form';
|
||||
import { useRef } from 'react';
|
||||
|
||||
const formSchema = z.object({
|
||||
fullName: z.string(),
|
||||
email: z.string().email("Must be a vaild email!"),
|
||||
fullName: z.string().optional(),
|
||||
email: z.string().email("Must be a valid email!"),
|
||||
password: z.string().min(8, "Must be at least 8 characters!"),
|
||||
});
|
||||
|
||||
const LoginForm = () => {
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
@ -33,63 +34,85 @@ const LoginForm = () => {
|
||||
},
|
||||
});
|
||||
|
||||
// Create wrapper functions for the server actions
|
||||
const handleLogin = async () => {
|
||||
if (await form.trigger()) {
|
||||
const formData = new FormData(formRef.current!);
|
||||
await login(formData);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSignup = async () => {
|
||||
if (await form.trigger()) {
|
||||
const formData = new FormData(formRef.current!);
|
||||
await signup(formData);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fullName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel htmlFor='fullName'>
|
||||
Full Name
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input id='fullName' type='text' {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Full name is only required when signing up.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel htmlFor='email'>
|
||||
Email:
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input id='email' type='email' {...field} required />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Password:
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input id='password' type='password' {...field} required />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button formAction={login}>
|
||||
Log in
|
||||
</Button>
|
||||
<Button formAction={signup}>
|
||||
Sign up
|
||||
</Button>
|
||||
</Form>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<Form {...form}>
|
||||
<form ref={formRef} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fullName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel htmlFor='fullName'>
|
||||
Full Name
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input id='fullName' type='text' {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Full name is only required when signing up.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel htmlFor='email'>
|
||||
Email
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input id='email' type='email' {...field} required />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Password
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input id='password' type='password' {...field} required />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex gap-4 pt-2">
|
||||
<Button type="button" onClick={handleLogin}>
|
||||
Log in
|
||||
</Button>
|
||||
<Button type="button" onClick={handleSignup}>
|
||||
Sign up
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
@ -2,6 +2,7 @@
|
||||
import Image from 'next/image';
|
||||
import { TVToggle, useTVMode } from '@/components/context/TVMode';
|
||||
import { ThemeToggle } from '@/components/context/Theme';
|
||||
import AvatarDropdown from '@/components/auth/AvatarDropdown';
|
||||
|
||||
const Header = () => {
|
||||
const { tvMode } = useTVMode();
|
||||
@ -20,6 +21,7 @@ const Header = () => {
|
||||
<div className='flex flex-row my-auto items-center pt-2 pr-0 md:pt-4 md:pr-8'>
|
||||
<TVToggle />
|
||||
<ThemeToggle />
|
||||
<AvatarDropdown />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
50
src/components/ui/avatar.tsx
Normal file
50
src/components/ui/avatar.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
201
src/components/ui/dropdown-menu.tsx
Normal file
201
src/components/ui/dropdown-menu.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
@ -23,7 +23,6 @@ export const login = async (formData: FormData) => {
|
||||
}
|
||||
|
||||
revalidatePath('/', 'layout')
|
||||
redirect('/account')
|
||||
};
|
||||
|
||||
export const signup = async (formData: FormData) => {
|
||||
@ -32,6 +31,7 @@ export const signup = async (formData: FormData) => {
|
||||
// type-casting here for convenience
|
||||
// in practice, you should validate your inputs
|
||||
const data = {
|
||||
fullName: formData.get('fullName') as string,
|
||||
email: formData.get('email') as string,
|
||||
password: formData.get('password') as string,
|
||||
}
|
||||
@ -43,5 +43,4 @@ export const signup = async (formData: FormData) => {
|
||||
}
|
||||
|
||||
revalidatePath('/', 'layout')
|
||||
redirect('/account')
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { createBrowserClient } from '@supabase/ssr'
|
||||
|
||||
export function createClient() {
|
||||
export const createClient = () => {
|
||||
return createBrowserClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
)
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user