перед обновление для создания обьявления

This commit is contained in:
Zikil 2024-11-21 23:52:26 +07:00
parent 7b40ce2c1a
commit 2a90b6f2b0
44 changed files with 2279 additions and 208 deletions

28
@types/next-auth.d.ts vendored Normal file
View File

@ -0,0 +1,28 @@
// Ref: https://next-auth.js.org/getting-started/typescript#module-augmentation
import { DefaultSession, DefaultUser } from 'next-auth';
import { JWT, DefaultJWT } from 'next-auth/jwt';
import type { UserRole } from '@prisma/client';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: UserRole;
name: string;
image: string;
};
}
interface User extends DefaultUser {
id: number;
role: UserRole;
}
}
declare module 'next-auth/jwt' {
interface JWT extends DefaultJWT {
id: string;
role: UserRole;
}
}

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
// import { useParams } from 'next/navigation'; // import { useParams } from 'next/navigation';
import { MapPin, Calendar, Phone, MessageCircle, Share2, Flag, Heart } from 'lucide-react'; import { MapPin, Calendar, Phone, MessageCircle, Share2, Flag, Heart } from 'lucide-react';
import { adts } from '@/data/adt'; // import { adts } from '@/data/adt';
import { prisma } from '@/prisma/prisma-client'; import { prisma } from '@/prisma/prisma-client';
import Header from '@/components/Header'; import Header from '@/components/Header';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
@ -27,12 +27,12 @@ export default async function AdtPage({params: { id } }: { params: { id: string
return ( return (
<> <>
<Header />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<div className="bg-white rounded-xl shadow-sm overflow-hidden"> <div className="bg-white rounded-xl shadow-sm overflow-hidden">
<img src={adt.image} alt={adt.title} className="w-full h-[400px] object-cover" /> <img src={String(adt.image)} alt={adt.title} className="w-full h-[400px] object-cover" />
<div className="p-6"> <div className="p-6">
<div className="flex justify-between items-start mb-4"> <div className="flex justify-between items-start mb-4">
<h1 className="text-2xl font-semibold">{adt.title}</h1> <h1 className="text-2xl font-semibold">{adt.title}</h1>
@ -79,8 +79,8 @@ export default async function AdtPage({params: { id } }: { params: { id: string
className="w-12 h-12 rounded-full" className="w-12 h-12 rounded-full"
/> />
<div> <div>
<h3 className="font-semibold">{user.name}</h3> <h3 className="font-semibold">{user?.name}</h3>
<p className="text-sm text-gray-500">Member {String(user.createdAt)}</p> <p className="text-sm text-gray-500">Member {String(user?.createdAt)}</p>
</div> </div>
</div> </div>

View File

@ -1,105 +1,112 @@
// 'use client'
import React from 'react'; import React from 'react';
import { ImagePlus, X } from 'lucide-react'; import { ImagePlus, X } from 'lucide-react';
import { AdtCreateForm } from '@/components/shared/adt-create/adt-create-form';
export default function CreateListing() { export default function CreateListing() {
return ( return (
<main className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="bg-white rounded-xl shadow-sm p-6"> <AdtCreateForm />
<h1 className="text-2xl font-semibold mb-6">Create New Listing</h1>
// <main className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
// <div className="bg-white rounded-xl shadow-sm p-6">
// <h1 className="text-2xl font-semibold mb-6">Create New Listing</h1>
<form className="space-y-6"> // <form className="space-y-6">
<div> // <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> // <label className="block text-sm font-medium text-gray-700 mb-2">
Title // Title
</label> // </label>
<input // <input
type="text" // type="text"
className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" // className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
placeholder="Enter listing title" // placeholder="Enter listing title"
/> // />
</div> // </div>
<div> // <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> // <label className="block text-sm font-medium text-gray-700 mb-2">
Category // Category
</label> // </label>
<select className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"> // <select className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500">
<option>Select a category</option> // <option>Select a category</option>
<option>Vehicles</option> // <option>Vehicles</option>
<option>Real Estate</option> // <option>Real Estate</option>
<option>Electronics</option> // <option>Electronics</option>
<option>Fashion</option> // <option>Fashion</option>
<option>Jobs</option> // <option>Jobs</option>
<option>Sports</option> // <option>Sports</option>
<option>Art</option> // <option>Art</option>
<option>Books</option> // <option>Books</option>
</select> // </select>
</div> // </div>
<div> // <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> // <label className="block text-sm font-medium text-gray-700 mb-2">
Price // Price
</label> // </label>
<div className="relative"> // <div className="relative">
<span className="absolute left-4 top-2 text-gray-500">$</span> // <span className="absolute left-4 top-2 text-gray-500">$</span>
<input // <input
type="number" // type="number"
className="w-full pl-8 pr-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" // className="w-full pl-8 pr-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
placeholder="0.00" // placeholder="0.00"
/> // />
</div> // </div>
</div> // </div>
<div> // <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> // <label className="block text-sm font-medium text-gray-700 mb-2">
Description // Description
</label> // </label>
<textarea // <textarea
rows={4} // rows={4}
className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" // className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
placeholder="Describe your item..." // placeholder="Describe your item..."
/> // />
</div> // </div>
<div> // <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> // <label className="block text-sm font-medium text-gray-700 mb-2">
Photos // Photos
</label> // </label>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4"> // <div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
<button className="aspect-square rounded-lg border-2 border-dashed border-gray-300 flex flex-col items-center justify-center hover:border-indigo-500 hover:bg-indigo-50"> // <button className="aspect-square rounded-lg border-2 border-dashed border-gray-300 flex flex-col items-center justify-center hover:border-indigo-500 hover:bg-indigo-50">
<ImagePlus className="h-8 w-8 text-gray-400" /> // <ImagePlus className="h-8 w-8 text-gray-400" />
<span className="mt-2 text-sm text-gray-500">Add Photo</span> // <span className="mt-2 text-sm text-gray-500">Add Photo</span>
</button> // </button>
</div> // </div>
</div> // </div>
<div> // <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> // <label className="block text-sm font-medium text-gray-700 mb-2">
Location // Location
</label> // </label>
<input // <input
type="text" // type="text"
className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" // className="w-full px-4 py-2 rounded-lg border border-gray-200 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
placeholder="Enter location" // placeholder="Enter location"
/> // />
</div> // </div>
<div className="flex gap-3"> // <div className="flex gap-3">
<button // <button
type="submit" // type="submit"
className="flex-1 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700" // className="flex-1 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
> // >
Create Listing // Create Listing
</button> // </button>
<button // <button
type="button" // type="button"
className="px-4 py-2 rounded-lg border border-gray-200 hover:bg-gray-50" // className="px-4 py-2 rounded-lg border border-gray-200 hover:bg-gray-50"
> // >
Cancel // Cancel
</button> // </button>
</div> // </div>
</form> // </form>
</div> // </div>
</main> // </main>
); );
} }

View File

@ -1,6 +1,8 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import localFont from "next/font/local"; import localFont from "next/font/local";
import "../globals.css"; import "../globals.css";
import { Suspense } from "react";
import Header from "@/components/Header";
export const metadata: Metadata = { export const metadata: Metadata = {
@ -14,12 +16,11 @@ export default function HomeLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <main className="min-h-screen">
<body <Suspense>
// className={`${geistSans.variable} ${geistMono.variable} antialiased`} <Header />
> </Suspense>
{children} {children}
</body> </main>
</html>
); );
} }

View File

@ -0,0 +1,14 @@
import { InfoBlock } from "@/components/shared/info-block";
export default function UnauthorizedPage() {
return (
<div className="flex flex-col items-center justify-center mt-40">
<InfoBlock
title="Доступ запрещён"
text="Данную страницу могут просматривать только авторизованные пользователи"
imageUrl="/assets/images/lock.png"
/>
</div>
);
}

View File

@ -13,7 +13,6 @@ export default async function Home() {
return ( return (
<> <>
<div className="min-h-screen bg-gray-100"> <div className="min-h-screen bg-gray-100">
<Header />
<Categories /> <Categories />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">

View File

@ -1,13 +1,33 @@
import React from 'react'; import React, { use } from 'react';
import { Settings, Package, Heart, Bell } from 'lucide-react'; import { Settings, Package, Heart, Bell } from 'lucide-react';
import ListingCard from '@/components/ListingCard'; import ListingCard from '@/components/ListingCard';
import { adts } from '@/data/adt'; import { adts } from '@/data/adt';
import Header from '@/components/Header'; import Header from '@/components/Header';
import { getUserSession } from '@/lib/get-user-session';
import { redirect } from 'next/navigation';
import { prisma } from '@/prisma/prisma-client';
import Link from 'next/link';
export default async function Profile() {
const session = await getUserSession()
if (!session) {
return redirect('/not-auth')
}
const user = await prisma.user.findFirst({
where: {
id: Number(session.id)
},
include: {
adts: true
}
})
console.log(user?.adts)
export default function Profile() {
return ( return (
<> <>
<Header />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="bg-white rounded-xl shadow-sm mb-8"> <div className="bg-white rounded-xl shadow-sm mb-8">
@ -21,13 +41,13 @@ export default function Profile() {
<div className="pt-16 pb-8 px-8"> <div className="pt-16 pb-8 px-8">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
<h1 className="text-2xl font-bold">John Doe</h1> <h1 className="text-2xl font-bold">{session.name}</h1>
<p className="text-gray-500">San Francisco, CA</p> <p className="text-gray-500">San Francisco, CA</p>
</div> </div>
<button className="flex items-center gap-2 px-4 py-2 rounded-lg border border-gray-200 hover:bg-gray-50"> <Link href='/profile/settings' className="flex items-center gap-2 px-4 py-2 rounded-lg border border-gray-200 hover:bg-gray-50">
<Settings className="h-5 w-5" /> <Settings className="h-5 w-5" />
<span>Edit Profile</span> <span>Edit Profile</span>
</button> </Link>
</div> </div>
</div> </div>
<div className="border-t"> <div className="border-t">
@ -55,8 +75,8 @@ export default function Profile() {
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{adts.slice(0, 3).map((adt) => ( {user?.adts.map((adt) => (
<ListingCard key={adt.id} {...adt} /> <ListingCard key={adt.id} id={String(adt.id)} image={adt.image} {...adt}/>
))} ))}
</div> </div>
</main> </main>

View File

@ -0,0 +1,33 @@
import Header from "@/components/Header"
import { ProfileForm } from "@/components/shared/profile-form"
import { getUserSession } from "@/lib/get-user-session"
import { prisma } from "@/prisma/prisma-client"
import { redirect } from "next/navigation"
export default async function Profile() {
const session = await getUserSession()
if (!session) {
return redirect('/not-auth')
}
const user = await prisma.user.findFirst({
where: {
id: Number(session?.id)
},
include: {
adts: true
}
})
if (!user) {
return redirect('/not-auth')
}
console.log(user?.adts)
return (
<ProfileForm data={user} />
)
}

97
app/actions.ts Normal file
View File

@ -0,0 +1,97 @@
'use server'
import { getUserSession } from "@/lib/get-user-session";
import { prisma } from "@/prisma/prisma-client";
import { Category, Prisma } from "@prisma/client";
import { hashSync } from "bcrypt";
export async function updateUserInfo(body: Prisma.UserUpdateInput) {
try {
const currentUser = await getUserSession();
if (!currentUser) {
throw new Error('Not found user')
}
const findUser = await prisma.user.findFirst({
where: {
id: Number(currentUser.id),
},
});
await prisma.user.update({
where: {
id: Number(currentUser.id),
},
data: {
name: body.name,
email: body.email,
password: body.password ? hashSync(body.password as string, 10) : findUser?.password ,
}
})
} catch (error) {
console.log('Error [UPDATE_USER]', error);
throw error;
}
}
export async function registerUser(body: Prisma.UserCreateInput) {
try {
const user = await prisma.user.findFirst({
where: {
email: body.email
}
})
if (user) {
throw new Error('user exist')
}
await prisma.user.create({
data: {
name: body.name,
email: body.email,
password: hashSync(body.password as string, 10) ,
}
})
} catch (error) {
console.log('Erro [create user]', error);
throw error;
}
}
export async function createAdt(body: Prisma.AdtCreateInput, categories: Category[]) {
try {
const currentUser = await getUserSession();
if (!currentUser) {
throw new Error('Not found user')
}
await prisma.adt.create({
data: {
title: body.title,
categories: {
connect: categories?.map((category) => ({
id: category.id,
})),
},
price: body.price,
description: body.description,
image: body.image,
location: body.location,
userId: Number(currentUser.id),
}
})
} catch (error) {
console.log('Error [create adt]', error);
throw error;
}
}

View File

@ -0,0 +1,153 @@
import NextAuth, { AuthOptions } from "next-auth"
import GithubProvider from "next-auth/providers/github"
import CredentialsProvider from 'next-auth/providers/credentials';
import { prisma } from "@/prisma/prisma-client";
import { compare, hashSync } from "bcrypt";
import { Role } from "@prisma/client";
export const authOptions: AuthOptions = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID || '',
clientSecret: process.env.GITHUB_SECRET || '',
profile(profile) {
return {
id: profile.id,
name: profile.name || profile.login,
email: profile.email,
image: profile.avatar_url,
role: 'USER' as Role
}
}
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
email: {label: 'Email', type: 'text'},
password: {label: 'Password', type: 'password'},
},
async authorize(credentials) {
if (!credentials) {
return null;
}
const values = {
email: credentials.email
}
const findUser = await prisma.user.findFirst({
where: values
})
if (!findUser) {
return null;
}
const isPasswordValid = await compare(credentials.password, findUser.password);
if (!isPasswordValid) {
return null;
}
// verified //
return {
id: String(findUser.id),
email: findUser.email,
name: findUser.name,
role: findUser.role,
}
}
})
],
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: 'jwt'
},
callbacks: {
async signIn({ user, account}) {
try {
if (account?.provaider === 'credentials') {
return true
}
if (!user.email){
return false
}
const findUser = await prisma.user.findFirst({
where: {
OR: [
{ provider: account?.provider, providerId: account?.providerId },
{ email: user.email }
]
}
})
if (findUser) {
await prisma.user.update({
where: {
id: findUser.id
},
data: {
provider: account?.provider,
providerId: account?.providerAccountId
}
})
return true
}
await prisma.user.create({
data: {
email: user.email,
name: user.name || 'User #' + user.id,
password: hashSync(user.id.toString(), 10), // ИЗМЕНИТЬ
provider: account?.provider,
providerId: account?.providerAccountId
}
})
return true;
} catch (error) {
console.error('Error [SIGNIN]', error)
return false
}
},
async jwt({ token }) {
if (!token.email) {
return token;
}
const findUser = await prisma.user.findFirst({
where: {
email: token.email
}
})
if (findUser) {
token.id = String(findUser.id);
token.email = String(findUser.email);
token.name = String(findUser.name);
token.role = String(findUser.role);
}
return token
},
session({ session, token }) {
if (session?.user) {
session.user.id = token.id,
session.user.role = token.role
}
return session;
}
}
}
export const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }

View File

@ -1,9 +1,9 @@
"use client"
import type { Metadata } from "next"; import type { Metadata } from "next";
import localFont from "next/font/local"; import localFont from "next/font/local";
import "./globals.css"; import "./globals.css";
import { SessionProvider } from "next-auth/react"
export default function RootLayout({ export default function RootLayout({
@ -13,10 +13,10 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body <body>
// className={`${geistSans.variable} ${geistMono.variable} antialiased`} <SessionProvider>
> {children}
{children} </SessionProvider>
</body> </body>
</html> </html>
); );

View File

@ -2,8 +2,14 @@
import React from 'react'; import React from 'react';
import { Search, PlusCircle, Bell, User } from 'lucide-react'; import { Search, PlusCircle, Bell, User } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useSession, signIn } from 'next-auth/react';
import { Button } from './ui/button';
import { ProfileButton } from './shared/profile-button';
import { AuthModal } from './shared/modals/auth-modal/auth-modal';
export default function Header() { export default function Header() {
const [openAuthModal, setOpenAuthModal] = React.useState(false)
return ( return (
<header className="sticky top-0 z-50 bg-white shadow-sm"> <header className="sticky top-0 z-50 bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -39,12 +45,17 @@ export default function Header() {
2 2
</span> </span>
</button> </button>
<Link
<AuthModal open={openAuthModal} onClose={() => setOpenAuthModal(false)} />
<ProfileButton onClickSignIn={() => setOpenAuthModal(true)} />
{/* <Link
href="/profile" href="/profile"
className="p-2 hover:bg-gray-100 rounded-full" className="p-2 hover:bg-gray-100 rounded-full"
> >
<User className="h-6 w-6 text-gray-600" /> <User className="h-6 w-6 text-gray-600" />
</Link> </Link> */}
</div> </div>
</div> </div>
</div> </div>

View File

@ -11,7 +11,7 @@ interface ListingCardProps {
price?: string; price?: string;
location?: string; location?: string;
image?: string; image?: string;
date: string; date?: string;
} }
export default function ListingCard({ id, title, price, location, image, date }: ListingCardProps) { export default function ListingCard({ id, title, price, location, image, date }: ListingCardProps) {

View File

@ -0,0 +1,133 @@
'use client'
import React, { useEffect, useState } from "react";
import { FormProvider, useForm } from 'react-hook-form';
import { formAdtCreateSchema, TFormAdtCreateValues } from "./schemas";
import { zodResolver } from '@hookform/resolvers/zod';
import { Title } from "@/components/shared/title";
import { FormInput } from "@/components/shared/form";
import { Button } from "@/components/ui/button";
import { createAdt } from "@/app/actions";
import { prisma } from "@/prisma/prisma-client";
import { getUserSession } from "@/lib/get-user-session";
import { Category } from "@prisma/client";
interface Props {
onClose?: VoidFunction;
}
export const AdtCreateForm: React.FC<Props> = ({onClose}) => {
const [categories, setCategories] = useState<Category[]>([]);
useEffect(() => {
const fetchCategories = async () => {
const categoriesData = await fetch('/api/category').then(res => res.json());
setCategories(categoriesData);
};
fetchCategories();
}, []);
// const currentUser = await getUserSession();
const form = useForm<TFormAdtCreateValues>({
// resolver: zodResolver(formAdtCreateSchema),
defaultValues: {
title: '',
categories: categories,
price: '0',
description: '',
image: '',
location: '',
}
})
const onSubmit = async (data: TFormAdtCreateValues) => {
try {
await createAdt({
title: data.title,
price: data.price.toString(),
description: data.description,
image: data.image,
location: data.location,
// categories: data.categories,
}, categories)
// if (!resp?.ok) {
// throw Error();
// }
onClose?.()
} catch (error) {
console.error('Error [LOGIN]', error)
}
}
return (
<FormProvider {...form}>
<form className="flex flex-col gap-5" onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex justify-between items-center">
<div className="mr-2">
<Title text="Создать объявление" size='md' className="font-bold" />
<p className="text-gray-400">Заполните все поля для создания объявления</p>
</div>
</div>
<FormInput name='title' label='Заголовок' required />
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">Категории</label>
<select
multiple
{...form.register('categories')}
className="w-full px-4 py-2 rounded-lg border border-gray-200"
>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
<FormInput
name='price'
label='Цена'
type="number"
required
/>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">Описание</label>
<textarea
{...form.register('description')}
className="w-full px-4 py-2 rounded-lg border border-gray-200"
rows={4}
/>
</div>
<FormInput
name='image'
label='Ссылка на изображение'
required
/>
<FormInput
name='location'
label='Местоположение'
required
/>
<Button
loading={form.formState.isSubmitting}
className="h-12 text-base"
type='submit'
>
Создать объявление
</Button>
</form>
</FormProvider>
)
}

View File

@ -0,0 +1,17 @@
import {z} from 'zod'
export const formAdtCreateSchema = z.object({
title: z.string().min(1, {message: 'заголовок обязателен'}),
categories: z.array(z.object({
id: z.number(),
name: z.string()
})).min(1, {message: 'выберите хотя бы одну категорию'}),
price: z.string().min(0, {message: 'цена должна быть положительным числом'}),
description: z.string().min(1, {message: 'описание обязательно'}),
image: z.string().url({message: 'неверный формат URL'}),
location: z.string().min(1, {message: 'местоположение обязательно'}),
// createdAt: z.date().default(new Date()),
// userId: z.string().uuid({message: 'неверный формат UUID'}),
});
export type TFormAdtCreateValues = z.infer<typeof formAdtCreateSchema>

View File

@ -0,0 +1,21 @@
import { cn } from '@/lib/utils';
import { X } from 'lucide-react';
import React from 'react';
interface Props {
className?: string;
onClick?: VoidFunction;
}
export const ClearButton: React.FC<Props> = ({ onClick, className }) => {
return (
<button
onClick={onClick}
className={cn(
'absolute right-4 top-1/2 -translate-y-1/2 opacity-30 hover:opacity-100 cursor-pointer',
className,
)}>
<X className="h-5 w-5" />
</button>
);
};

View File

@ -0,0 +1,10 @@
import { cn } from '@/lib/utils';
import React from 'react';
interface Props {
className?: string;
}
export const Container: React.FC<React.PropsWithChildren<Props>> = ({ className, children }) => {
return <div className={cn('mx-auto max-w-[1280px]', className)}>{children}</div>;
};

View File

@ -0,0 +1,11 @@
import { cn } from '@/lib/utils';
import React from 'react';
interface Props {
text: string;
className?: string;
}
export const ErrorText: React.FC<Props> = ({ text, className }) => {
return <p className={cn('text-red-500 text-sm', className)}>{text}</p>;
};

View File

@ -0,0 +1,48 @@
'use client';
import { useFormContext } from 'react-hook-form';
import { ClearButton } from '../clear-button';
import { ErrorText } from '../error-text';
import { RequiredSymbol } from '../required-symbol';
import { Input } from '@/components/ui/input';
interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
name: string;
label?: string;
required?: boolean;
className?: string;
}
export const FormInput: React.FC<Props> = ({ className, name, label, required, ...props }) => {
const {
register,
formState: { errors },
watch,
setValue,
} = useFormContext();
const value = watch(name);
const errorText = errors[name]?.message as string;
const onClickClear = () => {
setValue(name, '', { shouldValidate: true });
};
return (
<div className={className}>
{label && (
<p className="font-medium mb-2">
{label} {required && <RequiredSymbol />}
</p>
)}
<div className="relative">
<Input className="h-12 text-md" {...register(name)} {...props} />
{value && <ClearButton onClick={onClickClear} />}
</div>
{errorText && <ErrorText text={errorText} className="mt-2" />}
</div>
);
};

View File

@ -0,0 +1,45 @@
'use client';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { Textarea } from '../../ui/textarea';
import { ClearButton } from '../clear-button';
interface Props extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
className?: string;
name: string;
label?: string;
required?: boolean;
}
export const FormTextarea: React.FC<Props> = ({ className, name, label, required, ...props }) => {
const {
register,
formState: { errors },
watch,
setValue,
} = useFormContext();
const value = watch(name);
const errorText = errors[name]?.message as string;
const onClickClear = () => {
setValue(name, '');
};
return (
<div className={className}>
<p className="font-medium mb-2">
{label} {required && <span className="text-red-500">*</span>}
</p>
<div className="relative">
<Textarea className="h-12 text-md" {...register(name)} {...props} />
{value && <ClearButton onClick={onClickClear} />}
</div>
{errorText && <p className="text-red-500 text-sm mt-2">{errorText}</p>}
</div>
);
};

View File

@ -0,0 +1,2 @@
export { FormInput } from './form-input';
export { FormTextarea } from './form-textarea';

View File

@ -0,0 +1,42 @@
import React from 'react';
import { Button } from '../ui/button';
import { ArrowLeft } from 'lucide-react';
import { Title } from './title';
import Link from 'next/link';
import { cn } from '@/lib/utils';
interface Props {
title: string;
text: string;
className?: string;
imageUrl?: string;
}
export const InfoBlock: React.FC<Props> = ({ className, title, text, imageUrl }) => {
return (
<div className={cn(className, 'flex items-center justify-between w-[840px] gap-12')}>
<div className="flex flex-col">
<div className="w-[445px]">
<Title size="lg" text={title} className="font-extrabold" />
<p className="text-gray-400 text-lg">{text}</p>
</div>
<div className="flex gap-5 mt-11">
<Link href="/">
<Button variant="outline" className="gap-2">
<ArrowLeft />
На главную
</Button>
</Link>
<a href="">
<Button variant="outline" className="text-gray-500 border-gray-400 hover:bg-gray-50">
Обновить
</Button>
</a>
</div>
</div>
<img src={imageUrl} alt={title} width={300} />
</div>
);
};

View File

@ -0,0 +1,72 @@
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { signIn } from "next-auth/react";
import React from "react";
import { LoginForm } from "./forms/login-form";
import { RegisterForm } from "./forms/register-form";
interface Props {
open: boolean;
onClose: () => void;
}
export const AuthModal: React.FC<Props> = ({ open, onClose}) => {
const [ type, setType ] = React.useState<'login' | 'register'>('login');
const onSwitchType = () => {
setType(type === 'login' ? 'register' : 'login')
}
const handleClose = () => {
onClose()
}
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="w-[450px] bg-white p-10">
{
type === 'login' ? <LoginForm onClose={handleClose} /> : <RegisterForm onClose={handleClose} />
}
<hr />
<div className="flex gap-2">
<Button
variant='secondary'
onClick={() =>
signIn('github', {
callbackUrl: '/',
redirect: true,
})
}
type="button"
className="gap-2 h-12 p-2 flex-1"
>
<img className="2-6 h-6" src="https://github.githubassets.com/favicons/favicon.svg" />
GitHub
</Button>
<Button
variant='secondary'
onClick={() =>
signIn('google', {
callbackUrl: '/',
redirect: true,
})
}
type="button"
className="gap-2 h-12 p-2 flex-1"
>
<img className="2-6 h-6" src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" />
Google
</Button>
</div>
<Button variant='outline' onClick={onSwitchType} type='button' className="h-12">
{type === 'login' ? 'Регистрация' : 'Войти'}
</Button>
</DialogContent>
</Dialog>
)
}

View File

@ -0,0 +1,63 @@
import React from "react";
import { FormProvider, useForm } from 'react-hook-form';
import { formLoginSchema, TFormLoginValues } from "./schemas";
import { zodResolver } from '@hookform/resolvers/zod';
import { Title } from "@/components/shared/title";
import { FormInput } from "@/components/shared/form";
import { Button } from "@/components/ui/button";
import { signIn } from "next-auth/react";
interface Props {
onClose?: VoidFunction;
}
export const LoginForm: React.FC<Props> = ({onClose}) => {
const form = useForm<TFormLoginValues>({
resolver: zodResolver(formLoginSchema),
defaultValues: {
email: '',
password: '',
}
})
const onSubmit = async (data: TFormLoginValues) => {
try {
const resp = await signIn('credentials', {
...data,
redirect: false
})
if (!resp?.ok) {
throw Error();
}
onClose?.()
} catch (error) {
console.error('Error [LOGIN]', error)
}
}
return (
<FormProvider {...form}>
<form className="flex flex-col gap-5" onSubmit={form.handleSubmit(onSubmit)}>
<div className="fles justify-between items-center">
<div className="mr-2">
<Title text="Вход в аккаунт" size='md' className="font-bold" />
<p className="text-gray-400">Введите свою почту, чтобы войти</p>
</div>
{/* <img src="..." /> */}
</div>
<FormInput name='email' label='E-Mail' required />
<FormInput name='password' label='Password' required />
<Button loading={form.formState.isSubmitting} className="h-12 text-base" type='submit' >
{
'Войти'
}
</Button>
</form>
</FormProvider>
)
}

View File

@ -0,0 +1,67 @@
import React from "react";
import { FormProvider, useForm } from 'react-hook-form';
import { formLoginSchema, formRegisterSchema, TFormLoginValues, TFormRegisterValues } from "./schemas";
import { zodResolver } from '@hookform/resolvers/zod';
import { Title } from "@/components/shared/title";
import { FormInput } from "@/components/shared/form";
import { Button } from "@/components/ui/button";
import { signIn } from "next-auth/react";
import { registerUser } from "@/app/actions";
interface Props {
onClose?: VoidFunction;
}
export const RegisterForm: React.FC<Props> = ({onClose}) => {
const form = useForm<TFormRegisterValues>({
resolver: zodResolver(formRegisterSchema),
defaultValues: {
name: '',
email: '',
password: '',
confirmPassword: '',
}
})
const onSubmit = async (data: TFormRegisterValues) => {
try {
await registerUser({
email: data.email,
name: data.name,
password: data.password,
})
// if (!resp?.ok) {
// throw Error();
// }
onClose?.()
} catch (error) {
console.error('Error [LOGIN]', error)
}
}
return (
<FormProvider {...form}>
<form className="flex flex-col gap-5" onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="fles justify-between items-center">
<div className="mr-2">
<Title text="Вход в аккаунт" size='md' className="font-bold" />
<p className="text-gray-400">Введите свою почту, чтобы войти</p>
</div>
</div> */}
<FormInput name='name' label='Name' required />
<FormInput name='email' label='E-Mail' required />
<FormInput name='password' label='Password' required />
<FormInput name='confirmPassword' label='ConfirmPassword' required />
<Button loading={form.formState.isSubmitting} className="h-12 text-base" type='submit' >
{
'Sign Up'
}
</Button>
</form>
</FormProvider>
)
}

View File

@ -0,0 +1,21 @@
import {z} from 'zod'
export const passwordSchema = z.string().min(6, {message: 'пароль должен содержать не менее 6 символов'})
export const formLoginSchema = z.object({
email: z.string().email({message: 'введите корректную почту'}),
password: passwordSchema
})
export const formRegisterSchema = formLoginSchema.merge(
z.object({
name: z.string().min(2, {message: 'введите имя и фамилию'}),
confirmPassword: passwordSchema,
})
).refine(data => data.password === data.confirmPassword, {
message: 'Пароли не совпадают',
path: ['confirmPassword']
});
export type TFormLoginValues = z.infer<typeof formLoginSchema>
export type TFormRegisterValues = z.infer<typeof formRegisterSchema>

View File

@ -0,0 +1,36 @@
import { useSession } from "next-auth/react";
import React from "react";
import { Button } from "../ui/button";
import { CircleUser, User } from "lucide-react";
import Link from "next/link";
interface Props {
onClickSignIn?: () => void;
classname?: string;
}
export const ProfileButton: React.FC<Props> = ({ onClickSignIn, classname}) => {
const { data: session } = useSession();
return (
<div className={classname}>
{
!session ? <Button onClick={onClickSignIn} variant='outline' className="relative p-2 rounded-full">
<div className='flex items-center gap-2'>
<User className="h-6 w-6 text-gray-600" />
SignIn
</div>
</Button> : <Link href="/profile">
<Button variant='secondary' className="flex items-center gap-2">
<CircleUser size={18} />
</Button>
</Link>
}
</div>
)
}

View File

@ -0,0 +1,84 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";
import { FormProvider, useForm } from "react-hook-form";
import { formRegisterSchema, TFormRegisterValues } from "./modals/auth-modal/forms/schemas";
import { User } from "@prisma/client";
import { signOut } from "next-auth/react";
import { Container } from "./container";
import { Title } from "./title";
import { FormInput } from "./form/form-input";
import { Button } from "../ui/button";
import { updateUserInfo } from "@/app/actions";
interface Props {
data: User
}
export const ProfileForm: React.FC<Props> = ({ data }) => {
const form = useForm({
resolver: zodResolver(formRegisterSchema),
defaultValues: {
name: data.name,
email: data.email,
password: '',
confirmPassword: '',
},
});
const onSubmit = async (data: TFormRegisterValues) => {
try {
await updateUserInfo({
email: data.email,
name: data.name,
password: data.password,
});
// toast.error('Данные обновлены 📝', {
// icon: '✅',
// });
} catch (error) {
// return toast.error('Ошибка при обновлении данных', {
// icon: '❌',
// });
console.log('error update', error)
}
};
const onClickSignOut = () => {
signOut({
callbackUrl: '/',
});
};
return (
<Container className="my-10">
<Title text="Личные данные" size='md' className="font-bold" />
<FormProvider {...form}>
<form className="flex flex-col gap-5 w-96 mt-10" onSubmit={form.handleSubmit(onSubmit)}>
<FormInput name="email" label="E-Mail" required />
<FormInput name="name" label="Полное имя" required />
<FormInput type="password" name="password" label="Новый пароль" required />
<FormInput type="password" name="confirmPassword" label="Повторите пароль" required />
<Button disabled={form.formState.isSubmitting} className="text-base mt-10" type="submit">
Сохранить
</Button>
<Button
onClick={onClickSignOut}
variant="secondary"
disabled={form.formState.isSubmitting}
className="text-base"
type="button">
Выйти
</Button>
</form>
</FormProvider>
</Container>
)
}

View File

@ -0,0 +1,5 @@
import React from 'react';
export const RequiredSymbol: React.FC = () => {
return <span className="text-red-500">*</span>;
};

View File

@ -0,0 +1,36 @@
import clsx from 'clsx';
import React from 'react';
type TitleSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
interface Props {
size?: TitleSize;
className?: string;
text: string;
}
export const Title: React.FC<Props> = ({ text, size = 'sm', className }) => {
const mapTagBySize = {
xs: 'h5',
sm: 'h4',
md: 'h3',
lg: 'h2',
xl: 'h1',
'2xl': 'h1',
} as const;
const mapClassNameBySize = {
xs: 'text-[16px]',
sm: 'text-[22px]',
md: 'text-[26px]',
lg: 'text-[32px]',
xl: 'text-[40px]',
'2xl': 'text-[48px]',
} as const;
return React.createElement(
mapTagBySize[size],
{ className: clsx(mapClassNameBySize[size], className) },
text,
);
};

View File

@ -3,6 +3,7 @@ import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Loader } from "lucide-react"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-neutral-300", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-neutral-300",
@ -37,18 +38,21 @@ const buttonVariants = cva(
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { VariantProps<typeof buttonVariants> {
asChild?: boolean asChild?: boolean,
loading?: boolean,
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, children, disabled, loading, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button"
return ( return (
<Comp <Comp
disabled={disabled || loading}
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref} ref={ref}
{...props} {...props}>
/> {!loading ? children : <Loader className="w-5 h-5 animate-spin" />}
</Comp>
) )
} }
) )

View File

@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-200 border-neutral-900 shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 dark:border-neutral-800 dark:border-neutral-50 dark:focus-visible:ring-neutral-300 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

122
components/ui/dialog.tsx Normal file
View File

@ -0,0 +1,122 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

22
components/ui/input.tsx Normal file
View File

@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-neutral-200 bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-neutral-950 placeholder:text-neutral-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:border-neutral-800 dark:file:text-neutral-50 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-300",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

140
components/ui/sheet.tsx Normal file
View File

@ -0,0 +1,140 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-neutral-950 dark:text-neutral-50", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@ -0,0 +1,15 @@
import { cn } from "@/lib/utils"
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-neutral-900/10 dark:bg-neutral-50/10", className)}
{...props}
/>
)
}
export { Skeleton }

View File

@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-neutral-200 bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-neutral-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:border-neutral-800 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-300",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
export { Textarea }

View File

@ -1,74 +0,0 @@
export const adts = [
{
id: '1',
title: "2020 Tesla Model 3 Long Range",
price: "$41,999",
location: "San Francisco, CA",
image: "https://images.unsplash.com/photo-1560958089-b8a1929cea89?auto=format&fit=crop&w=800",
date: "Posted 2 hours ago"
},
{
id: '2',
title: "Modern Studio Apartment in Downtown",
price: "$2,200/mo",
location: "Seattle, WA",
image: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=800",
date: "Posted 5 hours ago"
},
{
id: '3',
title: "MacBook Pro M2 16-inch",
price: "$2,499",
location: "Austin, TX",
image: "https://images.unsplash.com/photo-1517336714731-489689fd1ca8?auto=format&fit=crop&w=800",
date: "Posted 1 day ago"
},
{
id: '4',
title: "Vintage Leather Jacket",
price: "$299",
location: "Portland, OR",
image: "https://images.unsplash.com/photo-1551028719-00167b16eac5?auto=format&fit=crop&w=800",
date: "Posted 2 days ago"
},
{
id: '5',
title: "Professional DSLR Camera Kit",
price: "$1,899",
location: "New York, NY",
image: "https://images.unsplash.com/photo-1516035069371-29a1b244cc32?auto=format&fit=crop&w=800",
date: "Posted 3 days ago"
},
{
id: '6',
title: "Handcrafted Wooden Dining Table",
price: "$899",
location: "Denver, CO",
image: "https://images.unsplash.com/photo-1577140917170-285929fb55b7?auto=format&fit=crop&w=800",
date: "Posted 4 days ago"
},
{
id: '7',
title: "Smart Bluetooth Speaker",
price: "$99",
location: "Los Angeles, CA",
image: "https://images.unsplash.com/photo-1517336714731-489689fd1ca8?auto=format&fit=crop&w=800",
date: "Posted 5 days ago"
},
{
id: '8',
title: "Luxury Designer Watch",
price: "$1,299",
location: "Chicago, IL",
image: "https://images.unsplash.com/photo-1551028719-00167b16eac5?auto=format&fit=crop&w=800",
date: "Posted 6 days ago"
},
{
id: '9',
title: "Gaming Laptop",
price: "$1,499",
location: "Miami, FL",
image: "https://images.unsplash.com/photo-1516035069371-29a1b244cc32?auto=format&fit=crop&w=800",
date: "Posted 7 days ago"
}
];

8
lib/get-user-session.ts Normal file
View File

@ -0,0 +1,8 @@
import { authOptions } from "@/app/api/auth/[...nextauth]/route"
import { getServerSession } from "next-auth"
export const getUserSession = async () => {
const session = await getServerSession(authOptions);
return session?.user ?? null
}

702
package-lock.json generated
View File

@ -8,7 +8,10 @@
"name": "bazar", "name": "bazar",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.9.1",
"@prisma/client": "^5.22.0", "@prisma/client": "^5.22.0",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"axios": "^1.7.7", "axios": "^1.7.7",
@ -17,11 +20,14 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "15.0.3", "next": "15.0.3",
"next-auth": "^4.24.10",
"prisma": "^5.22.0", "prisma": "^5.22.0",
"react": "19.0.0-rc-66855b96-20241106", "react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106",
"react-hook-form": "^7.53.2",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
@ -46,6 +52,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
@ -119,6 +137,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@hookform/resolvers": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz",
"integrity": "sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==",
"license": "MIT",
"peerDependencies": {
"react-hook-form": "^7.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@ -819,6 +846,15 @@
"node": ">=12.4.0" "node": ">=12.4.0"
} }
}, },
"node_modules/@panva/hkdf": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -892,6 +928,42 @@
"@prisma/debug": "5.22.0" "@prisma/debug": "5.22.0"
} }
}, },
"node_modules/@radix-ui/primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-checkbox": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz",
"integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-presence": "1.1.1",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-use-size": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": { "node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
@ -907,6 +979,326 @@
} }
} }
}, },
"node_modules/@radix-ui/react-context": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz",
"integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.1",
"@radix-ui/react-focus-guards": "1.1.1",
"@radix-ui/react-focus-scope": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-portal": "1.1.2",
"@radix-ui/react-presence": "1.1.1",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.6.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
"integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==",
"license": "MIT",
"dependencies": {
"react-remove-scroll-bar": "^2.3.6",
"react-style-singleton": "^2.2.1",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.0",
"use-sidecar": "^1.1.2"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/react-remove-scroll-bar": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz",
"integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==",
"license": "MIT",
"dependencies": {
"react-style-singleton": "^2.2.1",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/react-style-singleton": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
"integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
"license": "MIT",
"dependencies": {
"get-nonce": "^1.0.0",
"invariant": "^2.2.4",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/use-callback-ref": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
"integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/use-sidecar": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
"integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
"license": "MIT",
"dependencies": {
"detect-node-es": "^1.1.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz",
"integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-escape-keydown": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
"integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-scope": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz",
"integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-id": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
"integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-portal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz",
"integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-presence": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
"integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": { "node_modules/@radix-ui/react-slot": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
@ -925,6 +1317,105 @@
} }
} }
}, },
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
"integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
"integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-previous": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
"integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-size": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
"integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@rtsao/scc": { "node_modules/@rtsao/scc": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@ -1001,7 +1492,7 @@
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"
@ -1415,6 +1906,18 @@
"dev": true, "dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/aria-hidden": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
"integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/aria-query": { "node_modules/aria-query": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@ -1950,6 +2453,15 @@
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -2128,6 +2640,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -3171,6 +3689,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/get-nonce": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/get-symbol-description": { "node_modules/get-symbol-description": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
@ -3470,6 +3997,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@ -3933,11 +4469,19 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/jose": {
"version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
@ -4089,7 +4633,6 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0" "js-tokens": "^3.0.0 || ^4.0.0"
@ -4344,6 +4887,38 @@
} }
} }
}, },
"node_modules/next-auth": {
"version": "4.24.10",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.10.tgz",
"integrity": "sha512-8NGqiRO1GXBcVfV8tbbGcUgQkAGsX4GRzzXXea4lDikAsJtD5KiEY34bfhUOjHLvr6rT6afpcxw2H8EZqOV6aQ==",
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@panva/hkdf": "^1.0.2",
"cookie": "^0.7.0",
"jose": "^4.15.5",
"oauth": "^0.9.15",
"openid-client": "^5.4.0",
"preact": "^10.6.3",
"preact-render-to-string": "^5.1.19",
"uuid": "^8.3.2"
},
"peerDependencies": {
"@auth/core": "0.34.2",
"next": "^12.2.5 || ^13 || ^14 || ^15",
"nodemailer": "^6.6.5",
"react": "^17.0.2 || ^18",
"react-dom": "^17.0.2 || ^18"
},
"peerDependenciesMeta": {
"@auth/core": {
"optional": true
},
"nodemailer": {
"optional": true
}
}
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -4435,6 +5010,12 @@
"set-blocking": "^2.0.0" "set-blocking": "^2.0.0"
} }
}, },
"node_modules/oauth": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==",
"license": "MIT"
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -4562,6 +5143,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==",
"license": "MIT",
"engines": {
"node": "^10.13.0 || >=12.0.0"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -4571,6 +5161,42 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"node_modules/openid-client": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.0.tgz",
"integrity": "sha512-4GCCGZt1i2kTHpwvaC/sCpTpQqDnBzDzuJcJMbH+y1Q5qI8U8RBvoSh28svarXszZHR5BAMXbJPX1PGPRE3VOA==",
"license": "MIT",
"dependencies": {
"jose": "^4.15.9",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
},
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/openid-client/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/openid-client/node_modules/object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@ -4891,6 +5517,28 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/preact": {
"version": "10.24.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/preact-render-to-string": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
"integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
"license": "MIT",
"dependencies": {
"pretty-format": "^3.8.0"
},
"peerDependencies": {
"preact": ">=10"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -4901,6 +5549,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
"license": "MIT"
},
"node_modules/prisma": { "node_modules/prisma": {
"version": "5.22.0", "version": "5.22.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
@ -4989,6 +5643,22 @@
"react": "19.0.0-rc-66855b96-20241106" "react": "19.0.0-rc-66855b96-20241106"
} }
}, },
"node_modules/react-hook-form": {
"version": "7.53.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.2.tgz",
"integrity": "sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -5053,6 +5723,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/regexp.prototype.flags": { "node_modules/regexp.prototype.flags": {
"version": "1.5.3", "version": "1.5.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
@ -6092,6 +6768,15 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -6375,6 +7060,15 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }

View File

@ -15,7 +15,10 @@
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.9.1",
"@prisma/client": "^5.22.0", "@prisma/client": "^5.22.0",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"axios": "^1.7.7", "axios": "^1.7.7",
@ -24,11 +27,14 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "15.0.3", "next": "15.0.3",
"next-auth": "^4.24.10",
"prisma": "^5.22.0", "prisma": "^5.22.0",
"react": "19.0.0-rc-66855b96-20241106", "react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106",
"react-hook-form": "^7.53.2",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",

View File

@ -15,6 +15,9 @@ model User {
name String? name String?
password String password String
provider String?
providerId String?
role Role @default(USER) role Role @default(USER)
adts Adt[] adts Adt[]
@ -35,8 +38,8 @@ model Adt {
image String? image String?
status Status @default(CHECKING) status Status @default(CHECKING)
user User @relation(fields: [userId], references: [id]) user User? @relation(fields: [userId], references: [id])
userId Int userId Int?
categories Category[] categories Category[]

View File

@ -12,6 +12,7 @@ async function up() {
password: hashSync("123456", 10), password: hashSync("123456", 10),
// verified: new Date(), // verified: new Date(),
role: "USER", role: "USER",
provider: 'credentials'
}, },
{ {
name: "user2", name: "user2",
@ -19,6 +20,7 @@ async function up() {
password: hashSync("123456", 10), password: hashSync("123456", 10),
// verified: new Date(), // verified: new Date(),
role: "USER", role: "USER",
provider: 'credentials'
}, },
{ {
name: "admin", name: "admin",
@ -26,6 +28,7 @@ async function up() {
password: hashSync("123456", 10), password: hashSync("123456", 10),
// verified: new Date(), // verified: new Date(),
role: "ADMIN", role: "ADMIN",
provider: 'credentials'
} }
] ]
}); });

View File

@ -22,6 +22,6 @@
"@/*": ["./*"] "@/*": ["./*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "@types/*.d.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }