Skip to content

Commit 3b8c1bb

Browse files
committed
Add Analysis History, Creator Studio, and System Status pages with initial layout and functionality
1 parent e467f91 commit 3b8c1bb

3 files changed

Lines changed: 310 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react'
2+
import type { NextPage } from 'next'
3+
import { motion } from 'framer-motion'
4+
import { History, Music, FileAudio, Trash2 } from 'lucide-react'
5+
import { MainLayout } from '@/components/Layout/MainLayout'
6+
import { useLanguage } from '@/context/LanguageContext'
7+
import { useLocalHistory, HISTORY_KEYS } from '@/hooks/useLocalHistory'
8+
import type { AnalysisResult } from '@/hooks/analysisTypes'
9+
10+
const AnalysisHistoryPage: NextPage = () => {
11+
const { t } = useLanguage()
12+
const ah = t.analysisHistory
13+
const { lastEntry, remove } = useLocalHistory<AnalysisResult>(HISTORY_KEYS.ANALYSIS)
14+
15+
return (
16+
<MainLayout
17+
title={ah?.meta?.title || 'Analysis History - CrownCode'}
18+
description={ah?.meta?.description || 'View your recent analysis results.'}
19+
keywords={ah?.meta?.keywords || 'analysis history, AI music detection, results'}
20+
>
21+
<div style={{ maxWidth: 800, margin: '0 auto', padding: '4rem 1.5rem' }}>
22+
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}>
23+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
24+
<History size={28} style={{ color: '#ff4444' }} />
25+
<h1 style={{ fontSize: '2rem', fontWeight: 800 }}>
26+
{ah?.title || 'Analysis History'}
27+
</h1>
28+
</div>
29+
<p style={{ color: '#999', fontSize: '1rem', marginBottom: 32 }}>
30+
{ah?.subtitle || 'Your most recent analysis result is shown below. History is stored locally in your browser.'}
31+
</p>
32+
</motion.div>
33+
34+
{lastEntry ? (
35+
<motion.div
36+
initial={{ opacity: 0, y: 10 }}
37+
animate={{ opacity: 1, y: 0 }}
38+
style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 16, padding: 24 }}
39+
>
40+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
41+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
42+
{lastEntry.input.includes('http') ? <Music size={18} style={{ color: '#ff4444' }} /> : <FileAudio size={18} style={{ color: '#ff4444' }} />}
43+
<span style={{ fontWeight: 600, fontSize: '0.95rem' }}>{lastEntry.input}</span>
44+
</div>
45+
<button
46+
onClick={remove}
47+
style={{ background: 'none', border: 'none', color: '#666', cursor: 'pointer', padding: 4 }}
48+
title={ah?.clearBtn || 'Clear history'}
49+
>
50+
<Trash2 size={16} />
51+
</button>
52+
</div>
53+
<div style={{ color: '#aaa', fontSize: '0.85rem' }}>
54+
{new Date(lastEntry.timestamp).toLocaleString()}
55+
</div>
56+
{lastEntry.result && (
57+
<div style={{ marginTop: 12, padding: 12, background: 'rgba(0,0,0,0.2)', borderRadius: 8, fontSize: '0.85rem', color: '#ccc' }}>
58+
<pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', margin: 0 }}>
59+
{JSON.stringify(lastEntry.result, null, 2).slice(0, 500)}
60+
{JSON.stringify(lastEntry.result).length > 500 ? '...' : ''}
61+
</pre>
62+
</div>
63+
)}
64+
</motion.div>
65+
) : (
66+
<motion.div
67+
initial={{ opacity: 0 }}
68+
animate={{ opacity: 1 }}
69+
style={{ textAlign: 'center', padding: '4rem 0', color: '#666' }}
70+
>
71+
<History size={48} style={{ opacity: 0.3, marginBottom: 16 }} />
72+
<p>{ah?.empty || 'No analysis history yet. Run an analysis from the AI Music Detection page to see results here.'}</p>
73+
</motion.div>
74+
)}
75+
</div>
76+
</MainLayout>
77+
)
78+
}
79+
80+
export default AnalysisHistoryPage
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React from 'react'
2+
import type { NextPage } from 'next'
3+
import { motion } from 'framer-motion'
4+
import { Palette, Wand2, Layers, ArrowRight } from 'lucide-react'
5+
import { MainLayout } from '@/components/Layout/MainLayout'
6+
import { useLanguage } from '@/context/LanguageContext'
7+
8+
const CreatorStudioPage: NextPage = () => {
9+
const { t } = useLanguage()
10+
const cs = t.creatorStudio
11+
12+
const features = [
13+
{ icon: Palette, title: cs?.features?.remix?.title || 'Audio Remix', desc: cs?.features?.remix?.desc || 'Combine and transform audio samples with AI assistance.' },
14+
{ icon: Wand2, title: cs?.features?.generate?.title || 'AI Generate', desc: cs?.features?.generate?.desc || 'Generate new audio patterns from text prompts.' },
15+
{ icon: Layers, title: cs?.features?.layers?.title || 'Multi-Track', desc: cs?.features?.layers?.desc || 'Layer multiple tracks and export studio-quality output.' },
16+
]
17+
18+
return (
19+
<MainLayout
20+
title={cs?.meta?.title || 'Creator Studio - CrownCode'}
21+
description={cs?.meta?.description || 'AI-assisted audio creation workspace.'}
22+
keywords={cs?.meta?.keywords || 'creator studio, audio, AI, remix'}
23+
>
24+
<div style={{ maxWidth: 900, margin: '0 auto', padding: '4rem 1.5rem' }}>
25+
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}>
26+
<span style={{ display: 'inline-block', padding: '4px 12px', borderRadius: 20, background: 'rgba(255,68,68,0.15)', color: '#ff4444', fontSize: 12, fontWeight: 600, marginBottom: 16 }}>
27+
Coming Soon
28+
</span>
29+
<h1 style={{ fontSize: '2.5rem', fontWeight: 800, marginBottom: 12 }}>
30+
{cs?.title || 'Creator Studio'}
31+
</h1>
32+
<p style={{ color: '#999', fontSize: '1.1rem', maxWidth: 600, marginBottom: 48 }}>
33+
{cs?.subtitle || 'AI-assisted audio creation workspace. Remix, generate, and export — all in your browser.'}
34+
</p>
35+
</motion.div>
36+
37+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: 24, marginBottom: 48 }}>
38+
{features.map((f, i) => {
39+
const Icon = f.icon
40+
return (
41+
<motion.div
42+
key={i}
43+
initial={{ opacity: 0, y: 20 }}
44+
animate={{ opacity: 1, y: 0 }}
45+
transition={{ delay: 0.1 * i }}
46+
style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 16, padding: 24 }}
47+
>
48+
<Icon size={28} style={{ color: '#ff4444', marginBottom: 12 }} />
49+
<h3 style={{ fontSize: '1.1rem', fontWeight: 600, marginBottom: 8 }}>{f.title}</h3>
50+
<p style={{ color: '#888', fontSize: '0.9rem', lineHeight: 1.5 }}>{f.desc}</p>
51+
</motion.div>
52+
)
53+
})}
54+
</div>
55+
56+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: 0.4 }} style={{ textAlign: 'center' }}>
57+
<p style={{ color: '#666', fontSize: '0.9rem' }}>
58+
{cs?.comingSoonNote || 'This feature is under active development. Stay tuned for updates.'}
59+
</p>
60+
<a href="/" style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: '#ff4444', marginTop: 16, fontSize: '0.9rem' }}>
61+
{cs?.backHome || 'Back to Home'} <ArrowRight size={14} />
62+
</a>
63+
</motion.div>
64+
</div>
65+
</MainLayout>
66+
)
67+
}
68+
69+
export default CreatorStudioPage
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import React, { useEffect, useState } from 'react'
2+
import type { NextPage } from 'next'
3+
import { motion } from 'framer-motion'
4+
import { Activity, CheckCircle, XCircle, RefreshCw } from 'lucide-react'
5+
import { MainLayout } from '@/components/Layout/MainLayout'
6+
import { useLanguage } from '@/context/LanguageContext'
7+
import { fetchWithTimeout } from '@/hooks/useAsyncRequest'
8+
9+
interface ServiceStatus {
10+
name: string
11+
url: string
12+
status: 'ok' | 'error' | 'loading'
13+
latency?: number
14+
}
15+
16+
const SystemStatusPage: NextPage = () => {
17+
const { t } = useLanguage()
18+
const ss = t.systemStatus
19+
const [services, setServices] = useState<ServiceStatus[]>([
20+
{ name: 'Frontend API', url: '/api/health', status: 'loading' },
21+
{ name: 'Version', url: '/api/version', status: 'loading' },
22+
])
23+
const [checking, setChecking] = useState(false)
24+
25+
const checkServices = async () => {
26+
setChecking(true)
27+
const hfUrl = process.env.NEXT_PUBLIC_API_URL
28+
29+
const targets: { name: string; url: string }[] = [
30+
{ name: 'Frontend API', url: '/api/health' },
31+
{ name: 'Version', url: '/api/version' },
32+
]
33+
if (hfUrl) {
34+
targets.push({ name: 'HF Backend', url: `${hfUrl}/api/health` })
35+
}
36+
37+
const results = await Promise.all(
38+
targets.map(async (svc) => {
39+
const start = Date.now()
40+
try {
41+
const res = await fetchWithTimeout(svc.url, { timeout: 10_000 })
42+
return {
43+
name: svc.name,
44+
url: svc.url,
45+
status: res.ok ? ('ok' as const) : ('error' as const),
46+
latency: Date.now() - start,
47+
}
48+
} catch {
49+
return {
50+
name: svc.name,
51+
url: svc.url,
52+
status: 'error' as const,
53+
latency: Date.now() - start,
54+
}
55+
}
56+
}),
57+
)
58+
59+
setServices(results)
60+
setChecking(false)
61+
}
62+
63+
useEffect(() => {
64+
checkServices()
65+
// eslint-disable-next-line react-hooks/exhaustive-deps
66+
}, [])
67+
68+
const allOk = services.every((s) => s.status === 'ok')
69+
70+
return (
71+
<MainLayout
72+
title={ss?.meta?.title || 'System Status - CrownCode'}
73+
description={ss?.meta?.description || 'Live status of CrownCode services.'}
74+
keywords={ss?.meta?.keywords || 'system status, health, uptime'}
75+
>
76+
<div style={{ maxWidth: 700, margin: '0 auto', padding: '4rem 1.5rem' }}>
77+
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}>
78+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
79+
<Activity size={28} style={{ color: '#ff4444' }} />
80+
<h1 style={{ fontSize: '2rem', fontWeight: 800 }}>
81+
{ss?.title || 'System Status'}
82+
</h1>
83+
</div>
84+
85+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 32 }}>
86+
<span
87+
style={{
88+
display: 'inline-block',
89+
width: 10,
90+
height: 10,
91+
borderRadius: '50%',
92+
background: allOk ? '#22c55e' : '#ef4444',
93+
}}
94+
/>
95+
<span style={{ color: '#999' }}>
96+
{allOk
97+
? (ss?.allOperational || 'All systems operational')
98+
: (ss?.someIssues || 'Some services have issues')}
99+
</span>
100+
<button
101+
onClick={checkServices}
102+
disabled={checking}
103+
style={{
104+
marginLeft: 'auto',
105+
background: 'none',
106+
border: '1px solid rgba(255,255,255,0.1)',
107+
borderRadius: 8,
108+
padding: '6px 12px',
109+
color: '#ccc',
110+
cursor: 'pointer',
111+
display: 'flex',
112+
alignItems: 'center',
113+
gap: 6,
114+
fontSize: '0.85rem',
115+
}}
116+
>
117+
<RefreshCw size={14} className={checking ? 'animate-spin' : ''} />
118+
{ss?.refresh || 'Refresh'}
119+
</button>
120+
</div>
121+
</motion.div>
122+
123+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
124+
{services.map((svc, i) => (
125+
<motion.div
126+
key={svc.name}
127+
initial={{ opacity: 0, x: -10 }}
128+
animate={{ opacity: 1, x: 0 }}
129+
transition={{ delay: 0.05 * i }}
130+
style={{
131+
display: 'flex',
132+
alignItems: 'center',
133+
justifyContent: 'space-between',
134+
background: 'rgba(255,255,255,0.03)',
135+
border: '1px solid rgba(255,255,255,0.08)',
136+
borderRadius: 12,
137+
padding: '16px 20px',
138+
}}
139+
>
140+
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
141+
{svc.status === 'ok' ? (
142+
<CheckCircle size={18} style={{ color: '#22c55e' }} />
143+
) : svc.status === 'error' ? (
144+
<XCircle size={18} style={{ color: '#ef4444' }} />
145+
) : (
146+
<RefreshCw size={18} style={{ color: '#888' }} className="animate-spin" />
147+
)}
148+
<span style={{ fontWeight: 600, fontSize: '0.95rem' }}>{svc.name}</span>
149+
</div>
150+
<span style={{ color: '#666', fontSize: '0.85rem' }}>
151+
{svc.latency !== undefined ? `${svc.latency}ms` : '...'}
152+
</span>
153+
</motion.div>
154+
))}
155+
</div>
156+
</div>
157+
</MainLayout>
158+
)
159+
}
160+
161+
export default SystemStatusPage

0 commit comments

Comments
 (0)