Skip to content

Conversation

@evereq
Copy link
Contributor

@evereq evereq commented Dec 30, 2025

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


Note

Introduces end-to-end Sponsorships and Collections, adds item view tracking and client stats, and updates platform/cron/payment integrations and env/config.

  • Collections: New admin UI (create/edit/delete, assign items) with REST APIs; public collections list/detail pages with ISR and cache invalidation
  • Sponsorships: User sponsor page + checkout (Stripe/LemonSqueezy/Polar), public ads endpoint; admin sponsorships dashboard with approve/reject/cancel/delete; extended Polar/LemonSqueezy webhooks for sponsor ads activation/cancellation/renewal
  • Tracking & dashboard: Item view tracking endpoint with bot/owner filtering and cookie dedupe; ItemViewTracker hooked into item page; new /api/client/dashboard/stats
  • Platform extract: Secure proxy POST /api/extract to Ever Works Platform (gracefully disables when not configured)
  • Cron & sync: New cron routes for subscription reminders and expirations; sync cron now uses config-based secret
  • Auth/UX: Redirect defaults to /client/dashboard; Facebook env vars renamed to FB_CLIENT_ID/SECRET; added CurrencyProvider; listing pages wrap in SponsorAdsProvider; categories/tags layout/pagination tweaks
  • Docs/Env/CI: .env.example and README expanded (Platform API, email provider, LemonSqueezy variants); CI uses FB_* secrets

Written by Cursor Bugbot for commit 3230af6. This will update automatically on new commits. Configure here.


Summary by cubic

Introduces Sponsor Ads and Collections admin systems with multi-provider checkout and public display, plus subscription auto-renewal/expiration, item view analytics, and currency management. Also migrates to a typed config service, updates env/CI vars, and improves UI across listings and billing.

  • New Features

    • Sponsor Ads: admin pages and APIs, Stripe/LemonSqueezy/Polar checkout, webhook activation, listing/sidebars display with rotating cards.
    • Collections Admin: create/edit/delete, batch item assignment, public collection pages, active-only visibility.
    • Subscriptions: auto-renewal toggle (API + UI), renewal reminders cron, expiration cron, plan-status API, expired/warning banners.
    • Analytics: unique daily item view tracking (server endpoint + client tracker), integrated into client dashboard stats.
    • Currency: user currency API, app-wide CurrencyProvider, locale-aware formatting in billing.
    • Client Dashboard: real stats API with views, engagement charts, and top items.
  • Migration

    • Configure PLATFORM_API_URL and PLATFORM_API_SECRET_TOKEN to enable URL metadata extraction.
    • Set sponsor ad price/variant IDs for Stripe/LemonSqueezy/Polar (weekly/monthly) before enabling checkout.
    • Provide CRON_SECRET for cron endpoints (sync, subscription reminders/expiration).
    • CI env rename: use FB_CLIENT_ID and FB_CLIENT_SECRET (replaces FACEBOOK_* in workflow).
    • Ensure APP_URL and EMAIL settings are present for webhooks/emails; project now reads config via the ConfigService.

Written for commit 3230af6. Summary will update on new commits.

ariefgp and others added 29 commits December 19, 2025 17:56
  - Remove NODE_ENV-based logic that generated random admin credentials
    during build ([email protected])
  - Always use predefined
  - Add warning log when using defaults in production
  - Add one-time migration to fix existing deployments with auto-generated
    admin accounts (updates both email and password)
- Added OUR_MOST_POPULAR_ITEMS key for popular items section
- Added SORT_POPULAR_ITEMS key for sort menu aria label
feat: implement client dashboard real data stats API
* feat(db): add sponsorAds schema for sponsor advertisements

  - Add SponsorAdStatus enum (pending, approved, rejected, active, expired, cancelled)
  - Add SponsorAdInterval enum (weekly, monthly)
  - Add sponsorAds table with:
    - User/item references
    - Status and interval tracking
    - Payment provider integration fields
    - Admin review fields (reviewedBy, reviewedAt, rejectionReason)
    - Subscription period (startDate, endDate)
    - Cancellation tracking
  - Add indexes for common query patterns
  - Export SponsorAd and NewSponsorAd types

* feat(constants): add SponsorAdPricing enum

  - Add SponsorAdPricing enum with:
    - WEEKLY = 10000 ($100.00 in cents)
    - MONTHLY = 30000 ($300.00 in cents)

* feat(validations): add sponsor-ad validation schemas

  - Add sponsorAdStatuses and sponsorAdIntervals const arrays
  - Add createSponsorAdSchema for user submissions
  - Add updateSponsorAdSchema for admin updates
  - Add approveSponsorAdSchema for admin approval
  - Add rejectSponsorAdSchema with required reason (min 10 chars)
  - Add cancelSponsorAdSchema for cancellation
  - Add querySponsorAdsSchema for listing with pagination, filters, sorting
  - Export TypeScript types for all schemas

* feat(types): add sponsor-ad TypeScript types

  - Add SponsorAdStatus and SponsorAdIntervalType type aliases
  - Add request types: Create, Update, Approve, Reject, Cancel
  - Add response types: SponsorAdResponse, SponsorAdListResponse
  - Add SponsorAdListOptions for query parameters
  - Add SponsorAdStats for dashboard statistics
  - Add SponsorAdDashboardResponse for admin dashboard
  - Add SponsorAdWithUser extended type with user/reviewer info

* feat(repository): add sponsor-ad repository for database operations

* feat(service): add sponsor-ad service for business logic

* feat(api): add admin API routes for sponsor ads management

* feat(hooks): add useAdminSponsorAds hook for admin panel
  - React Query integration for data fetching & caching
  - Query keys factory for cache management

* feat(admin): add sponsorships management page

* feat(i18n,nav): add sponsorships translations and admin navigation

