Skip to content

Conversation

@Innocent-Akim
Copy link
Contributor

@Innocent-Akim Innocent-Akim commented Dec 30, 2025

Security improvements:

  • Add URL validation to prevent open redirect vulnerabilities in checkout routes
  • Use generic error messages for 500 responses to avoid exposing sensitive details
  • Add JSON parsing validation with proper 400 responses for malformed requests
  • Validate item existence before recording views to prevent data pollution
  • Add customer ID validation for Polar checkout (consistent with Stripe)

Bug fixes:

  • Fix pagination issues (restore standard pagination UI, fix page reset on search)
  • Fix webhook handlers to properly detect sponsor ad renewals (check subscription metadata)
  • Fix admin stats to show global counts instead of page-scoped counts
  • Fix modal closing logic to check operation success before closing
  • Fix collection card spinner to only show on regular left-click navigation
  • Fix item view recording to validate item existence for all users
  • Fix limit parameter validation to handle NaN and invalid values properly
  • Fix isActive consistency in admin collections page

i18n improvements:

  • Replace hardcoded strings with translation keys in collection components
  • Add missing translations for aria-labels and status messages

Code quality:

  • Remove unused props and imports
  • Improve error handling consistency across API routes
  • Add proper existence checks before database operations
  • Fix cache revalidation to use correct identifiers (slug vs id)

Affected areas:

  • API routes (sponsor ads, collections, items, payments, webhooks)
  • Admin pages (collections, sponsorships)
  • UI components (collections, categories, billing)
  • Webhook handlers (Stripe, Polar)

Summary

Related Issues

Description

Changes Made

  • [ ]
  • [ ]
  • [ ]

Screenshots / Videos

Type of Change

  • 🐛 Bug fix (non-breaking change addressing an issue)
  • ✨ New feature (non-breaking change adding functionality)
  • 💥 Breaking change (fix or feature causing existing functionality to change)
  • 📝 Documentation update
  • 🔧 Configuration change
  • 🧪 Test addition or modification
  • 🛠️ CI/CD infrastructure improvement
  • ♻️ Refactoring (no functional changes)
  • 🎨 Code style/formatting update
  • 🚀 Performance improvement

Implementation Details

Testing Instructions

Deployment Notes

Checklist

  • I have performed a self-review of my code
  • I have added/updated tests that prove my fix is effective or my feature works
  • I have updated relevant documentation
  • My changes generate no new warnings or errors
  • I have verified my changes in multiple browsers/environments (if applicable)
  • New and existing unit tests pass locally with my changes
  • I have made corresponding changes to the documentation
  • My code follows the established code style of the project
  • I have commented my code, particularly in hard-to-understand areas

Additional Context


Summary by cubic

Strengthens security across checkout, webhooks, and API routes, fixes pagination and admin inconsistencies, and aligns UI with i18n. Also prevents bad analytics by validating items before counting views.

  • Bug Fixes

    • Restored standard pagination and reset to page 1 on search.
    • Webhooks correctly detect sponsor ad renewals via subscription metadata (Stripe/Polar).
    • Admin fixes: global stats (not page-scoped), consistent isActive in collections, modal closes only after success.
    • UI fixes: collection card spinner only on regular left-click navigation.
    • API fixes: validate limit param (NaN/invalid), and verify item exists before recording a view.
  • Refactors

    • Security: validate checkout URLs to block open redirects, validate customer IDs for Polar, parse JSON with 400s on errors, use generic 500 responses, and add existence checks before DB ops.
    • i18n: replaced hardcoded strings in collections and added missing aria-label/status translations.
    • Consistency: standardized API error handling and fixed cache revalidation to use the right identifier (slug vs id).

Written for commit 024e951. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Admin collections: global statistics, improved create/edit flows, assign-items modal enhancements.
    • Categories grid: standard pagination UI added.
  • Bug Fixes

    • Navigation spinner only on primary clicks.
    • Better API error handling and invalid-JSON detection; clearer 404/400 responses.
  • Enhancements

    • Expanded localization keys for collection and listing UI.
    • Stronger redirect validation for sponsor checkout and refined sponsor-ad flows.
    • Sponsorship filters: always reset page on search and added clear-filters action.

✏️ Tip: You can customize this high-level summary in your review settings.

Security improvements:
- Add URL validation to prevent open redirect vulnerabilities in checkout routes
- Use generic error messages for 500 responses to avoid exposing sensitive details
- Add JSON parsing validation with proper 400 responses for malformed requests
- Validate item existence before recording views to prevent data pollution
- Add customer ID validation for Polar checkout (consistent with Stripe)

Bug fixes:
- Fix pagination issues (restore standard pagination UI, fix page reset on search)
- Fix webhook handlers to properly detect sponsor ad renewals (check subscription metadata)
- Fix admin stats to show global counts instead of page-scoped counts
- Fix modal closing logic to check operation success before closing
- Fix collection card spinner to only show on regular left-click navigation
- Fix item view recording to validate item existence for all users
- Fix limit parameter validation to handle NaN and invalid values properly
- Fix isActive consistency in admin collections page

i18n improvements:
- Replace hardcoded strings with translation keys in collection components
- Add missing translations for aria-labels and status messages

Code quality:
- Remove unused props and imports
- Improve error handling consistency across API routes
- Add proper existence checks before database operations
- Fix cache revalidation to use correct identifiers (slug vs id)

Affected areas:
- API routes (sponsor ads, collections, items, payments, webhooks)
- Admin pages (collections, sponsorships)
- UI components (collections, categories, billing)
- Webhook handlers (Stripe, Polar)
@vercel
Copy link

vercel bot commented Dec 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
ever-works-website-template Ready Ready Preview, Comment Dec 31, 2025 7:02pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors admin collections UI data fetching and modal flows, tightens admin API auth/validation and error handling, simplifies category listing props/pagination, adds i18n keys and localized strings, and hardens multiple public/payment/sponsor-ad routes with JSON/URL validation and improved webhook handling.

Changes

