From ecc2e0c0c854bf1adffa88a8a1e0ab591f356e12 Mon Sep 17 00:00:00 2001 From: Viktor Svertoka Date: Sun, 1 Feb 2026 22:53:30 +0200 Subject: [PATCH] ref(files): refactoring code & bag fix --- .coderabbit.yaml | 6 + .../workflows/shop-janitor-restock-stale.yml | 3 +- .../app/[locale]/shop/admin/orders/page.tsx | 14 +- .../products/_components/product-form.tsx | 11 +- .../app/[locale]/shop/cart/CartPageClient.tsx | 1 - .../checkout/payment/StripePaymentClient.tsx | 102 +- .../shop/checkout/payment/[orderId]/page.tsx | 2 - .../success/OrderStatusAutoRefresh.tsx | 1 - .../[locale]/shop/checkout/success/page.tsx | 5 - .../app/[locale]/shop/orders/[id]/page.tsx | 1 - frontend/app/[locale]/shop/orders/page.tsx | 3 - frontend/app/[locale]/shop/page.tsx | 6 +- .../[locale]/shop/products/[slug]/page.tsx | 13 +- frontend/app/[locale]/shop/products/page.tsx | 1 - frontend/app/api/ai/explain/route.ts | 42 +- .../app/api/shop/admin/orders/[id]/route.ts | 6 - .../admin/orders/reconcile-stale/route.ts | 9 - .../app/api/shop/admin/products/[id]/route.ts | 14 +- .../shop/admin/products/[id]/status/route.ts | 2 - frontend/app/api/shop/cart/rehydrate/route.ts | 4 - frontend/app/api/shop/checkout/route.ts | 19 +- .../internal/orders/restock-stale/route.ts | 24 +- .../app/api/shop/webhooks/stripe/route.ts | 57 +- frontend/checkout.json | 1 - frontend/components/q&a/AIWordHelper.tsx | 573 +- frontend/components/q&a/AccordionList.tsx | 6 +- frontend/components/q&a/SelectableText.tsx | 2 - frontend/components/quiz/CountdownTimer.tsx | 32 +- frontend/components/quiz/QuizContainer.tsx | 265 +- frontend/components/shared/HeaderButton.tsx | 4 - .../components/shop/add-to-cart-button.tsx | 7 +- .../shop/admin/admin-pagination.tsx | 4 +- .../admin/admin-product-delete-button.tsx | 1 - frontend/components/shop/cart-provider.tsx | 5 - frontend/components/shop/product-card.tsx | 4 +- frontend/components/shop/product-filters.tsx | 5 - frontend/components/shop/product-sort.tsx | 3 - frontend/components/shop/products-toolbar.tsx | 6 - frontend/components/shop/shop-hero.tsx | 3 - frontend/components/ui/button.tsx | 72 +- frontend/components/ui/particle-canvas.tsx | 474 +- frontend/db/queries/quiz.ts | 7 +- frontend/db/schema/shop.ts | 18 +- frontend/db/seed-quiz-angular-advanced.ts | 128 +- frontend/db/seed-quiz-angular.ts | 118 +- frontend/db/seed-quiz-css-advanced.ts | 125 +- frontend/db/seed-quiz-css.ts | 117 +- frontend/db/seed-quiz-html-advanced.ts | 117 +- frontend/db/seed-quiz-html.ts | 117 +- frontend/db/seed-quiz-javascript-advanced.ts | 120 +- frontend/db/seed-quiz-javascript.ts | 119 +- frontend/db/seed-quiz-nodejs-advanced.ts | 128 +- frontend/db/seed-quiz-nodejs.ts | 126 +- frontend/db/seed-quiz-react.ts | 1 - frontend/db/seed-quiz-typescript.ts | 121 +- frontend/parse/README.md | 5827 -- frontend/parse/questions.json | 13419 ----- frontend/parse/start.json | 19314 ------- .../typescript-advanced-quiz-part1.json | 434 - .../typescript-advanced-quiz-part2.json | 434 - .../typescript-advanced-quiz-part3.json | 434 - .../typescript-advanced-quiz-part4.json | 434 - .../fundamentals/typescript-quiz-part1.json | 434 - .../fundamentals/typescript-quiz-part2.json | 434 - .../fundamentals/typescript-quiz-part3.json | 434 - .../fundamentals/typescript-quiz-part4.json | 434 - frontend/project-structure.txt | 649 - .../scripts/shop-janitor-restock-stale.mjs | 7 - json/00-git.json | 25516 -------- json/01-html.json | 17864 ------ json/03-javascript.json | 48089 ---------------- json/05-react.json | 25165 -------- .../advanced/angular-advanced-quiz-part1.json | 434 - .../advanced/angular-advanced-quiz-part2.json | 434 - .../advanced/angular-advanced-quiz-part3.json | 434 - .../advanced/angular-advanced-quiz-part4.json | 434 - .../advanced/seed-quiz-angular-advanced.ts | 240 - .../beginner_medium/angular-quiz-part1.json | 434 - .../beginner_medium/angular-quiz-part2.json | 434 - .../beginner_medium/angular-quiz-part3.json | 434 - .../beginner_medium/angular-quiz-part4.json | 434 - .../beginner_medium/seed-quiz-angular.ts | 240 - .../css/advanced/css-advanced-quiz-part1.json | 234 - .../css/advanced/css-advanced-quiz-part2.json | 234 - .../css/advanced/css-advanced-quiz-part3.json | 234 - .../css/advanced/css-advanced-quiz-part4.json | 234 - .../css/advanced/seed-quiz-css-advanced.ts | 240 - .../css/fundamentals/css-quiz-part1.json | 434 - .../css/fundamentals/css-quiz-part2.json | 434 - .../css/fundamentals/css-quiz-part3.json | 434 - .../css/fundamentals/css-quiz-part4.json | 434 - .../quizzes/css/fundamentals/seed-quiz-css.ts | 240 - json/quizzes/git/git-quiz-part1.json | 434 - json/quizzes/git/git-quiz-part2.json | 434 - json/quizzes/git/git-quiz-part3.json | 434 - json/quizzes/git/git-quiz-part4.json | 434 - .../advanced/html-advanced-quiz-part1.json | 434 - .../advanced/html-advanced-quiz-part2.json | 434 - .../advanced/html-advanced-quiz-part3.json | 434 - .../advanced/html-advanced-quiz-part4.json | 434 - .../html/fundamentals/html-quiz-part1.json | 434 - .../html/fundamentals/html-quiz-part2.json | 434 - .../html/fundamentals/html-quiz-part3.json | 434 - .../html/fundamentals/html-quiz-part4.json | 434 - .../javascript-advanced-quiz-part1.json | 434 - .../javascript-advanced-quiz-part2.json | 434 - .../javascript-advanced-quiz-part3.json | 144 - .../javascript-advanced-quiz-part4.json | 144 - .../fundamentals/javascript-quiz-part1.json | 434 - .../fundamentals/javascript-quiz-part2.json | 434 - .../fundamentals/javascript-quiz-part3.json | 434 - .../fundamentals/javascript-quiz-part4.json | 434 - .../advanced/nodejs-advanced-quiz-part1.json | 434 - .../advanced/nodejs-advanced-quiz-part2.json | 434 - .../advanced/nodejs-advanced-quiz-part3.json | 434 - .../advanced/nodejs-advanced-quiz-part4.json | 434 - .../advanced/seed-quiz-nodejs-advanced.ts | 240 - .../beginner_medium/nodejs-quiz-part1.json | 434 - .../beginner_medium/nodejs-quiz-part2.json | 434 - .../beginner_medium/nodejs-quiz-part3.json | 434 - .../beginner_medium/nodejs-quiz-part4.json | 434 - .../seed-quiz-nodejs-fundamentals.ts | 240 - json/quizzes/react/react-quiz-data-part1.json | 254 - json/quizzes/react/react-quiz-data-part2.json | 244 - .../vue/beginner_medium/seed-quiz-vue.ts | 239 - .../vue/beginner_medium/vue-quiz-part1.json | 434 - .../vue/beginner_medium/vue-quiz-part2.json | 434 - .../vue/beginner_medium/vue-quiz-part3.json | 434 - .../vue/beginner_medium/vue-quiz-part4.json | 434 - 129 files changed, 1774 insertions(+), 182352 deletions(-) create mode 100644 .coderabbit.yaml delete mode 100644 frontend/checkout.json delete mode 100644 frontend/parse/start.json delete mode 100644 frontend/parse/typescript/advanced/typescript-advanced-quiz-part1.json delete mode 100644 frontend/parse/typescript/advanced/typescript-advanced-quiz-part2.json delete mode 100644 frontend/parse/typescript/advanced/typescript-advanced-quiz-part3.json delete mode 100644 frontend/parse/typescript/advanced/typescript-advanced-quiz-part4.json delete mode 100644 frontend/parse/typescript/fundamentals/typescript-quiz-part1.json delete mode 100644 frontend/parse/typescript/fundamentals/typescript-quiz-part2.json delete mode 100644 frontend/parse/typescript/fundamentals/typescript-quiz-part3.json delete mode 100644 frontend/parse/typescript/fundamentals/typescript-quiz-part4.json delete mode 100644 frontend/project-structure.txt delete mode 100644 json/00-git.json delete mode 100644 json/01-html.json delete mode 100644 json/03-javascript.json delete mode 100644 json/05-react.json delete mode 100644 json/quizzes/angular/advanced/angular-advanced-quiz-part1.json delete mode 100644 json/quizzes/angular/advanced/angular-advanced-quiz-part2.json delete mode 100644 json/quizzes/angular/advanced/angular-advanced-quiz-part3.json delete mode 100644 json/quizzes/angular/advanced/angular-advanced-quiz-part4.json delete mode 100644 json/quizzes/angular/advanced/seed-quiz-angular-advanced.ts delete mode 100644 json/quizzes/angular/beginner_medium/angular-quiz-part1.json delete mode 100644 json/quizzes/angular/beginner_medium/angular-quiz-part2.json delete mode 100644 json/quizzes/angular/beginner_medium/angular-quiz-part3.json delete mode 100644 json/quizzes/angular/beginner_medium/angular-quiz-part4.json delete mode 100644 json/quizzes/angular/beginner_medium/seed-quiz-angular.ts delete mode 100644 json/quizzes/css/advanced/css-advanced-quiz-part1.json delete mode 100644 json/quizzes/css/advanced/css-advanced-quiz-part2.json delete mode 100644 json/quizzes/css/advanced/css-advanced-quiz-part3.json delete mode 100644 json/quizzes/css/advanced/css-advanced-quiz-part4.json delete mode 100644 json/quizzes/css/advanced/seed-quiz-css-advanced.ts delete mode 100644 json/quizzes/css/fundamentals/css-quiz-part1.json delete mode 100644 json/quizzes/css/fundamentals/css-quiz-part2.json delete mode 100644 json/quizzes/css/fundamentals/css-quiz-part3.json delete mode 100644 json/quizzes/css/fundamentals/css-quiz-part4.json delete mode 100644 json/quizzes/css/fundamentals/seed-quiz-css.ts delete mode 100644 json/quizzes/git/git-quiz-part1.json delete mode 100644 json/quizzes/git/git-quiz-part2.json delete mode 100644 json/quizzes/git/git-quiz-part3.json delete mode 100644 json/quizzes/git/git-quiz-part4.json delete mode 100644 json/quizzes/html/advanced/html-advanced-quiz-part1.json delete mode 100644 json/quizzes/html/advanced/html-advanced-quiz-part2.json delete mode 100644 json/quizzes/html/advanced/html-advanced-quiz-part3.json delete mode 100644 json/quizzes/html/advanced/html-advanced-quiz-part4.json delete mode 100644 json/quizzes/html/fundamentals/html-quiz-part1.json delete mode 100644 json/quizzes/html/fundamentals/html-quiz-part2.json delete mode 100644 json/quizzes/html/fundamentals/html-quiz-part3.json delete mode 100644 json/quizzes/html/fundamentals/html-quiz-part4.json delete mode 100644 json/quizzes/js/advanced/javascript-advanced-quiz-part1.json delete mode 100644 json/quizzes/js/advanced/javascript-advanced-quiz-part2.json delete mode 100644 json/quizzes/js/advanced/javascript-advanced-quiz-part3.json delete mode 100644 json/quizzes/js/advanced/javascript-advanced-quiz-part4.json delete mode 100644 json/quizzes/js/fundamentals/javascript-quiz-part1.json delete mode 100644 json/quizzes/js/fundamentals/javascript-quiz-part2.json delete mode 100644 json/quizzes/js/fundamentals/javascript-quiz-part3.json delete mode 100644 json/quizzes/js/fundamentals/javascript-quiz-part4.json delete mode 100644 json/quizzes/node/advanced/nodejs-advanced-quiz-part1.json delete mode 100644 json/quizzes/node/advanced/nodejs-advanced-quiz-part2.json delete mode 100644 json/quizzes/node/advanced/nodejs-advanced-quiz-part3.json delete mode 100644 json/quizzes/node/advanced/nodejs-advanced-quiz-part4.json delete mode 100644 json/quizzes/node/advanced/seed-quiz-nodejs-advanced.ts delete mode 100644 json/quizzes/node/beginner_medium/nodejs-quiz-part1.json delete mode 100644 json/quizzes/node/beginner_medium/nodejs-quiz-part2.json delete mode 100644 json/quizzes/node/beginner_medium/nodejs-quiz-part3.json delete mode 100644 json/quizzes/node/beginner_medium/nodejs-quiz-part4.json delete mode 100644 json/quizzes/node/beginner_medium/seed-quiz-nodejs-fundamentals.ts delete mode 100644 json/quizzes/react/react-quiz-data-part1.json delete mode 100644 json/quizzes/react/react-quiz-data-part2.json delete mode 100644 json/quizzes/vue/beginner_medium/seed-quiz-vue.ts delete mode 100644 json/quizzes/vue/beginner_medium/vue-quiz-part1.json delete mode 100644 json/quizzes/vue/beginner_medium/vue-quiz-part2.json delete mode 100644 json/quizzes/vue/beginner_medium/vue-quiz-part3.json delete mode 100644 json/quizzes/vue/beginner_medium/vue-quiz-part4.json diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..0867ee01 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +reviews: + auto_review: + enabled: true + base_branches: + - develop diff --git a/.github/workflows/shop-janitor-restock-stale.yml b/.github/workflows/shop-janitor-restock-stale.yml index a5b70d6c..c304db38 100644 --- a/.github/workflows/shop-janitor-restock-stale.yml +++ b/.github/workflows/shop-janitor-restock-stale.yml @@ -2,7 +2,7 @@ name: Shop janitor - restock stale orders on: schedule: - - cron: "*/5 * * * *" # every 5 minutes (UTC) + - cron: "*/5 * * * *" workflow_dispatch: {} concurrency: @@ -30,7 +30,6 @@ jobs: with: node-version: "20" - # Step-level guard: тут secrets дозволені - name: Guard config (skip if secrets missing) id: guard run: | diff --git a/frontend/app/[locale]/shop/admin/orders/page.tsx b/frontend/app/[locale]/shop/admin/orders/page.tsx index 7979aa7f..460f55a6 100644 --- a/frontend/app/[locale]/shop/admin/orders/page.tsx +++ b/frontend/app/[locale]/shop/admin/orders/page.tsx @@ -20,7 +20,6 @@ export const metadata: Metadata = { description: 'View and manage orders in the DevLovers shop catalog.', }; - export const dynamic = 'force-dynamic'; const PAGE_SIZE = 25; @@ -61,7 +60,6 @@ export default async function AdminOrdersPage({ const page = parsePage(sp.page); const offset = (page - 1) * PAGE_SIZE; - // overfetch for hasNext without COUNT const { items: all } = await getAdminOrdersPage({ limit: PAGE_SIZE + 1, offset, @@ -147,12 +145,16 @@ export default async function AdminOrdersPage({
-
{t('table.items')}
+
+ {t('table.items')} +
{vm.itemCount}
-
{t('table.provider')}
+
+ {t('table.provider')} +
-
{t('table.orderId')}
+
+ {t('table.orderId')} +
> >({}); - // Hydrate state from initialValues once per product in EDIT mode. - // In edit: slug must come from DB and stay stable (no title->slug regeneration). useEffect(() => { if (mode !== 'edit') { hydratedKeyRef.current = null; @@ -221,8 +219,6 @@ export function ProductForm({ if (hydratedKeyRef.current === key) return; - // Reset transient UI state when switching between products in EDIT mode. - // Do NOT do this in submit: it breaks retries (e.g., clears selected image). setError(null); setSlugError(null); setImageError(null); @@ -251,8 +247,7 @@ export function ProductForm({ }, [mode, initialValues, productId]); const slugValue = useMemo(() => { - if (mode === 'edit') return slug; // slug в edit має бути стабільним (з БД) - // In create mode, always derive from current title to avoid stale slug on fast submit. + if (mode === 'edit') return slug; return localSlugify(title); }, [mode, slug, title]); @@ -967,8 +962,8 @@ export function ProductForm({ ? 'Creating...' : 'Updating...' : mode === 'create' - ? 'Create product' - : 'Save changes'} + ? 'Create product' + : 'Save changes'} diff --git a/frontend/app/[locale]/shop/cart/CartPageClient.tsx b/frontend/app/[locale]/shop/cart/CartPageClient.tsx index acf8dc50..9fb12085 100644 --- a/frontend/app/[locale]/shop/cart/CartPageClient.tsx +++ b/frontend/app/[locale]/shop/cart/CartPageClient.tsx @@ -422,7 +422,6 @@ export default function CartPage() { {t('checkout.message')}

