fix: 专业版单设备时隐藏席位文案
This commit is contained in:
1 parent
ff016347cd
commit
f71eef0eba
1 file changed
+46
-78
@@ -4,18 +4,8 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
Check,
|
||||
Crown,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
Shield,
|
||||
} from 'lucide-react'
|
||||
import {
|
||||
proFeatureLabel,
|
||||
useEntitlementStore,
|
||||
type ProFeature,
|
||||
} from '../../stores/entitlementStore'
|
||||
import { Check, Crown, ExternalLink, Loader2, Shield } from 'lucide-react'
|
||||
import { proFeatureLabel, useEntitlementStore, type ProFeature } from '../../stores/entitlementStore'
|
||||
import { checkout as licenseCheckout } from '../../platform/licenseApi'
|
||||
import { createLogger } from '../../utils/logger'
|
||||
|
||||
@@ -38,10 +28,7 @@ function looksLikeValidLicenseKey(value: string): boolean {
|
||||
return trimmed.startsWith('LIC-') || trimmed.startsWith('LICOFF-') || trimmed.length >= 8
|
||||
}
|
||||
|
||||
function activatedLabel(
|
||||
t: (key: string) => string,
|
||||
planType: string | null
|
||||
): string {
|
||||
function activatedLabel(t: (key: string) => string, planType: string | null): string {
|
||||
switch (planType) {
|
||||
case 'yearly':
|
||||
return t('pro.activatedYearly')
|
||||
@@ -97,38 +84,39 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
|
||||
const highlight = feature ? proFeatureLabel(feature) : null
|
||||
|
||||
const startCheckout = useCallback(async (planChoice: 'yearly' | 'perpetual') => {
|
||||
setSelectedPlan(planChoice)
|
||||
setCheckoutLoading(true)
|
||||
setCheckoutError(null)
|
||||
const trimmedEmail = emailInput.trim()
|
||||
if (!trimmedEmail || !trimmedEmail.includes('@')) {
|
||||
setCheckoutError(t('pro.emailRequired'))
|
||||
setCheckoutLoading(false)
|
||||
return
|
||||
}
|
||||
try {
|
||||
await setEmail(trimmedEmail)
|
||||
const id =
|
||||
deviceId ||
|
||||
(await window.opsxApi.licenseDeviceId().catch(() => ''))
|
||||
const result = await licenseCheckout({
|
||||
customerEmail: trimmedEmail,
|
||||
deviceId: id,
|
||||
planType: planChoice,
|
||||
})
|
||||
if (!result.checkoutUrl) {
|
||||
throw new Error(t('pro.checkoutUnavailable', { status: 'no-url' }))
|
||||
const startCheckout = useCallback(
|
||||
async (planChoice: 'yearly' | 'perpetual') => {
|
||||
setSelectedPlan(planChoice)
|
||||
setCheckoutLoading(true)
|
||||
setCheckoutError(null)
|
||||
const trimmedEmail = emailInput.trim()
|
||||
if (!trimmedEmail || !trimmedEmail.includes('@')) {
|
||||
setCheckoutError(t('pro.emailRequired'))
|
||||
setCheckoutLoading(false)
|
||||
return
|
||||
}
|
||||
await window.opsxApi.openExternal(result.checkoutUrl)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
logger.warn('checkout failed', message)
|
||||
setCheckoutError(message)
|
||||
} finally {
|
||||
setCheckoutLoading(false)
|
||||
}
|
||||
}, [deviceId, emailInput, setEmail, t])
|
||||
try {
|
||||
await setEmail(trimmedEmail)
|
||||
const id = deviceId || (await window.opsxApi.licenseDeviceId().catch(() => ''))
|
||||
const result = await licenseCheckout({
|
||||
customerEmail: trimmedEmail,
|
||||
deviceId: id,
|
||||
planType: planChoice,
|
||||
})
|
||||
if (!result.checkoutUrl) {
|
||||
throw new Error(t('pro.checkoutUnavailable', { status: 'no-url' }))
|
||||
}
|
||||
await window.opsxApi.openExternal(result.checkoutUrl)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
logger.warn('checkout failed', message)
|
||||
setCheckoutError(message)
|
||||
} finally {
|
||||
setCheckoutLoading(false)
|
||||
}
|
||||
},
|
||||
[deviceId, emailInput, setEmail, t]
|
||||
)
|
||||
|
||||
const handleActivate = useCallback(async () => {
|
||||
setActivateError(null)
|
||||
@@ -162,14 +150,12 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-y-auto bg-dark-900">
|
||||
<div className="mx-auto w-full max-w-3xl px-6 py-8">
|
||||
<header className="mb-8 flex items-start gap-3 border-b border-dark-700/40 pb-6">
|
||||
<header className="border-dark-700/40 mb-8 flex items-start gap-3 border-b pb-6">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-accent-blue/20 text-accent-blue">
|
||||
<Crown className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h1 className="text-lg font-semibold tracking-tight text-content-primary">
|
||||
{t('pro.title')}
|
||||
</h1>
|
||||
<h1 className="text-lg font-semibold tracking-tight text-content-primary">{t('pro.title')}</h1>
|
||||
<p className="mt-1 text-sm text-txt-muted">
|
||||
{comingSoon && highlight
|
||||
? t('pro.subtitleComingSoon', { feature: highlight })
|
||||
@@ -189,7 +175,7 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
? t('pro.expiresAt', { date: new Date(expiresAt * 1000).toLocaleDateString() })
|
||||
: t('pro.lifetime')
|
||||
: null}
|
||||
{plan === 'pro' && maxDevices
|
||||
{plan === 'pro' && maxDevices && maxDevices > 1
|
||||
? ` · ${t('pro.devices', { count: maxDevices })}`
|
||||
: null}
|
||||
</p>
|
||||
@@ -213,23 +199,15 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
placeholder="you@example.com"
|
||||
className="w-full rounded-lg border border-dark-600 bg-dark-900 px-3 py-2 text-sm text-content-primary outline-none focus:border-accent-blue"
|
||||
/>
|
||||
{checkoutError && (
|
||||
<p className="text-xs text-red-400">{checkoutError}</p>
|
||||
)}
|
||||
{checkoutError && <p className="text-xs text-red-400">{checkoutError}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6 grid gap-3 sm:grid-cols-3">
|
||||
{/* Free */}
|
||||
<div className={cardClass('free')}>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-left"
|
||||
onClick={() => setSelectedPlan('free')}
|
||||
>
|
||||
<div className="text-xs font-medium uppercase tracking-wide text-txt-faint">
|
||||
{t('pro.planFree')}
|
||||
</div>
|
||||
<button type="button" className="w-full text-left" onClick={() => setSelectedPlan('free')}>
|
||||
<div className="text-xs font-medium uppercase tracking-wide text-txt-faint">{t('pro.planFree')}</div>
|
||||
<div className="mt-1 text-xl font-semibold text-content-primary">{t('pro.planFreePrice')}</div>
|
||||
<div className="mt-1 text-xs text-txt-muted">{t('pro.planFreeDesc')}</div>
|
||||
</button>
|
||||
@@ -248,14 +226,8 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
|
||||
{/* Yearly */}
|
||||
<div className={cardClass('yearly', true)}>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-left"
|
||||
onClick={() => setSelectedPlan('yearly')}
|
||||
>
|
||||
<div className="text-xs font-medium uppercase tracking-wide text-accent-blue">
|
||||
{t('pro.planYearly')}
|
||||
</div>
|
||||
<button type="button" className="w-full text-left" onClick={() => setSelectedPlan('yearly')}>
|
||||
<div className="text-xs font-medium uppercase tracking-wide text-accent-blue">{t('pro.planYearly')}</div>
|
||||
<div className="mt-1 text-xl font-semibold text-content-primary">¥99</div>
|
||||
<div className="mt-1 text-xs text-txt-muted">{t('pro.planYearlyDesc')}</div>
|
||||
</button>
|
||||
@@ -292,11 +264,7 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
|
||||
{/* Perpetual */}
|
||||
<div className={cardClass('perpetual', true)}>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-left"
|
||||
onClick={() => setSelectedPlan('perpetual')}
|
||||
>
|
||||
<button type="button" className="w-full text-left" onClick={() => setSelectedPlan('perpetual')}>
|
||||
<div className="text-xs font-medium uppercase tracking-wide text-accent-blue">
|
||||
{t('pro.planPerpetual')}
|
||||
</div>
|
||||
@@ -346,7 +314,7 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
</button>
|
||||
|
||||
{showActivate && (
|
||||
<div className="mt-3 space-y-2 rounded-xl border border-dark-700/60 bg-dark-800/50 p-4">
|
||||
<div className="border-dark-700/60 bg-dark-800/50 mt-3 space-y-2 rounded-xl border p-4">
|
||||
<label className="ui-label" htmlFor="license-token">
|
||||
{t('pro.pasteLicense')}
|
||||
</label>
|
||||
@@ -384,7 +352,7 @@ export default function UpgradeView({ feature, comingSoon }: UpgradeViewProps) {
|
||||
</button>
|
||||
)}
|
||||
|
||||
<footer className="mt-10 space-y-2 border-t border-dark-700/40 pt-5 text-[11px] leading-relaxed text-txt-faint">
|
||||
<footer className="border-dark-700/40 mt-10 space-y-2 border-t pt-5 text-[11px] leading-relaxed text-txt-faint">
|
||||
<p className="flex items-start gap-1.5">
|
||||
<Shield className="mt-0.5 h-3 w-3 flex-shrink-0" />
|
||||
{t('pro.paymentNote')}
|
||||
|
||||
Reference in new issue
Block a user