Cohort / File(s) Summary
Admin collections UI
app/[locale]/admin/collections/page.tsx
Added server/client queries for full-collection aggregates alongside paginated listing; introduced create/edit/assign modal handlers, wired create/update/assign API calls, and adjusted UI structure and state flows.
Admin collections API
app/api/admin/collections/route.ts, app/api/admin/collections/[id]/route.ts
Added admin-auth guards, JSON-parse guard for POST, stricter page/limit parsing (GET), clearer 400/404/409/500 error distinctions, and slug-based cache invalidation (old/new slug) on update/delete.
Admin sponsorships UI
app/[locale]/admin/sponsorships/page.tsx
Normalized callbacks with useCallback, always reset page on search change, added clear-filters helper, and make force-approve wait for success before closing modal.
Sponsor-ads APIs & checkout
app/api/admin/sponsor-ads/*, app/api/sponsor-ads/*, app/api/sponsor-ads/checkout/route.ts
Tightened request parsing/validation, added redirect URL validation in checkout, introduced "pending_payment" status, normalized error handling/messages, and adjusted provider-specific checkout flows.
Categories listing & page
app/[locale]/categories/listing-categories.tsx, app/[locale]/categories/page.tsx
Removed pagination props/data (tags/items/total) from listing props and page data flow; simplified ListingCategoriesProps and HomeTwoLayout signature; stopped passing paginated totals to listing components.
Categories grid component
components/categories-grid.tsx
Added optional standard pagination (totalPages calc, UniversalPagination, handlePageChange), synchronized page state with URL/searchParams, preserved infinite pagination path.
Public APIs & payment routes
app/api/items/[slug]/views/route.ts, app/api/payment/[subscriptionId]/route.ts, app/api/extract/route.ts
Added pre-check for item existence and owner-exclusion consolidation, wrapped JSON parsing in try/catch with 400 on invalid JSON, improved external API error parsing to avoid non-JSON failures.
Webhooks & billing handlers
app/api/polar/webhook/handlers.ts, app/api/stripe/webhook/route.ts
Made sponsor-ad service calls throw on failure (explicit try/catch), improved logging, and extended Stripe metadata inspection to detect sponsor-ad IDs across multiple payload shapes.
Components i18n & UX
components/admin/collections/assign-items-modal.tsx, components/collections/collection-card.tsx, components/collections/collection-detail.tsx, components/billing/expired-plan-banner.tsx, components/collections/*
Introduced useTranslations and new translation keys, replaced hardcoded strings, updated collection UIs, added primary-click-only navigation spinner handling, and reorganized JSX/layouts.
Locale messages
messages/en.json
Added collection-management and common UI translation keys (VIEW_COLLECTION, ADD/CREATE/EDIT_COLLECTION, loading/empty states, assign/save/delete labels, etc.).

Sequence Diagram(s)

(Skipped — changes span multiple independent UI/API improvements rather than a single new multi-component sequential flow.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • evereq
  • ariefgp
  • paradoxe35

Poem

"🐇 I hopped through lines of code so bright,
Tidied routes and modals late at night.
Collections counted, translations spun,
Pages paged and webs were won—
A carrot-toast to fixes done! 🥕"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.63% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Review AI suggestions for PRs #443 and #444' is vague and does not clearly describe the actual changes made. It references reviewing external PRs rather than summarizing the primary change. Revise the title to clearly summarize the main change, such as 'Apply security hardening, bug fixes, and i18n updates across API routes and components' or 'Add checkout URL validation, improve error handling, and update translations'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/review-ai-suggestions-pr-443-444

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Innocent-Akim Innocent-Akim changed the title fix: security improvements, bug fixes, and i18n consistency Review AI suggestions for PRs #443 and #444 Dec 30, 2025
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 23 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="app/api/sponsor-ads/checkout/route.ts">

<violation number="1" location="app/api/sponsor-ads/checkout/route.ts:235">
P1: The error message is exposed to clients in 500 responses, which contradicts the PR&#39;s stated security improvement. Internal error messages may reveal sensitive details like database errors or file paths. Use a generic message instead.</violation>
</file>

<file name="app/api/payment/[subscriptionId]/route.ts">

<violation number="1" location="app/api/payment/[subscriptionId]/route.ts:30">
P2: Destructuring `body` without validating it&#39;s a non-null object can throw if the JSON body is `null`, causing a 500 error instead of the intended 400. Consider adding an object validation check before destructuring.</violation>
</file>

<file name="components/collections/collection-card.tsx">

<violation number="1" location="components/collections/collection-card.tsx:109">
P2: Hardcoded string &#39;View collection&#39; should use the translation system for i18n consistency. The PR description mentions replacing hardcoded strings with translation keys in collection components, but this string was missed. Consider using `t(&#39;VIEW_COLLECTION&#39;)` or similar.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
components/billing/expired-plan-banner.tsx (1)

267-267: Hardcoded fallback string not i18n-friendly.

The fallback 'Subscription' should use a translation key for consistency with the i18n changes in this PR.

-	const planName = PAYMENT_PLAN_NAMES[planStatus.planId as PaymentPlan] || 'Subscription';
+	const planName = PAYMENT_PLAN_NAMES[planStatus.planId as PaymentPlan] || t('DEFAULT_SUBSCRIPTION_NAME');

Based on learnings, avoid hard-coded English strings; use next-intl messages where relevant.

app/api/payment/[subscriptionId]/route.ts (1)

23-35: Consider using Zod for type-safe request validation.

The codebase consistently uses Zod for API request validation (stripe, lemonsqueezy, and sponsor-ad routes all follow this pattern). Replace the manual type checking with a schema-based approach.

Define a schema in lib/validations/payment.ts:

Zod schema approach
import { z } from 'zod';
import { PaymentProvider } from '@/lib/constants';

export const updateSubscriptionPaymentSchema = z.object({
	enabled: z.boolean(),
	paymentProvider: z.nativeEnum(PaymentProvider).optional().default(PaymentProvider.STRIPE)
});

Then in the route handler:

-		let body: unknown;
-		try {
-			body = await request.json();
-		} catch (parseError) {
-			return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 });
-		}
-
-		const { enabled, paymentProvider } = body as { enabled?: unknown; paymentProvider?: string };
-		const provider = paymentProvider || 'stripe';
-
-		if (typeof enabled !== 'boolean') {
-			return NextResponse.json({ error: 'Invalid request body. "enabled" must be a boolean.' }, { status: 400 });
-		}
-
-		const validProviders = Object.values(PaymentProvider) as string[];
-		if (!validProviders.includes(provider)) {
-			return NextResponse.json(
-				{ error: `Invalid payment provider. Must be one of: ${validProviders.join(', ')}` },
-				{ status: 400 }
-			);
-		}
+		const body = await request.json();
+		const result = updateSubscriptionPaymentSchema.safeParse(body);
+		
+		if (!result.success) {
+			return NextResponse.json(
+				{ error: 'Invalid request body', details: result.error.format() },
+				{ status: 400 }
+			);
+		}
+		
+		const { enabled, paymentProvider: provider } = result.data;
🧹 Nitpick comments (12)
app/api/extract/route.ts (1)

152-153: Consider explicit error handling for successful response parsing (optional).

The successful response JSON parsing relies on the outer try-catch for error handling. While this is acceptable and aligns with the overall error handling pattern, adding explicit error handling here would provide clearer error messages if the platform API returns an invalid JSON response for a successful status code.

However, since the platform API should always return valid JSON for successful responses, the current approach is adequate.

🔎 Optional: Add explicit error handling
-		const data = await response.json();
-		return NextResponse.json(data);
+		try {
+			const data = await response.json();
+			return NextResponse.json(data);
+		} catch {
+			return NextResponse.json(
+				{ success: false, error: 'Invalid response format from platform API' },
+				{ status: 500 }
+			);
+		}
app/api/admin/collections/[id]/route.ts (1)

247-249: Redundant null check on beforeUpdate.

beforeUpdate is guaranteed to be truthy at this point due to the check at line 239-241. The condition can be simplified.

🔎 Suggested simplification
-		if (beforeUpdate && beforeUpdate.slug !== (updateData.slug || id)) {
+		if (beforeUpdate.slug !== (updateData.slug || id)) {
			revalidatePath(`/collections/${beforeUpdate.slug}`); // old slug
		}
app/[locale]/categories/listing-categories.tsx (2)

19-19: Unused paginationType destructured from hook.

paginationType is destructured but no longer used after pagination removal. Consider removing it to avoid confusion.

-	const { layoutHome = LayoutHome.HOME_ONE, paginationType } = useLayoutTheme();
+	const { layoutHome = LayoutHome.HOME_ONE } = useLayoutTheme();

51-109: Consider extracting duplicate breadcrumb component.

HomeOneLayout and HomeTwoLayout contain nearly identical breadcrumb markup (lines 57-99 and 117-159). Consider extracting a shared CategoriesBreadcrumb component to reduce duplication and improve maintainability.

Also applies to: 111-165

app/[locale]/admin/collections/page.tsx (2)

44-44: Magic number for limit may not scale.

The hardcoded limit=1000 assumes collections won't exceed this count. Consider extracting to a constant or using a dedicated stats endpoint.

+const STATS_FETCH_LIMIT = 1000;
+
 const { data: allCollectionsData } = useQuery({
   queryKey: ['admin', 'collections', 'stats'],
   queryFn: async () => {
     const response = await serverClient.get<{
       success: boolean;
       collections: Collection[];
       total: number;
-    }>('/api/admin/collections?limit=1000&includeInactive=true');
+    }>(`/api/admin/collections?limit=${STATS_FETCH_LIMIT}&includeInactive=true`);

93-98: Native confirm() blocks the UI.

Using window.confirm() is functional but blocks the main thread. Consider using a confirmation modal component for better UX consistency with the rest of the admin interface.

app/api/admin/sponsor-ads/route.ts (1)

121-125: Consider using structured logging instead of console.log.

These debug logs expose potentially sensitive data (full query results and stats). Consider using the structured Logger class from @/lib/logger with appropriate log levels, or remove these logs before merging to production.

app/api/stripe/webhook/route.ts (1)

625-655: Consider extracting shared metadata resolution logic.

Both isSponsorAdSubscription and getSponsorAdId duplicate the same metadata extraction pattern. Consider extracting a helper function to reduce duplication and ensure consistent behavior.

🔎 Proposed refactor
+/**
+ * Extract metadata from various possible locations in Stripe webhook data
+ */
+function extractMetadata(data: Record<string, unknown>): Record<string, string> | undefined {
+	return (
+		(data.metadata as Record<string, string> | undefined) ||
+		((data.subscription_data as Record<string, unknown>)?.metadata as Record<string, string> | undefined) ||
+		((data.subscription as Record<string, unknown>)?.metadata as Record<string, string> | undefined)
+	);
+}
+
 function isSponsorAdSubscription(data: Record<string, unknown>): boolean {
-	const metadata = data.metadata as Record<string, string> | undefined;
-	const subscriptionDataMetadata = (data.subscription_data as Record<string, unknown>)?.metadata as
-		| Record<string, string>
-		| undefined;
-	const subscriptionMetadata = (data.subscription as Record<string, unknown>)?.metadata as
-		| Record<string, string>
-		| undefined;
-
-	return (
-		metadata?.type === 'sponsor_ad' ||
-		subscriptionDataMetadata?.type === 'sponsor_ad' ||
-		subscriptionMetadata?.type === 'sponsor_ad'
-	);
+	const metadata = extractMetadata(data);
+	return metadata?.type === 'sponsor_ad';
 }

 function getSponsorAdId(data: Record<string, unknown>): string | null {
-	const metadata = data.metadata as Record<string, string> | undefined;
-	const subscriptionDataMetadata = (data.subscription_data as Record<string, unknown>)?.metadata as
-		| Record<string, string>
-		| undefined;
-	const subscriptionMetadata = (data.subscription as Record<string, unknown>)?.metadata as
-		| Record<string, string>
-		| undefined;
-
-	return metadata?.sponsorAdId || subscriptionDataMetadata?.sponsorAdId || subscriptionMetadata?.sponsorAdId || null;
+	const metadata = extractMetadata(data);
+	return metadata?.sponsorAdId || null;
 }
