Skip to main content

Sponsorship Management Components

The template/components/sponsorships/ directory implements the client-facing sponsorship management interface. It provides components for listing, filtering, viewing details, cancelling, and renewing sponsor ad placements. These components are used on the /client/sponsorships page where users manage their purchased sponsorships.

Architecture Overview

Source Files

FileDescription
index.tsBarrel exports for all sponsorship components
constants.tsStatus configuration and helper functions
sponsorship-stats-cards.tsxStatistics overview cards
sponsorship-filters.tsxStatus, interval, and search filters
sponsorship-list.tsxSponsorship list with loading and empty states
sponsorship-item.tsxIndividual sponsorship card with action buttons
sponsorship-detail-modal.tsxFull detail modal with pay/cancel/renew actions
cancel-dialog.tsxCancellation confirmation dialog
renew-dialog.tsxRenewal confirmation with pricing

Constants

Status Configuration

The SPONSOR_STATUS_CONFIG record maps each SponsorAdStatus to badge styling and translation keys.

type SponsorAdStatus = 'pending_payment' | 'pending' | 'active' | 'expired' | 'rejected' | 'cancelled';

interface StatusConfig {
bg: string; // Background color class
text: string; // Text color class
labelKey: string; // Translation key
}
StatusBackgroundText ColorLabel Key
pending_paymentbg-yellow-100text-yellow-700STATUS_PENDING_PAYMENT
pendingbg-blue-100text-blue-700STATUS_PENDING_REVIEW
activebg-green-100text-green-700STATUS_ACTIVE
expiredbg-gray-100text-gray-700STATUS_EXPIRED
rejectedbg-red-100text-red-700STATUS_REJECTED
cancelledbg-gray-100text-gray-700STATUS_CANCELLED

Helper Functions

formatSlugToTitle(slug: string): string

Converts a URL slug to a human-readable title by splitting on hyphens and capitalizing each word.

formatSlugToTitle('my-item-slug'); // "My Item Slug"

SponsorshipStatsCards

A 4-card statistics overview grid showing total, active, pending, and expired sponsorship counts.

Props

PropTypeDefaultDescription
statsSponsorAdStatsrequiredStatistics data object
isLoadingbooleanfalseShow loading skeleton

Usage

import { SponsorshipStatsCards } from '@/components/sponsorships';

<SponsorshipStatsCards stats={stats} isLoading={isLoading} />

Card Configuration

CardIconColorValue Source
TotalFiDollarSignBlueoverview.total
ActiveFiCheckGreenoverview.active
PendingFiClockYellowoverview.pendingPayment + overview.pending
ExpiredFiXCircleGrayoverview.expired

Skeleton

A SponsorshipStatsCardsSkeleton component is exported for use during page loading.

SponsorshipFilters

Provides status, interval, and search text filtering for the sponsorship list. Uses Radix UI dropdown menus for the filter selectors.

Props -- SponsorshipFiltersProps

PropTypeDefaultDescription
statusSponsorshipStatusFilterrequiredCurrent status filter
intervalSponsorshipIntervalFilterrequiredCurrent interval filter
searchstringrequiredCurrent search text
onStatusChange(status) => voidrequiredStatus change handler
onIntervalChange(interval) => voidrequiredInterval change handler
onSearchChange(search) => voidrequiredSearch change handler
isSearchingbooleanfalseShow search loading indicator
disabledbooleanfalseDisable all filter controls

Filter Options

type SponsorshipStatusFilter = SponsorAdStatus | 'all';
type SponsorshipIntervalFilter = 'weekly' | 'monthly' | 'all';

Status options: All, Active, Pending, Pending Payment, Expired, Rejected, Cancelled

Interval options: All, Weekly, Monthly

Usage

import { SponsorshipFilters } from '@/components/sponsorships';

<SponsorshipFilters
status={statusFilter}
interval={intervalFilter}
search={searchTerm}
onStatusChange={setStatusFilter}
onIntervalChange={setIntervalFilter}
onSearchChange={setSearchTerm}
/>

Sub-components

  • Status dropdown: Radix DropdownMenu with radio group selection and check indicators
  • Interval dropdown: Radix DropdownMenu with radio group selection
  • Search input: Text input with search icon, clear button, and loading spinner

SponsorshipList

Renders the list of sponsorship items with loading skeletons and an empty state call-to-action.

Props -- SponsorshipListProps

PropTypeDefaultDescription
itemsSponsorAd[]requiredSponsorship items to display
pricingConfigPricingConfigrequiredPricing configuration
isLoadingbooleanfalseShow skeleton loading state
skeletonCountnumber3Number of skeletons to show
emptyStateTitlestringi18n defaultCustom empty state title
emptyStateDescriptionstringi18n defaultCustom empty state description
onViewDetails(id: string) => voidundefinedView details callback
onCancel(ad: SponsorAd) => voidundefinedCancel callback
onPayNow(ad: SponsorAd) => voidundefinedPay now callback
onRenew(ad: SponsorAd) => voidundefinedRenew callback
isActionDisabledbooleanfalseDisable all action buttons

States

Empty State

When no items are present, displays a centered empty state with:

  • Dollar sign icon in a gradient container
  • Customizable title and description
  • "Create First Sponsorship" CTA button linking to /sponsor

SponsorshipItem

An individual sponsorship card displaying item name, status badge, dates, pricing, and action buttons.

Props -- SponsorshipItemProps

