перед обновление для создания обьявления
This commit is contained in:
parent
7b40ce2c1a
commit
2a90b6f2b0
28
@types/next-auth.d.ts
vendored
Normal file
28
@types/next-auth.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
// import { useParams } from 'next/navigation';
|
||||
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 Header from '@/components/Header';
|
||||
import { notFound } from 'next/navigation';
|
||||
@ -27,12 +27,12 @@ export default async function AdtPage({params: { id } }: { params: { id: string
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<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="lg:col-span-2">
|
||||
<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="flex justify-between items-start mb-4">
|
||||
<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"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="font-semibold">{user.name}</h3>
|
||||
<p className="text-sm text-gray-500">Member {String(user.createdAt)}</p>
|
||||
<h3 className="font-semibold">{user?.name}</h3>
|
||||
<p className="text-sm text-gray-500">Member {String(user?.createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,105 +1,112 @@
|
||||
// 'use client'
|
||||
|
||||
import React from 'react';
|
||||
import { ImagePlus, X } from 'lucide-react';
|
||||
import { AdtCreateForm } from '@/components/shared/adt-create/adt-create-form';
|
||||
|
||||
export default function CreateListing() {
|
||||
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">
|
||||
<h1 className="text-2xl font-semibold mb-6">Create New Listing</h1>
|
||||
|
||||
<AdtCreateForm />
|
||||
|
||||
|
||||
// <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">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
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"
|
||||
placeholder="Enter listing title"
|
||||
/>
|
||||
</div>
|
||||
// <form className="space-y-6">
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
// Title
|
||||
// </label>
|
||||
// <input
|
||||
// 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"
|
||||
// placeholder="Enter listing title"
|
||||
// />
|
||||
// </div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Category
|
||||
</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">
|
||||
<option>Select a category</option>
|
||||
<option>Vehicles</option>
|
||||
<option>Real Estate</option>
|
||||
<option>Electronics</option>
|
||||
<option>Fashion</option>
|
||||
<option>Jobs</option>
|
||||
<option>Sports</option>
|
||||
<option>Art</option>
|
||||
<option>Books</option>
|
||||
</select>
|
||||
</div>
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
// Category
|
||||
// </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">
|
||||
// <option>Select a category</option>
|
||||
// <option>Vehicles</option>
|
||||
// <option>Real Estate</option>
|
||||
// <option>Electronics</option>
|
||||
// <option>Fashion</option>
|
||||
// <option>Jobs</option>
|
||||
// <option>Sports</option>
|
||||
// <option>Art</option>
|
||||
// <option>Books</option>
|
||||
// </select>
|
||||
// </div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Price
|
||||
</label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-4 top-2 text-gray-500">$</span>
|
||||
<input
|
||||
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"
|
||||
placeholder="0.00"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
// Price
|
||||
// </label>
|
||||
// <div className="relative">
|
||||
// <span className="absolute left-4 top-2 text-gray-500">$</span>
|
||||
// <input
|
||||
// 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"
|
||||
// placeholder="0.00"
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
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"
|
||||
placeholder="Describe your item..."
|
||||
/>
|
||||
</div>
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
// Description
|
||||
// </label>
|
||||
// <textarea
|
||||
// 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"
|
||||
// placeholder="Describe your item..."
|
||||
// />
|
||||
// </div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Photos
|
||||
</label>
|
||||
<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">
|
||||
<ImagePlus className="h-8 w-8 text-gray-400" />
|
||||
<span className="mt-2 text-sm text-gray-500">Add Photo</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
// Photos
|
||||
// </label>
|
||||
// <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">
|
||||
// <ImagePlus className="h-8 w-8 text-gray-400" />
|
||||
// <span className="mt-2 text-sm text-gray-500">Add Photo</span>
|
||||
// </button>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Location
|
||||
</label>
|
||||
<input
|
||||
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"
|
||||
placeholder="Enter location"
|
||||
/>
|
||||
</div>
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
// Location
|
||||
// </label>
|
||||
// <input
|
||||
// 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"
|
||||
// placeholder="Enter location"
|
||||
// />
|
||||
// </div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
className="flex-1 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
|
||||
>
|
||||
Create Listing
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-4 py-2 rounded-lg border border-gray-200 hover:bg-gray-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
// <div className="flex gap-3">
|
||||
// <button
|
||||
// type="submit"
|
||||
// className="flex-1 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
|
||||
// >
|
||||
// Create Listing
|
||||
// </button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="px-4 py-2 rounded-lg border border-gray-200 hover:bg-gray-50"
|
||||
// >
|
||||
// Cancel
|
||||
// </button>
|
||||
// </div>
|
||||
// </form>
|
||||
// </div>
|
||||
// </main>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import "../globals.css";
|
||||
import { Suspense } from "react";
|
||||
import Header from "@/components/Header";
|
||||
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@ -14,12 +16,11 @@ export default function HomeLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
// className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
<main className="min-h-screen">
|
||||
<Suspense>
|
||||
<Header />
|
||||
</Suspense>
|
||||
{children}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
14
app/(root)/not-auth/page.tsx
Normal file
14
app/(root)/not-auth/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -13,7 +13,6 @@ export default async function Home() {
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<Header />
|
||||
<Categories />
|
||||
<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">
|
||||
|
||||
@ -1,13 +1,33 @@
|
||||
import React from 'react';
|
||||
import React, { use } from 'react';
|
||||
import { Settings, Package, Heart, Bell } from 'lucide-react';
|
||||
import ListingCard from '@/components/ListingCard';
|
||||
import { adts } from '@/data/adt';
|
||||
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 (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<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">
|
||||
@ -21,13 +41,13 @@ export default function Profile() {
|
||||
<div className="pt-16 pb-8 px-8">
|
||||
<div className="flex justify-between items-start">
|
||||
<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>
|
||||
</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" />
|
||||
<span>Edit Profile</span>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t">
|
||||
@ -55,8 +75,8 @@ export default function Profile() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{adts.slice(0, 3).map((adt) => (
|
||||
<ListingCard key={adt.id} {...adt} />
|
||||
{user?.adts.map((adt) => (
|
||||
<ListingCard key={adt.id} id={String(adt.id)} image={adt.image} {...adt}/>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
33
app/(root)/profile/settings/page.tsx
Normal file
33
app/(root)/profile/settings/page.tsx
Normal 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
97
app/actions.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
153
app/api/auth/[...nextauth]/route.ts
Normal file
153
app/api/auth/[...nextauth]/route.ts
Normal 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 }
|
||||
@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import "./globals.css";
|
||||
|
||||
|
||||
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
|
||||
|
||||
export default function RootLayout({
|
||||
@ -13,10 +13,10 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
// className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<body>
|
||||
<SessionProvider>
|
||||
{children}
|
||||
</SessionProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@ -2,8 +2,14 @@
|
||||
import React from 'react';
|
||||
import { Search, PlusCircle, Bell, User } from 'lucide-react';
|
||||
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() {
|
||||
const [openAuthModal, setOpenAuthModal] = React.useState(false)
|
||||
|
||||
return (
|
||||
<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">
|
||||
@ -39,12 +45,17 @@ export default function Header() {
|
||||
2
|
||||
</span>
|
||||
</button>
|
||||
<Link
|
||||
|
||||
<AuthModal open={openAuthModal} onClose={() => setOpenAuthModal(false)} />
|
||||
|
||||
<ProfileButton onClickSignIn={() => setOpenAuthModal(true)} />
|
||||
|
||||
{/* <Link
|
||||
href="/profile"
|
||||
className="p-2 hover:bg-gray-100 rounded-full"
|
||||
>
|
||||
<User className="h-6 w-6 text-gray-600" />
|
||||
</Link>
|
||||
</Link> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,7 +11,7 @@ interface ListingCardProps {
|
||||
price?: string;
|
||||
location?: string;
|
||||
image?: string;
|
||||
date: string;
|
||||
date?: string;
|
||||
}
|
||||
|
||||
export default function ListingCard({ id, title, price, location, image, date }: ListingCardProps) {
|
||||
|
||||
133
components/shared/adt-create/adt-create-form.tsx
Normal file
133
components/shared/adt-create/adt-create-form.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
17
components/shared/adt-create/schemas.ts
Normal file
17
components/shared/adt-create/schemas.ts
Normal 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>
|
||||
21
components/shared/clear-button.tsx
Normal file
21
components/shared/clear-button.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
10
components/shared/container.tsx
Normal file
10
components/shared/container.tsx
Normal 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>;
|
||||
};
|
||||
11
components/shared/error-text.tsx
Normal file
11
components/shared/error-text.tsx
Normal 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>;
|
||||
};
|
||||
48
components/shared/form/form-input.tsx
Normal file
48
components/shared/form/form-input.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
45
components/shared/form/form-textarea.tsx
Normal file
45
components/shared/form/form-textarea.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
2
components/shared/form/index.ts
Normal file
2
components/shared/form/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { FormInput } from './form-input';
|
||||
export { FormTextarea } from './form-textarea';
|
||||
42
components/shared/info-block.tsx
Normal file
42
components/shared/info-block.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
72
components/shared/modals/auth-modal/auth-modal.tsx
Normal file
72
components/shared/modals/auth-modal/auth-modal.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
63
components/shared/modals/auth-modal/forms/login-form.tsx
Normal file
63
components/shared/modals/auth-modal/forms/login-form.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
67
components/shared/modals/auth-modal/forms/register-form.tsx
Normal file
67
components/shared/modals/auth-modal/forms/register-form.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
21
components/shared/modals/auth-modal/forms/schemas.ts
Normal file
21
components/shared/modals/auth-modal/forms/schemas.ts
Normal 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>
|
||||
36
components/shared/profile-button.tsx
Normal file
36
components/shared/profile-button.tsx
Normal 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>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
84
components/shared/profile-form.tsx
Normal file
84
components/shared/profile-form.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
5
components/shared/required-symbol.tsx
Normal file
5
components/shared/required-symbol.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export const RequiredSymbol: React.FC = () => {
|
||||
return <span className="text-red-500">*</span>;
|
||||
};
|
||||
36
components/shared/title.tsx
Normal file
36
components/shared/title.tsx
Normal 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,
|
||||
);
|
||||
};
|
||||
@ -3,6 +3,7 @@ import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Loader } from "lucide-react"
|
||||
|
||||
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",
|
||||
@ -37,18 +38,21 @@ const buttonVariants = cva(
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
asChild?: boolean,
|
||||
loading?: boolean,
|
||||
}
|
||||
|
||||
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"
|
||||
return (
|
||||
<Comp
|
||||
disabled={disabled || loading}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
{...props}>
|
||||
{!loading ? children : <Loader className="w-5 h-5 animate-spin" />}
|
||||
</Comp>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
30
components/ui/checkbox.tsx
Normal file
30
components/ui/checkbox.tsx
Normal 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
122
components/ui/dialog.tsx
Normal 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
22
components/ui/input.tsx
Normal 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
140
components/ui/sheet.tsx
Normal 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,
|
||||
}
|
||||
15
components/ui/skeleton.tsx
Normal file
15
components/ui/skeleton.tsx
Normal 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 }
|
||||
22
components/ui/textarea.tsx
Normal file
22
components/ui/textarea.tsx
Normal 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 }
|
||||
74
data/adt.ts
74
data/adt.ts
@ -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
8
lib/get-user-session.ts
Normal 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
702
package-lock.json
generated
@ -8,7 +8,10 @@
|
||||
"name": "bazar",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@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",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"axios": "^1.7.7",
|
||||
@ -17,11 +20,14 @@
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "15.0.3",
|
||||
"next-auth": "^4.24.10",
|
||||
"prisma": "^5.22.0",
|
||||
"react": "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",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
@ -46,6 +52,18 @@
|
||||
"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": {
|
||||
"version": "1.3.1",
|
||||
"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_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": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
@ -819,6 +846,15 @@
|
||||
"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": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@ -892,6 +928,42 @@
|
||||
"@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": {
|
||||
"version": "1.1.0",
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||
@ -1001,7 +1492,7 @@
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
@ -1415,6 +1906,18 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
@ -1950,6 +2453,15 @@
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||
"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": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -2128,6 +2640,12 @@
|
||||
"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": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@ -3171,6 +3689,15 @@
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
|
||||
@ -3470,6 +3997,15 @@
|
||||
"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": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
|
||||
@ -3933,11 +4469,19 @@
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@ -4089,7 +4633,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"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": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
@ -4435,6 +5010,12 @@
|
||||
"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": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@ -4562,6 +5143,15 @@
|
||||
"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": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@ -4571,6 +5161,42 @@
|
||||
"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": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@ -4891,6 +5517,28 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"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": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@ -4901,6 +5549,12 @@
|
||||
"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": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
||||
@ -4989,6 +5643,22 @@
|
||||
"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": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@ -5053,6 +5723,12 @@
|
||||
"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": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
|
||||
@ -6092,6 +6768,15 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"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": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
@ -6375,6 +7060,15 @@
|
||||
"funding": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,10 @@
|
||||
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@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",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"axios": "^1.7.7",
|
||||
@ -24,11 +27,14 @@
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "15.0.3",
|
||||
"next-auth": "^4.24.10",
|
||||
"prisma": "^5.22.0",
|
||||
"react": "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",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
|
||||
@ -15,6 +15,9 @@ model User {
|
||||
name String?
|
||||
password String
|
||||
|
||||
provider String?
|
||||
providerId String?
|
||||
|
||||
role Role @default(USER)
|
||||
|
||||
adts Adt[]
|
||||
@ -35,8 +38,8 @@ model Adt {
|
||||
image String?
|
||||
status Status @default(CHECKING)
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
|
||||
categories Category[]
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ async function up() {
|
||||
password: hashSync("123456", 10),
|
||||
// verified: new Date(),
|
||||
role: "USER",
|
||||
provider: 'credentials'
|
||||
},
|
||||
{
|
||||
name: "user2",
|
||||
@ -19,6 +20,7 @@ async function up() {
|
||||
password: hashSync("123456", 10),
|
||||
// verified: new Date(),
|
||||
role: "USER",
|
||||
provider: 'credentials'
|
||||
},
|
||||
{
|
||||
name: "admin",
|
||||
@ -26,6 +28,7 @@ async function up() {
|
||||
password: hashSync("123456", 10),
|
||||
// verified: new Date(),
|
||||
role: "ADMIN",
|
||||
provider: 'credentials'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@ -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"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user