3import { useState } from 'react';
4import { useTheme } from '@/contexts/ThemeContext';
5import { useIcon, getIconThemeList, iconThemes, Icon } from '@/contexts/IconContext';
6import { getThemeList, getAllThemes, saveCustomTheme, deleteCustomTheme, Theme } from '@/lib/themes';
7import CustomThemeCreator from '@/components/CustomThemeCreator';
9export default function SettingsPage() {
10 const { theme, themeId, setTheme } = useTheme();
11 const { iconTheme, iconThemeId, setIconTheme } = useIcon();
12 const [activeTab, setActiveTab] = useState<'appearance' | 'iconography' | 'general'>('appearance');
13 const [showCustomCreator, setShowCustomCreator] = useState(false);
14 const [editingTheme, setEditingTheme] = useState<Theme | undefined>(undefined);
15 const [themeList, setThemeList] = useState(getThemeList());
16 const iconThemeList = getIconThemeList();
18 const handleSaveCustomTheme = (customTheme: Theme) => {
19 saveCustomTheme(customTheme);
20 setThemeList(getThemeList()); // Refresh the list
21 setTheme(customTheme.id); // Apply the new theme
22 setShowCustomCreator(false);
23 setEditingTheme(undefined);
26 const handleDeleteCustomTheme = (themeIdToDelete: string) => {
27 if (confirm('Are you sure you want to delete this custom theme?')) {
28 deleteCustomTheme(themeIdToDelete);
29 setThemeList(getThemeList()); // Refresh the list
30 if (themeId === themeIdToDelete) {
31 setTheme('fieldpine'); // Switch to default if deleting current theme
36 const handleEditCustomTheme = (themeToEdit: Theme) => {
37 setEditingTheme(themeToEdit);
38 setShowCustomCreator(true);
41 const allThemes = getAllThemes();
44 <div className="min-h-screen p-4 md:p-8" style={{ background: 'var(--bg)' }}>
45 <div className="max-w-6xl mx-auto">
47 <div className="mb-8">
48 <h1 className="text-4xl font-bold mb-2" style={{ color: 'var(--text)' }}>
51 <p className="text-lg" style={{ color: 'var(--muted)' }}>
52 Customize your EverydayPOS experience
57 <div className="flex gap-2 mb-6 border-b pb-2" style={{ borderColor: 'var(--border)' }}>
59 onClick={() => setActiveTab('appearance')}
60 className={`px-6 py-3 font-semibold rounded-t-lg transition-all ${
61 activeTab === 'appearance'
62 ? 'text-white shadow-lg'
63 : 'hover:bg-opacity-50'
66 background: activeTab === 'appearance' ? 'var(--brand)' : 'var(--surface)',
67 color: activeTab === 'appearance' ? '#ffffff' : 'var(--text)',
68 border: `2px solid ${activeTab === 'appearance' ? 'var(--brand)' : 'var(--border)'}`,
71 <Icon name="palette" size={20} className="inline-block mr-2" /> Appearance
74 onClick={() => setActiveTab('iconography')}
75 className={`px-6 py-3 font-semibold rounded-t-lg transition-all ${
76 activeTab === 'iconography'
77 ? 'text-white shadow-lg'
78 : 'hover:bg-opacity-50'
81 background: activeTab === 'iconography' ? 'var(--brand)' : 'var(--surface)',
82 color: activeTab === 'iconography' ? '#ffffff' : 'var(--text)',
83 border: `2px solid ${activeTab === 'iconography' ? 'var(--brand)' : 'var(--border)'}`,
86 <Icon name="category" size={20} className="inline-block mr-2" /> Iconography
89 onClick={() => setActiveTab('general')}
90 className={`px-6 py-3 font-semibold rounded-t-lg transition-all ${
91 activeTab === 'general'
92 ? 'text-white shadow-lg'
93 : 'hover:bg-opacity-50'
96 background: activeTab === 'general' ? 'var(--brand)' : 'var(--surface)',
97 color: activeTab === 'general' ? '#ffffff' : 'var(--text)',
98 border: `2px solid ${activeTab === 'general' ? 'var(--brand)' : 'var(--border)'}`,
101 <Icon name="settings" size={20} className="inline-block mr-2" /> General
105 {/* Appearance Tab */}
106 {activeTab === 'appearance' && (
107 <div className="space-y-6">
108 <div className="card p-6">
109 <div className="flex items-center justify-between mb-6">
111 <h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text)' }}>
114 <p style={{ color: 'var(--muted)' }}>
115 Choose a theme that matches your style. Your selection is saved automatically.
120 setEditingTheme(undefined);
121 setShowCustomCreator(true);
123 className="btn btn-gradient px-6 py-3 rounded-lg font-semibold flex items-center gap-2 hover-scale"
125 <Icon name="add" size={20} />
131 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
132 {themeList.map((t) => {
133 const isSelected = themeId === t.id;
134 const themeData = allThemes[t.id];
137 <div key={t.id} className="relative">
139 onClick={() => setTheme(t.id)}
140 className={`w-full relative p-5 rounded-xl transition-all duration-300 text-left ${
142 ? 'ring-4 ring-offset-2 scale-105 shadow-2xl'
143 : 'hover:scale-102 hover:shadow-lg'
146 background: themeData.colors.surface,
147 borderColor: themeData.colors.brand,
148 border: `3px solid ${isSelected ? themeData.colors.brand : themeData.colors.border}`,
151 {/* Selected Badge */}
154 className="absolute -top-2 -right-2 w-8 h-8 rounded-full flex items-center justify-center text-white font-bold shadow-lg z-10"
155 style={{ background: themeData.colors.brand }}
164 className="absolute top-2 left-2 px-2 py-1 rounded text-xs font-bold text-white shadow-sm"
165 style={{ background: themeData.colors.brand }}
171 {/* Theme Preview Colors */}
172 <div className="flex gap-2 mb-4">
174 className="w-12 h-12 rounded-lg shadow-sm"
175 style={{ background: themeData.colors.brand }}
178 className="w-12 h-12 rounded-lg shadow-sm"
179 style={{ background: themeData.colors.brand2 }}
182 className="w-12 h-12 rounded-lg shadow-sm"
183 style={{ background: themeData.colors.bg }}
189 className="text-lg font-bold mb-1"
190 style={{ color: themeData.colors.text }}
196 style={{ color: themeData.colors.muted }}
202 <div className="mt-4 pt-4" style={{ borderTop: `1px solid ${themeData.colors.border}` }}>
203 <div className="flex items-center gap-2">
205 className="w-6 h-6 rounded"
206 style={{ background: themeData.colors.success || themeData.colors.brand }}
209 className="w-6 h-6 rounded"
210 style={{ background: themeData.colors.warn }}
213 className="w-6 h-6 rounded"
214 style={{ background: themeData.colors.danger }}
217 className="w-6 h-6 rounded"
218 style={{ background: themeData.colors.info || themeData.colors.brand2 }}
224 {/* Custom Theme Actions */}
226 <div className="flex gap-2 mt-2">
228 onClick={() => handleEditCustomTheme(themeData)}
229 className="flex-1 px-3 py-2 bg-surface border border-border rounded-lg hover:bg-surface-2 transition text-sm font-semibold"
230 style={{ color: 'var(--text)' }}
232 <Icon name="edit" size={16} className="inline mr-1" />
236 onClick={() => handleDeleteCustomTheme(t.id)}
237 className="flex-1 px-3 py-2 bg-danger text-white rounded-lg hover:opacity-90 transition text-sm font-semibold"
239 <Icon name="delete" size={16} className="inline mr-1" />
250 {/* Current Theme Info */}
251 <div className="card p-6">
252 <h3 className="text-xl font-bold mb-4" style={{ color: 'var(--text)' }}>
253 Current Theme Details
255 <div className="space-y-3">
257 <span className="font-semibold" style={{ color: 'var(--text)' }}>Name: </span>
258 <span style={{ color: 'var(--muted)' }}>{theme.name}</span>
261 <span className="font-semibold" style={{ color: 'var(--text)' }}>Description: </span>
262 <span style={{ color: 'var(--muted)' }}>{theme.description}</span>
265 <span className="font-semibold" style={{ color: 'var(--text)' }}>Border Radius: </span>
266 <span style={{ color: 'var(--muted)' }}>{theme.radius.radius}</span>
270 {/* Color Palette Display */}
271 <div className="mt-6">
272 <h4 className="font-semibold mb-3" style={{ color: 'var(--text)' }}>
275 <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
276 {Object.entries(theme.colors).map(([key, value]) => (
277 <div key={key} className="flex flex-col items-center">
279 className="w-16 h-16 rounded-lg shadow-md mb-2"
280 style={{ background: value }}
282 <span className="text-xs font-mono" style={{ color: 'var(--muted)' }}>
285 <span className="text-xs font-mono" style={{ color: 'var(--muted)' }}>
296 {/* Iconography Tab */}
297 {activeTab === 'iconography' && (
298 <div className="space-y-6">
299 <div className="card p-6">
300 <h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text)' }}>
303 <p className="mb-6" style={{ color: 'var(--muted)' }}>
304 Choose an icon style for the entire application. Includes Google Material Icons, Material Symbols, and Emoji options.
307 {/* Icon Theme Grid */}
308 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
309 {iconThemeList.map((t) => {
310 const isSelected = iconThemeId === t.id;
311 const iconThemeData = iconThemes[t.id];
316 onClick={() => setIconTheme(t.id)}
317 className={`relative p-5 rounded-xl transition-all duration-300 text-left ${
319 ? 'ring-4 ring-offset-2 scale-105 shadow-2xl'
320 : 'hover:scale-102 hover:shadow-lg'
323 background: 'var(--surface)',
324 borderColor: 'var(--brand)',
325 border: `3px solid ${isSelected ? 'var(--brand)' : 'var(--border)'}`,
328 {/* Selected Badge */}
331 className="absolute -top-2 -right-2 w-8 h-8 rounded-full flex items-center justify-center text-white font-bold shadow-lg"
332 style={{ background: 'var(--brand)' }}
339 <div className="flex justify-center gap-3 mb-4 text-4xl">
340 {iconThemeData.style === 'emoji' ? (
350 className={iconThemeData.style}
352 fontFamily: iconThemeData.fontFamily,
353 fontVariationSettings: iconThemeData.style.startsWith('material-symbols')
354 ? `'FILL' ${iconThemeData.fill ? 1 : 0}, 'wght' ${iconThemeData.weight || 400}, 'GRAD' ${iconThemeData.grade || 0}, 'opsz' ${iconThemeData.opticalSize || 24}`
361 className={iconThemeData.style}
363 fontFamily: iconThemeData.fontFamily,
364 fontVariationSettings: iconThemeData.style.startsWith('material-symbols')
365 ? `'FILL' ${iconThemeData.fill ? 1 : 0}, 'wght' ${iconThemeData.weight || 400}, 'GRAD' ${iconThemeData.grade || 0}, 'opsz' ${iconThemeData.opticalSize || 24}`
372 className={iconThemeData.style}
374 fontFamily: iconThemeData.fontFamily,
375 fontVariationSettings: iconThemeData.style.startsWith('material-symbols')
376 ? `'FILL' ${iconThemeData.fill ? 1 : 0}, 'wght' ${iconThemeData.weight || 400}, 'GRAD' ${iconThemeData.grade || 0}, 'opsz' ${iconThemeData.opticalSize || 24}`
383 className={iconThemeData.style}
385 fontFamily: iconThemeData.fontFamily,
386 fontVariationSettings: iconThemeData.style.startsWith('material-symbols')
387 ? `'FILL' ${iconThemeData.fill ? 1 : 0}, 'wght' ${iconThemeData.weight || 400}, 'GRAD' ${iconThemeData.grade || 0}, 'opsz' ${iconThemeData.opticalSize || 24}`
397 {/* Icon Theme Info */}
399 className="text-lg font-bold mb-1"
400 style={{ color: 'var(--text)' }}
406 style={{ color: 'var(--muted)' }}
411 {/* Style Properties */}
412 <div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--border)' }}>
413 <div className="flex flex-wrap gap-2">
415 className="text-xs px-2 py-1 rounded"
416 style={{ background: 'var(--brand-2)', color: 'var(--text)' }}
418 {iconThemeData.style}
420 {iconThemeData.fill !== undefined && (
422 className="text-xs px-2 py-1 rounded"
423 style={{ background: 'var(--surface-2)', color: 'var(--muted)' }}
425 {iconThemeData.fill ? 'Filled' : 'Outlined'}
436 {/* Current Icon Theme Details */}
437 <div className="card p-6">
438 <h3 className="text-xl font-bold mb-4" style={{ color: 'var(--text)' }}>
439 Current Icon Theme Details
441 <div className="space-y-3">
443 <span className="font-semibold" style={{ color: 'var(--text)' }}>Name: </span>
444 <span style={{ color: 'var(--muted)' }}>{iconTheme.name}</span>
447 <span className="font-semibold" style={{ color: 'var(--text)' }}>Description: </span>
448 <span style={{ color: 'var(--muted)' }}>{iconTheme.description}</span>
451 <span className="font-semibold" style={{ color: 'var(--text)' }}>Style: </span>
452 <span style={{ color: 'var(--muted)' }}>{iconTheme.style}</span>
455 <span className="font-semibold" style={{ color: 'var(--text)' }}>Font Family: </span>
456 <span style={{ color: 'var(--muted)' }}>{iconTheme.fontFamily}</span>
458 {iconTheme.weight !== undefined && (
460 <span className="font-semibold" style={{ color: 'var(--text)' }}>Weight: </span>
461 <span style={{ color: 'var(--muted)' }}>{iconTheme.weight}</span>
466 {/* Icon Preview Gallery */}
467 <div className="mt-6">
468 <h4 className="font-semibold mb-3" style={{ color: 'var(--text)' }}>
471 <div className="grid grid-cols-4 md:grid-cols-8 gap-4">
473 'home', 'settings', 'person', 'search', 'shopping_cart', 'favorite', 'star', 'check',
474 'close', 'menu', 'edit', 'delete', 'save', 'print', 'email', 'phone',
475 'notifications', 'warning', 'error', 'info', 'help', 'lock', 'download', 'refresh'
476 ].map((iconName) => (
477 <div key={iconName} className="flex flex-col items-center gap-2">
478 <Icon name={iconName} size={32} />
479 <span className="text-xs text-center" style={{ color: 'var(--muted)' }}>
491 {activeTab === 'general' && (
492 <div className="card p-6">
493 <h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text)' }}>
496 <p style={{ color: 'var(--muted)' }}>
497 Additional settings coming soon...
503 {/* Custom Theme Creator Modal */}
504 {showCustomCreator && (
506 onSave={handleSaveCustomTheme}
508 setShowCustomCreator(false);
509 setEditingTheme(undefined);
511 existingTheme={editingTheme}