- {/* Fallback CTA if navigation fails after order was created */} {createdOrderId && !checkoutError ? (
DO NOT prefix locale manually. - * - Stripe return_url is an external redirect -> MUST include locale exactly once. - */ const IN_APP_SHOP_BASE = '/shop'; function normalizeLocale(raw: string): string { @@ -73,13 +72,21 @@ function buildInAppPath(path: string): string { return `${IN_APP_SHOP_BASE}${p}`; } -function buildStripeReturnUrl(params: { locale: string; inAppPath: string }): string { +function buildStripeReturnUrl(params: { + locale: string; + inAppPath: string; +}): string { const loc = normalizeLocale(params.locale); - const p = params.inAppPath.startsWith('/') ? params.inAppPath : `/${params.inAppPath}`; + const p = params.inAppPath.startsWith('/') + ? params.inAppPath + : `/${params.inAppPath}`; return new URL(`/${loc}${p}`, window.location.origin).toString(); } -function nextRouteForPaymentResult(params: { orderId: string; status?: string | null }) { +function nextRouteForPaymentResult(params: { + orderId: string; + status?: string | null; +}) { const { orderId, status } = params; const id = encodeURIComponent(orderId); @@ -88,7 +95,11 @@ function nextRouteForPaymentResult(params: { orderId: string; status?: string | if (!status) return success; - if (status === 'succeeded' || status === 'processing' || status === 'requires_capture') { + if ( + status === 'succeeded' || + status === 'processing' || + status === 'requires_capture' + ) { return success; } @@ -99,7 +110,6 @@ function nextRouteForPaymentResult(params: { orderId: string; status?: string | return success; } -/** Unified CTA (hero) */ const SHOP_HERO_CTA = cn( SHOP_CTA_BASE, SHOP_CTA_INTERACTIVE, @@ -110,7 +120,6 @@ const SHOP_HERO_CTA = cn( 'shadow-[var(--shop-hero-btn-shadow)] hover:shadow-[var(--shop-hero-btn-shadow-hover)]' ); -/** Unified outline */ const SHOP_OUTLINE = cn( SHOP_OUTLINE_BTN_BASE, SHOP_OUTLINE_BTN_INTERACTIVE, @@ -122,19 +131,22 @@ const SHOP_OUTLINE = cn( function HeroCtaInner({ children }: { children: React.ReactNode }) { return ( <> - {/* base gradient */}
-

{uiCurrency}

+

+ {uiCurrency} +

diff --git a/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx b/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx index 182dc50d..76f1139c 100644 --- a/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx +++ b/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx @@ -97,7 +97,6 @@ function HeroCtaLink({ }) { return ( - {/* base gradient */}