toast, en, show number etc

This commit is contained in:
Zikil 2024-11-23 20:43:10 +07:00
parent d653c15c90
commit 53920d41ef
17 changed files with 461 additions and 105 deletions

View File

@ -6,13 +6,15 @@ import { MapPin, Calendar, Phone, MessageCircle, Share2, Flag, Heart } from 'luc
import { prisma } from '@/prisma/prisma-client'; import { prisma } from '@/prisma/prisma-client';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { ShowNumberModal } from '@/components/shared/modals/show-number'; import { ShowNumberModal } from '@/components/shared/modals/show-number';
import { getUserSession } from '@/lib/get-user-session';
type Params = Promise<{ id: string }> type Params = Promise<{ id: string }>
export default async function AdtPage(props: { params: Params }) { export default async function AdtPage(props: { params: Params }) {
// const [ openShowNumberModal, setOpenShowNumberModal ] = React.useState(false)
const params = await props.params; const params = await props.params;
const session = await getUserSession();
const adt = await prisma.adt.findFirst({ const adt = await prisma.adt.findFirst({
where: { where: {
id: Number(params.id), id: Number(params.id),
@ -33,7 +35,6 @@ export default async function AdtPage(props: { params: Params }) {
return ( return (
<> <>
{/* <ShowNumberModal open={openShowNumberModal} onClose={() => setOpenShowNumberModal(false)} /> */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
@ -91,13 +92,14 @@ export default async function AdtPage(props: { params: Params }) {
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<button <ShowNumberModal phoneNumber={String(adt.user?.email)} session={session} />
{/* <button
// onClick={() => setOpenShowNumberModal(true)} // onClick={() => setOpenShowNumberModal(true)}
className="w-full flex items-center justify-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700" className="w-full flex items-center justify-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
> >
<Phone className="h-5 w-5" /> <Phone className="h-5 w-5" />
<span>Show Phone Number</span> <span>Show Phone Number</span>
</button> </button> */}
<button className="w-full flex items-center justify-center gap-2 bg-white border border-indigo-600 text-indigo-600 px-4 py-2 rounded-lg hover:bg-indigo-50"> <button className="w-full flex items-center justify-center gap-2 bg-white border border-indigo-600 text-indigo-600 px-4 py-2 rounded-lg hover:bg-indigo-50">
<MessageCircle className="h-5 w-5" /> <MessageCircle className="h-5 w-5" />
<span>Send Message (In development)</span> <span>Send Message (In development)</span>

View File

@ -2,47 +2,23 @@
import Categories from "@/components/Categories"; import Categories from "@/components/Categories";
import ListingCard from "@/components/ListingCard"; import ListingCard from "@/components/ListingCard";
import { BlockAdts } from "@/components/shared/block-adts";
import { getUserSession } from "@/lib/get-user-session"; import { getUserSession } from "@/lib/get-user-session";
import { prisma } from "@/prisma/prisma-client"; import { prisma } from "@/prisma/prisma-client";
import toast from "react-hot-toast";
export default async function Home() { export default async function Home() {
const adts = await prisma.adt.findMany() const adts = await prisma.adt.findMany()
const session = await getUserSession() // const session = await getUserSession()
// // console.log(user)
// if (!session) { // console.log("session",session)
// return redirect('/not-auth')
// }
// const user = await prisma.user.findFirst({
// where: {
// id: Number(session?.id)
// }
// })
// console.log(user)
console.log("session",session)
return ( return (
<> <>
<div className="min-h-screen bg-gray-100"> <div className="min-h-screen bg-gray-100">
<Categories /> <Categories />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex justify-between items-center mb-6"> <BlockAdts />
<h2 className="text-xl font-semibold">Featured Listings</h2>
<div className="flex gap-2">
<select className="px-4 py-2 rounded-lg border border-gray-200 bg-white">
<option>Most Recent</option>
<option>Price: Low to High</option>
<option>Price: High to Low</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{adts.map((adt) => (
<ListingCard key={adt.id} title={adt.title} image={String(adt.image)} price={String(adt.price)} location={String(adt.location)} date={String(adt.createdAt)} id={String(adt.id)} />
))}
</div>
</main> </main>
</div> </div>
</> </>

View File

@ -4,6 +4,7 @@ import type { Metadata } from "next";
import localFont from "next/font/local"; import localFont from "next/font/local";
import "./globals.css"; import "./globals.css";
import { SessionProvider } from "next-auth/react" import { SessionProvider } from "next-auth/react"
import { Toaster } from 'react-hot-toast';
export default function RootLayout({ export default function RootLayout({
@ -16,6 +17,7 @@ export default function RootLayout({
<body> <body>
<SessionProvider> <SessionProvider>
{children} {children}
<Toaster />
</SessionProvider> </SessionProvider>
</body> </body>
</html> </html>

View File

@ -6,6 +6,7 @@ import { useSession, signIn } from 'next-auth/react';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { ProfileButton } from './shared/profile-button'; import { ProfileButton } from './shared/profile-button';
import { AuthModal } from './shared/modals/auth-modal/auth-modal'; import { AuthModal } from './shared/modals/auth-modal/auth-modal';
import AddButton from './shared/add-button';
export default function Header() { export default function Header() {
const [openAuthModal, setOpenAuthModal] = React.useState(false) const [openAuthModal, setOpenAuthModal] = React.useState(false)
@ -42,24 +43,15 @@ export default function Header() {
<Search className="h-6 w-6 text-gray-600" /> <Search className="h-6 w-6 text-gray-600" />
</button> </button>
<Link <AddButton />
href="/adt/create"
className="p-2 hover:bg-gray-100 rounded-full"
>
<PlusCircle className="h-6 w-6 text-indigo-600" />
</Link>
<ProfileButton onClickSignIn={() => setOpenAuthModal(true)} /> <ProfileButton onClickSignIn={() => setOpenAuthModal(true)} />
</div> </div>
{/* Desktop Navigation */} {/* Desktop Navigation */}
<div className="hidden sm:flex items-center gap-4"> <div className="hidden sm:flex items-center gap-4">
<Link <AddButton />
href="/adt/create"
className="p-2 hover:bg-gray-100 rounded-full"
>
<PlusCircle className="h-6 w-6 text-indigo-600" />
</Link>
{/* <button className="relative p-2 hover:bg-gray-100 rounded-full"> {/* <button className="relative p-2 hover:bg-gray-100 rounded-full">
<Bell className="h-6 w-6 text-gray-600" /> <Bell className="h-6 w-6 text-gray-600" />
<span className="absolute top-0 right-0 h-4 w-4 bg-red-500 rounded-full text-xs text-white flex items-center justify-center"> <span className="absolute top-0 right-0 h-4 w-4 bg-red-500 rounded-full text-xs text-white flex items-center justify-center">

View File

@ -24,7 +24,7 @@ export default function ListingCard({ id, title, price, location, image, date }:
alt={title} alt={title}
className="w-full h-full object-cover rounded-t-xl" className="w-full h-full object-cover rounded-t-xl"
/> />
<button {/* <button
className="absolute top-3 right-3 p-2 bg-white/90 rounded-full hover:bg-white" className="absolute top-3 right-3 p-2 bg-white/90 rounded-full hover:bg-white"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@ -32,7 +32,7 @@ export default function ListingCard({ id, title, price, location, image, date }:
}} }}
> >
<Heart className="h-5 w-5 text-gray-600" /> <Heart className="h-5 w-5 text-gray-600" />
</button> </button> */}
</div> </div>
<div className="p-4"> <div className="p-4">
<div className="flex justify-between items-start mb-2"> <div className="flex justify-between items-start mb-2">

View File

@ -0,0 +1,38 @@
'use client';
import { useState } from 'react';
import { PlusCircle } from 'lucide-react';
import Link from 'next/link';
import { useSession } from 'next-auth/react';
import { AuthModal } from './modals/auth-modal/auth-modal';
import toast from 'react-hot-toast';
export default function AddButton() {
const [openAuthModal, setOpenAuthModal] = useState(false);
const { data: session } = useSession();
const handleClick = () => {
if (!session) {
toast.error("Authorization required")
setOpenAuthModal(true);
return;
}
};
return (
<>
<Link
href={session ? "/adt/create" : "#"}
onClick={handleClick}
className="p-2 hover:bg-gray-100 rounded-full"
>
<PlusCircle className="h-6 w-6 text-indigo-600" />
</Link>
<AuthModal
open={openAuthModal}
onClose={() => setOpenAuthModal(false)}
/>
</>
);
}

View File

@ -7,6 +7,7 @@ import { formAdtCreateSchema, type TFormAdtCreateValues } from './schemas';
import Image from 'next/image'; import Image from 'next/image';
import { Category } from '@prisma/client'; import { Category } from '@prisma/client';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import toast from 'react-hot-toast';
interface CreateAdtFormProps { interface CreateAdtFormProps {
categories: Category[]; categories: Category[];
@ -59,7 +60,9 @@ export default function AdtCreateForm({ categories }: CreateAdtFormProps) {
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Ошибка при создании объявления'); const err = await response.text()
toast.error("Error creating adt: " + err)
throw new Error('Error creating adt');
} }
reset(); reset();
@ -67,6 +70,8 @@ export default function AdtCreateForm({ categories }: CreateAdtFormProps) {
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ''; fileInputRef.current.value = '';
} }
toast.success("Adt created successfully")
// Здесь можно добавить уведомление об успешном создании // Здесь можно добавить уведомление об успешном создании
const data_f = await response.json(); const data_f = await response.json();
// Редирект на страницу созданного объявления // Редирект на страницу созданного объявления
@ -76,6 +81,7 @@ export default function AdtCreateForm({ categories }: CreateAdtFormProps) {
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
// toast.error("Error creating adt: " + error)
// Здесь можно добавить обработку ошибок // Здесь можно добавить обработку ошибок
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);

View File

@ -2,10 +2,10 @@ import {z} from 'zod'
export const formAdtCreateSchema = z.object({ export const formAdtCreateSchema = z.object({
title: z.string() title: z.string()
.min(5, 'Заголовок должен содержать минимум 5 символов') .min(2, 'Заголовок должен содержать минимум 5 символов')
.max(100, 'Заголовок не может быть длиннее 100 символов'), .max(100, 'Заголовок не может быть длиннее 100 символов'),
description: z.string() description: z.string()
.min(20, 'Описание должно содержать минимум 20 символов') .min(10, 'Описание должно содержать минимум 20 символов')
.max(1000, 'Описание не может быть длиннее 1000 символов') .max(1000, 'Описание не может быть длиннее 1000 символов')
.nullable(), .nullable(),
price: z.string() price: z.string()

View File

@ -0,0 +1,158 @@
"use client"
// Импортируем необходимые компоненты и типы
import { FC, useEffect, useState, useRef, useCallback } from 'react'
import ListingCard from '../ListingCard'
import { Adt } from '@prisma/client'
import toast from 'react-hot-toast'
interface BlockAdtsProps {}
export const BlockAdts: FC<BlockAdtsProps> = () => {
// Состояния для хранения объявлений и управления их отображением
const [adts, setAdts] = useState<Adt[]>([]) // Массив объявлений
const [sortBy, setSortBy] = useState('new') // Тип сортировки
const [isLoading, setIsLoading] = useState(true) // Флаг загрузки
const [isLoadingMore, setIsLoadingMore] = useState(false) // Флаг загрузки дополнительных объявлений
const [page, setPage] = useState(1) // Текущая страница
const [hasMore, setHasMore] = useState(true) // Флаг наличия дополнительных объявлений
// Создаем наблюдатель для бесконечной прокрутки
const observer = useRef<IntersectionObserver>()
const lastAdtElementRef = useCallback((node: HTMLDivElement) => {
if (isLoadingMore) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(entries => {
// Если последний элемент виден и есть еще объявления - загружаем следующую порцию
if (entries[0].isIntersecting && hasMore) {
loadMore()
}
})
if (node) observer.current.observe(node)
}, [isLoadingMore, hasMore])
// Функция загрузки объявлений
const loadAdts = async (pageNum: number, isLoadMore = false) => {
try {
// Устанавливаем соответствующий флаг загрузки
if (isLoadMore) {
setIsLoadingMore(true)
} else {
setIsLoading(true)
}
// Запрашиваем данные с сервера
const response = await fetch(`/api/adt?page=${pageNum}&sort=${sortBy}`)
const { data: newAdts, meta } = await response.json()
// Обновляем список объявлений
if (pageNum === 1) {
setAdts(newAdts)
} else {
setAdts(prev => [...prev, ...newAdts])
}
// Проверяем, есть ли еще объявления для загрузки
setHasMore(newAdts.length > 0 && pageNum < meta.totalPages)
} catch (error) {
console.error('Ошибка загрузки объявлений:', error)
} finally {
setIsLoading(false)
setIsLoadingMore(false)
}
}
// Загружаем объявления при изменении способа сортировки
useEffect(() => {
loadAdts(1)
}, [sortBy])
// Обработчик изменения сортировки
const handleSort = (event: React.ChangeEvent<HTMLSelectElement>) => {
setSortBy(event.target.value)
setAdts([])
setPage(1)
setHasMore(true)
}
// Функция загрузки дополнительных объявлений
const loadMore = () => {
if (!isLoadingMore && hasMore) {
const nextPage = page + 1
setPage(nextPage)
loadAdts(nextPage, true)
}
}
// Компонент-заглушка для отображения во время загрузки
const SkeletonCard = () => (
<div className="bg-white rounded-lg shadow-md p-4 animate-pulse">
<div className="w-full h-48 bg-gray-200 rounded-lg mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/2 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/4"></div>
</div>
)
return (
<div className="space-y-6">
{/* Заголовок и селектор сортировки */}
<div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">Listings</h2>
<div className="flex gap-2">
<select
className="px-4 py-2 rounded-lg border border-gray-200 bg-white"
onChange={handleSort}
value={sortBy}
>
<option value="new">Newest</option>
<option value="price_asc">Price: ascending</option>
<option value="price_desc">Price: descending</option>
</select>
</div>
</div>
{/* Сетка объявлений */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{isLoading ? (
// Отображаем заглушки во время начальной загрузки
<>
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
</>
) : (
// Отображаем список объявлений
adts.map((adt, index) => (
<div
key={adt.id}
ref={index === adts.length - 1 ? lastAdtElementRef : undefined}
>
<ListingCard
title={adt.title}
image={String(adt.image)}
price={String(adt.price)}
location={String(adt.location)}
date={String(adt.createdAt)}
id={String(adt.id)}
/>
</div>
))
)}
</div>
{/* Отображаем заглушки при загрузке дополнительных объявлений */}
{isLoadingMore && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-6">
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
</div>
)}
</div>
)
}

View File

@ -19,6 +19,7 @@ export const AuthModal: React.FC<Props> = ({ open, onClose}) => {
const handleClose = () => { const handleClose = () => {
onClose() onClose()
setType('login')
} }
return ( return (
@ -31,7 +32,7 @@ export const AuthModal: React.FC<Props> = ({ open, onClose}) => {
<hr /> <hr />
<div className="flex gap-2"> <div className="flex gap-2">
<Button {/* <Button
variant='secondary' variant='secondary'
onClick={() => onClick={() =>
signIn('github', { signIn('github', {
@ -44,9 +45,9 @@ export const AuthModal: React.FC<Props> = ({ open, onClose}) => {
> >
<img className="2-6 h-6" src="https://github.githubassets.com/favicons/favicon.svg" /> <img className="2-6 h-6" src="https://github.githubassets.com/favicons/favicon.svg" />
GitHub GitHub
</Button> </Button> */}
<Button {/* <Button
variant='secondary' variant='secondary'
onClick={() => onClick={() =>
signIn('google', { signIn('google', {
@ -59,11 +60,11 @@ export const AuthModal: React.FC<Props> = ({ open, onClose}) => {
> >
<img className="2-6 h-6" src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" /> <img className="2-6 h-6" src="https://fonts.gstatic.com/s/i/productlogos/googleg/v6/24px.svg" />
Google Google
</Button> </Button> */}
</div> </div>
<Button variant='outline' onClick={onSwitchType} type='button' className="h-12"> <Button variant='outline' onClick={onSwitchType} type='button' className="h-12">
{type === 'login' ? 'Регистрация' : 'Войти'} {type === 'login' ? 'Sign Up' : 'Login'}
</Button> </Button>
</DialogContent> </DialogContent>

View File

@ -6,6 +6,7 @@ import { Title } from "@/components/shared/title";
import { FormInput } from "@/components/shared/form"; import { FormInput } from "@/components/shared/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import toast from "react-hot-toast";
interface Props { interface Props {
onClose?: VoidFunction; onClose?: VoidFunction;
@ -28,11 +29,13 @@ export const LoginForm: React.FC<Props> = ({onClose}) => {
}) })
if (!resp?.ok) { if (!resp?.ok) {
toast.error("Incorrect login or password")
throw Error(); throw Error();
} }
onClose?.() onClose?.()
} catch (error) { } catch (error) {
toast.error("Error login")
console.error('Error [LOGIN]', error) console.error('Error [LOGIN]', error)
} }
} }
@ -42,8 +45,8 @@ export const LoginForm: React.FC<Props> = ({onClose}) => {
<form className="flex flex-col gap-5" onSubmit={form.handleSubmit(onSubmit)}> <form className="flex flex-col gap-5" onSubmit={form.handleSubmit(onSubmit)}>
<div className="fles justify-between items-center"> <div className="fles justify-between items-center">
<div className="mr-2"> <div className="mr-2">
<Title text="Вход в аккаунт" size='md' className="font-bold" /> <Title text="Login" size='md' className="font-bold" />
<p className="text-gray-400">Введите свою почту, чтобы войти</p> <p className="text-gray-400">Enter your email to login</p>
</div> </div>
{/* <img src="..." /> */} {/* <img src="..." /> */}
</div> </div>
@ -52,7 +55,7 @@ export const LoginForm: React.FC<Props> = ({onClose}) => {
<Button loading={form.formState.isSubmitting} className="h-12 text-base" type='submit' > <Button loading={form.formState.isSubmitting} className="h-12 text-base" type='submit' >
{ {
'Войти' 'Login'
} }
</Button> </Button>
</form> </form>

View File

@ -7,6 +7,7 @@ import { FormInput } from "@/components/shared/form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import { registerUser } from "@/app/actions"; import { registerUser } from "@/app/actions";
import toast from "react-hot-toast";
interface Props { interface Props {
onClose?: VoidFunction; onClose?: VoidFunction;
@ -35,22 +36,24 @@ export const RegisterForm: React.FC<Props> = ({onClose}) => {
// throw Error(); // throw Error();
// } // }
toast.success("User registered successfully. Login")
onClose?.() onClose?.()
} catch (error) { } catch (error) {
console.error('Error [LOGIN]', error) toast.error("Error register")
console.error('Error [REGISTER]', error)
} }
} }
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<form className="flex flex-col gap-5" onSubmit={form.handleSubmit(onSubmit)}> <form className="flex flex-col gap-5" onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="fles justify-between items-center"> <div className="fles justify-between items-center">
<div className="mr-2"> <div className="mr-2">
<Title text="Вход в аккаунт" size='md' className="font-bold" /> <Title text="Sign Up" size='md' className="font-bold" />
<p className="text-gray-400">Введите свою почту, чтобы войти</p> <p className="text-gray-400">Enter your name and email to sign up</p>
</div>
</div> </div>
</div> */}
<FormInput name='name' label='Name' required /> <FormInput name='name' label='Name' required />
<FormInput name='email' label='E-Mail' required /> <FormInput name='email' label='E-Mail' required />
<FormInput name='password' label='Password' required /> <FormInput name='password' label='Password' required />

View File

@ -1,45 +1,46 @@
import { Dialog } from "@/components/ui/dialog"; 'use client'
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { getUserSession } from "@/lib/get-user-session"; import { getUserSession } from "@/lib/get-user-session";
import React from "react"; import React from "react";
import { Phone } from "lucide-react";
interface Props { interface Props {
open: boolean; phoneNumber: string;
onClose: () => void; session: any;
} }
export const ShowNumberModal: React.FC<Props> = ({ open, onClose}) => { export const ShowNumberModal: React.FC<Props> = ({ phoneNumber, session }) => {
const [open, setOpen] = React.useState(false);
// const [ session, setSession ] = React.useState(null) // const [session, setSession] = React.useState<any>(null);
// React.useEffect(() => { // React.useEffect(() => {
// const getSession = async () => { // const getSession = async () => {
// const userSession = await getUserSession() // const userSession = await getUserSession();
// setSession(userSession) // setSession(userSession);
// } // };
// getSession() // getSession();
// }, []) // }, []);
// const session = await getUserSession()
const handleClose = () => {
onClose()
}
return ( return (
<Dialog open={open} onOpenChange={handleClose}> <>
{/* <DialogContent className="w-[450px] bg-white p-10"> */} <button
{/* { onClick={() => setOpen(true)}
// session && ( className="w-full flex items-center justify-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700"
// <h1 className="text-2xl font-bold mb-4">892348924823</h1> >
// ) <Phone className="h-5 w-5" />
} */} <span>Show Phone Number</span>
</button>
{/* <h1 className="text-2xl font-bold mb-4">892348924823</h1> */}
<hr />
<div className="flex gap-2">
</div>
{/* </DialogContent> */}
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="w-[450px] bg-white p-10">
{session ? (
<h1 className="text-2xl font-bold mb-4">{phoneNumber}</h1>
) : (
<h2 className="text-xl text-center">Please login to see the phone number</h2>
)}
</DialogContent>
</Dialog> </Dialog>
) </>
} );
};

View File

@ -11,6 +11,7 @@ import { Title } from "./title";
import { FormInput } from "./form/form-input"; import { FormInput } from "./form/form-input";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { updateUserInfo } from "@/app/actions"; import { updateUserInfo } from "@/app/actions";
import toast from "react-hot-toast";
interface Props { interface Props {
data: User data: User
@ -35,13 +36,13 @@ export const ProfileForm: React.FC<Props> = ({ data }) => {
password: formData.password, password: formData.password,
}); });
// toast.error('Данные обновлены 📝', { toast.success('Data updated 📝', {
// icon: '✅', icon: '✅',
// }); });
} catch (error) { } catch (error) {
// return toast.error('Ошибка при обновлении данных', { return toast.error('Error updating data', {
// icon: '❌', icon: '❌',
// }); });
console.log('error update', error) console.log('error update', error)
} }
}; };

27
package-lock.json generated
View File

@ -25,6 +25,7 @@
"react": "19.0.0-rc-66855b96-20241106", "react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.53.2",
"react-hot-toast": "^2.4.1",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8" "zod": "^3.23.8"
@ -2492,7 +2493,6 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/damerau-levenshtein": { "node_modules/damerau-levenshtein": {
@ -3795,6 +3795,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/goober": {
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
"integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@ -5659,6 +5668,22 @@
"react": "^16.8.0 || ^17 || ^18 || ^19" "react": "^16.8.0 || ^17 || ^18 || ^19"
} }
}, },
"node_modules/react-hot-toast": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
"integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==",
"license": "MIT",
"dependencies": {
"goober": "^2.1.10"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@ -33,6 +33,7 @@
"react": "19.0.0-rc-66855b96-20241106", "react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.53.2",
"react-hot-toast": "^2.4.1",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8" "zod": "^3.23.8"

View File

@ -97,5 +97,152 @@ export const categories = [
image: "https://images.unsplash.com/photo-1516035069371-29a1b244cc32?auto=format&fit=crop&w=800", image: "https://images.unsplash.com/photo-1516035069371-29a1b244cc32?auto=format&fit=crop&w=800",
// date: "Posted 7 days ago" // date: "Posted 7 days ago"
userId: 1 userId: 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",
userId: 1
},
{
title: "Винтажный велосипед",
price: "$450",
location: "Portland, OR",
image: "https://images.unsplash.com/photo-1485965120184-e220f721d03e?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Профессиональный набор для рисования",
price: "$299",
location: "Seattle, WA",
image: "https://images.unsplash.com/photo-1513364776144-60967b0f800f?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Кожаный диван",
price: "$1,299",
location: "Austin, TX",
image: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Коллекционные виниловые пластинки",
price: "$599",
location: "Nashville, TN",
image: "https://images.unsplash.com/photo-1539375665275-f9de415ef9ac?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Беговая дорожка NordicTrack",
price: "$899",
location: "Phoenix, AZ",
image: "https://images.unsplash.com/photo-1540497077202-7c8a3999166f?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Набор кухонной посуды",
price: "$399",
location: "Boston, MA",
image: "https://images.unsplash.com/photo-1556911220-bff31c812dba?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Горный велосипед Trek",
price: "$789",
location: "Denver, CO",
image: "https://images.unsplash.com/photo-1576435728678-68d0fbf94e91?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Игровая приставка PS5",
price: "$499",
location: "Las Vegas, NV",
image: "https://images.unsplash.com/photo-1606144042614-b2417e99c4e3?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Антикварный комод",
price: "$850",
location: "Charleston, SC",
image: "https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Дрон DJI Mavic Air 2",
price: "$799",
location: "San Diego, CA",
image: "https://images.unsplash.com/photo-1579829366248-204fe8413f31?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Электрогитара Fender",
price: "$1,199",
location: "Atlanta, GA",
image: "https://images.unsplash.com/photo-1564186763535-ebb21ef5277f?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Кофемашина Breville",
price: "$599",
location: "Seattle, WA",
image: "https://images.unsplash.com/photo-1517080317843-0b926a6ce350?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Умные часы Apple Watch",
price: "$349",
location: "Houston, TX",
image: "https://images.unsplash.com/photo-1546868871-7041f2a55e12?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Винтажная печатная машинка",
price: "$299",
location: "Portland, ME",
image: "https://images.unsplash.com/photo-1558522195-e1201b090344?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Телескоп Celestron",
price: "$899",
location: "Tucson, AZ",
image: "https://images.unsplash.com/photo-1566004100631-35d015d6a491?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Набор для йоги",
price: "$89",
location: "Santa Monica, CA",
image: "https://images.unsplash.com/photo-1544367567-0f2fcb009e0b?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Винный холодильник",
price: "$599",
location: "Napa Valley, CA",
image: "https://images.unsplash.com/photo-1585703900468-13c7a978ad86?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Электросамокат Xiaomi",
price: "$399",
location: "Chicago, IL",
image: "https://images.unsplash.com/photo-1589999562311-56254d5d4325?auto=format&fit=crop&w=800",
userId: 1
},
{
title: "Коллекционные монеты",
price: "$2,999",
location: "Washington, DC",
image: "https://images.unsplash.com/photo-1566321343730-237ec35e53f3?auto=format&fit=crop&w=800",
userId: 2
},
{
title: "Профессиональный микрофон",
price: "$299",
location: "Nashville, TN",
image: "https://images.unsplash.com/photo-1590602847861-f357a9332bbc?auto=format&fit=crop&w=800",
userId: 1
} }
]; ];