barsa ab429f91dc Enhance Internet Eligibility Features and Update Catalog Integration
- Added new fields for internet eligibility in the environment validation schema to support Salesforce integration.
- Updated CatalogCdcSubscriber to extract and handle additional eligibility details from Salesforce events.
- Refactored InternetEligibilityController to return detailed eligibility information, improving user experience.
- Enhanced CatalogCacheService with a new method for setting eligibility details, optimizing cache management.
- Updated InternetCatalogService to retrieve and process comprehensive eligibility data, ensuring accurate service availability checks.
- Improved public-facing components to reflect the new eligibility status and provide clearer user guidance during the checkout process.
2025-12-19 15:15:36 +09:00

177 lines
6.4 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 { useEffect, useRef, useState } from "react";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { useAuthStore } from "@/features/auth/services/auth.store";
import { useDashboardSummary, useDashboardTasks } from "@/features/dashboard/hooks";
import { TaskList, QuickStats, ActivityFeed } from "@/features/dashboard/components";
import { ErrorState } from "@/components/atoms/error-state";
import { PageLayout } from "@/components/templates";
import { cn } from "@/lib/utils";
import { InlineToast } from "@/components/atoms/inline-toast";
import { useInternetEligibility } from "@/features/catalog/hooks";
export function DashboardView() {
const { user, isAuthenticated, loading: authLoading, clearLoading } = useAuthStore();
const hideToastTimeout = useRef<number | null>(null);
const [eligibilityToast, setEligibilityToast] = useState<{
visible: boolean;
text: string;
tone: "info" | "success" | "warning" | "error";
}>({ visible: false, text: "", tone: "info" });
// Clear auth loading state when dashboard loads (after successful login)
useEffect(() => {
clearLoading();
}, [clearLoading]);
const { data: summary, isLoading: summaryLoading, error } = useDashboardSummary();
const { tasks, isLoading: tasksLoading, taskCount } = useDashboardTasks();
const { data: eligibility } = useInternetEligibility({ enabled: isAuthenticated });
// Combined loading state
const isLoading = authLoading || summaryLoading || !isAuthenticated;
useEffect(() => {
if (!isAuthenticated || !user?.id) return;
const status = eligibility?.status;
if (!status) return; // query not ready yet
const key = `cp:internet-eligibility:last:${user.id}`;
const last = localStorage.getItem(key);
if (status === "pending") {
localStorage.setItem(key, "PENDING");
return;
}
if (status === "eligible" && typeof eligibility?.eligibility === "string") {
const current = eligibility.eligibility.trim();
if (last === "PENDING") {
setEligibilityToast({
visible: true,
text: "Weve finished reviewing your address — you can now choose personalized internet plans.",
tone: "success",
});
if (hideToastTimeout.current) window.clearTimeout(hideToastTimeout.current);
hideToastTimeout.current = window.setTimeout(() => {
setEligibilityToast(t => ({ ...t, visible: false }));
hideToastTimeout.current = null;
}, 3500);
}
localStorage.setItem(key, current);
}
}, [eligibility?.eligibility, eligibility?.status, isAuthenticated, user?.id]);
useEffect(() => {
return () => {
if (hideToastTimeout.current) window.clearTimeout(hideToastTimeout.current);
};
}, []);
if (isLoading) {
return (
<PageLayout title="Dashboard" description="Overview of your account" loading>
<div className="space-y-8">
{/* Greeting skeleton */}
<div className="animate-pulse">
<div className="h-4 bg-muted rounded w-24 mb-3" />
<div className="h-10 bg-muted rounded w-56 mb-2" />
<div className="h-4 bg-muted rounded w-40" />
</div>
{/* Tasks skeleton */}
<div className="space-y-4">
<div className="h-24 bg-muted rounded-2xl" />
<div className="h-24 bg-muted rounded-2xl" />
</div>
{/* Bottom section skeleton */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="h-44 bg-muted rounded-2xl" />
<div className="h-44 bg-muted rounded-2xl" />
</div>
</div>
</PageLayout>
);
}
// Handle error state
if (error) {
const errorMessage =
typeof error === "string"
? error
: error instanceof Error
? error.message
: typeof error === "object" && error && "message" in error
? String((error as { message?: unknown }).message)
: "An unexpected error occurred";
return (
<PageLayout title="Dashboard" description="Overview of your account">
<ErrorState title="Error loading dashboard" message={errorMessage} variant="page" />
</PageLayout>
);
}
// Get user's display name
const displayName = user?.firstname || user?.email?.split("@")[0] || "there";
// Determine urgency level for task badge
const hasUrgentTask = tasks.some(t => t.tone === "critical");
return (
<PageLayout title="Dashboard" description="Overview of your account">
<InlineToast
visible={eligibilityToast.visible}
text={eligibilityToast.text}
tone={eligibilityToast.tone}
/>
{/* Greeting Section */}
<div className="mb-8">
<p className="text-sm font-medium text-muted-foreground">Welcome back</p>
<h2 className="text-3xl sm:text-4xl font-bold text-foreground mt-1">{displayName}</h2>
{/* Task status badge */}
{taskCount > 0 ? (
<div className="flex items-center gap-2 mt-3">
<span
className={cn(
"inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium",
hasUrgentTask ? "bg-danger/10 text-danger" : "bg-warning/10 text-warning"
)}
>
{hasUrgentTask && <ExclamationTriangleIcon className="h-4 w-4" />}
{taskCount === 1 ? "1 task needs attention" : `${taskCount} tasks need attention`}
</span>
</div>
) : (
<p className="text-sm text-muted-foreground mt-2">Everything is up to date</p>
)}
</div>
{/* Tasks Section - Main focus area */}
<section className="mb-10" aria-labelledby="tasks-heading">
<h3 id="tasks-heading" className="sr-only">
Your Tasks
</h3>
<TaskList tasks={tasks} isLoading={tasksLoading} maxTasks={4} />
</section>
{/* Bottom Section: Quick Stats + Recent Activity */}
<section className="grid grid-cols-1 lg:grid-cols-2 gap-6" aria-label="Account overview">
<QuickStats
activeSubscriptions={summary?.stats?.activeSubscriptions ?? 0}
openCases={summary?.stats?.openCases ?? 0}
recentOrders={summary?.stats?.recentOrders}
isLoading={summaryLoading}
/>
<ActivityFeed
activities={summary?.recentActivity || []}
maxItems={5}
isLoading={summaryLoading}
/>
</section>
</PageLayout>
);
}