Skip to content

Commit

Permalink
Seller signup (#346)
Browse files Browse the repository at this point in the history
* initialised next app and added homepage

* setup email, prisma, DB schema, nextAUTH

* fic

* seller signup

* merge
  • Loading branch information
ShivanshPlays authored Oct 31, 2024
1 parent f409745 commit 12eb665
Show file tree
Hide file tree
Showing 25 changed files with 1,184 additions and 123 deletions.
52 changes: 52 additions & 0 deletions scruter-nextjs/actions/seller/signup-action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use server"
import { generateAndSendOTP } from '@/lib/auth';
import prismadb from '@/lib/prismadb';
import { Prisma, Seller } from '@prisma/client';

export async function SellerCreate({
name,email
}:{
name:string,
email:string
}): Promise<{ success: boolean; error?: string ; data?:Seller}> {

const exitingSeller = await prismadb.seller.findUnique({
where: {
email: email,
},
});

if (exitingSeller) {
return {
success: false,
error: 'seller already exists',
};
}

try {
const res = await prismadb.seller.create({
data: {
name: name,
email: email,
},
});

if(!res){
return {success:false, error:"Error occured in seller creation"};
}

const resp=await generateAndSendOTP(res.email,"seller");

if(!resp){
return {success:false, error:"Error occured in sending otp"};
}

return { success: true , data:res};

} catch (err) {
if (err instanceof Prisma.PrismaClientKnownRequestError) {
console.log(err.message);
}
return { success: false, error: 'An unexpected error occurred.' };
}
}
135 changes: 135 additions & 0 deletions scruter-nextjs/app/(routes)/auth/components/otp-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use client';

import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from '@/components/ui/input-otp';

import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";

import { z } from "zod";

import * as React from 'react';

import { cn } from '@/lib/utils';
import { Icons } from '@/components/ui/icons';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Toaster, toast } from 'react-hot-toast';
import { SellerCreate } from '@/actions/seller/signup-action';
import { zodResolver } from "@hookform/resolvers/zod";

import { signIn } from "next-auth/react";
import { useForm } from 'react-hook-form';
import { ChevronLeftCircleIcon } from 'lucide-react';

interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {
roleType: 'user' | 'seller';
email:string;
setOtpOpen:(otp:boolean)=>void
}

const FormSchema = z.object({
pin: z.string().min(6, {
message: "Your one-time password must be 6 characters.",
}),
});


export function OtpForm({
className,
roleType,
email,
setOtpOpen,
...props
}: UserAuthFormProps) {
const [isLoading, setIsLoading] = React.useState<boolean>(false);

const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
pin: "",
},
});

async function onOTPSubmit(data: z.infer<typeof FormSchema>) {
// console.log(data.pin+email);
setIsLoading(true);

// toast.success(data.pin)
const result = await signIn("credentials", {
email,
otp: data.pin,
role: "seller",
redirect: false,
});
console.log(result);
if (!result?.ok) {
toast.error("Invalid email or otp");
} else {
toast.success(`Welcome!`);

// setTimeout(() => {
// window.location.href = `"/`; // Redirect on success
// }, 2000);
}
setIsLoading(false);
}


return (
<div className={cn('grid gap-6', className)} {...props}>
<Toaster />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onOTPSubmit)}
className="flex flex-col dark:text-gray-200 z-10 items-start justify-center py-10 md:py-0 pl-14 gap-4 w-2/4"
>
<FormField
control={form.control}
name="pin"
render={({ field }) => (
<FormItem className="flex gap-2 items-start justify-center flex-col">
<FormLabel className="text-2xl gap-2 flex items-center justify-center text-customTeal dark:text-Green font-bold">
<ChevronLeftCircleIcon onClick={()=>{setOtpOpen(false)}} className="h-5 w-5"/>
One-Time Password
</FormLabel>
<FormControl>
<InputOTP maxLength={6} {...field}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</FormControl>
<FormDescription>
Please enter the one-time password sent to your email.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<Button className="bg-customTeal dark:bg-Green" type="submit">
Submit
</Button>
</form>
</Form>
</div>
);
}
119 changes: 119 additions & 0 deletions scruter-nextjs/app/(routes)/auth/components/signup-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
'use client';