app/api/polar/webhook/handlers.ts (1)

152-166: Inconsistent email data construction pattern.

handleSubscriptionCreated (lines 152-166) and handleSubscriptionUpdated (lines 210-224) construct emailData directly without using createEmailData, while other handlers like handleSubscriptionCancelled (lines 275-293) use the {...createEmailData(..., emailConfig)} spread pattern. This inconsistency could lead to missing fields (e.g., companyName, companyUrl, supportEmail are manually added vs. automatically included via createEmailData).

🔎 Proposed fix for handleSubscriptionCreated (apply similar pattern to handleSubscriptionUpdated)
-			const emailData = {
-				customerName: customerInfo.customerName,
-				customerEmail: customerInfo.customerEmail,
-				planName,
-				amount,
-				currency: data.currency || DEFAULT_CURRENCY,
-				billingPeriod,
-				nextBillingDate: data.current_period_end ? formatBillingDate(data.current_period_end) : undefined,
-				subscriptionId: data.id || '',
-				manageSubscriptionUrl: `${APP_URL}/settings/subscription`,
-				companyName: emailConfig.companyName,
-				companyUrl: emailConfig.companyUrl,
-				supportEmail: emailConfig.supportEmail || '',
-				features: getSubscriptionFeatures(planName)
-			};
+			const emailData = {
+				...createEmailData(
+					{
+						customerName: customerInfo.customerName,
+						customerEmail: customerInfo.customerEmail,
+						planName,
+						amount,
+						currency: data.currency || DEFAULT_CURRENCY,
+						billingPeriod,
+						nextBillingDate: data.current_period_end ? formatBillingDate(data.current_period_end) : undefined,
+						subscriptionId: data.id || '',
+						manageSubscriptionUrl: `${APP_URL}/settings/subscription`,
+						features: getSubscriptionFeatures(planName)
+					},
+					emailConfig
+				)
+			};

Also applies to: 210-224

app/api/sponsor-ads/user/route.ts (1)

56-57: Consider using the same limit validation pattern as other routes.

app/api/sponsor-ads/route.ts uses a more robust validation pattern with Number() and Number.isFinite() checks. While Zod validation catches invalid values here, using consistent validation patterns across routes improves maintainability.

app/api/admin/sponsor-ads/[id]/reject/route.ts (1)

73-73: Consider explicit JSON error handling for consistency.

Line 73 silently converts invalid JSON to an empty object with .catch(() => ({})). While Zod validation catches missing fields, app/api/sponsor-ads/user/route.ts (lines 155-161) explicitly returns a 400 error for invalid JSON. Consider using the explicit pattern for consistency and clearer error messages.

🔎 Proposed fix
 		const { id } = await params;
-		const body = await request.json().catch(() => ({}));
+		let body: Record<string, unknown> = {};
+		try {
+			body = await request.json();
+		} catch {
+			return NextResponse.json(
+				{ success: false, error: 'Invalid JSON in request body' },
+				{ status: 400 }
+			);
+		}
app/api/admin/sponsor-ads/[id]/route.ts (1)

102-114: Consider using typed errors instead of string matching.

The error handling correctly distinguishes between expected 404 errors and unexpected 500 errors, which is good for both UX and security. However, checking error.message === 'Sponsor ad not found' is brittle and lacks type safety.

💡 Future improvement: typed error classes

Consider introducing typed error classes in the service layer for better maintainability:

// In lib/errors.ts or similar
export class NotFoundError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'NotFoundError';
  }
}

// In the service
throw new NotFoundError('Sponsor ad not found');

// In the route handler
if (error instanceof NotFoundError) {
  return NextResponse.json({ success: false, error: error.message }, { status: 404 });
}

This provides type safety and makes the error handling contract explicit between the service and route layers.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f4d9d8 and 4e6b669.

📒 Files selected for processing (23)
  • app/[locale]/admin/collections/page.tsx
  • app/[locale]/admin/sponsorships/page.tsx
  • app/[locale]/categories/listing-categories.tsx
  • app/[locale]/categories/page.tsx
  • app/api/admin/collections/[id]/route.ts
  • app/api/admin/collections/route.ts
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/extract/route.ts
  • app/api/items/[slug]/views/route.ts
  • app/api/payment/[subscriptionId]/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/sponsor-ads/user/route.ts
  • app/api/stripe/webhook/route.ts
  • components/admin/collections/assign-items-modal.tsx
  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • components/collections/collection-card.tsx
  • components/collections/collection-detail.tsx
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Node.js >= 20.19.0

Files:

  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • app/api/extract/route.ts
  • app/api/admin/collections/route.ts
  • components/collections/collection-detail.tsx
  • components/collections/collection-card.tsx
  • app/api/items/[slug]/views/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/admin/collections/[id]/route.ts
  • app/[locale]/categories/page.tsx
  • app/api/sponsor-ads/user/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/stripe/webhook/route.ts
  • app/[locale]/categories/listing-categories.tsx
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/[locale]/admin/sponsorships/page.tsx
  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • app/api/payment/[subscriptionId]/route.ts
components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

