Skip to content

Coupon Module

Module untuk manage coupons/sponsor codes di Naluri.

Overview

Coupon module menampilkan dan mengelola:

  • Coupon/Sponsor codes
  • Trial periods
  • Member assignments
  • Blood test configurations
  • Domain whitelists
  • Focus Dashboard access

File Structure

src/modules/coupon/
├── Coupon.tsx              # Parent layout dengan tab navigation
├── CouponList.tsx          # Listing page dengan search/filter
├── tabs/
│   ├── Details.tsx         # Coupon details & trial management
│   ├── AllMember.tsx       # All members list
│   ├── Groups.tsx          # Member groups
│   ├── Onboarding.tsx      # Domain whitelist & blood test
│   └── FocusDashboard.tsx  # Focus Dashboard access
├── components/
│   ├── CouponTable.tsx     # Data table component
│   ├── EditDependentCouponCodeModal.tsx
│   ├── FocusDashboardToggle.tsx
│   └── GrantAccessSelector.tsx
└── store/
    └── Coupon.store.ts     # Type definitions

Data Types

CouponCodeDetails

typescript
interface CouponCodeDetails {
  id: string;              // coupons.id (sponsorCodeId)
  name: string;            // coupon_codes.name
  couponId: string;        // Reference ke parent coupon
  sponsor: string;         // Sponsor name
  planId: string;
  active: boolean;
  couponCodes?: Array<{
    couponId: string;
    id: string;            // coupon_codes.id (couponCodeId)
    name: string;
  }>;
  plan: {
    id: string;
    title: string;
    description: string;
    priceCents: number;
    currency: string;
    recurrence: number;
    active: boolean;
    trialDays: number;
    private: boolean;
    subscriptions: Subscription[];
  };
}

Tab Components

Details Tab

File: tabs/Details.tsx

FeatureAPI EndpointDescription
View coupon infoGET /coupon/show/{id}Load coupon details
Update trial datePUT /coupon/update-end-dateChange trial period
Dependent codesGET/PUT /coupon/dependent-sponsor-code/{id}Manage dependent relationships

Onboarding Tab

File: tabs/Onboarding.tsx

FeatureAPI EndpointDescription
Domain whitelistGET/PUT /coupon/{code}/domain-whitelistsManage allowed domains
Blood test availabilityGET/POST/PUT/DELETE /coupon/{id}/blood-test-availabilityConfigure periods
Blood test locationsGET/POST/PUT/DELETE /coupon/{id}/blood-test-locationsManage locations

Focus Dashboard Tab

File: tabs/FocusDashboard.tsx

FeatureAPI EndpointDescription
Get detailsGET /focus-dashboard/details/{sponsorCodeId}Load current config
Search usersGET /focus-dashboard/search-usersFind members/nalurians
Manage accessPOST /focus-dashboard/manage-accessEnable/disable access

API Hooks

Queries (GET)

typescript
// src/api/queries/coupon.ts
useCouponList()                    // List all coupons
useCouponDetail(couponId)          // Single coupon details
useGetDependentCouponCode(id)      // Dependent code config

// src/api/queries/focusDashboard.ts
useFocusDashboardDetails(sponsorCodeId)
useSearchFocusDashboardUsers(email, type)

Mutations (POST/PUT/DELETE)

typescript
// src/api/mutations/coupon.ts
useUpdateCouponEndDate()
useUpdateDependentSponsorCode()
useUpdateCouponDomainWhitelists()

// src/api/mutations/focusDashboard.ts
useManageFocusDashboardAccess()
CouponList

    │ click row

Coupon.tsx (parent layout)

    ├── useCouponDetail(couponId)  ──▶  Fetch data

    └── <Outlet context={couponData} />

              ├── Details tab
              ├── AllMember tab
              ├── Groups tab
              ├── Onboarding tab
              └── FocusDashboard tab

Key Patterns

Outlet Context

typescript
// Parent (Coupon.tsx)
<Outlet context={{ ...couponData, setCSVData }} />

// Child tab
const context = useOutletContext<CouponCodeDetails>();
const sponsorCodeId = context.id;

Form State Management

typescript
// Track changes
const [enabled, setEnabled] = useState(false);
const [initialEnabled, setInitialEnabled] = useState(false);

const isDirty = useMemo(() => {
  return enabled !== initialEnabled;
}, [enabled, initialEnabled]);

// Only save if dirty
<Button disabled={!isDirty} onClick={handleSave}>
  Save Changes
</Button>

Batch Operations

typescript
// Save multiple coupon codes at once
const requests = Object.entries(couponCodeToUsers).map(
  ([couponCodeId, userIds]) =>
    manageFocusDashboardAccess.mutateAsync({
      sponsorCodeId,
      couponCodeId,
      action: enabled ? 'enable' : 'disable',
      userIds,
    })
);
await Promise.all(requests);

Last updated: March 2026

Internal Documentation