* fix(admin): use correct Select component API in sponsorships page

* fix(api): use correct Zod error property in sponsor-ads routes

* fix(hooks): correct sortBy type in use-admin-sponsor-ads hook

* fix(validations): remove unsupported Zod 3 syntax from sponsor-ad schema

* feat(hooks): add use-user-sponsor-ads hook for user sponsor ad management

  Changes:
  - Created /hooks/use-user-sponsor-ads.ts
  - Implements React Query hook for users to manage their sponsor ad submissions
  - Features:
    - Fetch user's sponsor ads with pagination and status filtering
    - Create new sponsor ad submission
    - Cancel existing sponsor ad
    - Query cache management with auto-refresh
    - Toast notifications for success/error states

* feat(api): add public sponsor ads API route

  Changes:
  - Created /app/api/sponsor-ads/route.ts - Public GET endpoint for active sponsor ads
  - Updated /lib/repositories/sponsor-ad.repository.ts - Added optional limit parameter to getActiveSponsorAds
  - Updated /lib/services/sponsor-ad.service.ts - Added optional limit parameter to getActiveSponsorAds

* feat(api): add user sponsor ads API routes

  Changes:
  - Created /app/api/sponsor-ads/user/route.ts
    - GET: Fetch user's sponsor ads with pagination and status filtering
    - POST: Create new sponsor ad submission
  - Created /app/api/sponsor-ads/user/[id]/cancel/route.ts
    - POST: Cancel user's own sponsor ad with ownership verification

* feat(api): add sponsor ads checkout route with multi-provider support

  Changes:
  - Created /app/api/sponsor-ads/checkout/route.ts
    - POST: Create checkout session for approved sponsor ads
    - Supports Stripe, LemonSqueezy, and Polar payment providers
    - Uses environment variables for price IDs:
        - STRIPE_SPONSOR_WEEKLY_PRICE_ID, STRIPE_SPONSOR_MONTHLY_PRICE_ID
      - LEMONSQUEEZY_SPONSOR_WEEKLY_VARIANT_ID, LEMONSQUEEZY_SPONSOR_MONTHLY_VARIANT_ID
      - POLAR_SPONSOR_WEEKLY_PRICE_ID, POLAR_SPONSOR_MONTHLY_PRICE_ID
    - Validates sponsor ad ownership and approval status
    - Includes sponsor ad metadata in checkout session
  - Fixed /app/api/sponsor-ads/user/route.ts
    - Replaced incorrect getPaymentProvider import with ACTIVE_PAYMENT_PROVIDER constant

* feat(components): add sponsor form component

* Create Sponsor Components Index

* feat(pages): add sponsor submission page

* feat(webhook): add sponsor ad handling to Stripe webhook

* feat(webhook): add sponsor ad handling to LemonSqueezy webhook

* feat(webhook): add sponsor ad handling to Polar webhook

* feat(i18n): add sponsor translations to all locale files

* fix(polar): add metadata property to webhook types

* fix(checkout): use createSubscription for LemonSqueezy and Polar providers

* fix(checkout): convert variantId to number for LemonSqueezy

* fix(checkout): use empty string fallback for Polar customerId

* feat(sponsor-ads): create sponsor badge component

* feat(sponsor-ads): create sponsor card component with time-based rotation

* feat(sponsor-ads): create sidebar sponsor component for item detail page

* feat(sponsor-ads): create hook for fetching active sponsor ads

* feat(sponsor-ads): add new display components to exports

* feat(sponsor-ads): add SponsorAdsProvider context and integrate in globals-client

* feat(sponsor-ads): inject sponsor card in LayoutGrid after first row

* feat(sponsor-ads): inject sponsor card in LayoutMasonry after first row

* feat(sponsor-ads): inject sponsor card in LayoutClassic after first items

* feat(sponsor-ads): add sidebar sponsor to item detail page

* feat(sponsor-ads): add Sponsor block to pricing page

* feat(i18n): add more language translation

* refactor(admin): redesign sponsorships page with modern component architecture

* feat(sponsor): redesign /sponsor page with modern styling

* feat(i18n): add translation

* refactor(sponsor-form): replace separate search + select with HeroUI Autocomplete

* fix(sponsor): improve copy and hide summary until item selected

* refactor(sponsor-form): fix blinking issue on SearchableSelect

* fix(sponsor): issue component is being cut out

* fix(sponsor-form): remove redundant selected item preview

* feat(db-sponsor): add sponsor table migrations

* fix(sponsor-ads): allow null values in create sponsor ad validation

* fix(admin-sponsor-ads): handle invalid status filter values in API

* refactor(sponsor-ads): implement payment-first flow with auto-activation

  - Add `pending_payment` status, remove `approved` status from enum
  - Change default status to `pending_payment` on submission
  - Auto-activate ads on admin approval (pending → active)
  - Add `confirmPayment` service method for webhook payment confirmation
  - Add force-approve modal for approving without payment detected
  - Update webhooks (Stripe, LemonSqueezy, Polar) to use confirmPayment
  - Fix admin table auto-refresh using refetchQueries
  - Update button visibility logic based on new status flow

* fix(admin/sponsorships): improve badge design, translations, and search debouncing

  - Update status badge styling with proper color/variant configs per status
    - active: success/solid, pending: warning/flat, rejected: danger/flat
    - Add custom classNames for better padding and font styling
  - Fix missing translation: add STATUS_APPROVED for legacy data compatibility
  - Fix filter dropdown: use pending_payment instead of removed approved status
  - Fix search causing page reload on every keystroke
    - Replace useDebounceSearch with useDebounceValue (400ms delay)
    - Sync search term to API hook only when debounced value changes
    - Remove immediate setHookSearchTerm call from handleSearchChange

* fix(sponsor-card): match category styling to regular Item cards

* fix(sponsor-table): show delete button for all sponsor ad statuses