components/**/*.{ts,tsx}: UI components should be placed in the components/** directory
Keep React components mostly presentational and lean, avoiding complex business logic

Files:

  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • components/collections/collection-detail.tsx
  • components/collections/collection-card.tsx
  • components/admin/collections/assign-items-modal.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer TypeScript files over JavaScript

Run pnpm tsc --noEmit for TypeScript type-checking

Files:

  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • app/api/extract/route.ts
  • app/api/admin/collections/route.ts
  • components/collections/collection-detail.tsx
  • components/collections/collection-card.tsx
  • app/api/items/[slug]/views/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/admin/collections/[id]/route.ts
  • app/[locale]/categories/page.tsx
  • app/api/sponsor-ads/user/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/stripe/webhook/route.ts
  • app/[locale]/categories/listing-categories.tsx
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/[locale]/admin/sponsorships/page.tsx
  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • app/api/payment/[subscriptionId]/route.ts
components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place UI and layout components in components/**

Files:

  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • components/collections/collection-detail.tsx
  • components/collections/collection-card.tsx
  • components/admin/collections/assign-items-modal.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer TypeScript for all code

Files:

  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • app/api/extract/route.ts
  • app/api/admin/collections/route.ts
  • components/collections/collection-detail.tsx
  • components/collections/collection-card.tsx
  • app/api/items/[slug]/views/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/admin/collections/[id]/route.ts
  • app/[locale]/categories/page.tsx
  • app/api/sponsor-ads/user/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/stripe/webhook/route.ts
  • app/[locale]/categories/listing-categories.tsx
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/[locale]/admin/sponsorships/page.tsx
  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • app/api/payment/[subscriptionId]/route.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Follow Prettier config with tabs, 4-space tabWidth, and 120-char printWidth
Prefer async/await over raw Promise chains in TypeScript/JavaScript code
Validate input with Zod where appropriate; follow existing schemas in lib/validations
Keep i18n-friendly: avoid hard-coded English strings in logic; use next-intl messages where relevant

Files:

  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • app/api/extract/route.ts
  • app/api/admin/collections/route.ts
  • components/collections/collection-detail.tsx
  • components/collections/collection-card.tsx
  • app/api/items/[slug]/views/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/admin/collections/[id]/route.ts
  • app/[locale]/categories/page.tsx
  • app/api/sponsor-ads/user/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/stripe/webhook/route.ts
  • app/[locale]/categories/listing-categories.tsx
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/[locale]/admin/sponsorships/page.tsx
  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • app/api/payment/[subscriptionId]/route.ts
**/components/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/components/**/*.{jsx,tsx}: For forms, prefer react-hook-form + Zod; follow patterns in existing auth/profile forms
Place business logic in lib/services or lib/repositories, not in components; keep components mostly presentational and data-fetching

Files:

  • components/billing/expired-plan-banner.tsx
  • components/categories-grid.tsx
  • components/collections/collection-detail.tsx
  • components/collections/collection-card.tsx
  • components/admin/collections/assign-items-modal.tsx
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Routes and pages should be placed in app/[locale]/** and app/api/** directories following Next.js App Router conventions

Files:

  • app/api/extract/route.ts
  • app/api/admin/collections/route.ts
  • app/api/items/[slug]/views/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/admin/collections/[id]/route.ts
  • app/[locale]/categories/page.tsx
  • app/api/sponsor-ads/user/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/stripe/webhook/route.ts
  • app/[locale]/categories/listing-categories.tsx
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/[locale]/admin/sponsorships/page.tsx
  • app/[locale]/admin/collections/page.tsx
  • app/api/payment/[subscriptionId]/route.ts
app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Next.js App Router under app/[locale]/** and app/api/**

Files:

  • app/api/extract/route.ts
  • app/api/admin/collections/route.ts
  • app/api/items/[slug]/views/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/admin/collections/[id]/route.ts
  • app/[locale]/categories/page.tsx
  • app/api/sponsor-ads/user/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/stripe/webhook/route.ts
  • app/[locale]/categories/listing-categories.tsx
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/[locale]/admin/sponsorships/page.tsx
  • app/[locale]/admin/collections/page.tsx
  • app/api/payment/[subscriptionId]/route.ts
app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

For API routes: put shared logic in lib/services or lib/repositories; keep handlers thin with validation, service call, and result mapping to HTTP response

Files:

  • app/api/extract/route.ts
  • app/api/admin/collections/route.ts
  • app/api/items/[slug]/views/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/admin/sponsor-ads/route.ts
  • app/api/polar/webhook/handlers.ts
  • app/api/admin/collections/[id]/route.ts
  • app/api/sponsor-ads/user/route.ts
  • app/api/sponsor-ads/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
  • app/api/stripe/webhook/route.ts
  • app/api/admin/sponsor-ads/[id]/reject/route.ts
  • app/api/payment/[subscriptionId]/route.ts
🧠 Learnings (4)
📚 Learning: 2025-11-30T21:22:22.296Z
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-30T21:22:22.296Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Keep i18n-friendly: avoid hard-coded English strings in logic; use next-intl messages where relevant

Applied to files:

  • components/billing/expired-plan-banner.tsx
  • components/collections/collection-detail.tsx
📚 Learning: 2025-11-24T18:35:04.571Z
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T18:35:04.571Z
Learning: Applies to app/**/*.{ts,tsx,js,jsx} : Use Next.js App Router under app/[locale]/** and app/api/**

Applied to files:

  • components/categories-grid.tsx
📚 Learning: 2025-11-30T21:22:22.296Z
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-30T21:22:22.296Z
Learning: Applies to app/api/**/*.{ts,tsx} : For API routes: put shared logic in lib/services or lib/repositories; keep handlers thin with validation, service call, and result mapping to HTTP response

Applied to files:

  • app/api/admin/collections/route.ts
  • app/api/admin/collections/[id]/route.ts
  • app/api/admin/sponsor-ads/[id]/route.ts
