Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 6s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 35s
Deploy / Build AI Services Image (push) Failing after 6s
CI / AI Services (Python) — Smoke (push) Failing after 5s
Deploy / Build API Image (push) Failing after 5s
Deploy / Build Web Image (push) Failing after 5s
E2E Tests / Playwright E2E (push) Failing after 18s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 4s
Security Scanning / Trivy Scan — API Image (push) Failing after 43s
Security Scanning / Trivy Scan — Web Image (push) Failing after 38s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 44s
Security Scanning / Trivy Filesystem Scan (push) Failing after 36s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Seed stores null (not -1) for unlimited quotas on the ENTERPRISE tier. PlanDto now types these as `number | null`. PricingPage treats null the same as -1 — both render 'Không giới hạn' instead of 'null tin đăng'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
1.9 KiB
TypeScript
81 lines
1.9 KiB
TypeScript
import { apiClient } from './api-client';
|
|
|
|
export interface PlanDto {
|
|
id: string;
|
|
tier: string;
|
|
name: string;
|
|
priceMonthlyVND: string;
|
|
priceYearlyVND: string;
|
|
maxListings: number | null; // null = unlimited (ENTERPRISE tier)
|
|
maxSavedSearches: number | null;
|
|
features: Record<string, boolean | number | string>;
|
|
isActive: boolean;
|
|
}
|
|
|
|
export interface SubscriptionInfo {
|
|
id: string;
|
|
planTier: string;
|
|
status: string;
|
|
currentPeriodStart: string;
|
|
currentPeriodEnd: string;
|
|
cancelledAt: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface BillingHistoryDto {
|
|
subscription: SubscriptionInfo | null;
|
|
payments: Array<{
|
|
id: string;
|
|
provider: string;
|
|
type: string;
|
|
amountVND: string;
|
|
status: string;
|
|
createdAt: string;
|
|
}>;
|
|
total: number;
|
|
}
|
|
|
|
export interface QuotaCheckResult {
|
|
metric: string;
|
|
used: number;
|
|
limit: number;
|
|
remaining: number;
|
|
}
|
|
|
|
export interface CreateSubscriptionResult {
|
|
subscriptionId: string;
|
|
planTier: string;
|
|
status: string;
|
|
currentPeriodStart: string;
|
|
currentPeriodEnd: string;
|
|
}
|
|
|
|
export const subscriptionApi = {
|
|
getPlans: () => apiClient.get<PlanDto[]>('/subscriptions/plans'),
|
|
|
|
getPlanByTier: (tier: string) =>
|
|
apiClient.get<PlanDto>(`/subscriptions/plans/${tier}`),
|
|
|
|
getBillingHistory: (limit = 20, offset = 0) =>
|
|
apiClient.get<BillingHistoryDto>(
|
|
`/subscriptions/billing?limit=${limit}&offset=${offset}`,
|
|
),
|
|
|
|
checkQuota: (metric: string) =>
|
|
apiClient.get<QuotaCheckResult>(`/subscriptions/quota/${metric}`),
|
|
|
|
createSubscription: (planTier: string, billingCycle: 'monthly' | 'yearly') =>
|
|
apiClient.post<CreateSubscriptionResult>('/subscriptions', {
|
|
planTier,
|
|
billingCycle,
|
|
}),
|
|
|
|
upgradeSubscription: (newPlanTier: string) =>
|
|
apiClient.post<{ message: string }>('/subscriptions/upgrade', {
|
|
newPlanTier,
|
|
}),
|
|
|
|
cancelSubscription: (_reason: string) =>
|
|
apiClient.delete<{ message: string }>('/subscriptions'),
|
|
};
|