From 639ac4f5ecf10ffd8e6c4ccf4a33a1d080ff1f52 Mon Sep 17 00:00:00 2001 From: Zikil Date: Tue, 1 Apr 2025 23:52:37 +0700 Subject: [PATCH] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 10244 -> 10244 bytes .dockerignore | 59 ++++ .env.production | 17 ++ Dockerfile.backend | 28 ++ Dockerfile.frontend | 24 ++ README.md | 104 +++++++ backend/.env.docker | 10 + backend/app.db | Bin 159744 -> 0 bytes .../app/__pycache__/config.cpython-310.pyc | Bin 2571 -> 2561 bytes backend/app/config.py | 5 +- backend/requirements.txt | 113 ++++++- docker-compose.prod.yml | 114 +++++++ docker-compose.yml | 183 +++++------ frontend/.DS_Store | Bin 8196 -> 8196 bytes frontend/app/(main)/.DS_Store | Bin 8196 -> 8196 bytes frontend/app/(main)/catalog/[slug]/page.tsx | 286 +++++++++--------- frontend/app/(main)/checkout/success/page.tsx | 31 +- frontend/app/.DS_Store | Bin 10244 -> 10244 bytes frontend/app/admin/dashboard/page.tsx | 4 +- frontend/app/cart-backup/page.tsx | 107 ------- frontend/app/checkout-backup/page.tsx | 205 ------------- frontend/components/.DS_Store | Bin 8196 -> 8196 bytes .../components/product/ProductDetails.tsx | 46 +-- frontend/lib/admin-api.ts | 195 ++++++++++++ frontend/lib/auth.tsx | 25 +- frontend/public/.DS_Store | Bin 6148 -> 6148 bytes frontend/public/images/.DS_Store | Bin 6148 -> 6148 bytes init-letsencrypt.sh | 64 ++++ nginx/nginx.conf | 63 ++++ nginx/nginx.prod.conf | 111 +++++++ 30 files changed, 1189 insertions(+), 605 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.production create mode 100644 Dockerfile.backend create mode 100644 Dockerfile.frontend create mode 100644 README.md create mode 100644 backend/.env.docker delete mode 100644 backend/app.db create mode 100644 docker-compose.prod.yml delete mode 100644 frontend/app/cart-backup/page.tsx delete mode 100644 frontend/app/checkout-backup/page.tsx create mode 100644 frontend/lib/admin-api.ts create mode 100755 init-letsencrypt.sh create mode 100644 nginx/nginx.conf create mode 100644 nginx/nginx.prod.conf diff --git a/.DS_Store b/.DS_Store index b38128b440ed8f83e47f3e73065e784931a1c035..b7db6ae001b4674dc7f527e19e497adf95ae9c9a 100644 GIT binary patch delta 260 zcmZn(XbG4g&d4*dP;8=}P_a6P+)Qko2)0%7SHzK4ZoZ34QcivnP>jQ;YQv+y zXOB6e%BSF!FUSB{nOgwV!@zF$VY9I0GDa5Le;+s}8%m2zz9<#L>~wF#W&`Q>jGF~m zVwooku&8b}6gb8O-Su~kR KRu-uDDH8yLbVM!y delta 694 zcmZn(XbG4g&dJTdz{17AAi+IRL0y)afq{V$h>d_4$QEE=$YV%hh-XM<$Y;piSh$#Z zvVjT{lp%>Bhar<8IVasP zI5|JJ0H_THP~>y-U0jlK@{@p)96ufvINUpP%n`^&Q6bpkMLpso9CCE0*x@MMaJq5NINgiPisXZMtc)p|Hs0i$XEXc4rQ3HV}T#xVc~U2IHU!mL5q+2r(!Cqo2VQ7}M}%u(9wx W^JIP%&&e?=%O?L5Q5;+j?*ssx1*~8I diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..51b1b71 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +# Общие файлы +.git +.github +.gitignore +.DS_Store +README.md +**/test +**/.env + +# Файлы и директории для Node.js +**/node_modules +**/npm-debug.log +**/.next +**/dist +**/build +**/.coverage +**/coverage + +# Файлы и директории для Python +**/__pycache__ +**/*.py[cod] +**/*.so +**/*.egg +**/*.egg-info +**/dist +**/build +**/.pytest_cache +**/.coverage +**/htmlcov +**/.tox +**/.mypy_cache + +# Виртуальное окружение Python +**/venv +**/.venv +**/env +**/.env + +# Файлы базы данных +**/*.sqlite +**/*.db + +# Логи и временные файлы +**/logs +**/*.log +**/tmp +**/*.tmp + +# Файлы IDE +**/.idea +**/.vscode +**/*.swp +**/*.swo + +# Загружаемые файлы, кроме необходимых для работы +# backend/uploads/* +# !backend/uploads/products +# backend/uploads/products/* +# !backend/uploads/products/.gitkeep \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..f28d00b --- /dev/null +++ b/.env.production @@ -0,0 +1,17 @@ +# Доменное имя сайта без протокола (http/https) +DOMAIN_NAME=dressedforsuccess.shop + +# Протокол (http или https) +PROTOCOL=https + +# API URL для браузера (клиентская сторона) +NEXT_PUBLIC_API_URL=${PROTOCOL}://${DOMAIN_NAME}/api + +# Base URL для статических файлов +NEXT_PUBLIC_BASE_URL=${PROTOCOL}://${DOMAIN_NAME} + +# Режим отладки +NEXT_PUBLIC_DEBUG=false + +# Мокирование API +NEXT_PUBLIC_MOCK_API=false \ No newline at end of file diff --git a/Dockerfile.backend b/Dockerfile.backend new file mode 100644 index 0000000..4586976 --- /dev/null +++ b/Dockerfile.backend @@ -0,0 +1,28 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Установка зависимостей системы +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +# Установка зависимостей Python +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Копирование кода приложения +COPY backend/ . + +# Копирование .env.docker в .env для использования в контейнере +COPY backend/.env.docker ./app/.env + +# Создание директории для загрузок если её нет +RUN mkdir -p /app/uploads/products + +# Настройка разрешений для директории загрузок +RUN chmod -R 777 /app/uploads + +# Открытие порта +EXPOSE 8000 + +# Запуск приложения с Uvicorn +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/Dockerfile.frontend b/Dockerfile.frontend new file mode 100644 index 0000000..5984ed1 --- /dev/null +++ b/Dockerfile.frontend @@ -0,0 +1,24 @@ +FROM node:20-alpine + +WORKDIR /app + +# Копирование файлов package.json и package-lock.json +COPY frontend/package*.json ./ + +# Установка зависимостей с флагом --legacy-peer-deps +RUN npm ci --legacy-peer-deps + +# Копирование исходного кода +COPY frontend/ ./ + +# Копирование .env.docker в .env.local для использования в контейнере +COPY frontend/.env.docker ./.env.local + +# Сборка приложения +RUN npm run build + +# Открытие порта +EXPOSE 3000 + +# Запуск приложения +CMD ["npm", "start"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fcc0de --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Dressed for Success - Интернет-магазин одежды + +## О проекте + +Интернет-магазин модной одежды Dressed for Success, созданный с использованием современных технологий: + +- **Фронтенд**: Next.js, React, TypeScript, Tailwind CSS +- **Бэкенд**: FastAPI, SQLAlchemy, PostgreSQL +- **Развертывание**: Docker, Docker Compose + +## Требования + +Для запуска проекта вам потребуются: + +- Docker +- Docker Compose + +## Запуск проекта + +### 1. Клонирование репозитория + +```bash +git clone https://github.com/username/dressed_for_success_store.git +cd dressed_for_success_store +``` + +### 2. Запуск через Docker Compose + +```bash +docker-compose up -d +``` + +Это запустит: +- Бэкенд на порту 8000 +- Фронтенд на порту 3000 +- PostgreSQL на порту 5432 + +### 3. Доступ к приложению + +- **Фронтенд**: http://localhost:3000 +- **API бэкенда**: http://localhost:8000/api +- **Документация API**: http://localhost:8000/docs + +## Остановка проекта + +```bash +docker-compose down +``` + +Для удаления томов (данных базы данных и загруженных файлов): + +```bash +docker-compose down -v +``` + +## Разработка + +### Структура проекта + +``` +. +├── backend/ # Бэкенд на FastAPI +│ ├── app/ # Код приложения +│ ├── uploads/ # Загружаемые файлы +│ └── requirements.txt # Зависимости Python +│ +├── frontend/ # Фронтенд на Next.js +│ ├── app/ # Код Next.js приложения +│ ├── components/ # React компоненты +│ ├── lib/ # Библиотеки и утилиты +│ └── public/ # Статические файлы +│ +├── docker-compose.yml # Конфигурация Docker Compose +├── Dockerfile.backend # Dockerfile для бэкенда +└── Dockerfile.frontend # Dockerfile для фронтенда +``` + +## Переменные окружения + +### Фронтенд + +Основные переменные окружения для фронтенда (файл `.env.local` или `.env.docker`): + +``` +NEXT_PUBLIC_API_URL=http://localhost:8000/api +NEXT_PUBLIC_BASE_URL=http://localhost:8000 +NEXT_PUBLIC_DEBUG=false +NEXT_PUBLIC_MOCK_API=false +``` + +### Бэкенд + +Основные переменные окружения для бэкенда (файл `.env` или `.env.docker`): + +``` +DATABASE_URL=postgresql://postgres:postgres@postgres:5432/shop_db +SECRET_KEY=supersecretkey +DEBUG=0 +UPLOAD_DIRECTORY=/app/uploads +``` + +## Лицензия + +[MIT License](LICENSE) \ No newline at end of file diff --git a/backend/.env.docker b/backend/.env.docker new file mode 100644 index 0000000..76e4300 --- /dev/null +++ b/backend/.env.docker @@ -0,0 +1,10 @@ +# Настройки базы данных +# DATABASE_URL=postgresql://postgres:postgres@postgres:5432/shop_db + +# Настройки приложения +DEBUG=0 +SECRET_KEY=supersecretkey +# UPLOAD_DIRECTORY=/app/uploads + +# Настройки CORS +FRONTEND_URL=http://frontend:3000 \ No newline at end of file diff --git a/backend/app.db b/backend/app.db deleted file mode 100644 index 819cd93b163234ecc66da83dd1558dde4d044288..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159744 zcmeI)&u`r3nZR*TmS?1~WZ7{N)^*fURkP8c@wRGEql-ncm1H_qB9D^M$cR%E1U;lA zBBPlx$#LXlbMTKQr=o|=Vefn1bB{gt)B**%|3QIX_tq5M!@loJayajZqwWsYT8!`w z5Q+2t8uIfzeEE`8vitsa5c}4CCp>av>#eCPQ-(41W6PSFnpzhBz9Ih2{*j+&vJc|7 zF|a*qb9w6TcRsr)8!!GK$YsUfrzQ>kZr5 zt8Kl%XIopf+x7?6aARwyW@R0$RoT5-d%0v*mkj^J;DML5d?g+FXUEsd z=F*b!#b)e2X!+R~+3%Oru^Q_e+xEb|ROZB3TeXJ$wq3VsI}NM0x4r$QRhetMN4|A$ zy}tRAb-hCFePM;?N6j$k#zCiTHS7->a+GcuG=1yN_RhK(d0=!eh?<>VI~Jz>2C{wA z4SgqQM{y{i#Xynu&hAc4)&!B`ilRrpwXw6aZLh1sW~bHiRYNB~jAWeHKj?&y2a2`t zZ-%}b`<^3C-`gU3jjg-3b=$tPzPH`5RyX(Rb-UJZWa(~W{qB2JIb^Tv4R?{n@71^N zuGjBd@7njRRS^eSc4wz6BYLqUzZ+(-Y+Q+{G?l@ zy8lwi{6W?DEK8+cE_ZL8#Zdh2r8G)?3AyUF||yQ^Smc*U<))$)rjL=el%~lyxHV-QI&%5FPs7 z*~Gq5-B{4^qu>*D(yQN>hw-yo8A7$K@Sl8gN#Q@+Rrsnx{g~JA(+(r?M_w~Vo<<`a zU+napCbpQGJ-=G1R_9CRk4$4qWpDJbC6;!M+l!Oa!%@fVtf^lMUZrfVEgMfRWSQRV z&&BHe$v<97;f7D5yoKr@!;lxHr_;STvF=zljtagc@bcD`;n?O?=^UOHB&S$owrnmh z8_&hmCYA_!ZN4?9#XB!QWzHTM^pPjrV9@&u1##gQ%Yl6EPfw?UYyO$dqup$uw3&1e zMO^PI)x&bxd~3ycQtrpAzbZ*))qxBB?5MOD>Z0-@FG~~S!&1ro?uzlS=!-;Vi zM(W4378_M`N>tPi6|Z~_JMI1zRGx)hH;O*)gkE9m{UD5DXK)UXU0QBIVPObmp(kd# zv$qKmY**5I_I{1Q0*~ z0R$#VfcO8C6fO1@0R#|0009ILKmY**5I_Kd@dUX4AJ2_m2q1s}0tg_000IagfB*sr zOp*Y9|3695VqXzJ009ILKmY**5I_I{1P~ZcfcO97xzP&&1Q0*~0R#|0009ILKmdVB z65#%SlA^`FB7gt_2q1s}0tg_000IagFrEN^|397^y%0bE0R#|0009ILKmY**5SSzZ z-v3WhwAfb!5I_I{1Q0*~0R#|0009KX6X5=TJU4nFfB*srAbqKmY**5I_I{1Q0*~0R$#VfcyVR ziWd8d00IagfB*srAbT<@9aiPoe|=Oq#&rR~L_NE*YN&ZO=arj-77U@p{cTa-vr6 zAp7v@X5C(I*w$Wc>-|03+N#~QKd^=yTRSx?>tLCh*~ z3_N|rz^gtxzE(DumW(epWA{PJ&&J4pznqTMSl`&T2lk~hC&t>UHSD+Tx>eh0Shc{$jD?C4HhCw$DI&G_Af6$Plbi<(OTX(j1*2TyJqkBQr z?DX2PFzq*x?VE1sJ3%{&Ljf%YimZ2bcWSaGh#XfGJ@T!Moth>L548CdaT6s-W+*5pA zdJKBMq&)DGZk6i(OC|FMRpYZPm3omMI>V;|tpV?Q- zX0>X3ZmSg2Tj-x&($Y<@vsOnYoobjzQVUsnKu#-Y`)h>>IN0L*Sqqg7LHs!Ttc@AP z!m_ZY+Yxmwr`hp}>s9uK#iNQ;_56Le*NU}VJ+qWc6}6<1Wm&%LTq&7vm_{edm#%vt z(nL*aa_eduyAv~7znPkRbDjl5b(n(U* ziO6?*4_ZNV=zC`q`$~0VLC24RPt-}TeqSEO&uV1|)waTa^2sHI|7=&`s|xjFUc*m2 zjKm*#%@}zajc|Oi({q~GVrusMYNcA8FPT3wjVYDA(Zf~{`;Oa-lhead$Ly@BUkhHP zY_2UEPcCGc-t5oC>io$+UP|GHPolhq>LA0A7p14uy}6QkbJ;j5_>#cOTUUl-n^&cC zcwUg4VvX6dxx8#V7gL*9BILFC)|?jay!@0odt}f@o^XRf?=KX@g{AFuwZB$-tQF7&gb(qgEK%8R@#O^gpqCG)#0 z#>1j75}A>=FP}(_ye`X)yd+7Eg^Oi#WyN^rCdrXk4~$#V(<5&)PJR@n!N{B`UjDtX zRH#(zGh%&i8c}AgE})KoAJbZFRM9C>Q9D$;@;U6Z`&UqT7Ixhz`nVH%g{}94Fp8bQIY4%4 zxdnxVA(VxlnCZ^uYAN?EoBg};IOX&|ecJS&1Yu46x*%>~XXQ=o!tBqcu1H|NV!&u71>*34fd%Nn=swjRep zGjdv;gGm4MEiHh0-6@fjyEZg$`j6yX8b9v#^0D#*#b?a)B!TsL?d zB^w9b?Dk@C2(=?(Xt9(USQ3T)z8{8uI9MhY>Vdqw6ANr{Pv!RS@y599|E=4vR7ab^ zRWQ}0Cl6-uW-Yy277AL2>?XRf&w!EE>BM#7oo|R`_YK3%W|Yu>6!;&@`?GXwd3XlM zTgW*e{iJr{*{!9rdE0lnitY~Re6tf0Hamk<8i*fBlWZXlAX)kEgr=lDk>-tT6hh zmrd_ll%u%Q{3z>@p7Q^%3;O;=Zghl)vm!lit%JH5>|YGB1%*%$%ZY!wUNU!Yj=Gy1 zj+V1FT=LqbA18~9J7&pz{krk9EYHLWOgzF7AxJj9KAcnidO0H`ACmlOzFIb~UpKy3 zR{4|EBtK0pSF%1MZ|~FjFbn8VsNJ|1Wm&Mg_C56=H15cUXzr1G)LZZrI64fvU2*-$ zRucpHCBx8lAIl4dBc8Y%cKXZD)wNq{?5x%kPicZy)PLk0hi>yDIcU3g^gv&Cv^%kQ zW|iG?jy7QyadJNCF=^1qI zTf z0tg_000IagfB*srAb`Mm72v=BKd+JHxDY@90R#|0009ILKmY**5Xc4i|NrL}Y$1RE z0tg_000IagfB*srAaGs$7$KmY**5I_I{1Q0*~0R#}p1^D~_+=49x5I_I{ z1Q0*~0R#|0009Kfs{rr+&ue5kE(8!j009ILKmY**5I_I{1abl1|K}ELA%Fk^2q1s} z0tg_000Iaga9#y?|9@U1%W)xq00IagfB*srAb61|IaPhLI42-5I_I{1Q0*~0R#|0 h;Jga({{Os2mg7PI0R#|0009ILKmY**5I`Uo_-~=7y+Qy0 diff --git a/backend/app/__pycache__/config.cpython-310.pyc b/backend/app/__pycache__/config.cpython-310.pyc index f7b0c7031a5ee4fb0992b06ea9856f806bf1aba7..38f76e3711c5e11388e19627b9ec75209366925f 100644 GIT binary patch delta 757 zcmZ9JO>fgM0EX=(ZId?rZqv_g9fFAosZ-Z6$~0gtD-4)ewGSkGAnLG5Ta_dv83-;D zDmU0+@`(e|fM0<41>E@qMB>B=i4(s7JM4gnl~=Ey_q8nBuX8VRaVs8=0`{58FXQMq zJ_W(?g|~-TL-_%~g^v(vxQGfQO!5JeBLMEB z9W591(*e7Z#n}Mn1DHK6=5U@BwSeVLnT89D6#}OHW%P?U3sw3<{N8)9W?QajIK+K2 zURhW$h^ZgA#96s-&fb$ zyX{VUr>pO6Z_-ex1JiUPbS>kj_IjIJ?R9D%A=K$%WbR>>U9~T| zp5rHVy>FV9*Y}2&>FTCOPP!>sli3vU^wxo^VT^L&Lb&?P><4me3 zNYS_x7vnHGQ1YEf6LKl7j@ys-Z&Seh&^E-dex%a$CzD}x^szwlezuz94 z&yUn1mXFT8J7nl%n5Jm@lR%6k)6B%(bwiS&f++e94NCT|H3@izY^ zR1cX94+&8jhDVsh0yMONSd>J49R{rs#n%zgDp6SsJkbqgBC34GWPxdrt5A!RxbKUB zPLPD}TL7&w9TuYdJ`HkjUYsUiUK0H60FBR~?nA?GqR&~98emNMEIDT5AkPvrnDNnjb>w~-RpKXpRL=LJ24&RJ{r|4mD8ktDz1*K`+K84c=YOOoPVj2rO2CQ2D!;!B{KfblKM1YjB!3^u-7tVUDF`#ca%Vx4P1yLcllG{$ zdneRyRDtQA!R7$bt?anvwlDzzaJ4qd delta 154 zcmZp1XmQw}CcvT~d&F+CmVgxNtWWvto=o->kcG0c1!S2O)-9OaEwGZwBYd)%UOa|;+ifL*y6C@3}goL~y0!DeHj%}mTQ^J6E!6p>*qtYhB#WHOhi XJd~v;s=ze!z~%tat?anvwlDzzPzN=| diff --git a/frontend/app/(main)/.DS_Store b/frontend/app/(main)/.DS_Store index c3f38e647bf24400fab3c1722fc6be2a7d0c4715..2a0b9c6bce0d24aa2888ad94298b3ac082477a79 100644 GIT binary patch delta 85 zcmZp1XmQveC&=X2Jy}68iZNhvwV)!?>XVb_3Fa^zV3=$mBr;h+h>t059+0WPk-Se* k;>X!zjzEss!!vi~ kySrzOIRZIilkx5L80vINr6O!7zPsoiI0Q!j@M*si- diff --git a/frontend/app/(main)/catalog/[slug]/page.tsx b/frontend/app/(main)/catalog/[slug]/page.tsx index ce7c412..613c314 100644 --- a/frontend/app/(main)/catalog/[slug]/page.tsx +++ b/frontend/app/(main)/catalog/[slug]/page.tsx @@ -3,7 +3,7 @@ import { Suspense, useState } from "react" import Link from "next/link" import { notFound } from "next/navigation" -import { ArrowLeft, ChevronRight, Truck, RotateCcw, Heart, ShoppingBag, Mail, Phone, MapPin } from "lucide-react" +import { ArrowLeft, ChevronRight, Truck, RotateCcw, Heart } from "lucide-react" import catalogService, { ProductDetails } from "@/lib/catalog" import { formatPrice } from "@/lib/utils" import { Separator } from "@/components/ui/separator" @@ -77,98 +77,111 @@ export default function ProductPage({ params }: ProductPageProps) { } return ( - <> -
-
- {/* Навигация */} - -
- - - Вернуться в каталог - - - {/* Хлебные крошки */} - - - Главная - - - - Каталог - - {product.category_name && ( - <> - - - {product.category_name} - - - )} - - {product.name} - -
-
- - {/* Основной контент товара */} -
- {/* Блок изображений */} - +
+ {/* Навигация */} + +
+ - }> -
-
- {/* Используем проверку времени создания для выявления новинок (товары, созданные в течение последних 30 дней) */} - {new Date(product.created_at).getTime() > Date.now() - 30 * 24 * 60 * 60 * 1000 && ( - - Новинка - - )} - {product.discount_price && ( - - -{Math.round((1 - product.discount_price / product.price) * 100)}% - - )} -
- - -
-
- + + Вернуться в каталог + - {/* Блок информации о товаре */} - + + Главная + + + + Каталог + + {product.category_name && ( + <> + + + {product.category_name} + + + )} + + {product.name} + +
+
+ + {/* Основной контент товара */} +
+ {/* Блок изображений */} + + }> +
+
+ {/* Используем проверку времени создания для выявления новинок (товары, созданные в течение последних 30 дней) */} + {new Date(product.created_at).getTime() > Date.now() - 30 * 24 * 60 * 60 * 1000 && ( + + Новинка + + )} + {product.discount_price && ( + + -{Math.round((1 - product.discount_price / product.price) * 100)}% + + )} +
+ + {/* Кнопка добавления в избранное */} +
+ +
+ + +
+
+
+ + {/* Блок информации о товаре */} + +
{/* Категория и название */} - + {product.description ? ( @@ -273,9 +286,9 @@ export default function ProductPage({ params }: ProductPageProps) { transition={{ duration: 0.5, delay: 0.6 }} className="grid grid-cols-1 sm:grid-cols-2 gap-4" > -
+
-
+

Доставка

@@ -285,9 +298,9 @@ export default function ProductPage({ params }: ProductPageProps) {

-
+
-
+

Возврат

@@ -297,20 +310,17 @@ export default function ProductPage({ params }: ProductPageProps) {

- -
+
+
-
- - - - + + ) } function ProductSkeleton() { return ( -
+
@@ -319,43 +329,45 @@ function ProductSkeleton() {
- +
-
-
- - - -
- - - -
- -
- {[1, 2, 3, 4].map((i) => ( - - ))} +
+ +
+ + +
- - -
- - - -
-
- - + + + +
+ +
+ {[1, 2, 3, 4].map((i) => ( + + ))} +
+ +
- -
- -
- - -
+ + + +
+
+ + +
+ +
+ +
+ + +
+
diff --git a/frontend/app/(main)/checkout/success/page.tsx b/frontend/app/(main)/checkout/success/page.tsx index d7f1025..ad25010 100644 --- a/frontend/app/(main)/checkout/success/page.tsx +++ b/frontend/app/(main)/checkout/success/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import Image from "next/image"; -import { useEffect, useState } from "react"; +import { useEffect, useState, Suspense } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Check, ChevronLeft, Package } from "lucide-react"; @@ -10,7 +10,8 @@ import { formatPrice } from "@/lib/utils"; import { OrderItem } from "@/types/order"; import { Separator } from "@/components/ui/separator"; -export default function CheckoutSuccessPage() { +// Компонент с доступом к параметрам URL +function CheckoutSuccessContent() { const router = useRouter(); const searchParams = useSearchParams(); const [orderInfo, setOrderInfo] = useState({ @@ -164,4 +165,30 @@ export default function CheckoutSuccessPage() {
); +} + +// Компонент загрузки для Suspense +function CheckoutSuccessLoading() { + return ( +
+
+
+

+

+
+
+
+
+
+
+ ); +} + +// Основной компонент страницы +export default function CheckoutSuccessPage() { + return ( + }> + + + ); } \ No newline at end of file diff --git a/frontend/app/.DS_Store b/frontend/app/.DS_Store index a5c929baeb69f3e3e12796b954778d3e5d75b92a..23a91656a676c4652d76e7f14900da0fe67903de 100644 GIT binary patch delta 61 zcmV-D0K)%-P=rvBPXRcwP`eKSIFk$z9Rmafw393lW&z-{lMoC7vriK!0h4hR7qea! TsS2@xfB~}yAo>KefffS;ORN&? delta 510 zcmZn(XbG6$&*-o*U^hRb!(<)-S!U+%rjvC9(iksno-Dw_RL{+j%#g@X#8ASZ%a80@|Rr=Y0T#jm^| z1KAk@n`aB@FxK+}9ht$93U+Ef(6M-(F1rcnFakP|y}-A`^gPr}ICS~J!YDT%;TOW% iV7gGfq`)xwov_sAd%`QZHnS`IVsVCt4#g%eV*&smad_(h diff --git a/frontend/app/admin/dashboard/page.tsx b/frontend/app/admin/dashboard/page.tsx index bd46b43..d4ae516 100644 --- a/frontend/app/admin/dashboard/page.tsx +++ b/frontend/app/admin/dashboard/page.tsx @@ -3,7 +3,9 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; import { BarChart3, Package, Tag, Users, ShoppingBag } from 'lucide-react'; -import { fetchDashboardStats, fetchRecentOrders, fetchPopularProducts, Order, Product } from '@/lib/api'; +import { fetchDashboardStats, fetchRecentOrders, fetchPopularProducts } from '@/lib/admin-api'; +import { Order } from '@/lib/orders'; +import { Product } from '@/lib/catalog'; // Компонент статистической карточки interface StatCardProps { diff --git a/frontend/app/cart-backup/page.tsx b/frontend/app/cart-backup/page.tsx deleted file mode 100644 index 5615695..0000000 --- a/frontend/app/cart-backup/page.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import React from 'react'; -import { useCart } from '@/hooks/useCart'; -import { CartItem } from '@/components/cart/CartItem'; -import { CartSummary } from '@/components/cart/CartSummary'; -import { EmptyCart } from '@/components/cart/EmptyCart'; -import { Button } from '@/components/ui/button'; -import { Trash2, ArrowLeft, Loader2 } from 'lucide-react'; -import Link from 'next/link'; - -export default function CartPage() { - const { - cart, - loading, - error, - updateCartItem, - removeFromCart, - clearCart - } = useCart(); - - const hasItems = cart.items.length > 0; - - const handleUpdateQuantity = async (id: number, quantity: number) => { - await updateCartItem(id, quantity); - }; - - const handleRemoveItem = async (id: number) => { - await removeFromCart(id); - }; - - const handleClearCart = async () => { - await clearCart(); - }; - - if (loading) { - return ( -
- -

Загрузка корзины...

-
- ); - } - - if (error) { - return ( -
-

{error}

- -
- ); - } - - if (!hasItems) { - return ; - } - - return ( -
-
-
-

Корзина

- -
- -
-
- {cart.items.map((item) => ( - - ))} - -
- - - Продолжить покупки - -
-
- -
- -
-
-
-
- ); -} \ No newline at end of file diff --git a/frontend/app/checkout-backup/page.tsx b/frontend/app/checkout-backup/page.tsx deleted file mode 100644 index 0f11992..0000000 --- a/frontend/app/checkout-backup/page.tsx +++ /dev/null @@ -1,205 +0,0 @@ -"use client"; - -import { useState, useEffect } from 'react'; -import { useRouter } from 'next/navigation'; -import { useToast } from '@/components/ui/use-toast'; -import { useCart } from '@/hooks/useCart'; -import { AddressForm } from '@/components/checkout/AddressForm'; -import { PaymentMethodSelector } from '@/components/checkout/PaymentMethodSelector'; -import { CheckoutSummary } from '@/components/checkout/CheckoutSummary'; -import { OrderCreate, OrderItemCreate, PaymentMethod } from '@/types/order'; -import orderService from '@/lib/orders'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Separator } from '@/components/ui/separator'; -import { ArrowLeft, Loader2 } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { useSession } from 'next-auth/react'; -import Link from 'next/link'; - -interface AddressValues { - address_line1: string; - address_line2?: string; - city: string; - state: string; - postal_code: string; - country: string; - is_default: boolean; -} - -export default function CheckoutPage() { - const { data: session, status } = useSession(); - const { cart, loading: cartLoading, error: cartError } = useCart(); - const [address, setAddress] = useState(null); - const [paymentMethod, setPaymentMethod] = useState('credit_card'); - const [activeTab, setActiveTab] = useState('address'); - const [isSubmitting, setIsSubmitting] = useState(false); - const router = useRouter(); - const { toast } = useToast(); - - // Перенаправляем неавторизованных пользователей на страницу входа - useEffect(() => { - if (status === 'unauthenticated') { - router.push('/login?redirect=/checkout'); - } - }, [status, router]); - - // Перенаправляем пользователей с пустой корзиной обратно в корзину - useEffect(() => { - if (!cartLoading && cart && cart.items.length === 0) { - toast({ - title: 'Корзина пуста', - description: 'Перед оформлением заказа добавьте товары в корзину', - variant: 'destructive', - }); - router.push('/cart'); - } - }, [cart, cartLoading, router, toast]); - - if (status === 'loading' || cartLoading) { - return ( -
- -
- ); - } - - if (status === 'unauthenticated') { - return null; // Перенаправление происходит в useEffect - } - - if (cartError) { - return ( -
-
-

Произошла ошибка

-

{cartError}

- - - -
-
- ); - } - - const handleAddressSubmit = (values: AddressValues) => { - setAddress(values); - setActiveTab('payment'); - }; - - const handlePlaceOrder = async () => { - if (!address || !cart) return; - - try { - setIsSubmitting(true); - - // Этот метод для демонстрации. В реальном приложении здесь должно быть создание адреса - // и получение его ID для использования в заказе - const shippingAddressId = 1; // Placeholder ID для демонстрации - - // Создаем массив элементов заказа из товаров в корзине - const orderItems: OrderItemCreate[] = cart.items.map(item => ({ - variant_id: item.variant_id, - quantity: item.quantity - })); - - const orderData: OrderCreate = { - shipping_address_id: shippingAddressId, - payment_method: paymentMethod, - order_items: orderItems - }; - - const response = await orderService.createOrder(orderData); - - toast({ - title: 'Заказ оформлен успешно', - description: `Номер заказа: ${response.order?.id || 'не присвоен'}`, - }); - - // Предполагается, что заказ был создан успешно - router.push(`/orders/${response.order?.id || ''}`); - } catch (error) { - console.error('Ошибка при создании заказа:', error); - toast({ - title: 'Ошибка при оформлении заказа', - description: 'Пожалуйста, попробуйте еще раз или обратитесь в службу поддержки', - variant: 'destructive', - }); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
-
- -

Оформление заказа

-
- -
-
- - - Адрес доставки - Способ оплаты - - - - - Адрес доставки - - - - - - - - - - Способ оплаты - - - - - - - - - -
- -
- {cart && ( - - )} -
-
-
- ); -} \ No newline at end of file diff --git a/frontend/components/.DS_Store b/frontend/components/.DS_Store index fbab188fd808f232c52af51e658d45c693d55dfa..c63e3e01795e31c59e23c83a0d795f0f1aeadb03 100644 GIT binary patch delta 77 zcmZp1XmQwJB*bL2d9sO68j}OV ({ id: variant.id, size_id: variant.size_id, - size_name: variant.size?.name || variant.size?.code || '', + size_name: variant.size?.name || variant.size?.value || '', stock: variant.stock })) .filter((size, index, self) => @@ -116,7 +116,7 @@ export function ProductDetails({ product }: ProductDetailsProps) { product_id: safeProduct.id, variant_id: selectedSize || 0, quantity: quantity - }) + } as any) .then(() => { setAddedToCart(true); toast.success("Товар добавлен в корзину") @@ -138,7 +138,7 @@ export function ProductDetails({ product }: ProductDetailsProps) { product_id: safeProduct.id, variant_id: selectedSize || 0, quantity: quantity - }) + } as any) .then(() => { // Перенаправление на страницу корзины window.location.href = '/cart'; @@ -155,10 +155,11 @@ export function ProductDetails({ product }: ProductDetailsProps) { {safeProduct.variants && safeProduct.variants.length > 0 && (
-

РАЗМЕР

+

РАЗМЕР

@@ -175,13 +176,13 @@ export function ProductDetails({ product }: ProductDetailsProps) { ) : ( - 0 && !selectedSize} - /> + )} @@ -281,7 +285,7 @@ export function ProductDetails({ product }: ProductDetailsProps) {