276 lines
10 KiB
TypeScript
276 lines
10 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import AppLayout from '../../components/layout/AppLayout';
|
||
import { useTheme } from '../../lib/theme';
|
||
import Button from '../../components/ui/Button';
|
||
|
||
type FontFamily = 'serif' | 'sans' | 'mono';
|
||
type FontSize = 'small' | 'medium' | 'large' | 'extra-large';
|
||
type ReadingWidth = 'narrow' | 'medium' | 'wide';
|
||
|
||
interface Settings {
|
||
theme: 'light' | 'dark';
|
||
fontFamily: FontFamily;
|
||
fontSize: FontSize;
|
||
readingWidth: ReadingWidth;
|
||
}
|
||
|
||
const defaultSettings: Settings = {
|
||
theme: 'light',
|
||
fontFamily: 'serif',
|
||
fontSize: 'medium',
|
||
readingWidth: 'medium',
|
||
};
|
||
|
||
export default function SettingsPage() {
|
||
const { theme, setTheme } = useTheme();
|
||
const [settings, setSettings] = useState<Settings>(defaultSettings);
|
||
const [saved, setSaved] = useState(false);
|
||
|
||
// Load settings from localStorage on mount
|
||
useEffect(() => {
|
||
const savedSettings = localStorage.getItem('storycove-settings');
|
||
if (savedSettings) {
|
||
try {
|
||
const parsed = JSON.parse(savedSettings);
|
||
setSettings({ ...defaultSettings, ...parsed, theme });
|
||
} catch (error) {
|
||
console.error('Failed to parse saved settings:', error);
|
||
setSettings({ ...defaultSettings, theme });
|
||
}
|
||
} else {
|
||
setSettings({ ...defaultSettings, theme });
|
||
}
|
||
}, [theme]);
|
||
|
||
// Save settings to localStorage
|
||
const saveSettings = () => {
|
||
localStorage.setItem('storycove-settings', JSON.stringify(settings));
|
||
|
||
// Apply theme change
|
||
setTheme(settings.theme);
|
||
|
||
// Apply font settings to CSS custom properties
|
||
const root = document.documentElement;
|
||
|
||
const fontFamilyMap = {
|
||
serif: 'Georgia, Times, serif',
|
||
sans: 'Inter, system-ui, sans-serif',
|
||
mono: 'Monaco, Consolas, monospace',
|
||
};
|
||
|
||
const fontSizeMap = {
|
||
small: '14px',
|
||
medium: '16px',
|
||
large: '18px',
|
||
'extra-large': '20px',
|
||
};
|
||
|
||
const readingWidthMap = {
|
||
narrow: '600px',
|
||
medium: '800px',
|
||
wide: '1000px',
|
||
};
|
||
|
||
root.style.setProperty('--reading-font-family', fontFamilyMap[settings.fontFamily]);
|
||
root.style.setProperty('--reading-font-size', fontSizeMap[settings.fontSize]);
|
||
root.style.setProperty('--reading-max-width', readingWidthMap[settings.readingWidth]);
|
||
|
||
setSaved(true);
|
||
setTimeout(() => setSaved(false), 2000);
|
||
};
|
||
|
||
const updateSetting = <K extends keyof Settings>(key: K, value: Settings[K]) => {
|
||
setSettings(prev => ({ ...prev, [key]: value }));
|
||
};
|
||
|
||
return (
|
||
<AppLayout>
|
||
<div className="max-w-2xl mx-auto space-y-8">
|
||
<div>
|
||
<h1 className="text-3xl font-bold theme-header">Settings</h1>
|
||
<p className="theme-text mt-2">
|
||
Customize your StoryCove reading experience
|
||
</p>
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
{/* Theme Settings */}
|
||
<div className="theme-card theme-shadow rounded-lg p-6">
|
||
<h2 className="text-xl font-semibold theme-header mb-4">Appearance</h2>
|
||
|
||
<div className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium theme-header mb-2">
|
||
Theme
|
||
</label>
|
||
<div className="flex gap-4">
|
||
<button
|
||
onClick={() => updateSetting('theme', 'light')}
|
||
className={`px-4 py-2 rounded-lg border transition-colors ${
|
||
settings.theme === 'light'
|
||
? 'theme-accent-bg text-white border-transparent'
|
||
: 'theme-card theme-text theme-border hover:border-gray-400'
|
||
}`}
|
||
>
|
||
☀️ Light
|
||
</button>
|
||
<button
|
||
onClick={() => updateSetting('theme', 'dark')}
|
||
className={`px-4 py-2 rounded-lg border transition-colors ${
|
||
settings.theme === 'dark'
|
||
? 'theme-accent-bg text-white border-transparent'
|
||
: 'theme-card theme-text theme-border hover:border-gray-400'
|
||
}`}
|
||
>
|
||
🌙 Dark
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Reading Settings */}
|
||
<div className="theme-card theme-shadow rounded-lg p-6">
|
||
<h2 className="text-xl font-semibold theme-header mb-4">Reading Experience</h2>
|
||
|
||
<div className="space-y-6">
|
||
{/* Font Family */}
|
||
<div>
|
||
<label className="block text-sm font-medium theme-header mb-2">
|
||
Font Family
|
||
</label>
|
||
<div className="flex gap-4 flex-wrap">
|
||
<button
|
||
onClick={() => updateSetting('fontFamily', 'serif')}
|
||
className={`px-4 py-2 rounded-lg border transition-colors font-serif ${
|
||
settings.fontFamily === 'serif'
|
||
? 'theme-accent-bg text-white border-transparent'
|
||
: 'theme-card theme-text theme-border hover:border-gray-400'
|
||
}`}
|
||
>
|
||
Serif
|
||
</button>
|
||
<button
|
||
onClick={() => updateSetting('fontFamily', 'sans')}
|
||
className={`px-4 py-2 rounded-lg border transition-colors font-sans ${
|
||
settings.fontFamily === 'sans'
|
||
? 'theme-accent-bg text-white border-transparent'
|
||
: 'theme-card theme-text theme-border hover:border-gray-400'
|
||
}`}
|
||
>
|
||
Sans Serif
|
||
</button>
|
||
<button
|
||
onClick={() => updateSetting('fontFamily', 'mono')}
|
||
className={`px-4 py-2 rounded-lg border transition-colors font-mono ${
|
||
settings.fontFamily === 'mono'
|
||
? 'theme-accent-bg text-white border-transparent'
|
||
: 'theme-card theme-text theme-border hover:border-gray-400'
|
||
}`}
|
||
>
|
||
Monospace
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Font Size */}
|
||
<div>
|
||
<label className="block text-sm font-medium theme-header mb-2">
|
||
Font Size
|
||
</label>
|
||
<div className="flex gap-4 flex-wrap">
|
||
{(['small', 'medium', 'large', 'extra-large'] as FontSize[]).map((size) => (
|
||
<button
|
||
key={size}
|
||
onClick={() => updateSetting('fontSize', size)}
|
||
className={`px-4 py-2 rounded-lg border transition-colors capitalize ${
|
||
settings.fontSize === size
|
||
? 'theme-accent-bg text-white border-transparent'
|
||
: 'theme-card theme-text theme-border hover:border-gray-400'
|
||
}`}
|
||
>
|
||
{size.replace('-', ' ')}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Reading Width */}
|
||
<div>
|
||
<label className="block text-sm font-medium theme-header mb-2">
|
||
Reading Width
|
||
</label>
|
||
<div className="flex gap-4">
|
||
{(['narrow', 'medium', 'wide'] as ReadingWidth[]).map((width) => (
|
||
<button
|
||
key={width}
|
||
onClick={() => updateSetting('readingWidth', width)}
|
||
className={`px-4 py-2 rounded-lg border transition-colors capitalize ${
|
||
settings.readingWidth === width
|
||
? 'theme-accent-bg text-white border-transparent'
|
||
: 'theme-card theme-text theme-border hover:border-gray-400'
|
||
}`}
|
||
>
|
||
{width}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Preview */}
|
||
<div className="theme-card theme-shadow rounded-lg p-6">
|
||
<h2 className="text-xl font-semibold theme-header mb-4">Preview</h2>
|
||
|
||
<div
|
||
className="p-4 theme-card border theme-border rounded-lg"
|
||
style={{
|
||
fontFamily: settings.fontFamily === 'serif' ? 'Georgia, Times, serif'
|
||
: settings.fontFamily === 'sans' ? 'Inter, system-ui, sans-serif'
|
||
: 'Monaco, Consolas, monospace',
|
||
fontSize: settings.fontSize === 'small' ? '14px'
|
||
: settings.fontSize === 'medium' ? '16px'
|
||
: settings.fontSize === 'large' ? '18px'
|
||
: '20px',
|
||
maxWidth: settings.readingWidth === 'narrow' ? '600px'
|
||
: settings.readingWidth === 'medium' ? '800px'
|
||
: '1000px',
|
||
}}
|
||
>
|
||
<h3 className="text-xl font-bold theme-header mb-2">Sample Story Title</h3>
|
||
<p className="theme-text mb-4">by Sample Author</p>
|
||
<p className="theme-text leading-relaxed">
|
||
This is how your story text will look with the current settings.
|
||
The quick brown fox jumps over the lazy dog. Lorem ipsum dolor sit amet,
|
||
consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore
|
||
et dolore magna aliqua.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div className="flex justify-end gap-4">
|
||
<Button
|
||
variant="ghost"
|
||
onClick={() => {
|
||
setSettings({ ...defaultSettings, theme });
|
||
}}
|
||
>
|
||
Reset to Defaults
|
||
</Button>
|
||
|
||
<Button
|
||
onClick={saveSettings}
|
||
className={saved ? 'bg-green-600 hover:bg-green-700' : ''}
|
||
>
|
||
{saved ? '✓ Saved!' : 'Save Settings'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</AppLayout>
|
||
);
|
||
} |