PropTypeDefaultDescription
sponsorAdSponsorAdrequiredSponsorship data
pricingConfigPricingConfigrequiredPricing configuration
onViewDetails(id: string) => voidundefinedView details callback
onCancel(ad: SponsorAd) => voidundefinedCancel callback
onPayNow(ad: SponsorAd) => voidundefinedPay now callback
onRenew(ad: SponsorAd) => voidundefinedRenew callback
isActionDisabledbooleanfalseDisable all action buttons

Action Availability by Status

StatusPay NowCancelRenew
pending_paymentYesYesNo
pendingNoYesNo
activeNoYesYes
expiredNoNoYes
rejectedNoNoNo
cancelledNoNoNo

Visual Elements

  • Item icon with gradient background
  • Item name (converted from slug)
  • Interval label and price display
  • Date range (start - end)
  • Status badge with color from SPONSOR_STATUS_CONFIG
  • View Details button
  • Action buttons (Pay Now, Renew, Cancel) using HeroUI Button
  • Rejection/cancellation reason display (when applicable)

Skeleton

A SponsorshipItemSkeleton component is exported for use during loading states.

SponsorshipDetailModal

A full-detail modal for viewing and managing a single sponsorship. Fetches fresh data via useSponsorAdDetail hook when opened.

Props -- SponsorshipDetailModalProps

PropTypeDefaultDescription
isOpenbooleanrequiredModal visibility state
sponsorshipIdstring | nullrequiredID of the sponsorship to display
onClose() => voidrequiredClose handler
onActionComplete() => voidundefinedCallback after successful action

Usage

import { SponsorshipDetailModal } from '@/components/sponsorships';

<SponsorshipDetailModal
isOpen={isDetailOpen}
sponsorshipId={selectedId}
onClose={() => setIsDetailOpen(false)}
onActionComplete={refetchList}
/>
  1. Status Badge -- Centered at top with full-width style
  2. Item Information -- Item name and slug in a card
  3. Subscription Details -- Interval, amount, payment provider
  4. Important Dates -- Created, start, end, reviewed dates
  5. Rejection/Cancellation Reason -- Conditionally shown in a colored alert box
  6. Cancel Confirmation -- Inline confirmation with confirm/cancel buttons

Actions

ActionConditionBehavior
Paystatus === 'pending_payment'POSTs to /api/sponsor-ads/checkout, redirects to checkout URL
Cancelpending_payment, pending, or activeShows inline confirmation, then POSTs to /api/sponsor-ads/user/{id}/cancel
Renewstatus === 'expired'Redirects to /sponsor page

States

  • Loading: Animated skeleton placeholder
  • Error: Alert icon with retry button
  • Data loaded: Full detail display with conditional action buttons

CancelDialog

A modal dialog for confirming sponsorship cancellation with an optional reason.

Props -- CancelDialogProps

PropTypeDefaultDescription
isOpenbooleanrequiredDialog visibility
sponsorAdSponsorAd | nullrequiredSponsorship being cancelled
cancelReasonstringrequiredCurrent reason text
isSubmittingbooleanrequiredSubmission in progress
onReasonChange(value: string) => voidrequiredReason text change handler
onConfirm() => voidrequiredConfirm cancellation
onClose() => voidrequiredClose dialog

Features

  • Amber gradient header with warning icon
  • Warning message about the cancellation consequences
  • Item preview showing name and interval
  • Optional cancellation reason textarea (max 500 characters with counter)
  • "Keep Sponsorship" and "Confirm Cancel" action buttons
  • Escape key closes the dialog (unless submitting)
  • Body scroll lock while open
  • Backdrop click to close

RenewDialog

A modal dialog for confirming sponsorship renewal with pricing information.

Props -- RenewDialogProps

PropTypeDefaultDescription
isOpenbooleanrequiredDialog visibility
sponsorAdSponsorAd | nullrequiredSponsorship being renewed
pricingConfigPricingConfigrequiredCurrent pricing configuration
isSubmittingbooleanrequiredSubmission in progress
onConfirm() => voidrequiredConfirm renewal
onClose() => voidrequiredClose dialog

Features

  • Green gradient header with refresh icon
  • Item preview showing name, interval, and renewal duration
  • Pricing box showing the renewal amount formatted with Intl.NumberFormat
  • Info box explaining the redirect to payment
  • "Cancel" and "Proceed to Payment" action buttons
  • Escape key closes the dialog (unless submitting)
  • Body scroll lock while open

Pricing Logic

The renewal price is determined by the sponsorship interval:

  • weekly interval uses pricingConfig.weeklyPrice
  • monthly interval (default) uses pricingConfig.monthlyPrice

PricingConfig Type

Shared across multiple components:

interface PricingConfig {
enabled: boolean;
weeklyPrice: number;
monthlyPrice: number;
currency: string;
}

Dependencies

  • @heroui/react -- Button, Textarea components
  • @radix-ui/react-dropdown-menu -- Filter dropdown menus
  • next-intl -- useTranslations for i18n
  • sonner -- Toast notifications for action feedback
  • lucide-react / react-icons/fi -- Icons
  • @/hooks/use-sponsor-ad-detail -- Fetch individual sponsorship data
  • @/lib/api/server-api-client -- API client for checkout and cancel actions
  • @/utils/date -- formatDateShort utility
  • @/lib/utils/currency-format -- formatCurrencyAmount utility