知识库首页 模版 subscription-portal-dialog.tsx.md

subscription portal dialog.tsx

本地来源:模版/模版文档对比说明/05-组件系统/subscription-portal-dialog.tsx.md

subscription-portal-dialog.tsx 订阅门户对话框组件详细分析

📋 文件概述和作用

subscription-portal-dialog.tsx 是一个客户端组件,用于提供订阅管理的对话框界面。它允许已有订阅的用户通过安全的客户门户访问其订阅设置、支付信息和账单历史。

📦 导入语句详细解释

"use client";
  • 声明这是一个客户端组件,因为需要使用React状态管理和浏览器API
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
  • 导入shadcn/ui对话框组件系列
  • 提供完整的模态对话框功能
import { Button } from "@/components/ui/button";
  • 导入Button组件,用于触发和操作按钮
import { useState } from "react";
  • 导入React的useState Hook,用于管理组件状态
import { ArrowRight, CreditCard, Receipt, Settings } from "lucide-react";
  • 导入Lucide React图标库的图标
  • ArrowRight:右箭头图标
  • CreditCard:信用卡图标
  • Receipt:收据图标
  • Settings:设置图标
import { createClient } from "@/utils/supabase/client";
  • 导入Supabase客户端工具函数
  • 用于与Supabase后端进行通信
import { useEffect } from "react";
  • 导入useEffect Hook,用于副作用处理

🔧 核心功能分析

状态管理

const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [hasCustomer, setHasCustomer] = useState(false);
  • isLoading: 控制加载状态
  • error: 存储错误信息
  • hasCustomer: 检查用户是否是付费客户

客户状态检查

useEffect(() => {
  const checkCustomer = async () => {
    try {
      const { data: { user } } = await supabase.auth.getUser();
      if (!user) return;

      const { data: customer } = await supabase
        .from("customers")
        .select("creem_customer_id")
        .eq("user_id", user.id)
        .single();

      setHasCustomer(!!customer?.creem_customer_id);
    } catch (err) {
      console.error("Error checking customer:", err);
      setHasCustomer(false);
    }
  };

  checkCustomer();
}, []);
  • 组件挂载时检查用户是否是付费客户
  • 从Supabase获取当前用户信息
  • 查询customers表检查是否有creem_customer_id
  • 错误处理:如果查询失败,设置hasCustomer为false

订阅管理处理

const handleManageSubscription = async () => {
  try {
    setIsLoading(true);
    setError(null);

    const response = await fetch("/api/creem/customer-portal");
    if (!response.ok) {
      throw new Error("Failed to get portal link");
    }

    const { customer_portal_link } = await response.json();
    window.open(customer_portal_link, "_blank");
  } catch (err) {
    console.error("Error getting portal link:", err);
    setError("Failed to access subscription portal. Please try again later.");
  } finally {
    setIsLoading(false);
  }
};
  • 调用API获取客户门户链接
  • 在新窗口中打开门户链接
  • 完整的错误处理和状态管理

🎨 UI结构分析

条件渲染

if (!hasCustomer) {
  return null;
}
  • 如果用户不是付费客户,不渲染任何内容
  • 实现了权限控制

对话框触发器

<DialogTrigger asChild>
  <Button variant="outline" className="w-full">
    Manage Plan
    <ArrowRight className="ml-2 h-4 w-4" />
  </Button>
</DialogTrigger>
  • 使用outline样式的按钮作为触发器
  • 全宽度布局
  • 包含右箭头图标

对话框内容

<DialogContent className="sm:max-w-[425px]">
  <DialogHeader>
    <DialogTitle>Subscription Management</DialogTitle>
    <DialogDescription>
      Access your subscription settings in our secure customer portal.
    </DialogDescription>
  </DialogHeader>
  • 设置对话框最大宽度
  • 标题和描述说明功能

功能展示区域

<div className="grid gap-4 py-4">
  <div className="grid gap-6">
    <div className="grid gap-4">
      {/* Payment Methods */}
      <div className="flex items-center gap-4">
        <div className="p-2 bg-primary/10 rounded-lg">
          <CreditCard className="h-5 w-5 text-primary" />
        </div>
        <div className="space-y-1">
          <p className="text-sm font-medium">Payment Methods</p>
          <p className="text-sm text-muted-foreground">
            Update your billing information
          </p>
        </div>
      </div>
      {/* ... 其他功能项 ... */}
    </div>
  </div>
</div>
  • 网格布局展示门户功能
  • 每个功能项包含图标和说明
  • 使用主题色的图标背景

错误显示

{error && (
  <div className="text-sm text-destructive bg-destructive/10 p-3 rounded-lg">
    {error}
  </div>
)}
  • 条件渲染错误信息
  • 使用destructive主题色
  • 圆角背景和内边距

操作按钮

<DialogFooter className="flex space-x-2 sm:space-x-0">
  <Button onClick={handleManageSubscription} disabled={isLoading}>
    {isLoading ? "Redirecting..." : "Continue to Portal"}
    <ArrowRight className="ml-2 h-4 w-4" />
  </Button>
</DialogFooter>
  • 响应式间距设计
  • 动态按钮文本
  • 加载状态禁用

🔐 安全性特性

  1. 用户认证检查: 确保只有登录用户可以访问
  2. 客户状态验证: 只有付费客户才能看到管理选项
  3. 错误处理: 完整的错误捕获和用户提示
  4. 外部链接安全: 在新窗口打开门户链接

💡 设计模式

  1. 条件渲染模式: 根据用户状态控制组件显示
  2. 状态管理模式: 使用React Hooks管理复杂状态
  3. 错误边界模式: 完整的错误处理机制
  4. 组合模式: 使用shadcn/ui的组合API

🎯 业务逻辑

  1. 权限控制: 只有付费客户可以访问
  2. 外部集成: 与Creem支付系统集成
  3. 用户体验: 清晰的功能说明和状态反馈
  4. 安全性: 通过API获取门户链接

📱 响应式设计

  • 对话框在小屏幕上自适应
  • 使用Tailwind CSS的响应式类
  • 合理的内边距和间距设计

🧪 使用示例

// 在Dashboard页面中使用
import { SubscriptionPortalDialog } from "@/components/dashboard/subscription-portal-dialog";

function Dashboard() {
  return (
    <div>
      <SubscriptionPortalDialog />
    </div>
  );
}

📝 最佳实践

  1. 错误处理: 完整的try-catch机制
  2. 状态管理: 清晰的状态控制
  3. 用户体验: 加载状态和错误提示
  4. 安全性: 权限验证和API调用安全
  5. 可访问性: 语义化的对话框组件

这个组件是订阅管理系统的关键部分,提供了安全、易用的客户门户访问入口。

本文档为站内渲染。原始文件本地路径:saas/source/templates/模版-模版文档对比说明-05-组件系统-subscription-portal-dialog-tsx-ae4e01.md(仅本地保留,不入库不部署)