📚 Learning: 2025-11-24T18:34:28.233Z
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T18:34:28.233Z
Learning: Applies to app/**/*.{ts,tsx} : Routes and pages should be placed in app/[locale]/** and app/api/** directories following Next.js App Router conventions

Applied to files:

  • app/[locale]/categories/page.tsx
🧬 Code graph analysis (9)
components/categories-grid.tsx (2)
lib/paginate.ts (1)
  • totalPages (3-5)
components/universal-pagination.tsx (1)
  • UniversalPagination (10-72)
components/collections/collection-card.tsx (2)
types/collection.ts (1)
  • Collection (4-15)
components/collections/index.ts (1)
  • CollectionCard (1-1)
app/api/items/[slug]/views/route.ts (1)
lib/repositories/item.repository.ts (1)
  • ItemRepository (6-283)
app/api/sponsor-ads/user/[id]/cancel/route.ts (4)
app/api/admin/sponsor-ads/[id]/reject/route.ts (1)
  • POST (61-124)
app/api/sponsor-ads/checkout/route.ts (1)
  • POST (101-239)
app/api/sponsor-ads/user/route.ts (1)
  • POST (147-203)
app/api/admin/sponsor-ads/[id]/approve/route.ts (1)
  • POST (42-118)
app/api/sponsor-ads/route.ts (2)
lib/services/sponsor-ad.service.ts (1)
  • sponsorAdService (431-431)
lib/logger.ts (1)
  • error (113-123)
app/api/sponsor-ads/checkout/route.ts (2)
lib/logger.ts (1)
  • error (113-123)
lib/payment/types/payment-types.ts (1)
  • CheckoutSessionParams (332-334)
app/[locale]/categories/listing-categories.tsx (3)
components/home-two/home-two-layout.tsx (1)
  • HomeTwoLayout (44-83)
components/home-two/index.ts (1)
  • HomeTwoLayout (6-6)
lib/content.ts (1)
  • Category (123-125)
components/admin/collections/assign-items-modal.tsx (6)
hooks/use-admin-items.ts (1)
  • useAdminItems (114-261)
lib/logger.ts (1)
  • error (113-123)
hooks/use-toast.ts (1)
  • toast (193-193)
components/ui/modal.tsx (5)
  • Modal (21-139)
  • ModalContent (141-143)
  • ModalHeader (145-147)
  • ModalBody (149-151)
  • ModalFooter (153-155)
lib/paginate.ts (1)
  • totalPages (3-5)
components/universal-pagination.tsx (1)
  • UniversalPagination (10-72)
app/[locale]/admin/collections/page.tsx (10)
types/collection.ts (1)
  • Collection (4-15)
hooks/use-admin-collections.ts (1)
  • useAdminCollections (99-219)
lib/api/server-api-client.ts (1)
  • apiUtils (476-530)
lib/repositories/collection.repository.ts (1)
  • assignItems (145-211)
lib/editor/components/primitive/card/card.tsx (1)
  • CardBody (74-74)
lib/paginate.ts (1)
  • totalPages (3-5)
components/universal-pagination.tsx (1)
  • UniversalPagination (10-72)
scripts/clone.cjs (1)
  • path (5-5)
components/admin/collections/collection-form.tsx (1)
  • CollectionForm (16-180)
components/admin/collections/assign-items-modal.tsx (1)
  • AssignItemsModal (20-186)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Lint and Build
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (36)
app/api/items/[slug]/views/route.ts (2)

44-51: LGTM! Item existence validation improves security.

The pre-check to validate item existence before recording views is well-implemented and aligns with the PR's security objectives. Returning a 404 for non-existent items prevents invalid view records and the generic error message appropriately avoids leaking system details.


53-57: LGTM! Efficient owner exclusion using consolidated item fetch.

The refactored owner-exclusion logic correctly reuses the item fetched earlier, eliminating the redundant fetch mentioned in the summary. The flow is clear and the session/ownership check is properly positioned before cookie and view-recording operations.

app/api/extract/route.ts (1)

140-149: Robust error handling for platform API failures.

The improved error handling gracefully handles multiple failure scenarios with a well-designed fallback chain: attempts JSON parsing first, falls back to status text, and uses a default message as the final fallback. Preserving the original status code is appropriate for a proxy endpoint.

components/collections/collection-card.tsx (5)

1-7: LGTM!

The imports are correctly organized, and the 'use client' directive is appropriately placed for this interactive component using hooks.


14-16: LGTM!

The hook setup is clean and follows React best practices. The isNavigating state and pathname tracking enable proper spinner behavior during navigation.


18-21: LGTM!

The useEffect properly handles cleanup when the pathname changes, preventing the spinner from getting stuck during back navigation or other route changes.


26-31: LGTM!

The click handler correctly prevents the navigation spinner from showing when users open the link in a new tab (Cmd/Ctrl+click) or use other navigation modifiers. This provides an excellent user experience.


131-135: LGTM!

The spinner overlay is properly implemented with good visual feedback during navigation. The backdrop blur and z-index ensure it's clearly visible to users.

components/billing/expired-plan-banner.tsx (1)

78-78: Good i18n improvement for accessibility.

The change from a hardcoded "Dismiss" string to tCommon('CLOSE') correctly follows i18n best practices. Based on learnings, this ensures the aria-label is translatable for non-English locales.

Also applies to: 241-241

components/collections/collection-detail.tsx (2)

135-135: Good i18n consistency for filter status messages.

The translation hook and keys (SHOWING_TOTAL_ITEMS, FILTER_STATUS) are correctly implemented with proper interpolation parameters. This aligns with the broader i18n effort in this PR.

Also applies to: 228-230


389-393: Consistent i18n usage across both layout variants.

The HOME_ONE layout correctly mirrors the i18n approach used in CollectionHomeTwoFilters, ensuring consistent user experience across layout options.

components/admin/collections/assign-items-modal.tsx (2)

62-73: Good error handling and modal closing logic.

The modal now only closes on successful save (onClose() called inside the success path), which aligns with the PR objective for modal closing logic fixes. Error messages are properly displayed via toast.


40-46: Helpful comment documenting refetch behavior.

The comment clarifying that React Query handles page/search changes via query key is good documentation for future maintainers.

app/api/admin/collections/[id]/route.ts (2)

238-252: Good cache invalidation strategy for slug changes.

The pre-fetch before update enables proper cache invalidation when slugs change. Revalidating both old and new slug paths ensures users don't see stale content.


322-326: Good: Explicit 404 before delete operation.

Fetching the collection before deletion ensures proper error handling and enables slug-based cache invalidation. This aligns with the PR's item-existence validation improvements.

app/api/admin/collections/route.ts (1)

284-293: Good JSON parsing validation.

The try/catch around request.json() properly handles malformed request bodies with a 400 response, aligning with the PR's objective for JSON parsing validation. The error is logged for debugging without leaking details to clients.

app/[locale]/categories/page.tsx (1)

23-30: Simplified data flow for categories page.

The page now correctly fetches only what's needed (categories) and passes a minimal prop surface to ListingCategories. This aligns with the related changes in listing-categories.tsx that removed pagination support.

app/[locale]/categories/listing-categories.tsx (1)

12-16: Clean interface simplification.

The ListingCategoriesProps interface is now minimal and focused, containing only the necessary props after removing pagination support.

app/[locale]/admin/collections/page.tsx (2)

36-51: Good approach for accurate global stats.

Fetching all collections separately for stats ensures the dashboard displays accurate totals regardless of pagination, which was a bug fix objective in this PR.


76-90: Modal closes only on successful operations.

The form submission handlers now correctly close the modal only after successful create/update operations, aligning with the PR's modal closing logic fix objective.

app/api/payment/[subscriptionId]/route.ts (1)

22-28: Excellent error handling for malformed JSON.

The try-catch wrapper prevents unhandled exceptions from bubbling up as 500 errors and provides a clear, secure 400 response. This aligns well with the PR's security objectives.

components/categories-grid.tsx (3)

36-39: Good use of functional update pattern.

The conditional state update prevents unnecessary re-renders when the page value from the URL matches the current state. This is a solid performance optimization.


101-118: Excellent pagination handler implementation.

The callback correctly:

  • Guards against non-standard pagination types
  • Manages URL parameters (removes 'page' when returning to page 1)
  • Controls scroll behavior with scroll: false followed by manual smooth scrolling
  • Includes all dependencies in the useCallback array

229-232: Clean integration of standard pagination.

The UniversalPagination component is correctly integrated with proper conditional rendering and appropriate props. The placement after the grid and the check for totalPages > 1 ensure a good user experience.

app/api/sponsor-ads/route.ts (1)

53-56: Robust limit validation implemented.

The validation logic correctly handles edge cases:

  • Number.isFinite() rejects NaN, Infinity, and -Infinity
  • Math.floor() handles decimal inputs
  • Math.min/max clamps to valid range [1, 50]
  • Non-numeric strings fall back to default 10
app/api/stripe/webhook/route.ts (1)

625-638: Extended metadata inspection improves compatibility with various Stripe event shapes.

The function now correctly checks metadata from three possible locations: top-level data.metadata, data.subscription_data.metadata, and data.subscription.metadata. This handles different Stripe webhook payload variants (checkout sessions, invoices, subscriptions).

app/api/sponsor-ads/checkout/route.ts (2)

28-58: Solid implementation for open redirect prevention.

The validateRedirectUrl function correctly:

  • Resolves relative URLs against the allowed origin
  • Validates protocol, hostname, and port match
  • Handles malformed URLs gracefully by returning null
  • Rejects protocol-relative URLs like //evil.com/path (URL constructor resolves to https://evil.com/path, which fails hostname check)

This security hardening aligns with the PR objectives.


378-380: Customer ID validation now consistent with Stripe checkout.

The Polar checkout now validates customerId before proceeding, matching the Stripe checkout behavior (lines 291-293). This consistency improvement aligns with the PR objectives.

app/api/polar/webhook/handlers.ts (1)

492-507: Improved error handling for sponsor ad operations.

The sponsor ad handlers now explicitly throw errors when operations fail, allowing the webhook handler to return an error response. This enables payment providers to retry failed webhooks, improving reliability.

app/api/sponsor-ads/user/route.ts (1)

155-161: Good addition of JSON parsing guard.

Explicit handling of malformed JSON with a 400 response aligns with the PR's security improvement goals. This prevents raw parsing errors from being exposed.

app/api/admin/sponsor-ads/[id]/reject/route.ts (1)

106-123: Well-structured error handling with appropriate status codes.

The error handling correctly differentiates:

  • 400 for validation errors ("Cannot reject")
  • 404 for not found errors
  • 500 with generic message for unexpected errors

This pattern is consistent with the PR's security improvement goals.

app/api/admin/sponsor-ads/route.ts (1)

32-32: No changes needed—the status enum is correct as designed.

The sponsor-ad workflow never included an "approved" status. The enum correctly implements a two-stage process: pending_payment (user submission awaiting payment) and pending (payment received, awaiting admin review), with active status when an admin approves it. This design is consistent across the type definition (lib/types/sponsor-ad.ts), database schema (lib/db/schema.ts), Swagger documentation, and validation array.

The "approved" status exists only in the separate item submission workflow, not in the sponsor-ads system. The Swagger enum on line 32 and validStatuses array on line 89 are correctly aligned.

Likely an incorrect or invalid review comment.

app/api/admin/sponsor-ads/[id]/route.ts (1)

30-56: LGTM! Next.js 15 async params correctly implemented.

The GET handler properly implements Next.js 15's async request APIs by typing params as Promise<{ id: string }> and awaiting it before use. The admin authentication check, error handling, and generic error messages align well with the PR's security objectives.

app/[locale]/admin/sponsorships/page.tsx (3)

64-67: LGTM! Pagination reset on search change.

The unconditional page reset when the search term changes (including when cleared) provides consistent UX behavior and aligns with standard pagination patterns.


74-80: LGTM! useCallback refactoring improves performance.

Wrapping handlers in useCallback with correct dependencies prevents unnecessary rerenders of child components. All dependency arrays are complete and appropriate.

Also applies to: 90-100, 136-142, 144-154, 156-161


102-109: Error handling is correct and appropriately implemented.

The three verification points are all confirmed:

  1. approveSponsorAd returns the expected type: { success: boolean; requiresForceApprove?: boolean }
  2. Error handling is sound — the hook wraps mutations in try-catch, returns success: false for failures, and deliberately skips toast notifications for PAYMENT_NOT_RECEIVED (handled as a UI flow via the modal)
  3. User feedback is provided: success toast ("Sponsor ad approved and activated"), error toasts for failures (except PAYMENT_NOT_RECEIVED), and the button shows isLoading={isSubmitting} state

The conditional modal closing (only closing when result.success is true) correctly prevents the modal from dismissing on failed operations, which improves the user experience by keeping the context visible and allowing retry.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Dec 30, 2025

Greptile Summary

This PR implements security hardening, bug fixes, and i18n improvements across checkout flows, webhooks, admin pages, and UI components.

Security Enhancements:

  • Added URL validation in app/api/sponsor-ads/checkout/route.ts to prevent open redirect vulnerabilities by validating successUrl and cancelUrl against the application origin
  • Added customer ID validation for Polar checkout to ensure customer exists before creating subscriptions
  • Implemented generic error messages for 500 responses to avoid exposing sensitive implementation details
  • Added JSON parsing validation in app/api/extract/route.ts to handle malformed external API responses gracefully
  • Added item existence validation in app/api/items/[slug]/views/route.ts before recording views to prevent data pollution

Bug Fixes:

  • Fixed webhook handlers (app/api/stripe/webhook/route.ts and app/api/polar/webhook/handlers.ts) to properly detect sponsor ad renewals by checking subscription metadata
  • Fixed pagination in categories page by restoring standard pagination UI
  • Fixed admin collections page to show global statistics instead of page-scoped counts
  • Fixed modal closing logic to verify operation success before closing
  • Fixed collection card spinner to only display on regular left-click navigation (not middle-click or Cmd/Ctrl+click)
  • Fixed limit parameter validation to handle NaN and invalid values properly

i18n Improvements:

  • Replaced hardcoded strings with translation keys in collection components
  • Added missing translations for aria-labels and status messages

Code Quality:

  • Improved error handling consistency across API routes
  • Added proper existence checks before database operations
  • Removed unused imports and improved code formatting

Confidence Score: 5/5

  • This PR is safe to merge with high confidence - it implements important security fixes and quality improvements
  • All changes are well-implemented security enhancements and bug fixes with no breaking changes. The URL validation prevents a real security vulnerability (open redirects), webhook fixes ensure correct subscription handling, and the other improvements enhance reliability and user experience
  • No files require special attention - all changes follow best practices

Important Files Changed

Filename Overview
app/api/sponsor-ads/checkout/route.ts Added comprehensive URL validation to prevent open redirect vulnerabilities, validates both successUrl and cancelUrl against allowed origin, logs security warnings for invalid URLs, and added customer ID validation for Polar checkout
app/api/items/[slug]/views/route.ts Added item existence validation before recording views to prevent data pollution, returns 404 for non-existent items, preventing invalid analytics data
app/api/polar/webhook/handlers.ts Fixed sponsor ad renewal detection by checking subscription metadata for type='sponsor_ad', ensuring renewals are properly processed and not treated as regular subscriptions
app/api/stripe/webhook/route.ts Fixed sponsor ad renewal detection by checking both subscription_data and subscription metadata for type='sponsor_ad', ensuring consistent handling across different webhook events
app/api/extract/route.ts Added proper JSON parsing validation with try-catch to handle malformed responses from external APIs, returns appropriate error messages instead of crashing on HTML error pages
app/[locale]/admin/collections/page.tsx Fixed admin stats to show global counts by fetching all collections separately, improved modal closing logic to only close on successful operations, fixed isActive consistency to use isActive !== false

Sequence Diagram

sequenceDiagram
    participant User
    participant CheckoutAPI as Checkout API
    participant Validator as URL Validator
    participant Provider as Payment Provider
    participant Webhook as Webhook Handler
    participant SponsorService as Sponsor Ad Service

    User->>CheckoutAPI: POST /api/sponsor-ads/checkout
    Note over CheckoutAPI: Auth & ownership checks
    
    CheckoutAPI->>Validator: validateRedirectUrl(successUrl)
    Validator->>Validator: Parse URL & check origin
    alt URL from different origin
        Validator-->>CheckoutAPI: null (rejected)
        Note over CheckoutAPI: Log security warning
        CheckoutAPI->>CheckoutAPI: Use default URL
    else URL from same origin
        Validator-->>CheckoutAPI: validated URL
    end
    
    CheckoutAPI->>Validator: validateRedirectUrl(cancelUrl)
    Validator-->>CheckoutAPI: validated/null
    
    alt Stripe Provider
        CheckoutAPI->>Provider: getCustomerId(user)
        Provider-->>CheckoutAPI: customerId
        CheckoutAPI->>Provider: create checkout session
    else Polar Provider
        CheckoutAPI->>Provider: getCustomerId(user)
        Note over CheckoutAPI: Now validates customer ID exists
        alt Customer ID missing
            CheckoutAPI-->>User: 500 error (generic message)
        end
        Provider-->>CheckoutAPI: customerId
        CheckoutAPI->>Provider: create checkout session
    end
    
    Note over CheckoutAPI,Provider: Metadata includes type='sponsor_ad'
    Provider-->>CheckoutAPI: checkout URL
    CheckoutAPI-->>User: Checkout session created
    
    User->>Provider: Complete payment
    Provider->>Webhook: Webhook event (payment succeeded)
    
    Webhook->>Webhook: Check metadata.type or subscription.metadata.type
    alt type === 'sponsor_ad'
        Webhook->>SponsorService: confirmPayment(sponsorAdId)
        Note over SponsorService: Activates sponsor ad
        SponsorService-->>Webhook: Success
    else Regular subscription
        Webhook->>Webhook: Handle as subscription
    end
    
    Webhook-->>Provider: 200 OK
Loading

- Add missing 404 handling in cancel sponsor ad route
- Complete internationalization of assign items modal
- Refactor categories-grid to use exported totalPages function
@Innocent-Akim Innocent-Akim self-assigned this Dec 30, 2025
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="app/[locale]/admin/collections/page.tsx">

<violation number="1" location="app/[locale]/admin/collections/page.tsx:137">
P2: Concatenating numbers with translated strings breaks i18n word order. Consider using interpolation like `t(&#39;TOTAL_COUNT&#39;, { count: total })` to allow translators to control the full phrase structure.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/api/sponsor-ads/checkout/route.ts (1)

109-114: Add JSON parsing validation with 400 response for malformed requests.

The PR description states "Add JSON parsing validation with proper 400 responses for malformed requests," but the current implementation doesn't wrap request.json() in a try-catch block. If the request contains malformed JSON, it will throw and be caught by the outer catch block (line 232), returning a 500 status instead of the expected 400 status.

Additionally, per coding guidelines, input should be validated with Zod schemas from lib/validations.

🔎 Proposed fix to add JSON parsing validation
+	let body;
+	try {
+		body = await request.json();
+	} catch {
+		return NextResponse.json(
+			{ success: false, error: 'Invalid JSON in request body' },
+			{ status: 400 }
+		);
+	}
+
-	const body = await request.json();
 	const { sponsorAdId, successUrl, cancelUrl } = body;
 
 	if (!sponsorAdId) {
 		return NextResponse.json({ success: false, error: 'Sponsor ad ID is required' }, { status: 400 });
 	}
🧹 Nitpick comments (1)
app/[locale]/admin/collections/page.tsx (1)

46-46: Consider API optimization for statistics.

Fetching all collections (up to 1000) on every page load just to compute two aggregate numbers is inefficient. This creates unnecessary network overhead and client-side processing.

Consider adding a dedicated /api/admin/collections/stats endpoint that returns pre-computed or efficiently calculated aggregate statistics:

// Server-side (proposed API)
GET /api/admin/collections/stats
Response: {
  total: number,
  active: number,
  totalItemsAssigned: number
}

// Client-side usage
const { data: stats } = useQuery({
  queryKey: ['admin', 'collections', 'stats'],
  queryFn: async () => {
    const response = await serverClient.get('/api/admin/collections/stats');
    return response.data;
  }
});

This would be more scalable and performant, especially as the number of collections grows.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e6b669 and 4708d56.

📒 Files selected for processing (8)
  • app/[locale]/admin/collections/page.tsx
  • app/api/payment/[subscriptionId]/route.ts
  • app/api/sponsor-ads/checkout/route.ts
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • components/admin/collections/assign-items-modal.tsx
  • components/categories-grid.tsx
  • components/collections/collection-card.tsx
  • messages/en.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/categories-grid.tsx
  • app/api/sponsor-ads/user/[id]/cancel/route.ts
  • app/api/payment/[subscriptionId]/route.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Node.js >= 20.19.0

Files:

  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • components/collections/collection-card.tsx
  • app/api/sponsor-ads/checkout/route.ts
components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

components/**/*.{ts,tsx}: UI components should be placed in the components/** directory
Keep React components mostly presentational and lean, avoiding complex business logic

Files:

  • components/admin/collections/assign-items-modal.tsx
  • components/collections/collection-card.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer TypeScript files over JavaScript

Run pnpm tsc --noEmit for TypeScript type-checking

Files:

  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • components/collections/collection-card.tsx
  • app/api/sponsor-ads/checkout/route.ts
components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place UI and layout components in components/**

Files:

  • components/admin/collections/assign-items-modal.tsx
  • components/collections/collection-card.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer TypeScript for all code

Files:

  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • components/collections/collection-card.tsx
  • app/api/sponsor-ads/checkout/route.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Follow Prettier config with tabs, 4-space tabWidth, and 120-char printWidth
Prefer async/await over raw Promise chains in TypeScript/JavaScript code
Validate input with Zod where appropriate; follow existing schemas in lib/validations
Keep i18n-friendly: avoid hard-coded English strings in logic; use next-intl messages where relevant

Files:

  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • components/collections/collection-card.tsx
  • app/api/sponsor-ads/checkout/route.ts
**/components/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/components/**/*.{jsx,tsx}: For forms, prefer react-hook-form + Zod; follow patterns in existing auth/profile forms
Place business logic in lib/services or lib/repositories, not in components; keep components mostly presentational and data-fetching

