Files
iddaai-fe/src/components/subscription/subscription-card.tsx
T
fahricansecer 5c8619b282
Deploy Iddaai Frontend / build-and-deploy (push) Failing after 34s
gg
2026-05-10 22:59:27 +03:00

221 lines
6.7 KiB
TypeScript

"use client";
import {
Box,
VStack,
Heading,
Text,
Flex,
Card,
Button,
Spinner,
HStack,
Textarea,
} from "@chakra-ui/react";
import { useTranslations } from "next-intl";
import { useColorModeValue } from "@/components/ui/color-mode";
import {
useMySubscription,
useCancelSubscription,
} from "@/lib/api/subscriptions/use-hooks";
import { PlanBadge } from "./plan-badge";
import { useSession } from "next-auth/react";
import { useState } from "react";
import { LuCalendar, LuTriangleAlert, LuX, LuCheck } from "react-icons/lu";
import { useRouter } from "@/i18n/navigation";
/**
* Subscription info card for the Profile page.
* Shows current plan, billing dates, cancel option.
*/
export function SubscriptionCard() {
const t = useTranslations("subscription");
const tCommon = useTranslations("common");
const { data: session } = useSession();
const { data: subData, isLoading } = useMySubscription(!!session);
const router = useRouter();
const cardBg = useColorModeValue("white", "gray.800");
const borderColor = useColorModeValue("gray.100", "gray.700");
const [showCancelForm, setShowCancelForm] = useState(false);
const [cancelReason, setCancelReason] = useState("");
const cancelMutation = useCancelSubscription();
const subscription = subData?.data ?? null;
const plan = subscription?.plan ?? session?.user?.subscriptionPlan ?? "free";
const isFree = plan === "free";
const handleCancel = async () => {
await cancelMutation.mutateAsync({ reason: cancelReason || undefined });
setShowCancelForm(false);
setCancelReason("");
};
if (isLoading) {
return (
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="xl">
<Card.Body>
<Flex justify="center" py={8}>
<Spinner size="sm" color="primary.500" />
</Flex>
</Card.Body>
</Card.Root>
);
}
return (
<Card.Root bg={cardBg} borderColor={borderColor} borderRadius="xl">
<Card.Header>
<Flex justify="space-between" align="center">
<Heading as="h3" size="sm">
{t("title")}
</Heading>
<PlanBadge plan={plan} />
</Flex>
</Card.Header>
<Card.Body pt={0}>
<VStack gap={4} align="stretch">
{/* Subscription Details */}
{subscription && !isFree && (
<>
{subscription.currentPeriodEnd && (
<Flex justify="space-between" align="center">
<HStack gap={2} color="fg.muted">
<LuCalendar size={14} />
<Text fontSize="sm">{t("next-billing")}</Text>
</HStack>
<Text fontSize="sm" fontWeight="semibold">
{new Date(
subscription.currentPeriodEnd,
).toLocaleDateString()}
</Text>
</Flex>
)}
{subscription.cancelEffectiveDate && (
<Box
p={3}
bg="orange.50"
_dark={{ bg: "orange.950" }}
borderRadius="lg"
>
<HStack gap={2}>
<LuTriangleAlert size={14} color="orange" />
<Text
fontSize="sm"
color="orange.600"
_dark={{ color: "orange.300" }}
>
{t("cancelled-info", {
date: new Date(
subscription.cancelEffectiveDate,
).toLocaleDateString(),
})}
</Text>
</HStack>
</Box>
)}
</>
)}
{/* Actions */}
<Flex gap={2} direction={{ base: "column", sm: "row" }}>
{isFree ? (
<Button
onClick={() => router.push("/pricing")}
colorPalette="primary"
variant="solid"
size="sm"
borderRadius="lg"
flex={1}
>
{t("upgrade-cta")}
</Button>
) : (
<>
<Button
onClick={() => router.push("/pricing")}
colorPalette="primary"
variant="outline"
size="sm"
borderRadius="lg"
flex={1}
>
{t("manage")}
</Button>
{!subscription?.cancelEffectiveDate && (
<Button
variant="ghost"
size="sm"
colorPalette="red"
onClick={() => setShowCancelForm(true)}
>
{t("cancel")}
</Button>
)}
</>
)}
</Flex>
{/* Cancel Confirmation */}
{showCancelForm && (
<Box
p={4}
borderWidth="1px"
borderColor="red.200"
_dark={{ borderColor: "red.800" }}
borderRadius="lg"
>
<VStack gap={3} align="stretch">
<Text
fontSize="sm"
fontWeight="semibold"
color="red.600"
_dark={{ color: "red.300" }}
>
{t("cancel-confirm-title")}
</Text>
<Text fontSize="xs" color="fg.muted">
{t("cancel-confirm-message")}
</Text>
<Textarea
size="sm"
placeholder={t("cancel-reason-placeholder")}
value={cancelReason}
onChange={(e) => setCancelReason(e.target.value)}
rows={2}
/>
<HStack gap={2} justify="flex-end">
<Button
variant="outline"
size="sm"
onClick={() => {
setShowCancelForm(false);
setCancelReason("");
}}
>
<LuX />
{tCommon("cancel")}
</Button>
<Button
colorPalette="red"
variant="solid"
size="sm"
loading={cancelMutation.isPending}
onClick={handleCancel}
>
<LuCheck />
{t("cancel")}
</Button>
</HStack>
</VStack>
</Box>
)}
</VStack>
</Card.Body>
</Card.Root>
);
}