feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped

This commit is contained in:
Yun Chan 2026-08-20 11:12:05 +09:00
parent 5cd1de6859
commit 708e20f747
406 changed files with 42464 additions and 6199 deletions

View file

@ -0,0 +1,236 @@
'use client'
// apps/admin/src/components/license-issuer-dialog.tsx
// D3RO Voice Admin — Ed25519 비대칭 암호화 라이선스 발급 다이얼로그
import { useState } from 'react'
import {
Dialog,
DialogTitle,
DialogContent,
Box,
Typography,
IconButton,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
Button,
Alert,
} from '@mui/material'
import {
issueSignedLicenseKey,
DEFAULT_LICENSE_PRIVATE_KEY,
} from '@d3ro/core/utils/crypto-license'
import type { LicenseTier } from '@d3ro/core/types'
import { d3roPalette, d3roFontMono, d3roRadius } from '@d3ro/ui/theme'
import { C, FONT_SANS, FONT_MONO, primaryButtonSx } from '@/lib/console-theme'
interface LicenseIssuerDialogProps {
open: boolean
onClose: () => void
}
export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps): React.ReactElement {
const [customerEmail, setCustomerEmail] = useState('')
const [tier, setTier] = useState<LicenseTier>('pro_plus')
const [validity, setValidity] = useState<'30d' | '365d' | 'lifetime'>('365d')
const [machineId, setMachineId] = useState('')
const [teamId, setTeamId] = useState('')
const [generatedKey, setGeneratedKey] = useState<string | null>(null)
const [copied, setCopied] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleGenerate = () => {
setError(null)
setCopied(false)
if (!customerEmail.trim()) {
setError('Customer Email is required')
return
}
try {
const now = Date.now()
let expiresAt: number | null = null
if (validity === '30d') {
expiresAt = now + 30 * 24 * 60 * 60 * 1000
} else if (validity === '365d') {
expiresAt = now + 365 * 24 * 60 * 60 * 1000
}
const payload = {
licenseId: `lic-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 6)}`,
tier,
customerEmail: customerEmail.trim(),
issuedAt: now,
expiresAt,
machineId: machineId.trim() || null,
teamId: teamId.trim() || undefined,
maxDevices: tier === 'enterprise' ? 999 : tier === 'team' ? 25 : tier === 'pro_plus' ? 5 : 3,
}
const key = issueSignedLicenseKey(payload, DEFAULT_LICENSE_PRIVATE_KEY)
setGeneratedKey(key)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to generate license key')
}
}
const handleCopy = () => {
if (!generatedKey) return
navigator.clipboard.writeText(generatedKey)
setCopied(true)
setTimeout(() => setCopied(false), 3000)
}
const inputSx = {
'& .MuiInputBase-root': {
fontFamily: d3roFontMono,
fontSize: 13,
color: d3roPalette.text.primary,
bgcolor: d3roPalette.bg.inset,
borderRadius: d3roRadius.button,
},
'& .MuiInputLabel-root': {
fontFamily: FONT_MONO,
fontSize: 12,
color: C.dim,
},
}
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
bgcolor: '#0a0d14',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '16px',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.8)',
p: 1,
},
}}
>
<DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', pb: 1 }}>
<Box>
<Typography sx={{ fontFamily: FONT_SANS, fontSize: '18px', fontWeight: 700, color: C.bright }}>
Issue Cryptographic License Key
</Typography>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: C.dim }}>
ED25519 ASYMMETRIC SIGNED OFFLINE / ENTERPRISE TOKEN
</Typography>
</Box>
<IconButton onClick={onClose} size="small" sx={{ color: C.dim }}>
✕
</IconButton>
</DialogTitle>
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>
<TextField
fullWidth
label="Customer Email *"
placeholder="enterprise-client@company.com"
value={customerEmail}
onChange={(e) => setCustomerEmail(e.target.value)}
sx={inputSx}
/>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 2 }}>
<FormControl fullWidth sx={inputSx}>
<InputLabel>Plan / Tier</InputLabel>
<Select value={tier} onChange={(e) => setTier(e.target.value as LicenseTier)} label="Plan / Tier">
<MenuItem value="pro">Pro ($9.9 / ₩12,900)</MenuItem>
<MenuItem value="pro_plus">Pro+ ($19.9 / ₩24,900)</MenuItem>
<MenuItem value="team">Team ($25 / ₩32,000)</MenuItem>
<MenuItem value="enterprise">Enterprise (Custom)</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth sx={inputSx}>
<InputLabel>Validity Period</InputLabel>
<Select value={validity} onChange={(e) => setValidity(e.target.value as '30d' | '365d' | 'lifetime')} label="Validity Period">
<MenuItem value="30d">30 Days (Monthly)</MenuItem>
<MenuItem value="365d">1 Year (Annual)</MenuItem>
<MenuItem value="lifetime">Lifetime (Permanent)</MenuItem>
</Select>
</FormControl>
</Box>
{(tier === 'team' || tier === 'enterprise') && (
<TextField
fullWidth
label="Team / Org Name (Optional)"
placeholder="Engineering AI Squad"
value={teamId}
onChange={(e) => setTeamId(e.target.value)}
sx={inputSx}
/>
)}
<TextField
fullWidth
label="Target Machine ID (Optional Hardware Lock)"
placeholder="e.g. 94dbaa34... (Leave blank for any device)"
value={machineId}
onChange={(e) => setMachineId(e.target.value)}
sx={inputSx}
/>
{error && <Alert severity="error" sx={{ fontFamily: FONT_MONO, fontSize: '12px' }}>{error}</Alert>}
<Button
variant="contained"
onClick={handleGenerate}
sx={{
...primaryButtonSx,
py: 1.2,
background: 'linear-gradient(135deg, #a855f7 0%, #3b82f6 100%)',
fontWeight: 700,
}}
>
⚡ Generate Ed25519 Signed License
</Button>
{generatedKey && (
<Box sx={{ mt: 1, p: 2, bgcolor: 'rgba(0, 0, 0, 0.4)', borderRadius: '10px', border: '1px solid rgba(168, 85, 247, 0.4)' }}>
<Typography sx={{ fontFamily: FONT_MONO, fontSize: '11px', color: '#a855f7', fontWeight: 700, mb: 0.5 }}>
SIGNED LICENSE KEY (Copy and paste into D3RO Voice Desktop App):
</Typography>
<Typography
sx={{
fontFamily: FONT_MONO,
fontSize: '11px',
color: C.bright,
p: 1.5,
bgcolor: 'rgba(255, 255, 255, 0.05)',
borderRadius: '6px',
wordBreak: 'break-all',
userSelect: 'all',
mb: 1.5,
}}
>
{generatedKey}
</Typography>
<Button
variant="outlined"
fullWidth
onClick={handleCopy}
sx={{
fontFamily: FONT_MONO,
fontSize: '12px',
borderColor: copied ? '#10b981' : '#a855f7',
color: copied ? '#10b981' : C.bright,
}}
>
{copied ? '✓ Copied to Clipboard!' : '📋 Copy License Key'}
</Button>
</Box>
)}
</DialogContent>
</Dialog>
)
}