Files:

  • components/admin/collections/assign-items-modal.tsx
  • components/collections/collection-card.tsx
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Routes and pages should be placed in app/[locale]/** and app/api/** directories following Next.js App Router conventions

Files:

  • app/[locale]/admin/collections/page.tsx
  • app/api/sponsor-ads/checkout/route.ts
app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Next.js App Router under app/[locale]/** and app/api/**

Files:

  • app/[locale]/admin/collections/page.tsx
  • app/api/sponsor-ads/checkout/route.ts
app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

For API routes: put shared logic in lib/services or lib/repositories; keep handlers thin with validation, service call, and result mapping to HTTP response

Files:

  • app/api/sponsor-ads/checkout/route.ts
🧠 Learnings (1)
📚 Learning: 2025-11-30T21:22:22.296Z
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-30T21:22:22.296Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Keep i18n-friendly: avoid hard-coded English strings in logic; use next-intl messages where relevant

Applied to files:

  • components/admin/collections/assign-items-modal.tsx
  • app/[locale]/admin/collections/page.tsx
  • components/collections/collection-card.tsx
🧬 Code graph analysis (3)
app/[locale]/admin/collections/page.tsx (5)
types/collection.ts (1)
  • Collection (4-15)
hooks/use-admin-collections.ts (1)
  • useAdminCollections (99-219)
lib/api/server-api-client.ts (2)
  • serverClient (452-452)
  • apiUtils (476-530)
lib/repositories/collection.repository.ts (1)
  • assignItems (145-211)
components/universal-pagination.tsx (1)
  • UniversalPagination (10-72)
components/collections/collection-card.tsx (1)
types/collection.ts (1)
  • Collection (4-15)
app/api/sponsor-ads/checkout/route.ts (2)
lib/services/sponsor-ad.service.ts (1)
  • sponsorAdService (431-431)
lib/payment/types/payment-types.ts (1)
  • CheckoutSessionParams (332-334)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Lint and Build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
app/api/sponsor-ads/checkout/route.ts (4)

28-58: LGTM! Strong security implementation.

The URL validation helper effectively prevents open redirect vulnerabilities by verifying that URLs belong to the application domain. The implementation correctly:

  • Uses URL constructor for parsing
  • Validates protocol, hostname, and port match
  • Returns null for invalid formats
  • Has appropriate error handling

158-179: LGTM! Excellent security hardening.

The URL validation implementation properly prevents open redirect vulnerabilities by:

  • Validating both success and cancel URLs against the allowed origin
  • Logging security warnings with context (userId, sponsorAdId) when invalid URLs are provided
  • Falling back to safe default URLs when validation fails

This aligns well with the PR's security objectives.


232-237: LGTM! Past security concerns addressed.

The error handling now correctly uses a generic error message for 500 responses, avoiding exposure of sensitive details to clients. The full error is still logged to console for debugging purposes, which is the right approach.

This resolves the concerns raised in previous review comments from cubic-dev-ai[bot] and coderabbitai[bot].


377-379: LGTM! Good validation addition.

The customer ID validation for Polar checkout ensures that a valid customer exists before creating a subscription. This is consistent with the Stripe implementation (lines 290-292) and prevents potential issues with missing or invalid customer references.

components/collections/collection-card.tsx (1)

1-138: LGTM! Previous i18n issue has been resolved.

The hardcoded "View collection" string has been properly replaced with t('VIEW_COLLECTION'). The component now follows i18n best practices throughout, and the navigation spinner implementation with proper guards for middle-click and modifier keys is well done.

messages/en.json (1)

92-152: LGTM! Translation keys added for collection management.

The new translation keys properly support the internationalization requirements for collection management features. All entries follow the established pattern and naming conventions.

components/admin/collections/assign-items-modal.tsx (1)

1-187: LGTM! All hardcoded strings have been properly internationalized.

All previously flagged hardcoded strings have been replaced with appropriate translation keys using both common and listing namespaces. The component now follows i18n best practices, and the logic for state management, item selection, and data fetching is well-structured.

app/[locale]/admin/collections/page.tsx (1)

100-118: Good implementation of assign flow with proper fallback logic.

The handleAssign function properly prefers items already stored on the collection, with a sensible fallback to fetch if not present. The handleAssignSave implementation is clean and delegates to the hook properly.

@Innocent-Akim Innocent-Akim marked this pull request as draft December 30, 2025 21:59
- Increase max limit for collections endpoint (100 -> 1000)
- Add missing 'EDIT' translation key in common section
- Improve pagination parameter validation for collections
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 25 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/api/admin/clients/dashboard/route.ts (2)

253-256: Consider extracting pagination validation to a shared utility.

The pagination validation logic is robust with proper NaN handling and bounds checking. However, similar validation appears in other admin routes (e.g., app/api/admin/collections/route.ts lines 136-153).

💡 Potential refactor to reduce duplication

Consider creating a shared utility function in lib/validations or lib/utils:

export function validatePagination(
  page: string | null,
  limit: string | null,
  maxLimit: number = 100
): { page: number; limit: number } | { error: string } {
  const rawPage = Number(page ?? 1);
  const rawLimit = Number(limit ?? 10);
  
  const validPage = Number.isFinite(rawPage) && rawPage > 0 ? Math.floor(rawPage) : 1;
  const validLimit = Number.isFinite(rawLimit) 
    ? Math.min(Math.max(1, Math.floor(rawLimit)), maxLimit) 
    : 10;
  
  return { page: validPage, limit: validLimit };
}

Then use it in multiple routes:

const { page, limit } = validatePagination(
  searchParams.get('page'),
  searchParams.get('limit'),
  100
);

266-284: Consider extracting date parsing utility for reusability.

The parseDateBound function provides robust date handling with UTC normalization and is well-implemented. If date range filtering is used in other admin endpoints, consider extracting this to a shared utility module.

💡 Suggested location

Create lib/utils/date-parsing.ts:

/**
 * Parse date parameter for filtering with proper UTC handling.
 * Treats YYYY-MM-DD as date-only in UTC.
 * @param v - Date string from query parameter
 * @param bound - 'start' for beginning of day, 'end' for end of day
 * @returns Parsed Date or undefined if invalid
 */
