тут нихуя не работает, ебаный рот этого казино
This commit is contained in:
19
frontend/style/app/api/cart/route.ts
Normal file
19
frontend/style/app/api/cart/route.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getCart, saveCart, clearCart, CartItem } from '@/lib/cartStorage'
|
||||
|
||||
export async function GET() {
|
||||
const cart = getCart()
|
||||
return NextResponse.json(cart)
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const cartItems: CartItem[] = await request.json()
|
||||
saveCart(cartItems)
|
||||
return NextResponse.json({ message: 'Cart updated successfully' })
|
||||
}
|
||||
|
||||
export async function DELETE() {
|
||||
clearCart()
|
||||
return NextResponse.json({ message: 'Cart cleared successfully' })
|
||||
}
|
||||
|
||||
12
frontend/style/app/api/disable-preview/route.ts
Normal file
12
frontend/style/app/api/disable-preview/route.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const id = searchParams.get('id')
|
||||
|
||||
draftMode().disable()
|
||||
|
||||
return NextResponse.redirect(new URL(`/product/${id}`, request.url))
|
||||
}
|
||||
|
||||
23
frontend/style/app/api/preview/route.ts
Normal file
23
frontend/style/app/api/preview/route.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const secret = searchParams.get('secret')
|
||||
const id = searchParams.get('id')
|
||||
|
||||
// Check the secret and next parameters
|
||||
// This secret should be only known to this API route and the CMS
|
||||
if (secret !== process.env.PREVIEW_SECRET || !id) {
|
||||
return NextResponse.json({ message: 'Invalid token' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Enable Preview Mode by setting the cookies
|
||||
const res = NextResponse.next()
|
||||
res.cookies.set('__prerender_bypass', process.env.PRERENDER_BYPASS_TOKEN || '')
|
||||
res.cookies.set('__next_preview_data', process.env.PREVIEW_DATA_TOKEN || '')
|
||||
|
||||
// Redirect to the path from the fetched post
|
||||
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
|
||||
return NextResponse.redirect(new URL(`/product/${id}`, request.url))
|
||||
}
|
||||
|
||||
@@ -4,17 +4,12 @@ import { CartSummary } from "@/components/cart-summary"
|
||||
export default function CartPage() {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="flex gap-8">
|
||||
<h1 className="text-2xl font-bold mb-6">Корзина</h1>
|
||||
<div className="flex flex-col md:flex-row gap-8">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-2xl font-bold">Корзина</h1>
|
||||
<button className="text-blue-600 hover:underline">
|
||||
Поделиться
|
||||
</button>
|
||||
</div>
|
||||
<CartItems />
|
||||
</div>
|
||||
<div className="w-80">
|
||||
<div className="w-full md:w-80">
|
||||
<CartSummary />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
10
frontend/style/app/favorites/page.tsx
Normal file
10
frontend/style/app/favorites/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FavoriteItems } from "@/components/favorite-items"
|
||||
|
||||
export default function FavoritesPage() {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<FavoriteItems />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,78 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,35 @@
|
||||
import type { Metadata } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import "./globals.css";
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from 'next/font/google'
|
||||
import { Header } from "@/components/header"
|
||||
import { CartProvider } from "@/contexts/cart-context"
|
||||
import { FavoritesProvider } from "@/contexts/favorites-context"
|
||||
import { AuthProvider } from "@/contexts/auth-context"
|
||||
|
||||
const geistSans = localFont({
|
||||
src: "./fonts/GeistVF.woff",
|
||||
variable: "--font-geist-sans",
|
||||
weight: "100 900",
|
||||
});
|
||||
const geistMono = localFont({
|
||||
src: "./fonts/GeistMonoVF.woff",
|
||||
variable: "--font-geist-mono",
|
||||
weight: "100 900",
|
||||
});
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
title: "Online Store",
|
||||
description: "E-commerce platform",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<body className={inter.className}>
|
||||
<AuthProvider>
|
||||
<CartProvider>
|
||||
<FavoritesProvider>
|
||||
<Header />
|
||||
<main className="min-h-screen bg-gray-50 px-4 sm:px-6 lg:px-8">{children}</main>
|
||||
</FavoritesProvider>
|
||||
</CartProvider>
|
||||
</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
52
frontend/style/app/login/page.tsx
Normal file
52
frontend/style/app/login/page.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { useAuth } from "@/contexts/auth-context"
|
||||
|
||||
export default function LoginPage() {
|
||||
const [email, setEmail] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const router = useRouter()
|
||||
const { login } = useAuth()
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
// In a real application, you would validate credentials here
|
||||
login()
|
||||
router.push("/cart")
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-2xl font-bold mb-6">Войти</h1>
|
||||
<form onSubmit={handleSubmit} className="space-y-4 max-w-md mx-auto">
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="password">Пароль</Label>
|
||||
<Input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full">Войти</Button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { ProductGrid } from "@/components/product-grid"
|
||||
import { Header } from "@/components/header"
|
||||
|
||||
import { Banner } from "@/components/banner"
|
||||
import { ProductFilters } from "@/components/product-filters"
|
||||
import { Footer } from "@/components/footer"
|
||||
import { SAMPLE_PRODUCTS } from "@/lib/sample-products"
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<Header/>
|
||||
|
||||
<div className="container mx-auto py-8">
|
||||
<Banner />
|
||||
<div className="my-8 flex gap-8">
|
||||
<ProductFilters />
|
||||
<div className="my-8 flex flex-col md:flex-row gap-8">
|
||||
<div className="w-full md:w-64">
|
||||
<ProductFilters />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-bold mb-6">Специальные предложения!</h2>
|
||||
<ProductGrid />
|
||||
<ProductGrid products={SAMPLE_PRODUCTS} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
50
frontend/style/app/product/[id]/page.tsx
Normal file
50
frontend/style/app/product/[id]/page.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { ProductDetail } from "@/components/product-detail"
|
||||
import { SAMPLE_PRODUCTS } from "@/lib/sample-products"
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
async function getProduct(id: string, isDraft: boolean) {
|
||||
// In a real application, you would fetch the product from an API or database
|
||||
// For this example, we'll use the SAMPLE_PRODUCTS
|
||||
const product = SAMPLE_PRODUCTS.find(p => p.id === parseInt(id))
|
||||
|
||||
if (!product) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isDraft) {
|
||||
// Simulate draft data
|
||||
return {
|
||||
...product,
|
||||
title: `[Draft] ${product.title}`,
|
||||
price: product.price * 0.9, // 10% discount in draft mode
|
||||
}
|
||||
}
|
||||
|
||||
return product
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }: { params: { id: string } }) {
|
||||
const draftModeData = draftMode()
|
||||
const isDraftMode = draftModeData ? draftModeData.isEnabled : false
|
||||
const product = await getProduct(params.id, isDraftMode)
|
||||
|
||||
if (!product) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<ProductDetail product={product} />
|
||||
{isDraftMode && (
|
||||
<div className="fixed bottom-0 left-0 w-full bg-yellow-400 text-black p-2 text-center">
|
||||
Preview Mode Enabled - {' '}
|
||||
<a className="underline" href={`/api/disable-preview?id=${params.id}`}>
|
||||
Exit Preview Mode
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
11
frontend/style/app/profile/page.tsx
Normal file
11
frontend/style/app/profile/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { UserProfile } from "@/components/user-profile"
|
||||
|
||||
export default function ProfilePage() {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-2xl font-bold mb-6">Личный кабинет</h1>
|
||||
<UserProfile />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
11
frontend/style/app/register/page.tsx
Normal file
11
frontend/style/app/register/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { RegisterForm } from "@/components/register-form"
|
||||
|
||||
export default function RegisterPage() {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-2xl font-bold mb-6">Регистрация</h1>
|
||||
<RegisterForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
25
frontend/style/app/search/page.tsx
Normal file
25
frontend/style/app/search/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ProductGrid } from "@/components/product-grid"
|
||||
import { SAMPLE_PRODUCTS } from "@/lib/sample-products"
|
||||
|
||||
export default function SearchPage({
|
||||
searchParams
|
||||
}: {
|
||||
searchParams: { q: string }
|
||||
}) {
|
||||
const searchTerm = searchParams.q || ''
|
||||
const filteredProducts = SAMPLE_PRODUCTS.filter(product =>
|
||||
product.title.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-2xl font-bold mb-6">Результаты поиска для "{searchTerm}"</h1>
|
||||
{filteredProducts.length > 0 ? (
|
||||
<ProductGrid products={filteredProducts} />
|
||||
) : (
|
||||
<p className="text-center text-gray-500">По вашему запросу ничего не найдено</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user