fix(desktop): surface configuration and provider failures instead of hiding them

Several desktop paths quietly substituted defaults or partial results: a
config write could fall back to a throwaway in-memory store, speech provider
errors were absorbed into empty transcriptions, and meeting exports built
file names from raw titles.

Writes now fail explicitly when the store is unavailable, provider and model
failures reach the UI as errors, and export names pass through one
sanitizer. Settings, license, ad, and support surfaces use the shared theme
tokens, unused hotkey helpers are gone, and the package gains strict
node/renderer typecheck configs plus red-team e2e scenarios for these flows.
This commit is contained in:
Yun Chan 2026-09-16 23:23:58 +09:00
parent c8d802d78f
commit 6ba25f53b7
43 changed files with 1721 additions and 204 deletions

View file

@ -282,9 +282,9 @@ export function AppLayout(): React.ReactElement {
bgcolor: d3roPalette.glass.surface,
transition: 'all 0.18s ease',
'&:hover': {
color: d3roPalette.tag.purpleText,
color: d3roPalette.tag.purple,
bgcolor: d3roPalette.glass.raised,
borderColor: d3roPalette.tag.purpleBorder,
borderColor: d3roPalette.tag.purple,
},
}}
>

View file

@ -53,7 +53,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
backgroundImage: 'none',
border: `1px solid ${d3roPalette.border.default}`,
boxShadow: d3roShadow.chassis,
borderRadius: d3roRadius.md,
borderRadius: d3roRadius.inner,
},
}}
>
@ -139,7 +139,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
@ -157,7 +157,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
fontWeight: 500,
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>
API Keys
@ -174,7 +174,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
}}
>
<Typography variant="body2" sx={{ fontWeight: 500, mb: 0.5 }}>
@ -201,7 +201,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
}}
>
<Typography variant="body2" sx={{ fontWeight: 500, mb: 0.5 }}>
@ -248,7 +248,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
mb: 2,
}}
>
@ -289,7 +289,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
}}
>
<Typography variant="body2" sx={{ fontWeight: 500, mb: 1 }}>
@ -320,7 +320,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@ -352,7 +352,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@ -384,7 +384,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@ -416,7 +416,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@ -457,7 +457,7 @@ export function CodexOAuthGuideModal({ open, onClose }: CodexOAuthGuideModalProp
fontSize: d3roTypo.label.size,
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>

View file

@ -4,7 +4,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { Box, LinearProgress, IconButton, Tooltip } from '@mui/material'
import { Copy, X, FileUp } from 'lucide-react'
import { MetalCard, PhosphorText, Led } from '@d3ro/ui/components/ds'
import { MetalCard, PhosphorText, Led, PhysicalButton } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roTypo, d3roRadius } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
import type {

View file

@ -46,7 +46,7 @@ export function LicenseTab(): React.ReactElement {
{/* ── 14일 Reverse-Trial 상태 배너 (활성화 시) ── */}
{licenseInfo?.isTrial && licenseInfo.trialExpiresAt && (
<MetalCard sx={{ bgcolor: 'rgba(255, 170, 0, 0.08)', borderColor: d3roPalette.accent.main }}>
<MetalCard sx={{ bgcolor: 'var(--d3-status-warning)', borderColor: d3roPalette.accent.main }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Sparkles size={18} color={d3roPalette.accent.main} />
<Box sx={{ flex: 1 }}>

View file

@ -392,7 +392,7 @@ export function OllamaGuideModal({ open, onClose }: OllamaGuideModalProps): Reac
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
fontWeight: 500,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>
{starting ? '실행 중...' : 'Ollama 자동 실행'}
@ -410,7 +410,7 @@ export function OllamaGuideModal({ open, onClose }: OllamaGuideModalProps): Reac
{/* ── Step 1: Ollama 설치 및 기동 ── */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Chip label="STEP 1" size="small" sx={{ fontWeight: 500, fontSize: '10px', height: 20, bgcolor: d3roPalette.accent.muted, color: d3roPalette.accent.main }} />
<Chip label="STEP 1" size="small" sx={{ fontWeight: 500, fontSize: '10px', height: 20, bgcolor: d3roPalette.accent.dim, color: d3roPalette.accent.main }} />
<Typography sx={{ ...typoSx('heading'), color: d3roPalette.text.primary }}>
Ollama
</Typography>
@ -462,7 +462,7 @@ export function OllamaGuideModal({ open, onClose }: OllamaGuideModalProps): Reac
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Chip label="STEP 2" size="small" sx={{ fontWeight: 500, fontSize: '10px', height: 20, bgcolor: d3roPalette.accent.muted, color: d3roPalette.accent.main }} />
<Chip label="STEP 2" size="small" sx={{ fontWeight: 500, fontSize: '10px', height: 20, bgcolor: d3roPalette.accent.dim, color: d3roPalette.accent.main }} />
<Typography sx={{ ...typoSx('heading'), color: d3roPalette.text.primary }}>
1-
</Typography>
@ -564,7 +564,7 @@ export function OllamaGuideModal({ open, onClose }: OllamaGuideModalProps): Reac
py: 0.25,
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>
(Pull)
@ -624,7 +624,7 @@ export function OllamaGuideModal({ open, onClose }: OllamaGuideModalProps): Reac
minWidth: 100,
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>
{testing ? '처리 중' : '테스트'}

View file

@ -31,7 +31,7 @@ import {
ArrowRight,
ShieldCheck,
} from 'lucide-react'
import { d3roPalette, d3roRadius, typoSx, d3roFontMono, d3roFontSans } from '@d3ro/ui/theme'
import { d3roPalette, d3roRadius, d3roShadow, typoSx, d3roFontMono, d3roFontSans } from '@d3ro/ui/theme'
import { Led } from '@d3ro/ui/components/ds'
import type { LLMModel, LLMStatus } from '@d3ro/core/types'
@ -311,7 +311,7 @@ export function OnboardingModal({ open, onClose }: OnboardingModalProps): React.
fontWeight: 500,
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>
AI
@ -504,7 +504,7 @@ export function OnboardingModal({ open, onClose }: OnboardingModalProps): React.
fontWeight: 500,
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>
{hasInstalledLlm ? 'Gemma 2 (2B) 추가 다운로드' : 'Gemma 2 (2B) 1-클릭 다운로드'}

View file

@ -202,10 +202,10 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
sx={{
p: 1.5,
cursor: 'pointer',
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
bgcolor: isSelected ? d3roPalette.bg.elevated : d3roPalette.bg.inset,
border: isSelected ? `1.5px solid ${d3roPalette.accent.main}` : `1px solid ${d3roPalette.border.subtle}`,
boxShadow: isSelected ? d3roShadow.elevated : 'none',
boxShadow: isSelected ? d3roShadow.card : 'none',
transition: 'all 120ms ease-out',
display: 'flex',
flexDirection: 'column',
@ -245,7 +245,7 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
height: 18,
fontSize: '9px',
fontWeight: 500,
bgcolor: isSelected ? d3roPalette.accent.muted : 'transparent',
bgcolor: isSelected ? d3roPalette.accent.dim : 'transparent',
color: isSelected ? d3roPalette.accent.main : d3roPalette.text.disabled,
border: isSelected ? 'none' : `1px solid ${d3roPalette.border.subtle}`,
}}
@ -263,7 +263,7 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
p: 2,
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.default}`,
borderRadius: d3roRadius.md,
borderRadius: d3roRadius.inner,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
@ -328,7 +328,7 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
p: 1.5,
bgcolor: d3roPalette.bg.elevated,
border: `1px solid ${d3roPalette.border.default}`,
borderRadius: d3roRadius.sm,
borderRadius: d3roRadius.small,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@ -354,7 +354,7 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
fontWeight: 500,
bgcolor: d3roPalette.accent.main,
color: d3roPalette.bg.app,
'&:hover': { bgcolor: d3roPalette.accent.hover },
'&:hover': { bgcolor: d3roPalette.accent.light },
}}
>
{isDownloading ? `다운로드 중 (${downloadPercent}%)` : '모델 다운로드'}
@ -440,7 +440,7 @@ export function STTTab({ config, updateConfig }: STTTabProps): React.ReactElemen
sx={{
bgcolor: d3roPalette.bg.inset,
border: `1px solid ${d3roPalette.border.subtle}`,
borderRadius: `${d3roRadius.sm} !important`,
borderRadius: `${d3roRadius.small} !important`,
'&:before': { display: 'none' },
}}
>

View file

@ -1,7 +1,7 @@
// src/renderer/components/meeting/MarkdownEditor.tsx
// Phase 14.5: 마크다운 렌더링/편집 토글 컴포넌트
import { useState } from 'react'
import { useState, useEffect, useCallback } from 'react'
import { Box } from '@mui/material'
import { MarkdownRenderer } from './MarkdownRenderer'
import { PhysicalButton } from '@d3ro/ui/components/ds'
@ -15,12 +15,26 @@ interface MarkdownEditorProps {
}
export function MarkdownEditor({
content,
content: initialContent,
onChange,
readOnly = false,
}: MarkdownEditorProps): React.ReactElement {
const { t } = useI18n()
const [preview, setPreview] = useState(true)
const [draft, setDraft] = useState(initialContent)
useEffect(() => {
setDraft(initialContent)
}, [initialContent])
const handleTextChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const val = e.target.value
setDraft(val)
onChange(val)
},
[onChange],
)
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
@ -49,12 +63,12 @@ export function MarkdownEditor({
{/* 콘텐츠 영역 */}
<Box sx={{ flex: 1, overflow: 'auto', minHeight: 0 }}>
{preview || readOnly ? (
<MarkdownRenderer content={content} />
<MarkdownRenderer content={draft} />
) : (
<Box
component="textarea"
value={content}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)}
value={draft}
onChange={handleTextChange}
sx={{
width: '100%',
height: '100%',

View file

@ -81,11 +81,11 @@ export function CheckoutModal({
left: '50%',
transform: 'translate(-50%, -50%)',
width: { xs: '90%', sm: 520 },
bgcolor: d3roPalette.bg.modal,
bgcolor: d3roPalette.bg.card,
backdropFilter: 'blur(28px)',
border: `1px solid ${d3roPalette.glass.hairline}`,
borderRadius: d3roRadius.outer,
boxShadow: d3roShadow.modal,
boxShadow: d3roShadow.dialog,
p: 3,
outline: 'none',
display: 'flex',
@ -139,9 +139,9 @@ export function CheckoutModal({
gap: 1.5
}}
>
<CheckCircle2 size={48} color={d3roPalette.tag.greenText} />
<CheckCircle2 size={48} color={d3roPalette.tag.green} />
<Typography
sx={{ fontFamily: d3roFontSans, fontSize: '18px', fontWeight: 600, color: '#fff' }}
sx={{ fontFamily: d3roFontSans, fontSize: '18px', fontWeight: 600, color: 'var(--d3-text-inverse)' }}
>
.
</Typography>
@ -155,7 +155,7 @@ export function CheckoutModal({
sx={{
mt: 2,
bgcolor: d3roPalette.accent.main,
color: '#fff',
color: 'var(--d3-text-inverse)',
px: 3,
borderRadius: '8px'
}}
@ -183,8 +183,8 @@ export function CheckoutModal({
borderRadius: d3roRadius.inner,
border: `1px solid ${tier === candidate ? d3roPalette.accent.main : d3roPalette.glass.hairline}`,
bgcolor:
tier === candidate ? 'rgba(59, 130, 246, 0.18)' : 'rgba(255,255,255,0.03)',
color: '#fff',
tier === candidate ? 'var(--d3-accent-glow)' : 'var(--d3-overlay-strong)',
color: 'var(--d3-text-inverse)',
fontFamily: d3roFontSans,
fontWeight: 500,
textTransform: 'none'
@ -199,7 +199,7 @@ export function CheckoutModal({
sx={{
p: 2,
borderRadius: d3roRadius.inner,
bgcolor: 'rgba(17, 26, 48, 0.7)',
bgcolor: 'var(--d3-bg-card-soft)',
border: `1px solid ${d3roPalette.accent.dim}`,
display: 'flex',
gap: 1.25,
@ -213,7 +213,7 @@ export function CheckoutModal({
fontFamily: d3roFontSans,
fontSize: '13px',
fontWeight: 500,
color: '#fff'
color: 'var(--d3-text-inverse)'
}}
>
Stripe
@ -252,7 +252,7 @@ export function CheckoutModal({
sx={{
py: 1.25,
bgcolor: d3roPalette.accent.main,
color: '#fff',
color: 'var(--d3-text-inverse)',
fontWeight: 500,
fontSize: '13px',
borderRadius: '10px',
@ -279,7 +279,7 @@ export function CheckoutModal({
sx={{
py: 1.25,
bgcolor: d3roPalette.accent.main,
color: '#fff',
color: 'var(--d3-text-inverse)',
fontWeight: 500,
fontSize: '13px',
borderRadius: '10px'

View file

@ -7,6 +7,7 @@ import {
Box,
Typography,
Button,
IconButton,
Tabs,
Tab,
TextField,
@ -115,11 +116,11 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
transform: 'translate(-50%, -50%)',
width: { xs: '95%', sm: 680 },
maxHeight: '90vh',
bgcolor: d3roPalette.bg.modal,
bgcolor: d3roPalette.bg.card,
backdropFilter: 'blur(28px)',
border: `1px solid ${d3roPalette.glass.hairline}`,
borderRadius: d3roRadius.outer,
boxShadow: d3roShadow.modal,
boxShadow: d3roShadow.dialog,
p: 3,
outline: 'none',
display: 'flex',
@ -131,14 +132,19 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
{/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: `1px solid ${d3roPalette.glass.hairline}`, pb: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Headphones size={20} color={d3roPalette.tag.purpleText} />
<Headphones size={20} color={d3roPalette.tag.purple} />
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '17px', fontWeight: 600, color: d3roPalette.text.primary }}>
D3RO Voice Customer Assistance (CA/CS)
</Typography>
</Box>
<Box onClick={onClose} sx={{ cursor: 'pointer', color: d3roPalette.text.dimLabel, '&:hover': { color: d3roPalette.text.primary } }}>
<IconButton
size="small"
onClick={onClose}
aria-label="닫기"
sx={{ cursor: 'pointer', color: d3roPalette.text.dimLabel, '&:hover': { color: d3roPalette.text.primary } }}
>
<X size={18} />
</Box>
</IconButton>
</Box>
{/* Navigation Tabs */}
@ -157,9 +163,9 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
fontWeight: 600,
color: d3roPalette.text.secondary,
textTransform: 'none',
'&.Mui-selected': { color: d3roPalette.tag.purpleText },
'&.Mui-selected': { color: d3roPalette.tag.purple },
},
'& .MuiTabs-indicator': { bgcolor: d3roPalette.tag.purpleText },
'& .MuiTabs-indicator': { bgcolor: d3roPalette.tag.purple },
}}
>
<Tab icon={<Bot size={14} />} iconPosition="start" label="AI 어시스턴트" />
@ -177,7 +183,7 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
overflowY: 'auto',
p: 2,
borderRadius: d3roRadius.inner,
bgcolor: 'rgba(10, 14, 28, 0.7)',
bgcolor: 'var(--d3-bg-inset-soft)',
border: `1px solid ${d3roPalette.glass.hairline}`,
display: 'flex',
flexDirection: 'column',
@ -192,8 +198,8 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
maxWidth: '85%',
p: 1.5,
borderRadius: msg.role === 'user' ? '14px 14px 2px 14px' : '14px 14px 14px 2px',
bgcolor: msg.role === 'user' ? d3roPalette.accent.main : 'rgba(26, 38, 68, 0.8)',
color: '#ffffff',
bgcolor: msg.role === 'user' ? d3roPalette.accent.main : 'var(--d3-bg-raised-soft)',
color: 'var(--d3-text-inverse)',
fontSize: '12px',
fontFamily: d3roFontSans,
lineHeight: 1.5,
@ -216,21 +222,21 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
<Button
size="small"
onClick={() => handleAskAI('마이크 인식이 안 돼요')}
sx={{ fontSize: '10px', textTransform: 'none', color: d3roPalette.text.secondary, bgcolor: 'rgba(255,255,255,0.05)', borderRadius: '999px', px: 1.5, flexShrink: 0 }}
sx={{ fontSize: '10px', textTransform: 'none', color: d3roPalette.text.secondary, bgcolor: 'var(--d3-overlay-strong)', borderRadius: '999px', px: 1.5, flexShrink: 0 }}
>
🎤
</Button>
<Button
size="small"
onClick={() => handleAskAI('CUDA GPU 가속 설정 방법')}
sx={{ fontSize: '10px', textTransform: 'none', color: d3roPalette.text.secondary, bgcolor: 'rgba(255,255,255,0.05)', borderRadius: '999px', px: 1.5, flexShrink: 0 }}
sx={{ fontSize: '10px', textTransform: 'none', color: d3roPalette.text.secondary, bgcolor: 'var(--d3-overlay-strong)', borderRadius: '999px', px: 1.5, flexShrink: 0 }}
>
CUDA GPU
</Button>
<Button
size="small"
onClick={() => handleAskAI('환불 신청 규정')}
sx={{ fontSize: '10px', textTransform: 'none', color: d3roPalette.text.secondary, bgcolor: 'rgba(255,255,255,0.05)', borderRadius: '999px', px: 1.5, flexShrink: 0 }}
sx={{ fontSize: '10px', textTransform: 'none', color: d3roPalette.text.secondary, bgcolor: 'var(--d3-overlay-strong)', borderRadius: '999px', px: 1.5, flexShrink: 0 }}
>
💳
</Button>
@ -247,8 +253,8 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
onKeyDown={(e) => e.key === 'Enter' && handleAskAI()}
sx={{
'& .MuiInputBase-root': {
bgcolor: 'rgba(10, 14, 28, 0.9)',
color: '#fff',
bgcolor: 'var(--d3-bg-inset-soft)',
color: 'var(--d3-text-inverse)',
fontSize: '12px',
fontFamily: d3roFontSans,
borderRadius: '10px',
@ -258,14 +264,14 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
<Button
onClick={() => handleAskAI()}
sx={{
bgcolor: d3roPalette.tag.purpleText,
color: '#fff',
bgcolor: d3roPalette.tag.purple,
color: 'var(--d3-text-inverse)',
px: 2.5,
borderRadius: '10px',
fontFamily: d3roFontSans,
fontWeight: 500,
fontSize: '12px',
'&:hover': { bgcolor: '#9333ea' },
'&:hover': { bgcolor: 'var(--d3-tag-purple)' },
}}
>
<Send size={14} />
@ -287,8 +293,8 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
}}
sx={{
'& .MuiInputBase-root': {
bgcolor: 'rgba(10, 14, 28, 0.9)',
color: '#fff',
bgcolor: 'var(--d3-bg-inset-soft)',
color: 'var(--d3-text-inverse)',
fontSize: '12px',
borderRadius: '10px',
},
@ -297,7 +303,7 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
<Box sx={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 1.5, pr: 0.5 }}>
{filteredFaqs.map((faq, idx) => (
<Box key={idx} sx={{ p: 2, borderRadius: d3roRadius.inner, bgcolor: 'rgba(17, 26, 48, 0.7)', border: `1px solid ${d3roPalette.glass.hairline}` }}>
<Box key={idx} sx={{ p: 2, borderRadius: d3roRadius.inner, bgcolor: 'var(--d3-bg-card-soft)', border: `1px solid ${d3roPalette.glass.hairline}` }}>
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '13px', fontWeight: 500, color: d3roPalette.text.primary, mb: 0.75 }}>
Q. {faq.q}
</Typography>
@ -315,8 +321,8 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
<Box sx={{ height: 420, overflowY: 'auto' }}>
{ticketSubmitted ? (
<Box sx={{ textAlign: 'center', py: 6, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1.5 }}>
<CheckCircle size={48} color={d3roPalette.tag.greenText} />
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '18px', fontWeight: 600, color: '#fff' }}>
<CheckCircle size={48} color={d3roPalette.tag.green} />
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '18px', fontWeight: 600, color: 'var(--d3-text-inverse)' }}>
!
</Typography>
<Typography sx={{ fontFamily: d3roFontMono, fontSize: '12px', color: d3roPalette.accent.light }}>
@ -348,7 +354,7 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
<TextField size="small" label="문의 제목" placeholder="문제를 요약해 주세요" required sx={{ '& .MuiInputBase-root': { fontSize: '12px' } }} />
<TextField multiline rows={3} size="small" label="상세 내용" placeholder="오류 발생 상황을 기재해 주세요..." required sx={{ '& .MuiInputBase-root': { fontSize: '12px' } }} />
<Box sx={{ p: 1.5, borderRadius: '8px', bgcolor: 'rgba(59, 130, 246, 0.1)', border: `1px solid ${d3roPalette.accent.dim}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ p: 1.5, borderRadius: '8px', bgcolor: 'var(--d3-accent-glow)', border: `1px solid ${d3roPalette.accent.dim}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Activity size={14} color={d3roPalette.accent.light} />
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '11px', color: d3roPalette.accent.light }}>
@ -360,7 +366,7 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
</Typography>
</Box>
<Button type="submit" sx={{ py: 1, bgcolor: d3roPalette.tag.purpleText, color: '#fff', fontWeight: 500, fontSize: '12px', borderRadius: '10px', '&:hover': { bgcolor: '#9333ea' } }}>
<Button type="submit" sx={{ py: 1, bgcolor: d3roPalette.tag.purple, color: 'var(--d3-text-inverse)', fontWeight: 500, fontSize: '12px', borderRadius: '10px', '&:hover': { bgcolor: 'var(--d3-tag-purple)' } }}>
</Button>
</form>
@ -370,7 +376,7 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
{/* TAB 3: 7-DAY REFUND CHECKER */}
{tabIndex === 3 && (
<Box sx={{ height: 420, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', p: 2, bgcolor: 'rgba(10, 14, 28, 0.7)', borderRadius: d3roRadius.inner }}>
<Box sx={{ height: 420, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', p: 2, bgcolor: 'var(--d3-bg-inset-soft)', borderRadius: d3roRadius.inner }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '14px', fontWeight: 500, color: d3roPalette.text.primary }}>
7 100%
@ -380,19 +386,19 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
</Typography>
{refundStatus === 'eligible' && (
<Box sx={{ p: 2, borderRadius: '10px', bgcolor: 'rgba(16, 185, 129, 0.15)', border: '1px solid rgba(16, 185, 129, 0.3)', display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '13px', fontWeight: 500, color: d3roPalette.tag.greenText }}>
<Box sx={{ p: 2, borderRadius: '10px', bgcolor: 'var(--d3-status-success)', border: '1px solid var(--d3-status-success)', display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '13px', fontWeight: 500, color: d3roPalette.tag.green }}>
100% !
</Typography>
<Typography sx={{ fontFamily: d3roFontMono, fontSize: '11px', color: '#fff' }}>
<Typography sx={{ fontFamily: d3roFontMono, fontSize: '11px', color: 'var(--d3-text-inverse)' }}>
결제일: 최근 2 ( )
</Typography>
<Typography sx={{ fontFamily: d3roFontMono, fontSize: '11px', color: '#fff' }}>
<Typography sx={{ fontFamily: d3roFontMono, fontSize: '11px', color: 'var(--d3-text-inverse)' }}>
사용량: 0% ( )
</Typography>
<Button
onClick={() => alert('토스페이먼츠 / Stripe 결제 취소가 접수되었습니다. 영업일 기준 2~3일 내 카드사 취소 처리됩니다.')}
sx={{ mt: 1, bgcolor: d3roPalette.tag.greenText, color: '#030712', fontWeight: 500, fontSize: '12px', borderRadius: '8px' }}
sx={{ mt: 1, bgcolor: d3roPalette.tag.green, color: 'var(--d3-scrim)', fontWeight: 500, fontSize: '12px', borderRadius: '8px' }}
>
</Button>
@ -406,7 +412,7 @@ export function SupportModal({ open, onClose }: SupportModalProps): React.ReactE
sx={{
py: 1.2,
bgcolor: d3roPalette.accent.main,
color: '#fff',
color: 'var(--d3-text-inverse)',
fontWeight: 500,
fontSize: '12px',
borderRadius: '10px',