* feat(sponsor-ads): add tags support to sponsor cards

* refactor(sponsor): use items data directly

* fix(admin/sponsorships): simplify action buttons to reduce ambiguity

---------

Co-authored-by: Ruslan Konviser <[email protected]>
* fix(categories): remove duplicate pagination in categories page
- Removed UniversalPagination from categories-grid component
- Centralized pagination control in listing-categories.tsx
- Pagination now renders only once instead of twice
- Code style cleanup (formatting)

* fix(categories): sync page state with URL params and remove dead code
- Added useEffect to sync internal page state with URL searchParams
- Page now updates when parent Paginate component changes URL
- Removed unused totalPages variable (dead code)
- Standard pagination now works correctly with URL-based navigation
- Added OUR_MOST_POPULAR_ITEMS key for popular items section
- Added SORT_POPULAR_ITEMS key for sort menu aria label
* feat(pricing): add conditional LemonSqueezy variant selection with setup fees

- Standard plan: uses WITH_SETUP variant when STANDARD_TRIAL_AMOUNT_ID is set
- Premium plan: uses WITH_SETUP variant when PREMIUM_TRIAL_AMOUNT_ID is set
- Added new env variables to .env.example

* fix(pricing): add Button import and improve LemonSqueezy variant comments

- Fixed missing Button import in pricing-section.tsx
- Added descriptive comments for LemonSqueezy variant selection logic
- Kept inline ternary for env vars (dynamic access doesn't work with NEXT_PUBLIC_)
  - Add isAdmin validation to all sponsor-ads admin API handlers
  - Return 403 Forbidden for non-admin users
  - Align with authorization pattern used in other admin routes
…#417)

- Remove NODE_ENV-based logic that generated random admin credentials
    during build ([email protected])
  - Always use predefined
  - Add warning log when using defaults in production
  - Add one-time migration to fix existing deployments with auto-generated
    admin accounts (updates both email and password)
 fix(categories): show category cards in Home Two layout and fix spacing
- Add isAdmin validation to all sponsor-ads admin API handlers
  - Return 403 Forbidden for non-admin users
  - Align with authorization pattern used in other admin routes
refactor: improve rating display sync and add SSR safety for custom events
* feat(collection-card): wrap collection card with Next.js Link for navigation

* feat(collection-detail): update filters and layout structure

* refactor(collection-detail): disable category filters on collection detail pages

* refactor(collection-detail): disable category filters on collection detail pages

* refactor(collection-detail): remove the nav to align with other design pages

* fix: resolve hero description nesting warning on collection detail

* fix: reset collection card spinner on route change

* feat(collections): apply sorting to filtered items in collection detail

* chore: remove unused featured-items hook/logic.

* chore: add custom backdropBlur 2px o support existing

* Restore previously removed code for home 2 filter categories

* Integrate Home 2 layout and filtering for the single collection page

* fix: harden collection detail hero props and count display
* feat: add subscription auto-renewal management feature

- Add auto-renewal fields to subscriptions table (auto_renewal, renewal_reminder_sent, last_renewal_attempt, failed_payment_count)
- Create useAutoRenewal React hook for client-side auto-renewal management
- Add API endpoint /api/payment/[subscriptionId] for GET/PATCH auto-renewal status
- Implement setAutoRenewal service method with payment provider sync
- Add renewal reminder queries and management functions
- Update Polar and Stripe providers to support subscription updates with auto-renewal metadata
- Add database migration 0015 for subscription renewal fields
- Export useAutoRenewal hook in hooks index

* fix: resolve multiple subscription and payment issues

- Remove redundant migration file 0015_add_subscription_renewal_fields.sql
- Add idempotent checks to 0015_wooden_wolfpack.sql migration
- Fix race condition in incrementFailedPaymentCount using atomic SQL increment
- Fix handleFailedPayment to throw error when subscription not found
- Fix trialEnd assignment bug in webhook-subscription.service.ts
- Fix hardcoded planId in createSubscription (add fallback to STANDARD)
- Fix useCallback dependency arrays in use-auto-renewal hook
- Fix autoRenewal default value to return undefined during loading
- Fix useEffect pattern for onSuccess/onError to only fire on state transitions
- Remove sensitive data from console.log in stripe-provider.ts
- Fix misleading comments in polar-provider.ts and use-auto-renewal.ts

* feat: implement subscription renewal reminders and cleanup jobs
- Add daily cron job endpoint at
- Integrate subscription renewal reminders into BackgroundJobManager
- Implement  to notify users 7 days before expiry
- Implement  to handle expired subscriptions
- Add beautiful HTML/text email template for renewal reminders
- Export new templates from the centralized mail template index

* feat(billing): implement auto-renewal toggle with multi-provider support
- Add useAutoRenewal hook with payment provider detection (Stripe, LemonSqueezy, Polar)
- Integrate hook into SubscriptionCard component with toggle button
- Update /api/payment/[subscriptionId] endpoints (GET, PATCH) to:
  - Support lookup by provider subscription ID (subscription_id column)
  - Read paymentProvider from request body/query params
  - Use internal database ID for setAutoRenewal updates
- Add optimistic updates with rollback on error
- Include toast notifications for success/error states

* fix(security): address code review security and quality issues
Security fixes:
- Add crypto.timingSafeEqual for cron secret comparison (timing attack prevention)
- Add escapeHtml/isValidUrl to subscription-renewal-reminder.ts (XSS prevention)
- Validate paymentProvider in API route (400 vs 500 error handling)
Code quality:
- Add resetRenewalStateAtomic for atomic dual state resets
- Include paymentProvider in React Query key (cache invalidation fix)
- Use subscription.subscriptionId for provider ID lookup
- Remove unused destructured variables from SubscriptionCard
- Remove debug console.log statements

* feat(db): add subscription auto-renewal migration and schema update
- Add migration 0019_add_subscription_renewal_fields.sql
- New columns: auto_renewal, renewal_reminder_sent, last_renewal_attempt, failed_payment_count
- Use IF NOT EXISTS checks for safe re-runs
- Update schema.ts with new subscription fields
- Update journal with migration entry idx 19

* feat(subscription): add renewal management fields and update webhook

- Add , , , and  columns to subscriptions table
- Remove explicit renewal tracking (reset/increment counters) from WebhookSubscriptionService to centralize logic

* fix(migration): make subscription columns addition idempotent

* fix: resolve subscription issues (migrations, cache, security)
Database:
- Fix duplicate columns in migration 0020 using idempotent checks.
- Refactor  to  for safety.
- Clean up Drizzle journal and remove conflicting migration files.
Application:
- Fix [useAutoRenewal](cci:1://file:///Users/akim/Documents/home/Project/ever_company/ever-works-website-template/hooks/use-auto-renewal.ts:165:0-474:1) cache keys to correctly handle non-Stripe providers.
- Harden email templates by escaping validated URLs.
- Fix Invalid Date rendering in subscription UI.

* fix(api): validate provider query param in payment GET handler

* fix(api): validate payment provider and fix trial logic
- Add validation for provider query param in GET /api/payment/[id] to reject invalid values.
- Fix bug in webhook-subscription.service.ts where trialEnd was incorrectly assigned trialStart.

* feat(subscription): add subscription expiration management system

- Add cron job for automatic subscription expiration checks
- Implement plan status API endpoint and hooks
- Create expired plan banner component for UI notifications
- Add subscription expiration email template
- Implement plan guards for access control
- Update subscription queries and service for expiration handling
- Add plan expiration utility functions

* feat(db): enforce subscription state consistency and sync schema

- Add CHECK constraint auto_renewal_check (NOT autoRenewal AND cancelAtPeriodEnd)
- Sync migration schema with DB schema
- Add composite PK to verificationTokens
- Update lastRenewalAttempt to use date mode

* fix: Resolve critical bugs in billing messages, security, and OpenAPI spec

- fix(i18n): Merge duplicate 'billing' keys in messages/en.json and fix EXPIRES key usage.
- fix(security): Normalize URLs in email templates to prevent HTML attribute breakout vulnerabilities.
- fix(cron): Format subscription amounts as dollars instead of raw cents in expiration emails.
- fix(api): Add missing session and sessionAuth security scheme definitions to OpenAPI spec.

* fix(security): Enforce HTML escaping on validated URLs in email templates

- Split URL validation and escaping into separate steps for clarity and security
- Ensure companyUrl and renewUrl are properly escaped before being inserted into the HTML
…enhancements (#424)

* fix(admin): reset pagination when sponsor ads filters change

* fix(security): add admin role verification to sponsor-ads endpoints

* refactor(types): use correct type assertion for sponsor ad status checks

* fix(api): handle malformed JSON gracefully in reject endpoint

* fix(api): hide internal config details in checkout error messages

* fix(a11y): add aria-label to sponsor badge when text is hidden

* fix(a11y): add Escape key handler and dialog role to reject modal

* fix(i18n): translate hard-coded strings in sponsor filters

* fix(i18n): add FILTERS and CLEAR_ALL translations to all locales

* fix(sponsor-ads): fix type errors and remove duplicate auth checks in admin routes
* chore: update dependencies and openapi spec

- Add security schemes and pagination schemas to OpenAPI specification
- Update project dependencies and lock file

* chore: update dependencies and openapi spec

- Add security schemes and pagination schemas to OpenAPI specification
- Update project dependencies and lock file

* chore: sync pnpm-lock.yaml with package.json

* chore: regenerate openapi.json

- Regenerate openapi.json to resolve conflicts and ensure consistency with the codebase

* fix(openapi): add cronSecret and shared schemas

- Add cronSecret security scheme to generate-openapi.ts
- Restore Pagination, PaginationMeta, and ErrorResponse schemas which were missing
- Regenerate openapi.json
* fix(pricing): update LemonSqueezy variant selection logic
Conditionally set lemonVariantId based on NEXT_PUBLIC_AUTHORIZED_TRIAL_AMOUNT to ensure the correct variant (with or without setup fee) is used for trials.

* fix(pricing): improve LemonSqueezy variant fallback logic
Update pricing configuration to robustly handle LemonSqueezy variant selection.
When 'NEXT_PUBLIC_AUTHORIZED_TRIAL_AMOUNT' is enabled, the code now checks if the specific setup-fee variant ID exists. If it is missing, it gracefully falls back to the standard variant ID, preventing potential runtime errors.
Feat(add-item): integrate automatic item extraction from URL
* created the admin page

* created and implemted the hooks for collection

* created and implemted the form to add, delete, update and assign for collections

* created and added the interfaces for collections

* created and implemeted the collection git service

* created and implemeted the collection repository

* created and implemeted the admin API for collection

* deleted the fake collection data, apdate collection page to display the created data from the DB

* replace the fake displaying data with the created data from the DB

* added link to admin collections into menu-items profil

* feat: add batch operations for collection assignment

* feat(collections): implement batch item assignment with cache support

* fix(security): prevent format string injection in console.warn

* docs: add OpenAPI documentation for collections admin endpoints

* Align collections paging cache with sibling route

* Updated DELETE handler to align with GET: when collectionRepository.delete(id) reports not found, it now returns 404 instead of 500 in

* Handled save errors visibly and keep the modal open. handleSave now toasts the error instead of silently closing, while still toggling saving in finally, and added toast import in assign-items-modal.tsx:5-42.

* Reworked paging scroll to target the modal list instead of the page: added a ref to the scroll container and use it in onPageChange to scroll to top. See assign-items-modal.tsx:5-34.

* Trim form data and normalize id/slug in collection form

* Restore i18n pluralization for item count badge

* Implemented tag normalization so collection items resolve string tag IDs to full tag objects before rendering, ensuring tags now display for assigned items. See

* Reset sync state and timers after successful sync

* fix: replace printf-style warning with safe logging

* Added missing 404 response to the collection PUT endpoint so non-existent IDs are documented. See openapi.json:29952-29958.

* Updated the DELETE collection spec to document soft vs permanent behavior, add the optional permanent query flag, clarify the success description, and include a 404 response

* Replaced the console error with structured logging and added the logger import.

* Enforce Zod schema validation on collection updates

* fix: correct gradient class and button loading state

* fix: validate slugs to prevent path traversal

* fix the sudgestions from AI

* Added a guard to the retry callback to avoid concurrent syncs and clear the timeout handle before retrying.(AI comment)

* fix: add rollback for failed assign-items updates

* Added the missing permanent query parameter to the delete collection endpoint in the OpenAPI spec, matching the documented behavior.

* Restored i18n for the item count badge by using the translation helper with a count fallback to items?.length or item_count.

* Adjusted rollback logic and comment: we now guard the collection rollback in a try/catch and log if it fails, while preserving the original error.

* Handled deleted collections in pending-merge: now next is authoritative and we only merge pending entries for IDs that still exist, preventing deleted collections from reappearing.

* Valid issue: optional isActive caused inconsistent handling. Reverted Collection.isActive to required boolean while services already default missing values.

* Fix delete semantics in OpenAPI spec

* fix the button style for best code practice

* Fixed the merge logic so next (newer edits) stays authoritative and pending entries are only carried forward for ids not in next, avoiding newer edits being overwritten by older pending data.

* Fix PUT collection spec parameters
- Replace Next.js Image component with reusable SiteLogo component
- Improve consistency across authentication UI
- Apply code formatting (quotes and indentation)
- Update openapi.json schema
- Add breadcrumb navigation to tags grid page
- Refactor code formatting (quotes, indentation)
- Improve layout structure with Container component
- Enhance UX with better navigation context
#425)

* feat(config): add typed ConfigService with Zod validation

  - Add lib/config/config-service.ts - centralized configuration singleton
  - Add lib/config/schemas/ with Zod schemas for all config sections:
    - core.schema.ts: NODE_ENV, APP_URL, DATABASE_URL, site info
    - auth.schema.ts: AUTH_SECRET, OAuth providers, JWT, cookies
    - email.schema.ts: SMTP, Resend, Novu providers
    - payment.schema.ts: Stripe, LemonSqueezy, Polar, trial amounts
    - analytics.schema.ts: PostHog, Sentry, Recaptcha, Vercel
    - integrations.schema.ts: Trigger.dev, Twenty CRM, cron
  - Add lib/config/utils/ with parsing and logging utilities
  - Add lib/config/types.ts with exported TypeScript types
  - Add lib/config/index.ts as module barrel export
  - ConfigService validates all 113 env vars at startup
  - Uses 'server-only' to prevent client-side usage
  - Fail-fast on critical errors, warnings for non-critical
  - Secrets automatically masked in logs
  - Tree-shakeable exports for each config section
  - No breaking changes - existing config files unchanged

* feat(config): migrate auth and payment to typed ConfigService

  Migrate security-critical auth and payment code to use the new
  typed ConfigService, replacing direct process.env access with
  validated configuration.

  Changes:
  - lib/auth/providers.ts: Use authConfig for OAuth credentials
  - lib/auth/config.ts: Use authConfig.supabase for Supabase config
  - auth.config.ts: Use authConfig for OAuth provider setup
  - lib/payment/config/payment-provider-manager.ts: Use paymentConfig for Stripe, LemonSqueezy, Polar
  - lib/payment/lib/lemonsqueezy.ts: Use paymentConfig and coreConfig
  - lib/payment/lib/providers/stripe-provider.ts: Use paymentConfig
  - lib/payment/lib/providers/lemonsqueezy-provider.ts: Use paymentConfig and coreConfig
  - lib/payment/lib/providers/polar-provider.ts: Use paymentConfig and coreConfig
  - lib/types.ts: Use paymentConfig for pricing plan constants
  - app/api/stripe/webhook/route.ts: Use coreConfig and emailConfig
  - app/api/polar/webhook/route.ts: Use coreConfig for NODE_ENV
  - app/api/polar/webhook/utils.ts: Use coreConfig and emailConfig
  - app/api/polar/checkout/route.ts: Use coreConfig for NODE_ENV
  - app/api/polar/subscription/portal/route.ts: Use coreConfig for NODE_ENV
  - app/api/polar/subscription/portal/utils.ts: Use coreConfig for URLs
  - app/api/polar/subscription/[subscriptionId]/reactivate/route.ts: Use coreConfig
  - app/api/polar/subscription/[subscriptionId]/cancel/route.ts: Use coreConfig
  - lib/config/server-config.ts: Wrap with ConfigService, add @deprecated notices
  - lib/config.ts: Re-export ConfigService to fix module resolution

* feat(config): migrate core services to use ConfigService

  - Migrate lib/db/ files (drizzle.ts, initialize.ts, seed.ts) to use coreConfig.DATABASE_URL and coreConfig.NODE_ENV
  - Migrate lib/mail/index.ts to use coreConfig.APP_URL and emailConfig (renamed import to avoid conflict)
  - Migrate lib/services/email-notification.service.ts with new getEmailServiceConfig() helper to reduce duplication
  - Migrate lib/background-jobs/ files (config.ts, job-factory.ts, local-job-manager.ts) to use integrationsConfig.triggerDev and coreConfig.NODE_ENV
  - Migrate lib/repositories/category.repository.ts to use coreConfig.content.dataRepository, ghToken, githubBranch
  - Migrate lib/repositories/tag.repository.ts to use coreConfig.content.*
  - Migrate lib/repositories/item.repository.ts to use coreConfig.content.*
  - Migrate lib/repositories/twenty-crm-config.repository.ts to use integrationsConfig.twentyCrm
  - Fix integrations.schema.ts TwentyCrmSyncMode to match actual types ('disabled' | 'platform' | 'direct_crm')
  - Skip drizzle.config.ts (CLI tool correctly uses dotenv)

* feat(config): complete ConfigService migration across codebase

  - Migrate lib/auth/index.ts to use coreConfig.DATABASE_URL and coreConfig.NODE_ENV
  - Migrate lib/auth/supabase/server.ts and middleware.ts to use authConfig.supabase.url/anonKey
  - Migrate lib/auth/session-cache.ts and cached-session.ts to use coreConfig.NODE_ENV for debug logging
  - Migrate lib/auth/error-handler.ts to use authConfig for OAuth provider credentials
  - Migrate app/api/auth/[...nextauth]/error-handler.ts to use coreConfig.NODE_ENV
  - Migrate lib/payment/lib/utils/prices.ts to use paymentConfig.pricing.free/standard/premium
  - Migrate lib/payment/services/payment-email.service.ts to use paymentConfig.stripe.*PriceId
  - Migrate lib/services/sync-service.ts to use coreConfig.NODE_ENV
  - Migrate lib/services/twenty-crm-sync-factory.ts to use integrationsConfig.twentyCrm.baseUrl/apiKey
  - Migrate lib/repository.ts to use coreConfig.content.ghToken and dataRepository
  - Migrate lib/newsletter/config.ts to use coreConfig.APP_URL and emailConfig.resend/novu.apiKey
  - Migrate lib/constants.ts to use coreConfig.NODE_ENV and analyticsConfig.posthog.personalApiKey/projectId
  - Migrate app/api/cron/sync/route.ts to use integrationsConfig.cron.secret
  - Migrate app/api/verify-recaptcha/route.ts to use analyticsConfig.recaptcha.secretKey and coreConfig.NODE_ENV
  - Fix integrations.schema.ts TwentyCrmSyncMode to match actual types ('disabled' | 'platform' | 'direct_crm')

* fix(config): correct AUTH_SECRET critical path from core to auth section

* fix(config): implement graceful fallback for non-critical validation errors

  - Add .catch() handlers to URL/email fields to provide defaults on invalid format
  - Add .catch() handlers to enum fields to fallback on invalid values
  - Fix loadAndValidate() to use simplified logic (no more broken re-parse)
  - Invalid URLs/emails/enums now silently use defaults instead of crashing
  - Remaining validation errors after .catch() are truly unrecoverable

* fix(config): resolve client/server boundary issues in config imports

  - Create lib/config/client.ts with client-safe siteConfig
  - Update client components to import from @/lib/config/client
  - Remove @/lib/config imports from payment providers (use process.env)
  - Update lib/types.ts to use NEXT_PUBLIC_ env vars for pricing defaults
  - Update lib/auth/config.ts to use NEXT_PUBLIC_SUPABASE_* directly
  - Add sandbox/apiUrl options to PolarConfig interface

* fix(config): add safe default for EMAIL_SUPPORT to prevent webhook failures

  - Schema marks EMAIL_SUPPORT as optional but getEmailConfig() throws when absent
  - This breaks Stripe/LemonSqueezy webhooks for installs without EMAIL_SUPPORT set
  - Add fallback default '[email protected]' consistent with existing patterns
  - Polar handlers already handle this gracefully; align Stripe/Lemon behavior

* fix(db): resolve server-only import breaking migration scripts

 Changes

 - Created lib/db/config.ts - Script-safe database configuration
 module that reads directly from process.env without server-only
 guard, allowing migration/seed scripts to run outside Next.js
 context
 - Updated lib/db/drizzle.ts - Replaced @/lib/config import with
 local ./config module; updated all coreConfig.DATABASE_URL and
 coreConfig.NODE_ENV usages to use getDatabaseUrl() and getNodeEnv()
 functions
 - Updated lib/db/seed.ts - Replaced config import with ./config;
 changed seed credential access to read directly from process.env
 (SEED_ADMIN_EMAIL, SEED_ADMIN_PASSWORD, SEED_FAKE_USER_COUNT) to
 avoid server-only issues
 - Updated lib/db/initialize.ts - Replaced @/lib/config import with
 ./config; updated all database URL and NODE_ENV checks to use
 script-safe functions
 - Fixed lib/config/schemas/integrations.schema.ts - Corrected
 TwentyCrmSyncMode enum values from 'disabled' | 'manual' |
 'automatic' to 'disabled' | 'platform' | 'direct_crm' to match type

* fix(db): resolve server-only import breaking migration scripts

* fix(config): remove server-only imports from client-accessible modules

* fix(payment): align FREE price env var name with config schema

* fix(payment): handle NaN fallback for price env vars

* feat(config): centralize client-side config access

* feat(config): add DISABLE_AUTO_SYNC to core config schema

* refactor(repository): remove dead githubBranch fallback

* refactor(mail): remove dead APP_URL fallback

* fix(types): correct trial env var names to match .env.example

* fix: resolve config inconsistencies and OAuth env var mismatch

* fix(config): respect explicit enabled=false in integration schemas

* fix(config): handle NaN in payment pricing collection

* fix(config): handle NaN in SMTP port collection

* fix(config): handle NaN in seed user count collection

* fix(polar): add support email fallback in webhook utils

* style(auth): move import to top of session-cache.ts

* docs(config): clarify client import path in barrel export

* fix(config): resolve server-only import chain and missing exports

* fix: remove unused sponsor hook and add APP_URL fallback

  - Comment out unused useActiveSponsorAds hook in item-detail.tsx
    The sponsor ads rendering was disabled but hook was still called,
    causing unnecessary API calls on every render

  - Add fallback for undefined APP_URL in email config
    Prevents malformed email links (password reset, verification)
    when APP_URL is not configured

* fix(payment): remove dead APP_URL fallback in payment-provider-manager

* fix(config): restore EMAIL_PROVIDER env var support

  - Add EMAIL_PROVIDER to email schema (string, default: 'resend')
  - Collect EMAIL_PROVIDER from environment variables
  - Add EMAIL_PROVIDER to .env.example with available options
  - Fix regression from config service migration that hardcoded 'resend'

* fix(config): use EMAIL_PROVIDER in email-notification service

* fix(mail): use production-safe APP_URL fallback

---------

Co-authored-by: Ruslan Konviser <[email protected]>
…n-content

refactor(auth): replace Image with SiteLogo component in login content
feat(tags): add breadcrumb navigation and improve grid layout
…erification

fix(payment): fix Polar webhook signature verification
* feat: add currency management system with auto-detection

- Add currency and country fields to client profiles schema
- Create currency detection service with geo-location support
- Add CurrencyProvider context for application-wide currency state
- Implement use-currency hook for currency management
- Add API endpoint for user currency preferences (PUT /api/user/currency)
- Integrate currency formatting in billing components
- Add currency utilities for formatting and conversion
- Update database migration to include country and currency fields

* fix: add country and currency fields to client profile queries

Add missing country and currency fields in all SELECT queries that
return ClientProfileWithAuth objects to fix TypeScript type errors.

The fields were added to the schema but were missing from the query
selections in getClientProfiles, getAllClientProfiles, and
searchClientProfiles functions.

* feat: improve currency system with validation, i18n, and formatting fixes

- Add Zod validation for ISO 4217 currency codes
- Fix decimal conventions (JPY/KRW: 0 decimals) and division logic
- Replace hardcoded locale with dynamic next-intl locale
- Add error handling and graceful fallbacks
- Update Croatia to EUR, fix migration idempotency
- Centralize currency formatting across billing components
- Export UpdateCurrencyOptions and update context types
#440)

* feat(analytics): implement unique daily item view tracking

  - Add item_views table with daily deduplication constraint
  - Add bot detection utility for filtering crawlers
  - Add recordItemView() query with ON CONFLICT DO NOTHING
  - Add POST /api/items/[slug]/views endpoint
    - Bot detection via User-Agent
    - Owner exclusion when authenticated
    - Cookie-based viewer_id (365 day TTL)
    - Returns 503 when database not configured

* feat(tracking): add client-side item view tracker

  - Create ItemViewTracker client component
  - Triggers POST /api/items/{slug}/views once on mount
  - Uses keepalive: true for reliable tracking
  - Errors are swallowed (best-effort)
  - Add tracker to item detail page

* refactor(analytics): centralize viewer cookie constants

* feat(dashboard): integrate view tracking into client dashboard stats

  - Import view query functions from item-view.queries.ts
  - Add view queries to Promise.all for parallel execution:
    - getTotalViewsCount, getRecentViewsCount
    - getDailyViewsData, getViewsPerItem
  - Update engagementChartData to display actual totalViews
  - Update mapTopItems() to include viewsPerItem parameter
  - Add injectViewsIntoActivityData() helper for activity chart
  - Set viewsAvailable: true in getStats() and getEmptyStats()
  - Update return values: totalViews, recentViews, activityChartDataWithViews

* feat(submissions): show real view counts in client submission list

  - Import and use getViewsPerItem() in ClientItemRepository
  - Fetch views in findByUserPaginated(), findByIdForUser(), findDeletedByUser()
  - Update SubmissionItem to always show views (not just approved items)
  - Keep likes display conditional for approved items only

* style(views): move owner check before cookie handling

  - Reorder steps to check owner exclusion before getting/creating viewer cookie
  - Prevents unnecessary cookie operations for owner requests

* fix(analytics): use UTC methods for date calculation in view queries

* fix issue on migrations

---------

Co-authored-by: Ruslan Konviser <[email protected]>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Dec 30, 2025

Skipped: This PR changes more files than the configured file change limit: (262 files found, 100 file limit)

@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 30, 2025 6:59pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

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.

@evereq evereq merged commit 97c3313 into stage Dec 30, 2025
10 of 11 checks passed
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.

19 issues found across 262 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

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/user/[id]/cancel/route.ts">

<violation number="1" location="app/api/sponsor-ads/user/[id]/cancel/route.ts:43">
P2: Internal error messages are exposed to clients for 500 errors. This could leak sensitive implementation details (database errors, connection info, etc.). Consider using a generic message for unexpected errors while keeping the specific message for expected validation errors (400).</violation>
</file>

<file name="components/categories-grid.tsx">

<violation number="1" location="components/categories-grid.tsx:28">
P1: Standard pagination mode is broken: the `UniversalPagination` component and `totalPages` calculation were removed, but page sync logic was added. Users in standard pagination mode have no way to navigate between pages. Either restore the pagination UI or remove this sync logic if standard pagination is no longer needed.</violation>
</file>

<file name="components/admin/collections/assign-items-modal.tsx">

<violation number="1" location="components/admin/collections/assign-items-modal.tsx:40">
P2: The `page` and `search` dependencies are redundant here. React Query already re-fetches automatically when these values change (they&#39;re part of the query key in `useAdminItems`). This causes double API calls on every page change or search update. Consider removing these dependencies to only refetch when the modal opens.</violation>
</file>

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

<violation number="1" location="app/api/sponsor-ads/route.ts:54">
P2: Missing validation for `limit` parameter. `parseInt` can return `NaN` for non-numeric strings, and negative values aren&#39;t handled. This could cause unexpected behavior in the database query.</violation>
</file>

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

<violation number="1" location="components/collections/collection-detail.tsx:227">
P2: Hardcoded English strings should use the translation system for consistency. Consider adding translation keys like `t(&quot;common.SHOWING_ITEMS&quot;, { count: totalCount })` and `t(&quot;common.SHOWING_FILTERED_ITEMS&quot;, { filtered: filteredCount, total: totalCount })`.</violation>

<violation number="2" location="components/collections/collection-detail.tsx:390">
P2: Hardcoded English strings should use the translation system for i18n consistency. The component already has access to `t` from `useTranslations()` - use it here as well.</violation>
</file>

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

<violation number="1" location="app/api/payment/[subscriptionId]/route.ts:21">
P2: Invalid JSON from the client will result in a 500 error instead of 400 Bad Request. Consider wrapping `request.json()` in a try/catch to return a proper 400 status for malformed requests.</violation>
</file>

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

<violation number="1" location="app/api/sponsor-ads/user/route.ts:199">
P2: Potential information disclosure: Internal error messages are exposed to clients. For 500 errors, use a generic message like `&quot;Failed to create sponsor ad&quot;` instead of the raw `error.message`, which could leak internal details (database errors, paths, etc.). The specific &quot;already have&quot; check for 400 responses is fine to keep.</violation>
</file>

<file name="app/[locale]/categories/listing-categories.tsx">

<violation number="1" location="app/[locale]/categories/listing-categories.tsx:21">
P2: Interface declares `items` as required but it&#39;s never used after this refactoring. Consider removing unused props (`total`, `start`, `tags`, `items`) from the interface and their type imports to reduce caller burden and improve maintainability.</violation>
</file>

<file name="app/api/user/plan-status/route.ts">

<violation number="1" location="app/api/user/plan-status/route.ts:111">
P2: Exposing internal error messages to clients can leak implementation details (database schema, third-party service info, etc.). Follow the project&#39;s established pattern of returning a generic error message. Use Sentry (already installed) for detailed error tracking instead.</violation>
</file>

<file name="app/api/polar/webhook/handlers.ts">

<violation number="1" location="app/api/polar/webhook/handlers.ts:511">
P1: Errors in sponsor ad handlers are silently swallowed instead of being re-thrown. Unlike email notifications (which are non-critical), sponsor ad operations are critical business functions. If `confirmPayment`, `cancelSponsorAd`, or `renewSponsorAd` fails, the webhook will still return success, preventing automatic retries and potentially leaving the system in an inconsistent state (e.g., payment received but ad not activated).</violation>
</file>

<file name="app/api/admin/sponsor-ads/[id]/route.ts">

<violation number="1" location="app/api/admin/sponsor-ads/[id]/route.ts:38">
P2: Exposing raw error messages to API clients could leak sensitive internal details (database errors, table names, connection info). For the 500 case, use a generic error message like the pattern in other API routes, while keeping the specific check for the known &quot;Sponsor ad not found&quot; error.</violation>
</file>

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

<violation number="1" location="app/api/admin/sponsor-ads/route.ts:32">
P2: Swagger documentation lists &quot;approved&quot; as valid status but code validates against &quot;pending_payment&quot; instead. Update the enum to match the actual valid values to avoid confusing API consumers.</violation>
</file>

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

<violation number="1" location="app/[locale]/admin/sponsorships/page.tsx:98">
P2: Missing success check before closing modal. Unlike `handleRejectConfirm`, this handler closes the modal and resets state regardless of whether the operation succeeded. If `approveSponsorAd` fails, the user won&#39;t know the operation didn&#39;t complete.</violation>
</file>

<file name="app/[locale]/tags/tags-grid-client.tsx">

<violation number="1" location="app/[locale]/tags/tags-grid-client.tsx:196">
P1: Critical classes appear to have been accidentally removed from the footer. The `bg-gradient-to-t` class requires color stops (`from-*`, `via-*`, `to-*`) to render anything, and `bottom-0` needs `sticky` positioning to have any effect. This breaks the sticky pagination footer&#39;s appearance and behavior.</violation>
</file>

<file name="components/billing/expired-plan-banner.tsx">

<violation number="1" location="components/billing/expired-plan-banner.tsx:240">
P2: The `aria-label=&quot;Dismiss&quot;` should be translated for i18n consistency. All other user-facing strings in this component use the translation function.</violation>
</file>

<file name="app/api/admin/collections/[id]/route.ts">

<violation number="1" location="app/api/admin/collections/[id]/route.ts:242">
P2: Missing existence check before update. If `collectionRepository.findById(id)` returns `null`, the code proceeds to call `update()` which may fail with an unclear error. Add a 404 check after fetching the collection.</violation>

<violation number="2" location="app/api/admin/collections/[id]/route.ts:325">
P2: Path revalidation uses `id` instead of the collection&#39;s actual slug. Since `id` and `slug` can differ (as evidenced by the PUT handler), you should fetch the collection before deleting to obtain its slug for proper cache invalidation.</violation>
</file>

<file name="app/api/admin/collections/route.ts">

<violation number="1" location="app/api/admin/collections/route.ts:284">
P2: Malformed JSON in request body will return 500 instead of 400. Consider wrapping `request.json()` in a try/catch to return a 400 Bad Request for invalid JSON payloads.</violation>
</file>

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

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

evereq pushed a commit that referenced this pull request Jan 1, 2026
* fix: security improvements, bug fixes, and i18n consistency

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)

* fix: improve error handling and internationalization

- Add missing 404 handling in cancel sponsor ad route
- Complete internationalization of assign items modal
- Refactor categories-grid to use exported totalPages function

* fix: resolve API errors and missing translation

- Increase max limit for collections endpoint (100 -> 1000)
- Add missing 'EDIT' translation key in common section
- Improve pagination parameter validation for collections
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.

5 participants