export function parseDateBound(v: string | null, bound: 'start' | 'end'): Date | undefined {
  if (!v) return undefined;
  
  // Handle YYYY-MM-DD format as UTC date-only
  if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
    const [y, m, d] = v.split('-').map(Number);
    return new Date(
      Date.UTC(
        y,
        m - 1,
        d,
        bound === 'end' ? 23 : 0,
        bound === 'end' ? 59 : 0,
        bound === 'end' ? 59 : 0,
        bound === 'end' ? 999 : 0
      )
    );
  }
  
  // Fallback to generic Date parsing
  const d = new Date(v);
  return Number.isNaN(d.getTime()) ? undefined : d;
}
app/api/admin/collections/route.ts (1)

136-153: Inline pagination validation duplicates logic.

The pagination validation logic here is similar to the validation in app/api/admin/clients/dashboard/route.ts (lines 253-256), though with different approaches—this uses explicit 400 responses while the dashboard route coerces invalid values.

Consider the shared utility approach suggested in the dashboard route review to standardize pagination handling across admin endpoints.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4708d56 and 024e951.

📒 Files selected for processing (3)
  • app/api/admin/clients/dashboard/route.ts
  • app/api/admin/collections/route.ts
  • messages/en.json
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Node.js >= 20.19.0

Files:

  • app/api/admin/collections/route.ts
  • app/api/admin/clients/dashboard/route.ts
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Routes and pages should be placed in app/[locale]/** and app/api/** directories following Next.js App Router conventions

Files:

  • app/api/admin/collections/route.ts
  • app/api/admin/clients/dashboard/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer TypeScript files over JavaScript

Run pnpm tsc --noEmit for TypeScript type-checking

Files:

  • app/api/admin/collections/route.ts
  • app/api/admin/clients/dashboard/route.ts
app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Next.js App Router under app/[locale]/** and app/api/**

Files:

  • app/api/admin/collections/route.ts
  • app/api/admin/clients/dashboard/route.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer TypeScript for all code

Files:

  • app/api/admin/collections/route.ts
  • app/api/admin/clients/dashboard/route.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Follow Prettier config with tabs, 4-space tabWidth, and 120-char printWidth
Prefer async/await over raw Promise chains in TypeScript/JavaScript code
Validate input with Zod where appropriate; follow existing schemas in lib/validations
Keep i18n-friendly: avoid hard-coded English strings in logic; use next-intl messages where relevant

Files:

  • app/api/admin/collections/route.ts
  • app/api/admin/clients/dashboard/route.ts
app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

For API routes: put shared logic in lib/services or lib/repositories; keep handlers thin with validation, service call, and result mapping to HTTP response

Files:

  • app/api/admin/collections/route.ts
  • app/api/admin/clients/dashboard/route.ts
🧠 Learnings (1)
📚 Learning: 2025-11-30T21:22:22.296Z
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-30T21:22:22.296Z
Learning: Applies to app/api/**/*.{ts,tsx} : For API routes: put shared logic in lib/services or lib/repositories; keep handlers thin with validation, service call, and result mapping to HTTP response

