feat: user form
This commit is contained in:
parent
e7f6912cca
commit
00d85806fe
|
@ -0,0 +1,86 @@
|
|||
import { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import UserAuthForm from "@/components/forms/user-auth-form";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Sign In",
|
||||
description: "Sign In to start your journey with skilled ai.",
|
||||
};
|
||||
|
||||
export default function AuthenticationPage() {
|
||||
return (
|
||||
<div className="relative h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
|
||||
<Link
|
||||
href="/examples/authentication"
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"absolute right-4 hidden top-4 md:right-8 md:top-8",
|
||||
)}
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<div className="relative hidden h-full flex-col bg-secondary p-10 dark:border-r lg:flex">
|
||||
<div className="absolute inset-0 bg-secondary" />
|
||||
<div className="relative z-20 flex items-center text-lg font-medium">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="mr-2 h-6 w-6"
|
||||
>
|
||||
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
|
||||
</svg>
|
||||
SkilledAi
|
||||
</div>
|
||||
<div className="relative z-20 mt-auto">
|
||||
<blockquote className="space-y-2">
|
||||
<p className="text-lg text-muted-foreground">
|
||||
“Empower your coding journey at SkilledAi. Access tutorials,
|
||||
challenges, and expert-led courses to master programming languages
|
||||
and tools. Join a supportive community for discussions and code
|
||||
reviews. Elevate your skills and stay ahead in the tech world with
|
||||
us!.”
|
||||
</p>
|
||||
{/* <footer className="text-sm">Ak</footer> */}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 lg:p-8 h-full flex items-center">
|
||||
<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>
|
||||
<UserAuthForm />
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import Providers from "@/components/layout/providers";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import "@uploadthing/react/styles.css";
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Home | Skilled Ai",
|
||||
description:
|
||||
"Empower your coding journey at Skilled Ai. Access tutorials, challenges, and expert-led courses to master programming languages and tools. Join a supportive community for discussions and code reviews. Elevate your skills and stay ahead in the tech world with us!",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={`${inter.className} overflow-hidden`}>
|
||||
<Providers>
|
||||
<Toaster />
|
||||
{children}
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react'
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<div></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Loading
|
|
@ -0,0 +1,36 @@
|
|||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function NotFound() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 mb-16 items-center justify-center text-center">
|
||||
<span className="bg-gradient-to-b from-foreground to-transparent bg-clip-text text-[10rem] font-extrabold leading-none text-transparent">
|
||||
404
|
||||
</span>
|
||||
<h2 className="my-2 font-heading text-2xl font-bold">
|
||||
Something's missing
|
||||
</h2>
|
||||
<p>
|
||||
Sorry, the page you are looking for doesn't exist or has been
|
||||
moved.
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center gap-2">
|
||||
<Button onClick={() => router.back()} variant="default" size="lg">
|
||||
Go back
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => router.push("/dashboard")}
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
>
|
||||
Back to Home
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import GoogleSignInButton from "../google-auth-button";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email({ message: "Enter a valid email address" }),
|
||||
password: z
|
||||
.string()
|
||||
.min(6, { message: "Password should be minimum 6 characters" }),
|
||||
});
|
||||
|
||||
type UserFormValue = z.infer<typeof formSchema>;
|
||||
|
||||
export default function UserAuthForm() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get("callbackUrl");
|
||||
const [loading, setLoading] = useState(false);
|
||||
// const defaultValues = {
|
||||
// email: "demo@gmail.com",
|
||||
// };
|
||||
const form = useForm<UserFormValue>({
|
||||
resolver: zodResolver(formSchema),
|
||||
// defaultValues,
|
||||
});
|
||||
|
||||
const onSubmit = async ({ email, password }: UserFormValue) => {
|
||||
console.log(email, password);
|
||||
const supabase = createClient();
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
||||
},
|
||||
});
|
||||
console.log(data);
|
||||
console.log(error);
|
||||
// signIn("credentials", {
|
||||
// email: data.email,
|
||||
// callbackUrl: callbackUrl ?? "/dashboard",
|
||||
// });
|
||||
|
||||
if (!error) {
|
||||
router.push("/dashboard");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-2 w-full"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter your email..."
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter your password..."
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button disabled={loading} className="ml-auto w-full" type="submit">
|
||||
Continue With Email
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">
|
||||
Or continue with
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<GoogleSignInButton />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,653 @@
|
|||
"use client";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Heading } from "@/components/ui/heading";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { profileSchema, type ProfileFormValues } from "@/lib/form-schema";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangleIcon, Trash, Trash2Icon } from "lucide-react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { SubmitHandler, useFieldArray, useForm } from "react-hook-form";
|
||||
|
||||
interface ProfileFormType {
|
||||
initialData: any | null;
|
||||
categories: any;
|
||||
}
|
||||
|
||||
export const CreateProfileOne: React.FC<ProfileFormType> = ({
|
||||
initialData,
|
||||
categories,
|
||||
}) => {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [imgLoading, setImgLoading] = useState(false);
|
||||
const title = initialData ? "Edit account" : "Create Your Profile";
|
||||
const description = initialData
|
||||
? "Edit a account."
|
||||
: "To create your account, we first need some basic information about you.";
|
||||
const toastMessage = initialData ? "Account updated." : "Account created.";
|
||||
const action = initialData ? "Save changes" : "Create";
|
||||
const [previousStep, setPreviousStep] = useState(0);
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [data, setData] = useState({});
|
||||
const delta = currentStep - previousStep;
|
||||
|
||||
const defaultValues = {
|
||||
jobs: [
|
||||
{
|
||||
jobtitle: "",
|
||||
employer: "",
|
||||
startdate: "",
|
||||
enddate: "",
|
||||
jobcountry: "",
|
||||
jobcity: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const form = useForm<ProfileFormValues>({
|
||||
resolver: zodResolver(profileSchema),
|
||||
defaultValues,
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
} = form;
|
||||
|
||||
const { append, remove, fields } = useFieldArray({
|
||||
control,
|
||||
name: "jobs",
|
||||
});
|
||||
|
||||
const onSubmit = async (data: ProfileFormValues) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
if (initialData) {
|
||||
// await axios.post(`/api/products/edit-product/${initialData._id}`, data);
|
||||
} else {
|
||||
// const res = await axios.post(`/api/products/create-product`, data);
|
||||
// console.log("product", res);
|
||||
}
|
||||
router.refresh();
|
||||
router.push(`/dashboard/products`);
|
||||
} catch (error: any) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// await axios.delete(`/api/${params.storeId}/products/${params.productId}`);
|
||||
router.refresh();
|
||||
router.push(`/${params.storeId}/products`);
|
||||
} catch (error: any) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const processForm: SubmitHandler<ProfileFormValues> = (data) => {
|
||||
console.log("data ==>", data);
|
||||
setData(data);
|
||||
// api call and reset
|
||||
// form.reset();
|
||||
};
|
||||
|
||||
type FieldName = keyof ProfileFormValues;
|
||||
|
||||
const steps = [
|
||||
{
|
||||
id: "Step 1",
|
||||
name: "Personal Information",
|
||||
fields: [
|
||||
"firstname",
|
||||
"lastname",
|
||||
"email",
|
||||
"contactno",
|
||||
"country",
|
||||
"city",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "Step 2",
|
||||
name: "Professional Informations",
|
||||
// fields are mapping and flattening for the error to be trigger for the dynamic fields
|
||||
fields: fields
|
||||
?.map((_, index) => [
|
||||
`jobs.${index}.jobtitle`,
|
||||
`jobs.${index}.employer`,
|
||||
`jobs.${index}.startdate`,
|
||||
`jobs.${index}.enddate`,
|
||||
`jobs.${index}.jobcountry`,
|
||||
`jobs.${index}.jobcity`,
|
||||
// Add other field names as needed
|
||||
])
|
||||
.flat(),
|
||||
},
|
||||
{ id: "Step 3", name: "Complete" },
|
||||
];
|
||||
|
||||
const next = async () => {
|
||||
const fields = steps[currentStep].fields;
|
||||
|
||||
const output = await form.trigger(fields as FieldName[], {
|
||||
shouldFocus: true,
|
||||
});
|
||||
|
||||
if (!output) return;
|
||||
|
||||
if (currentStep < steps.length - 1) {
|
||||
if (currentStep === steps.length - 2) {
|
||||
await form.handleSubmit(processForm)();
|
||||
}
|
||||
setPreviousStep(currentStep);
|
||||
setCurrentStep((step) => step + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const prev = () => {
|
||||
if (currentStep > 0) {
|
||||
setPreviousStep(currentStep);
|
||||
setCurrentStep((step) => step - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const countries = [{ id: "wow", name: "india" }];
|
||||
const cities = [{ id: "2", name: "kerala" }];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<Heading title={title} description={description} />
|
||||
{initialData && (
|
||||
<Button
|
||||
disabled={loading}
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Separator />
|
||||
<div>
|
||||
<ul className="flex gap-4">
|
||||
{steps.map((step, index) => (
|
||||
<li key={step.name} className="md:flex-1">
|
||||
{currentStep > index ? (
|
||||
<div className="group flex w-full flex-col border-l-4 border-sky-600 py-2 pl-4 transition-colors md:border-l-0 md:border-t-4 md:pb-0 md:pl-0 md:pt-4">
|
||||
<span className="text-sm font-medium text-sky-600 transition-colors ">
|
||||
{step.id}
|
||||
</span>
|
||||
<span className="text-sm font-medium">{step.name}</span>
|
||||
</div>
|
||||
) : currentStep === index ? (
|
||||
<div
|
||||
className="flex w-full flex-col border-l-4 border-sky-600 py-2 pl-4 md:border-l-0 md:border-t-4 md:pb-0 md:pl-0 md:pt-4"
|
||||
aria-current="step"
|
||||
>
|
||||
<span className="text-sm font-medium text-sky-600">
|
||||
{step.id}
|
||||
</span>
|
||||
<span className="text-sm font-medium">{step.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="group flex h-full w-full flex-col border-l-4 border-gray-200 py-2 pl-4 transition-colors md:border-l-0 md:border-t-4 md:pb-0 md:pl-0 md:pt-4">
|
||||
<span className="text-sm font-medium text-gray-500 transition-colors">
|
||||
{step.id}
|
||||
</span>
|
||||
<span className="text-sm font-medium">{step.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<Separator />
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(processForm)}
|
||||
className="space-y-8 w-full"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
currentStep === 1
|
||||
? "md:inline-block w-full"
|
||||
: "md:grid md:grid-cols-3 gap-8",
|
||||
)}
|
||||
>
|
||||
{currentStep === 0 && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="firstname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>First Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
disabled={loading}
|
||||
placeholder="John"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lastname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
disabled={loading}
|
||||
placeholder="Doe"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
disabled={loading}
|
||||
placeholder="johndoe@gmail.com"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="contactno"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Contact Number</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Enter you contact number"
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="country"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Country</FormLabel>
|
||||
<Select
|
||||
disabled={loading}
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
defaultValue={field.value}
|
||||
placeholder="Select a country"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{/* @ts-ignore */}
|
||||
{countries.map((country) => (
|
||||
<SelectItem key={country.id} value={country.id}>
|
||||
{country.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="city"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>City</FormLabel>
|
||||
<Select
|
||||
disabled={loading}
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
defaultValue={field.value}
|
||||
placeholder="Select a city"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{/* @ts-ignore */}
|
||||
{cities.map((city) => (
|
||||
<SelectItem key={city.id} value={city.id}>
|
||||
{city.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{currentStep === 1 && (
|
||||
<>
|
||||
{fields?.map((field, index) => (
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
defaultValue="item-1"
|
||||
key={field.id}
|
||||
>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger
|
||||
className={cn(
|
||||
"[&[data-state=closed]>button]:hidden [&[data-state=open]>.alert]:hidden relative !no-underline",
|
||||
errors?.jobs?.[index] && "text-red-700",
|
||||
)}
|
||||
>
|
||||
{`Work Experience ${index + 1}`}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="absolute right-8"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<Trash2Icon className="h-4 w-4 " />
|
||||
</Button>
|
||||
{errors?.jobs?.[index] && (
|
||||
<span className="absolute alert right-8">
|
||||
<AlertTriangleIcon className="h-4 w-4 text-red-700" />
|
||||
</span>
|
||||
)}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div
|
||||
className={cn(
|
||||
"md:grid md:grid-cols-3 gap-8 border p-4 rounded-md relative mb-4",
|
||||
)}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`jobs.${index}.jobtitle`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Job title</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`jobs.${index}.employer`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Employer</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`jobs.${index}.startdate`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Start date</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="date"
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`jobs.${index}.enddate`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>End date</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="date"
|
||||
disabled={loading}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`jobs.${index}.jobcountry`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Job country</FormLabel>
|
||||
<Select
|
||||
disabled={loading}
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
defaultValue={field.value}
|
||||
placeholder="Select your job country"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{countries.map((country) => (
|
||||
<SelectItem
|
||||
key={country.id}
|
||||
value={country.id}
|
||||
>
|
||||
{country.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`jobs.${index}.jobcity`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Job city</FormLabel>
|
||||
<Select
|
||||
disabled={loading}
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
defaultValue={field.value}
|
||||
placeholder="Select your job city"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{cities.map((city) => (
|
||||
<SelectItem key={city.id} value={city.id}>
|
||||
{city.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
))}
|
||||
|
||||
<div className="flex justify-center mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
className="flex justify-center"
|
||||
size={"lg"}
|
||||
onClick={() =>
|
||||
append({
|
||||
jobtitle: "",
|
||||
employer: "",
|
||||
startdate: "",
|
||||
enddate: "",
|
||||
jobcountry: "",
|
||||
jobcity: "",
|
||||
})
|
||||
}
|
||||
>
|
||||
Add More
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<div>
|
||||
<h1>Completed</h1>
|
||||
<pre className="whitespace-pre-wrap">
|
||||
{JSON.stringify(data)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <Button disabled={loading} className="ml-auto" type="submit">
|
||||
{action}
|
||||
</Button> */}
|
||||
</form>
|
||||
</Form>
|
||||
{/* Navigation */}
|
||||
<div className="mt-8 pt-5">
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={prev}
|
||||
disabled={currentStep === 0}
|
||||
className="rounded bg-white px-2 py-1 text-sm font-semibold text-sky-900 shadow-sm ring-1 ring-inset ring-sky-300 hover:bg-sky-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 19.5L8.25 12l7.5-7.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={next}
|
||||
disabled={currentStep === steps.length - 1}
|
||||
className="rounded bg-white px-2 py-1 text-sm font-semibold text-sky-900 shadow-sm ring-1 ring-inset ring-sky-300 hover:bg-sky-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue