barsa 9145b4aaed style: standardize conditional rendering syntax across components
- Updated multiple components to use consistent conditional rendering syntax by adding parentheses around conditions.
- Enhanced readability and maintainability of the code in components such as OtpInput, AddressCard, and others.
- Improved overall code quality and developer experience through uniformity in the codebase.
2026-03-05 15:52:26 +09:00

349 lines
12 KiB
TypeScript

"use client";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { cancellationService } from "@/features/subscriptions/api";
import type { CancellationPreview } from "@customer-portal/domain/subscriptions";
import { GlobeAltIcon, DevicePhoneMobileIcon, ClockIcon } from "@heroicons/react/24/outline";
import { PageLayout } from "@/components/templates/PageLayout";
import { Button } from "@/components/atoms";
import Link from "next/link";
import {
CancellationFlow,
Notice,
InfoNotice,
ServiceInfoGrid,
ServiceInfoItem,
CancellationSummary,
MinimumContractWarning,
} from "@/features/subscriptions/components/CancellationFlow";
import { useAuthStore } from "@/features/auth/stores/auth.store";
import { devErrorMessage, formatAddressLabel } from "@/shared/utils";
const SUBSCRIPTIONS_HREF = "/account/subscriptions";
// ============================================================================
// Pending Cancellation View (when Opportunity is already in △Cancelling)
// ============================================================================
function CancellationPendingView({
subscriptionId,
preview,
}: {
subscriptionId: string;
preview: CancellationPreview;
}) {
const icon = preview.serviceType === "internet" ? <GlobeAltIcon /> : <DevicePhoneMobileIcon />;
const title =
preview.serviceType === "internet"
? "Internet Cancellation Pending"
: "SIM Cancellation Pending";
return (
<PageLayout
icon={icon}
title={title}
description={preview.serviceName}
breadcrumbs={[
{ label: "Subscriptions", href: SUBSCRIPTIONS_HREF },
{ label: preview.serviceName, href: `/account/subscriptions/${subscriptionId}` },
{ label: "Cancellation Status" },
]}
>
<div className="max-w-2xl mx-auto">
<div className="bg-card border border-border rounded-xl p-6 sm:p-8">
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 bg-warning-soft rounded-full flex items-center justify-center">
<ClockIcon className="w-6 h-6 text-warning" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Cancellation In Progress</h2>
<p className="text-sm text-muted-foreground">
Your cancellation request is being processed.
</p>
</div>
</div>
<div className="space-y-4">
<div className="p-4 bg-muted/50 rounded-lg">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-muted-foreground">Service:</span>
<div className="font-medium text-foreground">{preview.serviceName}</div>
</div>
{preview.cancellationStatus?.scheduledEndDate && (
<div>
<span className="text-muted-foreground">Scheduled End:</span>
<div className="font-medium text-foreground">
{new Date(preview.cancellationStatus.scheduledEndDate).toLocaleDateString(
"en-US",
{ month: "long", year: "numeric" }
)}
</div>
</div>
)}
</div>
</div>
{preview.serviceType === "internet" &&
preview.cancellationStatus?.rentalReturnStatus && (
<div className="p-4 bg-info-soft/50 border-l-4 border-info rounded-r-lg">
<div className="text-sm font-medium text-foreground mb-1">
Equipment Return Status
</div>
<div className="text-sm text-muted-foreground">
{preview.cancellationStatus.rentalReturnStatus}
</div>
</div>
)}
<p className="text-sm text-muted-foreground">
You will receive an email confirmation when the cancellation is complete. If you have
questions, please contact our support team.
</p>
</div>
<div className="mt-6 pt-6 border-t border-border">
<Link href={`/account/subscriptions/${subscriptionId}`}>
<Button variant="outline">Back to Subscription</Button>
</Link>
</div>
</div>
</div>
</PageLayout>
);
}
// ============================================================================
// Main Component
// ============================================================================
function getServiceIcon(serviceType: string) {
return serviceType === "internet" ? <GlobeAltIcon /> : <DevicePhoneMobileIcon />;
}
function getServiceTitle(serviceType: string) {
return serviceType === "internet" ? "Cancel Internet Service" : "Cancel SIM Service";
}
function getConfirmMessage(serviceType: string) {
return serviceType === "internet"
? "Are you sure you want to cancel your Internet service? This will take effect at the end of {month}."
: "Are you sure you want to cancel your SIM subscription? This will take effect at the end of {month}.";
}
function useCancellationState(subscriptionId: string) {
const router = useRouter();
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [preview, setPreview] = useState<CancellationPreview | null>(null);
const [error, setError] = useState<string | null>(null);
const [formError, setFormError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [selectedMonthLabel, setSelectedMonthLabel] = useState<string>("");
useEffect(() => {
const fetchPreview = async () => {
try {
const data = await cancellationService.getPreview(subscriptionId);
setPreview(data);
} catch (e: unknown) {
setError(
devErrorMessage(
e,
"Failed to load cancellation information",
"Unable to load cancellation information right now. Please try again."
)
);
} finally {
setLoading(false);
}
};
void fetchPreview();
}, [subscriptionId]);
const handleSubmit = async (data: {
cancellationMonth: string;
confirmRead: boolean;
confirmCancel: boolean;
comments?: string;
}) => {
setSubmitting(true);
setFormError(null);
const monthInfo = preview?.availableMonths.find(m => m.value === data.cancellationMonth);
setSelectedMonthLabel(monthInfo?.label || data.cancellationMonth);
try {
await cancellationService.submit(subscriptionId, {
cancellationMonth: data.cancellationMonth,
confirmRead: data.confirmRead,
confirmCancel: data.confirmCancel,
comments: data.comments,
});
setSuccessMessage("Cancellation request submitted. You will receive a confirmation email.");
setTimeout(() => router.push(`/account/subscriptions/${subscriptionId}`), 2000);
} catch (e: unknown) {
setFormError(
devErrorMessage(
e,
"Failed to submit cancellation",
"Unable to submit your cancellation right now. Please try again."
)
);
} finally {
setSubmitting(false);
}
};
return {
loading,
submitting,
preview,
error,
formError,
successMessage,
selectedMonthLabel,
handleSubmit,
};
}
function CancellationFlowView({
subscriptionId,
preview,
formError,
successMessage,
submitting,
selectedMonthLabel,
onSubmit,
}: {
subscriptionId: string;
preview: CancellationPreview;
formError: string | null;
successMessage: string | null;
submitting: boolean;
selectedMonthLabel: string;
onSubmit: (data: {
cancellationMonth: string;
confirmRead: boolean;
confirmCancel: boolean;
comments?: string;
}) => Promise<void>;
}) {
const user = useAuthStore(state => state.user);
return (
<CancellationFlow
icon={getServiceIcon(preview.serviceType)}
title={getServiceTitle(preview.serviceType)}
description={preview.serviceName}
breadcrumbs={[
{ label: "Subscriptions", href: SUBSCRIPTIONS_HREF },
{ label: preview.serviceName, href: `/account/subscriptions/${subscriptionId}` },
{ label: "Cancel" },
]}
backHref={`/account/subscriptions/${subscriptionId}`}
backLabel="Back to Subscription"
availableMonths={preview.availableMonths}
customerEmail={preview.customerEmail}
loading={false}
error={null}
formError={formError}
successMessage={successMessage}
submitting={submitting}
confirmMessage={getConfirmMessage(preview.serviceType)}
confirmationDetails={[
{ label: "Service", value: preview.serviceName },
{ label: "Customer", value: preview.customerName },
{ label: "Email", value: preview.customerEmail },
{ label: "Service Address", value: formatAddressLabel(user?.address) || "—" },
]}
onSubmit={onSubmit}
warningBanner={
preview.isWithinMinimumTerm && preview.minimumContractEndDate ? (
<MinimumContractWarning endDate={preview.minimumContractEndDate} />
) : undefined
}
serviceInfo={
<ServiceInfoGrid>
{preview.serviceInfo.map((info, idx) => (
<ServiceInfoItem
key={idx}
label={info.label}
value={info.value}
mono={info.mono ?? false}
/>
))}
</ServiceInfoGrid>
}
termsContent={
<div className="space-y-3">
{preview.terms.map((term, idx) => (
<Notice key={idx} title={term.title}>
{term.content}
</Notice>
))}
</div>
}
summaryContent={
<>
<CancellationSummary
items={preview.serviceInfo.map(info => ({ label: info.label, value: info.value }))}
selectedMonth={selectedMonthLabel || "the selected month"}
/>
{preview.step3Notices && preview.step3Notices.length > 0 && (
<div className="space-y-3 mt-4">
{preview.step3Notices.map((notice, idx) => (
<InfoNotice key={idx} title={notice.title}>
{notice.content}
</InfoNotice>
))}
</div>
)}
</>
}
/>
);
}
export function CancelSubscriptionContainer() {
const params = useParams();
const subscriptionId = params["id"] as string;
const state = useCancellationState(subscriptionId);
if (state.loading || state.error) {
return (
<PageLayout
icon={<GlobeAltIcon />}
title="Cancel Subscription"
description="Loading cancellation information..."
breadcrumbs={[{ label: "Subscriptions", href: SUBSCRIPTIONS_HREF }, { label: "Cancel" }]}
loading={state.loading}
error={state.error}
>
<></>
</PageLayout>
);
}
if (!state.preview) return null;
if (state.preview.cancellationStatus?.stage === "△Cancelling") {
return <CancellationPendingView subscriptionId={subscriptionId} preview={state.preview} />;
}
return (
<CancellationFlowView
subscriptionId={subscriptionId}
preview={state.preview}
formError={state.formError}
successMessage={state.successMessage}
submitting={state.submitting}
selectedMonthLabel={state.selectedMonthLabel}
onSubmit={state.handleSubmit}
/>
);
}
export default CancelSubscriptionContainer;