Applied to files:

  • app/api/admin/collections/route.ts
🧬 Code graph analysis (2)
app/api/admin/collections/route.ts (3)
types/collection.ts (2)
  • CollectionListOptions (36-43)
  • CreateCollectionRequest (17-24)
lib/repositories/collection.repository.ts (1)
  • collectionRepository (269-269)
lib/cache-invalidation.ts (1)
  • invalidateContentCaches (57-70)
app/api/admin/clients/dashboard/route.ts (2)
lib/auth/admin-guard.ts (1)
  • checkAdminAuth (9-47)
lib/db/queries/client.queries.ts (1)
  • getAdminDashboardData (633-842)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
🔇 Additional comments (7)
messages/en.json (3)

92-92: LGTM! Well-placed translation key.

The VIEW_COLLECTION key is appropriately named, clearly worded, and logically positioned near related collection navigation keys.


190-190: LGTM! Generic action key appropriately placed.

The EDIT key fills a gap in generic action translations and is correctly positioned alongside other common actions (CANCEL, DELETE, SAVING). This generic placement allows reuse across different UI contexts.


138-152: Collection management translations are well-structured and properly implemented.

The translation keys follow consistent naming conventions and are clear and user-friendly. Both parameterized keys are correctly consumed in their respective components:

  • SELECTED_COUNT is properly interpolated in assign-items-modal.tsx with the count parameter
  • DELETE_COLLECTION_CONFIRM is properly interpolated in the collections admin page with the collection name

The i18n implementation correctly follows next-intl patterns.

app/api/admin/clients/dashboard/route.ts (2)

244-247: LGTM! Consistent auth pattern.

The route correctly uses checkAdminAuth() for authentication, which aligns with the auth-first pattern recommended in the PR objectives and implemented across other admin routes.


329-330: LGTM! Appropriate error handling for security.

The generic error message prevents information leakage while proper error logging is maintained. This aligns with the PR's security hardening objectives.

app/api/admin/collections/route.ts (2)

297-303: Excellent security practice with JSON parsing guard.

The try-catch block around request.json() with a 400 response for malformed JSON is a good security practice that prevents cryptic error messages and aligns with the PR's objective to "validate JSON parsing with 400 responses for malformed requests."


331-342: LGTM! Well-differentiated error handling.

The error handling appropriately distinguishes between:

  • 409 for "already exists" conflicts
  • 400 for validation errors (containing "must")
  • 500 for generic server errors

This aligns with the PR's objective to "standardize API error handling."

@Innocent-Akim Innocent-Akim requested a review from evereq December 31, 2025 19:08
>
{layoutHome === LayoutHome.HOME_ONE && <HomeOneLayout categories={props.categories} />}
{layoutHome === LayoutHome.HOME_TWO && <HomeTwoLayout categories={props.categories} />}
{paginationType === 'standard' && totalPages(props.categories.length) > 1 && (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Innocent-Akim why Pagination removed!?

const start = 0;
const basePath = "/categories";
// Calculate pagination info
const page = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Innocent-Akim why pagination REMOVED!??? What happened with "tags" that was passed to listing categories component?

.optional(),
icon_url: z.string().trim().optional(),
isActive: z.boolean().optional()
name: z
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Innocent-Akim not sure but somehow your PRs contain a lot of reformatting without actual changes, can it be that either you or other devs using different formatting style?

@evereq evereq merged commit 19a4223 into develop Jan 1, 2026
11 checks passed
@evereq evereq deleted the chore/review-ai-suggestions-pr-443-444 branch January 1, 2026 10:35
@evereq
Copy link
Contributor

evereq commented Jan 1, 2026

@Innocent-Akim I merged with Squash, if need more fixes please base on develop branch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants