Skip to content

Commit eaf3eb0

Browse files
ref(files): refactoring code & bag fix (#250)
1 parent fb63cc3 commit eaf3eb0

129 files changed

Lines changed: 1774 additions & 182352 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.coderabbit.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
2+
reviews:
3+
auto_review:
4+
enabled: true
5+
base_branches:
6+
- develop

.github/workflows/shop-janitor-restock-stale.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Shop janitor - restock stale orders
22

33
on:
44
schedule:
5-
- cron: "*/5 * * * *" # every 5 minutes (UTC)
5+
- cron: "*/5 * * * *"
66
workflow_dispatch: {}
77

88
concurrency:
@@ -30,7 +30,6 @@ jobs:
3030
with:
3131
node-version: "20"
3232

33-
# Step-level guard: тут secrets дозволені
3433
- name: Guard config (skip if secrets missing)
3534
id: guard
3635
run: |

frontend/app/[locale]/shop/admin/orders/page.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const metadata: Metadata = {
2020
description: 'View and manage orders in the DevLovers shop catalog.',
2121
};
2222

23-
2423
export const dynamic = 'force-dynamic';
2524

2625
const PAGE_SIZE = 25;
@@ -61,7 +60,6 @@ export default async function AdminOrdersPage({
6160
const page = parsePage(sp.page);
6261
const offset = (page - 1) * PAGE_SIZE;
6362

64-
// overfetch for hasNext without COUNT
6563
const { items: all } = await getAdminOrdersPage({
6664
limit: PAGE_SIZE + 1,
6765
offset,
@@ -147,12 +145,16 @@ export default async function AdminOrdersPage({
147145

148146
<dl className="mt-3 grid grid-cols-2 gap-x-3 gap-y-2 text-xs">
149147
<div>
150-
<dt className="text-muted-foreground">{t('table.items')}</dt>
148+
<dt className="text-muted-foreground">
149+
{t('table.items')}
150+
</dt>
151151
<dd className="text-foreground">{vm.itemCount}</dd>
152152
</div>
153153

154154
<div className="min-w-0">
155-
<dt className="text-muted-foreground">{t('table.provider')}</dt>
155+
<dt className="text-muted-foreground">
156+
{t('table.provider')}
157+
</dt>
156158
<dd
157159
className="truncate text-foreground"
158160
title={vm.paymentProvider}
@@ -162,7 +164,9 @@ export default async function AdminOrdersPage({
162164
</div>
163165

164166
<div className="col-span-2">
165-
<dt className="text-muted-foreground">{t('table.orderId')}</dt>
167+
<dt className="text-muted-foreground">
168+
{t('table.orderId')}
169+
</dt>
166170
<dd
167171
className="break-all font-mono text-[11px] text-muted-foreground"
168172
title={vm.id}

frontend/app/[locale]/shop/admin/products/_components/product-form.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,6 @@ export function ProductForm({
195195
Partial<Record<CurrencyCode, string>>
196196
>({});
197197

198-
// Hydrate state from initialValues once per product in EDIT mode.
199-
// In edit: slug must come from DB and stay stable (no title->slug regeneration).
200198
useEffect(() => {
201199
if (mode !== 'edit') {
202200
hydratedKeyRef.current = null;
@@ -221,8 +219,6 @@ export function ProductForm({
221219

222220
if (hydratedKeyRef.current === key) return;
223221

224-
// Reset transient UI state when switching between products in EDIT mode.
225-
// Do NOT do this in submit: it breaks retries (e.g., clears selected image).
226222
setError(null);
227223
setSlugError(null);
228224
setImageError(null);
@@ -251,8 +247,7 @@ export function ProductForm({
251247
}, [mode, initialValues, productId]);
252248

253249
const slugValue = useMemo(() => {
254-
if (mode === 'edit') return slug; // slug в edit має бути стабільним (з БД)
255-
// In create mode, always derive from current title to avoid stale slug on fast submit.
250+
if (mode === 'edit') return slug;
256251
return localSlugify(title);
257252
}, [mode, slug, title]);
258253

@@ -967,8 +962,8 @@ export function ProductForm({
967962
? 'Creating...'
968963
: 'Updating...'
969964
: mode === 'create'
970-
? 'Create product'
971-
: 'Save changes'}
965+
? 'Create product'
966+
: 'Save changes'}
972967
</button>
973968
</form>
974969
</section>

frontend/app/[locale]/shop/cart/CartPageClient.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,6 @@ export default function CartPage() {
422422
{t('checkout.message')}
423423
</p>
424424

425-
{/* Fallback CTA if navigation fails after order was created */}
426425
{createdOrderId && !checkoutError ? (
427426
<div className="flex justify-center">
428427
<Link

frontend/app/[locale]/shop/checkout/payment/StripePaymentClient.tsx

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import {
99
useElements,
1010
useStripe,
1111
} from '@stripe/react-stripe-js';
12-
import { loadStripe, type StripeElementsOptions, type Stripe } from '@stripe/stripe-js';
12+
import {
13+
loadStripe,
14+
type StripeElementsOptions,
15+
type Stripe,
16+
} from '@stripe/stripe-js';
1317

1418
import {
1519
currencyValues,
@@ -57,11 +61,6 @@ function toCurrencyCode(
5761
: resolveCurrencyFromLocale(locale);
5862
}
5963

60-
/**
61-
* IMPORTANT:
62-
* - In-app navigation uses next-intl routing -> DO NOT prefix locale manually.
63-
* - Stripe return_url is an external redirect -> MUST include locale exactly once.
64-
*/
6564
const IN_APP_SHOP_BASE = '/shop';
6665

6766
function normalizeLocale(raw: string): string {
@@ -73,13 +72,21 @@ function buildInAppPath(path: string): string {
7372
return `${IN_APP_SHOP_BASE}${p}`;
7473
}
7574

76-
function buildStripeReturnUrl(params: { locale: string; inAppPath: string }): string {
75+
function buildStripeReturnUrl(params: {
76+
locale: string;
77+
inAppPath: string;
78+
}): string {
7779
const loc = normalizeLocale(params.locale);
78-
const p = params.inAppPath.startsWith('/') ? params.inAppPath : `/${params.inAppPath}`;
80+
const p = params.inAppPath.startsWith('/')
81+
? params.inAppPath
82+
: `/${params.inAppPath}`;
7983
return new URL(`/${loc}${p}`, window.location.origin).toString();
8084
}
8185

82-
function nextRouteForPaymentResult(params: { orderId: string; status?: string | null }) {
86+
function nextRouteForPaymentResult(params: {
87+
orderId: string;
88+
status?: string | null;
89+
}) {
8390
const { orderId, status } = params;
8491
const id = encodeURIComponent(orderId);
8592

@@ -88,7 +95,11 @@ function nextRouteForPaymentResult(params: { orderId: string; status?: string |
8895

8996
if (!status) return success;
9097

91-
if (status === 'succeeded' || status === 'processing' || status === 'requires_capture') {
98+
if (
99+
status === 'succeeded' ||
100+
status === 'processing' ||
101+
status === 'requires_capture'
102+
) {
92103
return success;
93104
}
94105

@@ -99,7 +110,6 @@ function nextRouteForPaymentResult(params: { orderId: string; status?: string |
99110
return success;
100111
}
101112

102-
/** Unified CTA (hero) */
103113
const SHOP_HERO_CTA = cn(
104114
SHOP_CTA_BASE,
105115
SHOP_CTA_INTERACTIVE,
@@ -110,7 +120,6 @@ const SHOP_HERO_CTA = cn(
110120
'shadow-[var(--shop-hero-btn-shadow)] hover:shadow-[var(--shop-hero-btn-shadow-hover)]'
111121
);
112122

113-
/** Unified outline */
114123
const SHOP_OUTLINE = cn(
115124
SHOP_OUTLINE_BTN_BASE,
116125
SHOP_OUTLINE_BTN_INTERACTIVE,
@@ -122,19 +131,22 @@ const SHOP_OUTLINE = cn(
122131
function HeroCtaInner({ children }: { children: React.ReactNode }) {
123132
return (
124133
<>
125-
{/* base gradient */}
126134
<span
127135
className="absolute inset-0"
128-
style={shopCtaGradient('--shop-hero-btn-bg', '--shop-hero-btn-bg-hover')}
136+
style={shopCtaGradient(
137+
'--shop-hero-btn-bg',
138+
'--shop-hero-btn-bg-hover'
139+
)}
129140
aria-hidden="true"
130141
/>
131-
{/* hover wave overlay */}
132142
<span
133143
className={SHOP_CTA_WAVE}
134-
style={shopCtaGradient('--shop-hero-btn-bg-hover', '--shop-hero-btn-bg')}
144+
style={shopCtaGradient(
145+
'--shop-hero-btn-bg-hover',
146+
'--shop-hero-btn-bg'
147+
)}
135148
aria-hidden="true"
136149
/>
137-
{/* glass inset */}
138150
<span className={SHOP_CTA_INSET} aria-hidden="true" />
139151

140152
<span className="relative z-10">{children}</span>
@@ -155,7 +167,9 @@ function StripePaymentForm({ orderId, locale }: PaymentFormProps) {
155167
setErrorMessage(null);
156168

157169
if (!stripe || !elements) {
158-
setErrorMessage('Payment is not ready yet. Please try again in a moment.');
170+
setErrorMessage(
171+
'Payment is not ready yet. Please try again in a moment.'
172+
);
159173
return;
160174
}
161175

@@ -190,14 +204,20 @@ function StripePaymentForm({ orderId, locale }: PaymentFormProps) {
190204
} catch (error) {
191205
logError('stripe_payment_confirm_failed', error, { orderId });
192206
setErrorMessage('We couldn’t confirm your payment. Please try again.');
193-
router.push(buildInAppPath(`/checkout/error?orderId=${encodeURIComponent(orderId)}`));
207+
router.push(
208+
buildInAppPath(`/checkout/error?orderId=${encodeURIComponent(orderId)}`)
209+
);
194210
} finally {
195211
setSubmitting(false);
196212
}
197213
}
198214

199215
return (
200-
<form onSubmit={handleSubmit} className="space-y-4" aria-label="Stripe payment form">
216+
<form
217+
onSubmit={handleSubmit}
218+
className="space-y-4"
219+
aria-label="Stripe payment form"
220+
>
201221
<PaymentElement />
202222

203223
<button
@@ -206,7 +226,9 @@ function StripePaymentForm({ orderId, locale }: PaymentFormProps) {
206226
className={SHOP_HERO_CTA}
207227
aria-disabled={!stripe || submitting}
208228
>
209-
<HeroCtaInner>{submitting ? 'Processing...' : 'Submit payment'}</HeroCtaInner>
229+
<HeroCtaInner>
230+
{submitting ? 'Processing...' : 'Submit payment'}
231+
</HeroCtaInner>
210232
</button>
211233

212234
{errorMessage ? (
@@ -227,7 +249,10 @@ export default function StripePaymentClient({
227249
currency,
228250
locale,
229251
}: StripePaymentClientProps) {
230-
const uiCurrency = useMemo(() => toCurrencyCode(currency, locale), [currency, locale]);
252+
const uiCurrency = useMemo(
253+
() => toCurrencyCode(currency, locale),
254+
[currency, locale]
255+
);
231256

232257
const stripePromise = useMemo(() => {
233258
if (!paymentsEnabled || !publishableKey) return null;
@@ -244,18 +269,29 @@ export default function StripePaymentClient({
244269

245270
if (!paymentsEnabled) {
246271
return (
247-
<section className="space-y-3 text-sm text-muted-foreground" aria-label="Payments disabled">
272+
<section
273+
className="space-y-3 text-sm text-muted-foreground"
274+
aria-label="Payments disabled"
275+
>
248276
<p>Payments are disabled in this environment.</p>
249277

250-
<nav className="flex flex-col gap-3 sm:flex-row" aria-label="Next steps">
278+
<nav
279+
className="flex flex-col gap-3 sm:flex-row"
280+
aria-label="Next steps"
281+
>
251282
<Link
252-
href={buildInAppPath(`/checkout/success?orderId=${encodeURIComponent(orderId)}`)}
283+
href={buildInAppPath(
284+
`/checkout/success?orderId=${encodeURIComponent(orderId)}`
285+
)}
253286
className={cn(SHOP_HERO_CTA, 'w-full sm:w-auto')}
254287
>
255288
<HeroCtaInner>Continue</HeroCtaInner>
256289
</Link>
257290

258-
<Link href={buildInAppPath('/cart')} className={cn(SHOP_OUTLINE, 'w-full sm:w-auto')}>
291+
<Link
292+
href={buildInAppPath('/cart')}
293+
className={cn(SHOP_OUTLINE, 'w-full sm:w-auto')}
294+
>
259295
Back to cart
260296
</Link>
261297
</nav>
@@ -265,10 +301,16 @@ export default function StripePaymentClient({
265301

266302
if (!clientSecret || !clientSecret.trim()) {
267303
return (
268-
<section className="space-y-3 text-sm text-muted-foreground" aria-label="Payment initialization failed">
304+
<section
305+
className="space-y-3 text-sm text-muted-foreground"
306+
aria-label="Payment initialization failed"
307+
>
269308
<p>Payment cannot be initialized. Please try again later.</p>
270309

271-
<Link href={buildInAppPath('/cart')} className={cn(SHOP_OUTLINE, 'w-full sm:w-auto')}>
310+
<Link
311+
href={buildInAppPath('/cart')}
312+
className={cn(SHOP_OUTLINE, 'w-full sm:w-auto')}
313+
>
272314
Return to cart
273315
</Link>
274316
</section>
@@ -295,7 +337,9 @@ export default function StripePaymentClient({
295337
</span>
296338
</div>
297339

298-
<p className="text-xs uppercase tracking-wide text-muted-foreground">{uiCurrency}</p>
340+
<p className="text-xs uppercase tracking-wide text-muted-foreground">
341+
{uiCurrency}
342+
</p>
299343
</div>
300344

301345
<StripePaymentForm orderId={orderId} locale={locale} />

frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ function HeroCtaLink({
9797
}) {
9898
return (
9999
<Link href={href} className={cn(SHOP_HERO_CTA_SM, className)}>
100-
{/* base gradient */}
101100
<span
102101
className="absolute inset-0"
103102
style={shopCtaGradient(
@@ -115,7 +114,6 @@ function HeroCtaLink({
115114
)}
116115
aria-hidden="true"
117116
/>
118-
{/* glass inset */}
119117
<span className={SHOP_CTA_INSET} aria-hidden="true" />
120118

121119
<span className="relative z-10">{children}</span>

frontend/app/[locale]/shop/checkout/success/OrderStatusAutoRefresh.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,5 @@ export default function OrderStatusAutoRefresh({
3838
return () => window.clearInterval(id);
3939
}, [paymentStatus, router, maxMs, intervalMs]);
4040

41-
// Non-visual utility component (keeps page data fresh while payment settles).
4241
return <span className="sr-only" aria-live="polite" />;
4342
}

0 commit comments

Comments
 (0)