feat(desktop): SaaS [6] 로컬 우선 sync + login_required UI (빅뱅 Phase 3)
Phase 3 재정의: Supabase = source of truth 방향 철회. 로컬 entry point 철학 하에 "로컬이 SoT, 클라우드는 로그인 시 mirror"로 확정. CloudSyncService._initialSync(): - _onAuthenticated 끝에 fire-and-forget 호출 - pushAll → pullAll 순차 실행 - signin: 익명 로컬 데이터를 사용자 계정으로 업로드 - restore: 다른 기기 변경 반영 - 실패 시 warn만, 수동 Sync 버튼으로 재시도 가능 types.ts: - FeatureAccess.reason: 'login_required' 추가 - UpgradePromptEvent.reason: 'login_required' 추가 LicenseService: - consumeQuota(): login_required 케이스 — promptUpgrade + D3ROError(TierRequired) - promptUpgrade(): login_required 시 requiredTier='free' (로그인만 하면 free로 바로 사용 가능) - quota_exceeded 시 현재 tier 상위로 승격 제안 UpgradePromptModal: - isLoginRequired 분기 — title/desc 교체 - Primary button: "로그인하기" → d3ro:open-settings event (tab=cloud) i18n ko/en: - license.loginRequired.title/desc/signIn 추가
This commit is contained in:
parent
46b77f1118
commit
ceadf1b6f3
7 changed files with 103 additions and 6 deletions
|
|
@ -44,9 +44,15 @@ export function UpgradePromptModal(): React.ReactElement {
|
|||
if (!event) return <></>
|
||||
|
||||
const isQuota = event.reason === 'quota_exceeded'
|
||||
const isLoginRequired = event.reason === 'login_required'
|
||||
const featureLabel = t(`license.feature.${event.feature}`)
|
||||
const tierLabel = event.requiredTier === 'pro_plus' ? t('license.proPlus') : t('license.pro')
|
||||
|
||||
const openCloudSyncSettings = (): void => {
|
||||
setOpen(false)
|
||||
window.dispatchEvent(new CustomEvent('d3ro:open-settings', { detail: { tab: 'cloud' } }))
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
|
|
@ -77,7 +83,9 @@ export function UpgradePromptModal(): React.ReactElement {
|
|||
<LockIcon sx={{ fontSize: 20 }} />
|
||||
{isQuota
|
||||
? t('license.quotaExceeded.title', { feature: featureLabel })
|
||||
: t('license.tierRequired.title', { feature: featureLabel, tier: tierLabel })}
|
||||
: isLoginRequired
|
||||
? t('license.loginRequired.title', { feature: featureLabel })
|
||||
: t('license.tierRequired.title', { feature: featureLabel, tier: tierLabel })}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
|
|
@ -91,7 +99,9 @@ export function UpgradePromptModal(): React.ReactElement {
|
|||
>
|
||||
{isQuota
|
||||
? t('license.quotaExceeded.desc')
|
||||
: t('license.tierRequired.desc', { tier: tierLabel })}
|
||||
: isLoginRequired
|
||||
? t('license.loginRequired.desc')
|
||||
: t('license.tierRequired.desc', { tier: tierLabel })}
|
||||
</Typography>
|
||||
|
||||
{/* 쿼터 바 */}
|
||||
|
|
@ -155,7 +165,7 @@ export function UpgradePromptModal(): React.ReactElement {
|
|||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleLearnMore}
|
||||
onClick={isLoginRequired ? openCloudSyncSettings : handleLearnMore}
|
||||
sx={{
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: d3roTypo.compact.size,
|
||||
|
|
@ -170,7 +180,7 @@ export function UpgradePromptModal(): React.ReactElement {
|
|||
},
|
||||
}}
|
||||
>
|
||||
{t('license.learnMore')}
|
||||
{isLoginRequired ? t('license.loginRequired.signIn') : t('license.learnMore')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue