EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
page.tsx
Go to the documentation of this file.
1"use client";
2
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';
8
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();
17
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);
24 };
25
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
32 }
33 }
34 };
35
36 const handleEditCustomTheme = (themeToEdit: Theme) => {
37 setEditingTheme(themeToEdit);
38 setShowCustomCreator(true);
39 };
40
41 const allThemes = getAllThemes();
42
43 return (
44 <div className="min-h-screen p-4 md:p-8" style={{ background: 'var(--bg)' }}>
45 <div className="max-w-6xl mx-auto">
46 {/* Header */}
47 <div className="mb-8">
48 <h1 className="text-4xl font-bold mb-2" style={{ color: 'var(--text)' }}>
49 Settings
50 </h1>
51 <p className="text-lg" style={{ color: 'var(--muted)' }}>
52 Customize your EverydayPOS experience
53 </p>
54 </div>
55
56 {/* Tabs */}
57 <div className="flex gap-2 mb-6 border-b pb-2" style={{ borderColor: 'var(--border)' }}>
58 <button
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'
64 }`}
65 style={{
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)'}`,
69 }}
70 >
71 <Icon name="palette" size={20} className="inline-block mr-2" /> Appearance
72 </button>
73 <button
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'
79 }`}
80 style={{
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)'}`,
84 }}
85 >
86 <Icon name="category" size={20} className="inline-block mr-2" /> Iconography
87 </button>
88 <button
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'
94 }`}
95 style={{
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)'}`,
99 }}
100 >
101 <Icon name="settings" size={20} className="inline-block mr-2" /> General
102 </button>
103 </div>
104
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">
110 <div>
111 <h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text)' }}>
112 Theme Selection
113 </h2>
114 <p style={{ color: 'var(--muted)' }}>
115 Choose a theme that matches your style. Your selection is saved automatically.
116 </p>
117 </div>
118 <button
119 onClick={() => {
120 setEditingTheme(undefined);
121 setShowCustomCreator(true);
122 }}
123 className="btn btn-gradient px-6 py-3 rounded-lg font-semibold flex items-center gap-2 hover-scale"
124 >
125 <Icon name="add" size={20} />
126 Create Custom Theme
127 </button>
128 </div>
129
130 {/* Theme Grid */}
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];
135
136 return (
137 <div key={t.id} className="relative">
138 <button
139 onClick={() => setTheme(t.id)}
140 className={`w-full relative p-5 rounded-xl transition-all duration-300 text-left ${
141 isSelected
142 ? 'ring-4 ring-offset-2 scale-105 shadow-2xl'
143 : 'hover:scale-102 hover:shadow-lg'
144 }`}
145 style={{
146 background: themeData.colors.surface,
147 borderColor: themeData.colors.brand,
148 border: `3px solid ${isSelected ? themeData.colors.brand : themeData.colors.border}`,
149 }}
150 >
151 {/* Selected Badge */}
152 {isSelected && (
153 <div
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 }}
156 >
157
158 </div>
159 )}
160
161 {/* Custom Badge */}
162 {t.isCustom && (
163 <div
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 }}
166 >
167 Custom
168 </div>
169 )}
170
171 {/* Theme Preview Colors */}
172 <div className="flex gap-2 mb-4">
173 <div
174 className="w-12 h-12 rounded-lg shadow-sm"
175 style={{ background: themeData.colors.brand }}
176 />
177 <div
178 className="w-12 h-12 rounded-lg shadow-sm"
179 style={{ background: themeData.colors.brand2 }}
180 />
181 <div
182 className="w-12 h-12 rounded-lg shadow-sm"
183 style={{ background: themeData.colors.bg }}
184 />
185 </div>
186
187 {/* Theme Info */}
188 <h3
189 className="text-lg font-bold mb-1"
190 style={{ color: themeData.colors.text }}
191 >
192 {t.name}
193 </h3>
194 <p
195 className="text-sm"
196 style={{ color: themeData.colors.muted }}
197 >
198 {t.description}
199 </p>
200
201 {/* Mini Preview */}
202 <div className="mt-4 pt-4" style={{ borderTop: `1px solid ${themeData.colors.border}` }}>
203 <div className="flex items-center gap-2">
204 <div
205 className="w-6 h-6 rounded"
206 style={{ background: themeData.colors.success || themeData.colors.brand }}
207 />
208 <div
209 className="w-6 h-6 rounded"
210 style={{ background: themeData.colors.warn }}
211 />
212 <div
213 className="w-6 h-6 rounded"
214 style={{ background: themeData.colors.danger }}
215 />
216 <div
217 className="w-6 h-6 rounded"
218 style={{ background: themeData.colors.info || themeData.colors.brand2 }}
219 />
220 </div>
221 </div>
222 </button>
223
224 {/* Custom Theme Actions */}
225 {t.isCustom && (
226 <div className="flex gap-2 mt-2">
227 <button
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)' }}
231 >
232 <Icon name="edit" size={16} className="inline mr-1" />
233 Edit
234 </button>
235 <button
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"
238 >
239 <Icon name="delete" size={16} className="inline mr-1" />
240 Delete
241 </button>
242 </div>
243 )}
244 </div>
245 );
246 })}
247 </div>
248 </div>
249
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
254 </h3>
255 <div className="space-y-3">
256 <div>
257 <span className="font-semibold" style={{ color: 'var(--text)' }}>Name: </span>
258 <span style={{ color: 'var(--muted)' }}>{theme.name}</span>
259 </div>
260 <div>
261 <span className="font-semibold" style={{ color: 'var(--text)' }}>Description: </span>
262 <span style={{ color: 'var(--muted)' }}>{theme.description}</span>
263 </div>
264 <div>
265 <span className="font-semibold" style={{ color: 'var(--text)' }}>Border Radius: </span>
266 <span style={{ color: 'var(--muted)' }}>{theme.radius.radius}</span>
267 </div>
268 </div>
269
270 {/* Color Palette Display */}
271 <div className="mt-6">
272 <h4 className="font-semibold mb-3" style={{ color: 'var(--text)' }}>
273 Color Palette
274 </h4>
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">
278 <div
279 className="w-16 h-16 rounded-lg shadow-md mb-2"
280 style={{ background: value }}
281 />
282 <span className="text-xs font-mono" style={{ color: 'var(--muted)' }}>
283 {key}
284 </span>
285 <span className="text-xs font-mono" style={{ color: 'var(--muted)' }}>
286 {value}
287 </span>
288 </div>
289 ))}
290 </div>
291 </div>
292 </div>
293 </div>
294 )}
295
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)' }}>
301 Icon Style Selection
302 </h2>
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.
305 </p>
306
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];
312
313 return (
314 <button
315 key={t.id}
316 onClick={() => setIconTheme(t.id)}
317 className={`relative p-5 rounded-xl transition-all duration-300 text-left ${
318 isSelected
319 ? 'ring-4 ring-offset-2 scale-105 shadow-2xl'
320 : 'hover:scale-102 hover:shadow-lg'
321 }`}
322 style={{
323 background: 'var(--surface)',
324 borderColor: 'var(--brand)',
325 border: `3px solid ${isSelected ? 'var(--brand)' : 'var(--border)'}`,
326 }}
327 >
328 {/* Selected Badge */}
329 {isSelected && (
330 <div
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)' }}
333 >
334
335 </div>
336 )}
337
338 {/* Icon Preview */}
339 <div className="flex justify-center gap-3 mb-4 text-4xl">
340 {iconThemeData.style === 'emoji' ? (
341 <>
342 <span>🏠</span>
343 <span>⚙️</span>
344 <span>❤️</span>
345 <span>⭐</span>
346 </>
347 ) : (
348 <>
349 <span
350 className={iconThemeData.style}
351 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}`
355 : undefined,
356 }}
357 >
358 home
359 </span>
360 <span
361 className={iconThemeData.style}
362 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}`
366 : undefined,
367 }}
368 >
369 settings
370 </span>
371 <span
372 className={iconThemeData.style}
373 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}`
377 : undefined,
378 }}
379 >
380 favorite
381 </span>
382 <span
383 className={iconThemeData.style}
384 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}`
388 : undefined,
389 }}
390 >
391 star
392 </span>
393 </>
394 )}
395 </div>
396
397 {/* Icon Theme Info */}
398 <h3
399 className="text-lg font-bold mb-1"
400 style={{ color: 'var(--text)' }}
401 >
402 {t.name}
403 </h3>
404 <p
405 className="text-sm"
406 style={{ color: 'var(--muted)' }}
407 >
408 {t.description}
409 </p>
410
411 {/* Style Properties */}
412 <div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--border)' }}>
413 <div className="flex flex-wrap gap-2">
414 <span
415 className="text-xs px-2 py-1 rounded"
416 style={{ background: 'var(--brand-2)', color: 'var(--text)' }}
417 >
418 {iconThemeData.style}
419 </span>
420 {iconThemeData.fill !== undefined && (
421 <span
422 className="text-xs px-2 py-1 rounded"
423 style={{ background: 'var(--surface-2)', color: 'var(--muted)' }}
424 >
425 {iconThemeData.fill ? 'Filled' : 'Outlined'}
426 </span>
427 )}
428 </div>
429 </div>
430 </button>
431 );
432 })}
433 </div>
434 </div>
435
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
440 </h3>
441 <div className="space-y-3">
442 <div>
443 <span className="font-semibold" style={{ color: 'var(--text)' }}>Name: </span>
444 <span style={{ color: 'var(--muted)' }}>{iconTheme.name}</span>
445 </div>
446 <div>
447 <span className="font-semibold" style={{ color: 'var(--text)' }}>Description: </span>
448 <span style={{ color: 'var(--muted)' }}>{iconTheme.description}</span>
449 </div>
450 <div>
451 <span className="font-semibold" style={{ color: 'var(--text)' }}>Style: </span>
452 <span style={{ color: 'var(--muted)' }}>{iconTheme.style}</span>
453 </div>
454 <div>
455 <span className="font-semibold" style={{ color: 'var(--text)' }}>Font Family: </span>
456 <span style={{ color: 'var(--muted)' }}>{iconTheme.fontFamily}</span>
457 </div>
458 {iconTheme.weight !== undefined && (
459 <div>
460 <span className="font-semibold" style={{ color: 'var(--text)' }}>Weight: </span>
461 <span style={{ color: 'var(--muted)' }}>{iconTheme.weight}</span>
462 </div>
463 )}
464 </div>
465
466 {/* Icon Preview Gallery */}
467 <div className="mt-6">
468 <h4 className="font-semibold mb-3" style={{ color: 'var(--text)' }}>
469 Icon Preview Gallery
470 </h4>
471 <div className="grid grid-cols-4 md:grid-cols-8 gap-4">
472 {[
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)' }}>
480 {iconName}
481 </span>
482 </div>
483 ))}
484 </div>
485 </div>
486 </div>
487 </div>
488 )}
489
490 {/* General Tab */}
491 {activeTab === 'general' && (
492 <div className="card p-6">
493 <h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text)' }}>
494 General Settings
495 </h2>
496 <p style={{ color: 'var(--muted)' }}>
497 Additional settings coming soon...
498 </p>
499 </div>
500 )}
501 </div>
502
503 {/* Custom Theme Creator Modal */}
504 {showCustomCreator && (
505 <CustomThemeCreator
506 onSave={handleSaveCustomTheme}
507 onClose={() => {
508 setShowCustomCreator(false);
509 setEditingTheme(undefined);
510 }}
511 existingTheme={editingTheme}
512 />
513 )}
514 </div>
515 );
516}
517