import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from '@/components/ui/input-otp';

import * as React from 'react';

import { cn } from '@/lib/utils';
import { Icons } from '@/components/ui/icons';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Toaster, toast } from 'react-hot-toast';
import { SellerCreate } from '@/actions/seller/signup-action';
import { OtpForm } from './otp-form';

interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {
authType: 'signup' | 'login';
}

export function SellerSignupForm({
className,
authType,
...props
}: UserAuthFormProps) {
const [isLoading, setIsLoading] = React.useState<boolean>(false);

const [name, setName] = React.useState('');
const [email, setEmail] = React.useState('');
const [otpOpen, setOtpOpen] = React.useState(false);

async function onSubmit(event: React.SyntheticEvent) {
event.preventDefault();
setIsLoading(true);

if (!name || !email) {
toast.error('missing details');
return;
}
// toast.success(name+email);
const res = await SellerCreate({
name: name,
email: email,
});

if (!res.success && res.error) {
toast.error(res.error);
return;
}

toast.success('user created successfully, please enter OTP');

setOtpOpen(true);
setIsLoading(false);
}

return (
<div className={cn('grid gap-6', className)} {...props}>
<Toaster />
{!otpOpen && (
<form onSubmit={onSubmit}>
<div className="grid gap-2">
<div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Name
</Label>
{/* : {name} */}
<Input
id="email"
placeholder="John Doe"
type="text"
autoComplete="text"
autoCorrect="off"
disabled={isLoading}
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
<Label className="sr-only" htmlFor="email">
Email
</Label>
{/* : {email} */}
<Input
id="email"
placeholder="[email protected]"
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading}
value={email}
onChange={e => {
setEmail(e.target.value);
}}
/>
</div>
<Button type="submit" disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
{authType === 'signup' ? (
<p>Sign Up with Email</p>
) : (
<p>Sign In with Email</p>
)}
</Button>
</div>
</form>
)}

{otpOpen && <OtpForm email={email} setOtpOpen={setOtpOpen} roleType='seller'/>}
</div>
);
}
79 changes: 79 additions & 0 deletions scruter-nextjs/app/(routes)/auth/seller/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';

import { cn } from '@/lib/utils';
import { buttonVariants } from '@/components/ui/button';
import { SellerSignupForm} from '../../components/signup-form';

export const metadata: Metadata = {
title: 'Authentication',
description: 'Authentication forms built using the components.',
};

export default function AuthenticationPage() {
return (
<>
<div className="md:hidden">
<Image
src="/signupPattern.svg"
width={1280}
height={843}
alt="Authentication"
className="block dark:hidden"
/>
<Image
src="/signupPattern.svg"
width={1280}
height={843}
alt="Authentication"
className="hidden dark:block"
/>
</div>
<div className="container relative hidden h-[700px] flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
<Link
href="/auth/seller/login"
className={cn(
buttonVariants({ variant: 'ghost' }),
'absolute right-4 top-4 md:right-8 md:top-8'
)}
>
Login
</Link>
<div className={`hidden bg-[url("/signupPattern.svg")] bg-opacity-50 h-full flex-col p-10 text-white dark:border-r lg:flex`}>

</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>
<SellerSignupForm authType='signup'/>
<p className="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{' '}
<Link
href="/terms"
className="underline underline-offset-4 hover:text-primary"
>
Terms of Service
</Link>{' '}
and{' '}
<Link
href="/privacy"
className="underline underline-offset-4 hover:text-primary"
>
Privacy Policy
</Link>
.
</p>
</div>
</div>
</div>
</>
);
}
Loading

0 comments on commit 12eb665

Please sign in to comment.