TorbatYar/frontend/modules/loyalty/features/referrals/codes.tsx
Mortezakoohjani d579d0b142 feat(loyalty): add Loyalty Platform Frontend module
Add frontend/modules/loyalty with types, API client, design system, feature pages and thin App Router routes under app/loyalty/. BFF proxy at app/api/loyalty/. Include loyalty frontend docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 10:50:55 +03:30

109 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { Button, Dialog, FormField, Input, PageHeader, EmptyState } from "@/components/ds";
import { loyaltyApi } from "@/modules/loyalty/services/loyalty-api";
import { useTenantId } from "@/hooks/useTenantId";
import { LoyaltyBreadcrumbs } from "@/modules/loyalty/components/LoyaltyBreadcrumbs";
import { LoyaltyPageLoader, LoyaltyPageError } from "@/modules/loyalty/pages/shared";
import { LoyaltyTablePage, LoyaltyStatusChip } from "@/modules/loyalty/design-system";
export default function ReferralCodesPage() {
const { tenantId } = useTenantId();
const qc = useQueryClient();
const [open, setOpen] = useState(false);
const [referralProgramId, setRp] = useState("");
const [memberId, setMemberId] = useState("");
const [code, setCode] = useState("");
const q = useQuery({
queryKey: ["loyalty", tenantId, "referral-codes"],
queryFn: async () => {
const page = await loyaltyApi.referralCodes.list(tenantId!, { page: 1, page_size: 200 });
return "items" in page ? page.items : (page as never);
},
enabled: !!tenantId,
});
const createM = useMutation({
mutationFn: () =>
loyaltyApi.referralCodes.create(tenantId!, {
referral_program_id: referralProgramId,
member_id: memberId,
code: code || undefined,
}),
onSuccess: () => {
toast.success("کد صادر شد");
setOpen(false);
qc.invalidateQueries({ queryKey: ["loyalty", tenantId, "referral-codes"] });
},
onError: (e: Error) => toast.error(e.message),
});
const revokeM = useMutation({
mutationFn: (id: string) => loyaltyApi.referralCodes.revoke(tenantId!, id),
onSuccess: () => {
toast.success("ابطال شد");
qc.invalidateQueries({ queryKey: ["loyalty", tenantId, "referral-codes"] });
},
onError: (e: Error) => toast.error(e.message),
});
if (!tenantId || q.isLoading) return <LoyaltyPageLoader />;
if (q.error) return <LoyaltyPageError error={q.error} onRetry={() => q.refetch()} />;
return (
<div>
<LoyaltyBreadcrumbs items={[{ label: "کدهای معرفی" }]} />
<PageHeader
title="کدهای معرفی"
description="صدور و ابطال کد معرفی عضو."
actions={<Button onClick={() => setOpen(true)}>صدور کد</Button>}
/>
<LoyaltyTablePage
title="کدها"
columns={[
{ key: "code", header: "کد" },
{ key: "member_id", header: "عضو" },
{
key: "status",
header: "وضعیت",
render: (r) => <LoyaltyStatusChip status={String(r.status)} domain="referral_code" />,
},
{
key: "actions",
header: "عملیات",
searchable: false,
render: (r) =>
r.status === "active" ? (
<Button size="sm" variant="outline" onClick={() => revokeM.mutate(String(r.id))}>
ابطال
</Button>
) : null,
},
]}
rows={(q.data ?? []) as unknown as Record<string, unknown>[]}
empty={<EmptyState title="کدی نیست" action={<Button onClick={() => setOpen(true)}>صدور</Button>} />}
/>
<Dialog open={open} onClose={() => setOpen(false)} title="صدور کد معرفی">
<div className="space-y-3">
<FormField label="شناسه برنامه معرفی">
<Input value={referralProgramId} onChange={(e) => setRp(e.target.value)} />
</FormField>
<FormField label="شناسه عضو">
<Input value={memberId} onChange={(e) => setMemberId(e.target.value)} />
</FormField>
<FormField label="کد (اختیاری)">
<Input value={code} onChange={(e) => setCode(e.target.value)} />
</FormField>
<Button loading={createM.isPending} onClick={() => createM.mutate()}>
صدور
</Button>
</div>
</Dialog>
</div>
);
}