7c7f33fbd6
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m58s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m33s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 44s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m1s
CI/CD Pipeline / Unit Tests (push) Successful in 6m19s
CI/CD Pipeline / Integration Tests (push) Successful in 2m17s
CI/CD Pipeline / Frontend Lint (push) Failing after 40s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m35s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m13s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 58s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 2m22s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m55s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
51 lines
1.3 KiB
TypeScript
51 lines
1.3 KiB
TypeScript
import React from "react"
|
|
import type { BillingCycle } from "@/api/subscription"
|
|
|
|
interface BillingCycleSwitchProps {
|
|
value: BillingCycle
|
|
onChange: (cycle: BillingCycle) => void
|
|
monthlyPrice: number
|
|
yearlyPrice: number
|
|
}
|
|
|
|
/** 自定义计费周期切换组件 */
|
|
export const BillingCycleSwitch: React.FC<BillingCycleSwitchProps> = ({
|
|
value,
|
|
onChange,
|
|
monthlyPrice,
|
|
yearlyPrice,
|
|
}) => (
|
|
<div className="xx-billing-cycle-switch">
|
|
<button
|
|
type="button"
|
|
className={`xx-billing-cycle-btn ${value === "monthly" ? "active" : ""}`}
|
|
onClick={() => onChange("monthly")}
|
|
>
|
|
¥{monthlyPrice}/月
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={`xx-billing-cycle-btn ${value === "yearly" ? "active" : ""}`}
|
|
onClick={() => onChange("yearly")}
|
|
>
|
|
¥{yearlyPrice}/年
|
|
{yearlyPrice > 0 && monthlyPrice > 0 && (
|
|
<span className="xx-save">省 ¥{monthlyPrice * 12 - yearlyPrice}</span>
|
|
)}
|
|
</button>
|
|
</div>
|
|
)
|
|
|
|
interface SpinnerProps {
|
|
size?: "small" | "large"
|
|
}
|
|
|
|
/** 自定义 Spinner 组件 */
|
|
export const Spinner: React.FC<SpinnerProps> = ({ size = "large" }) => (
|
|
<div className={`xx-spinner xx-spinner--${size}`}>
|
|
<div className="xx-spinner-dot" />
|
|
<div className="xx-spinner-dot" />
|
|
<div className="xx-spinner-dot" />
|
|
</div>
|
|
)
|