-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add self signup page for new users (#34)
* feat: create signup page * feat: add invite funcitonality to users table
- Loading branch information
Showing
15 changed files
with
326 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 4 additions & 4 deletions
8
ethereal-nexus-dashboard/src/app/(layout)/(session)/(admin)/users/new/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,18 @@ | ||
import { Separator } from "@/components/ui/separator"; | ||
import React from 'react'; | ||
import UserForm from '@/components/user/user-form'; | ||
import UserInviteForm from '@/components/user/user-invitation-form'; | ||
|
||
export default async function NewUser({ params }: any) { | ||
return ( | ||
<div className="container space-y-6"> | ||
<div> | ||
<h3 className="text-lg font-medium">Projects</h3> | ||
<h3 className="text-lg font-medium">Invites</h3> | ||
<p className="text-sm text-muted-foreground"> | ||
Create a new User | ||
Invite a new User | ||
</p> | ||
</div> | ||
<Separator /> | ||
<UserForm /> | ||
<UserInviteForm /> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
import React from "react"; | ||
import DashboardLayout from "@/components/layout"; | ||
import { ThemeProvider } from '@/components/theme-provider'; | ||
|
||
export default async function RootLayout({ | ||
children, | ||
}: { | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
const env = process.env.NODE_ENV | ||
const env = process.env.NODE_ENV | ||
|
||
return <DashboardLayout>{children}</DashboardLayout>; | ||
return <ThemeProvider attribute="class" defaultTheme="system" enableSystem> | ||
<DashboardLayout>{children}</DashboardLayout> </ThemeProvider>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { Metadata } from 'next'; | ||
import Link from 'next/link'; | ||
|
||
import { cn } from '@/lib/utils'; | ||
import { buttonVariants } from '@/components/ui/button'; | ||
import { Meteors } from '@/components/ui/meteors'; | ||
import UserForm from '@/components/user/user-form'; | ||
|
||
export const metadata: Metadata = { | ||
title: "Authentication", | ||
description: "Authentication forms built using the components.", | ||
} | ||
|
||
export default function AuthenticationPage() { | ||
return ( | ||
<div | ||
className="container relative hidden h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0"> | ||
<Link | ||
href="/api/auth/signin" | ||
className={cn( | ||
buttonVariants({ variant: "ghost" }), | ||
"absolute right-4 top-4 md:right-8 md:top-8" | ||
)} | ||
> | ||
Login | ||
</Link> | ||
<div className="relative hidden h-full flex-col bg-slate-600 p-10 text-white lg:flex dark:border-r"> | ||
<Meteors /> | ||
</div> | ||
<div className="lg:p-8"> | ||
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]"> | ||
<div className="flex flex-col space-y-2 text-center"> | ||
<h1 className="text-2xl font-semibold tracking-tight"> | ||
Create an account | ||
</h1> | ||
<p className="text-sm text-muted-foreground"> | ||
Enter your email below to create your account | ||
</p> | ||
</div> | ||
<UserForm /> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} |
29 changes: 29 additions & 0 deletions
29
ethereal-nexus-dashboard/src/components/hooks/useCopyToClipboard.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { useCallback, useState } from 'react' | ||
|
||
type CopiedValue = string | null | ||
type CopyFn = (text: string) => Promise<boolean> | ||
|
||
|
||
export function useCopyToClipboard(): [CopiedValue, CopyFn] { | ||
const [copiedText, setCopiedText] = useState<CopiedValue>(null) | ||
|
||
const copy: CopyFn = useCallback(async text => { | ||
if (!navigator?.clipboard) { | ||
console.warn('Clipboard not supported') | ||
return false | ||
} | ||
|
||
// Try to save to clipboard then save it in the state if worked | ||
try { | ||
await navigator.clipboard.writeText(text) | ||
setCopiedText(text) | ||
return true | ||
} catch (error) { | ||
console.warn('Copy failed', error) | ||
setCopiedText(null) | ||
return false | ||
} | ||
}, []) | ||
|
||
return [copiedText, copy] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React from "react"; | ||
import { cn } from '@/lib/utils'; | ||
|
||
export const Meteors = ({ | ||
number, | ||
className, | ||
}: { | ||
number?: number; | ||
className?: string; | ||
}) => { | ||
const meteors = new Array(number || 20).fill(true); | ||
return ( | ||
<> | ||
{meteors.map((el, idx) => ( | ||
<span | ||
key={"meteor" + idx} | ||
className={cn( | ||
"animate-meteor-effect absolute top-1/2 left-1/2 h-0.5 w-0.5 rounded-[9999px] bg-slate-500 shadow-[0_0_0_1px_#ffffff10] rotate-[215deg]", | ||
"before:content-[''] before:absolute before:top-1/2 before:transform before:-translate-y-[50%] before:w-[50px] before:h-[1px] before:bg-gradient-to-r before:from-violet-300 before:to-transparent", | ||
className | ||
)} | ||
style={{ | ||
top: 0, | ||
left: Math.floor(Math.random() * (800 - -800) + -800) + "px", | ||
animationDelay: Math.random() * (0.8 - 0.2) + 0.2 + "s", | ||
animationDuration: Math.floor(Math.random() * (10 - 2) + 2) + "s", | ||
}} | ||
></span> | ||
))} | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
ethereal-nexus-dashboard/src/components/user/user-invitation-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
"use client"; | ||
|
||
import { useForm } from 'react-hook-form'; | ||
import { Button } from '@/components/ui/button'; | ||
|
||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; | ||
import { Input } from '@/components/ui/input'; | ||
import { zodResolver } from '@hookform/resolvers/zod'; | ||
import React, { useCallback, useState } from 'react'; | ||
import { NewInvite, newInviteSchema } from '@/data/users/dto'; | ||
import { insertInvite } from '@/data/users/actions'; | ||
import { useToast } from '@/components/ui/use-toast'; | ||
import { Separator } from '@/components/ui/separator'; | ||
import { Clipboard, ClipboardCheck } from 'lucide-react'; | ||
import { useCopyToClipboard } from '@/components/hooks/useCopyToClipboard'; | ||
|
||
type UserInviteFormProps = { | ||
onComplete?: () => void | ||
} | ||
|
||
export default function UserInviteForm({ onComplete }: UserInviteFormProps) { | ||
const { toast } = useToast() | ||
const [inviteKey, setKey] = useState<string | null>(null); | ||
const [copiedText, copy] = useCopyToClipboard() | ||
|
||
const inviteUrl = typeof window !== 'undefined' ? `${window?.location.protocol}//${window.location.host}/signup?key=${inviteKey}` : '' | ||
|
||
const form: any = useForm<NewInvite>({ | ||
resolver: zodResolver(newInviteSchema) | ||
}); | ||
|
||
async function handler(formdata) { | ||
const invite = await insertInvite(formdata); | ||
if (invite.success) { | ||
setKey(invite.data.key); | ||
toast({ | ||
title: 'User invite created successfully!', | ||
}); | ||
if(onComplete) onComplete(); | ||
} else { | ||
toast({ | ||
title: 'Failed to create invite.', | ||
description: invite.error.message, | ||
}); | ||
} | ||
} | ||
|
||
const handleCopy = useCallback(async () => { | ||
await copy(inviteUrl) | ||
toast({ | ||
title: 'Copied!', | ||
}); | ||
}, [inviteUrl, copy, toast]) | ||
|
||
return ( | ||
<Form {...form}> | ||
<form onSubmit={form.handleSubmit(handler)} className="space-y-4"> | ||
<FormField | ||
control={form.control} | ||
name="email" | ||
render={({field}) => ( | ||
<FormItem> | ||
<FormLabel>Email</FormLabel> | ||
<FormControl> | ||
<Input placeholder="johndoe@yourcompany.com" {...field} /> | ||
</FormControl> | ||
<FormDescription> | ||
This is the email of the user. | ||
</FormDescription> | ||
<FormMessage/> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormMessage /> | ||
{ | ||
inviteKey ? | ||
<> | ||
<Separator className="my-4" /> | ||
<div className="flex gap-2"> | ||
<Input className="w-full" disabled value={inviteUrl} /> | ||
<Button variant="secondary" type="button" onClick={handleCopy}> | ||
{ | ||
copiedText ? <ClipboardCheck className="h-4 w-4" /> : <Clipboard className="h-4 w-4" /> | ||
} | ||
</Button> | ||
</div> | ||
</> | ||
: null | ||
} | ||
<Button className='w-full' type="submit">Create Invite</Button> | ||
</form> | ||
</Form> | ||
); | ||
} |
Oops, something went wrong.