@shellui/core 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/ContentView-CZG-ro_B.js +146 -0
- package/dist/ContentView-CZG-ro_B.js.map +1 -0
- package/dist/CookiePreferencesView-MhO9FO-4.js +213 -0
- package/dist/CookiePreferencesView-MhO9FO-4.js.map +1 -0
- package/dist/DefaultLayout-Dbb3uJED.js +394 -0
- package/dist/DefaultLayout-Dbb3uJED.js.map +1 -0
- package/dist/FullscreenLayout-1SgPHWw-.js +30 -0
- package/dist/FullscreenLayout-1SgPHWw-.js.map +1 -0
- package/dist/HomeView-DYU-O_Il.js +21 -0
- package/dist/HomeView-DYU-O_Il.js.map +1 -0
- package/dist/NotFoundView-CeYjJNg0.js +52 -0
- package/dist/NotFoundView-CeYjJNg0.js.map +1 -0
- package/dist/OverlayShell-pzbqQW25.js +642 -0
- package/dist/OverlayShell-pzbqQW25.js.map +1 -0
- package/dist/SettingsView-Bndrta44.js +2207 -0
- package/dist/SettingsView-Bndrta44.js.map +1 -0
- package/dist/ViewRoute-ChSPabOy.js +32 -0
- package/dist/ViewRoute-ChSPabOy.js.map +1 -0
- package/dist/WindowsLayout-CXGNPKoY.js +633 -0
- package/dist/WindowsLayout-CXGNPKoY.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/components/ContentView.d.ts +10 -0
- package/dist/components/ContentView.d.ts.map +1 -0
- package/dist/components/HomeView.d.ts +2 -0
- package/dist/components/HomeView.d.ts.map +1 -0
- package/dist/components/LoadingOverlay.d.ts +2 -0
- package/dist/components/LoadingOverlay.d.ts.map +1 -0
- package/dist/components/NotFoundView.d.ts +2 -0
- package/dist/components/NotFoundView.d.ts.map +1 -0
- package/dist/components/RouteErrorBoundary.d.ts +2 -0
- package/dist/components/RouteErrorBoundary.d.ts.map +1 -0
- package/dist/components/ViewRoute.d.ts +7 -0
- package/dist/components/ViewRoute.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +32 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +20 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/button-group.d.ts +7 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button.d.ts +12 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/dialog.d.ts +24 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/drawer.d.ts +38 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/select.d.ts +5 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/sidebar.d.ts +46 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sonner.d.ts +6 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/switch.d.ts +8 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/constants/urls.d.ts +6 -0
- package/dist/constants/urls.d.ts.map +1 -0
- package/dist/constants/urls.js +8 -0
- package/dist/constants/urls.js.map +1 -0
- package/dist/features/alertDialog/DialogContext.d.ts +12 -0
- package/dist/features/alertDialog/DialogContext.d.ts.map +1 -0
- package/dist/features/config/ConfigProvider.d.ts +15 -0
- package/dist/features/config/ConfigProvider.d.ts.map +1 -0
- package/dist/features/config/types.d.ts +177 -0
- package/dist/features/config/types.d.ts.map +1 -0
- package/dist/features/config/useConfig.d.ts +8 -0
- package/dist/features/config/useConfig.d.ts.map +1 -0
- package/dist/features/cookieConsent/CookieConsentModal.d.ts +6 -0
- package/dist/features/cookieConsent/CookieConsentModal.d.ts.map +1 -0
- package/dist/features/cookieConsent/CookiePreferencesView.d.ts +2 -0
- package/dist/features/cookieConsent/CookiePreferencesView.d.ts.map +1 -0
- package/dist/features/cookieConsent/cookieConsent.d.ts +22 -0
- package/dist/features/cookieConsent/cookieConsent.d.ts.map +1 -0
- package/dist/features/cookieConsent/useCookieConsent.d.ts +15 -0
- package/dist/features/cookieConsent/useCookieConsent.d.ts.map +1 -0
- package/dist/features/drawer/DrawerContext.d.ts +24 -0
- package/dist/features/drawer/DrawerContext.d.ts.map +1 -0
- package/dist/features/layouts/AppLayout.d.ts +12 -0
- package/dist/features/layouts/AppLayout.d.ts.map +1 -0
- package/dist/features/layouts/DefaultLayout.d.ts +10 -0
- package/dist/features/layouts/DefaultLayout.d.ts.map +1 -0
- package/dist/features/layouts/FullscreenLayout.d.ts +9 -0
- package/dist/features/layouts/FullscreenLayout.d.ts.map +1 -0
- package/dist/features/layouts/LayoutProviders.d.ts +9 -0
- package/dist/features/layouts/LayoutProviders.d.ts.map +1 -0
- package/dist/features/layouts/OverlayShell.d.ts +10 -0
- package/dist/features/layouts/OverlayShell.d.ts.map +1 -0
- package/dist/features/layouts/WindowsLayout.d.ts +24 -0
- package/dist/features/layouts/WindowsLayout.d.ts.map +1 -0
- package/dist/features/layouts/utils.d.ts +16 -0
- package/dist/features/layouts/utils.d.ts.map +1 -0
- package/dist/features/modal/ModalContext.d.ts +20 -0
- package/dist/features/modal/ModalContext.d.ts.map +1 -0
- package/dist/features/sentry/initSentry.d.ts +14 -0
- package/dist/features/sentry/initSentry.d.ts.map +1 -0
- package/dist/features/settings/SettingsContext.d.ts +10 -0
- package/dist/features/settings/SettingsContext.d.ts.map +1 -0
- package/dist/features/settings/SettingsIcons.d.ts +22 -0
- package/dist/features/settings/SettingsIcons.d.ts.map +1 -0
- package/dist/features/settings/SettingsProvider.d.ts +5 -0
- package/dist/features/settings/SettingsProvider.d.ts.map +1 -0
- package/dist/features/settings/SettingsRoutes.d.ts +7 -0
- package/dist/features/settings/SettingsRoutes.d.ts.map +1 -0
- package/dist/features/settings/SettingsView.d.ts +2 -0
- package/dist/features/settings/SettingsView.d.ts.map +1 -0
- package/dist/features/settings/components/Advanced.d.ts +2 -0
- package/dist/features/settings/components/Advanced.d.ts.map +1 -0
- package/dist/features/settings/components/Appearance.d.ts +2 -0
- package/dist/features/settings/components/Appearance.d.ts.map +1 -0
- package/dist/features/settings/components/DataPrivacy.d.ts +2 -0
- package/dist/features/settings/components/DataPrivacy.d.ts.map +1 -0
- package/dist/features/settings/components/Develop.d.ts +2 -0
- package/dist/features/settings/components/Develop.d.ts.map +1 -0
- package/dist/features/settings/components/LanguageAndRegion.d.ts +2 -0
- package/dist/features/settings/components/LanguageAndRegion.d.ts.map +1 -0
- package/dist/features/settings/components/ServiceWorker.d.ts +2 -0
- package/dist/features/settings/components/ServiceWorker.d.ts.map +1 -0
- package/dist/features/settings/components/UpdateApp.d.ts +2 -0
- package/dist/features/settings/components/UpdateApp.d.ts.map +1 -0
- package/dist/features/settings/components/develop/DialogTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/DialogTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/DrawerTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/DrawerTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/ModalTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/ModalTestButtons.d.ts.map +1 -0
- package/dist/features/settings/components/develop/ToastTestButtons.d.ts +2 -0
- package/dist/features/settings/components/develop/ToastTestButtons.d.ts.map +1 -0
- package/dist/features/settings/hooks/useSettings.d.ts +2 -0
- package/dist/features/settings/hooks/useSettings.d.ts.map +1 -0
- package/dist/features/sonner/SonnerContext.d.ts +29 -0
- package/dist/features/sonner/SonnerContext.d.ts.map +1 -0
- package/dist/features/theme/ThemeProvider.d.ts +11 -0
- package/dist/features/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/features/theme/themes.d.ts +114 -0
- package/dist/features/theme/themes.d.ts.map +1 -0
- package/dist/features/theme/useTheme.d.ts +10 -0
- package/dist/features/theme/useTheme.d.ts.map +1 -0
- package/dist/i18n/I18nProvider.d.ts +9 -0
- package/dist/i18n/I18nProvider.d.ts.map +1 -0
- package/dist/i18n/config.d.ts +23 -0
- package/dist/i18n/config.d.ts.map +1 -0
- package/dist/i18n/translations/en/common.json.d.ts +19 -0
- package/dist/i18n/translations/en/cookieConsent.json.d.ts +53 -0
- package/dist/i18n/translations/en/settings.json.d.ts +358 -0
- package/dist/i18n/translations/fr/common.json.d.ts +19 -0
- package/dist/i18n/translations/fr/cookieConsent.json.d.ts +53 -0
- package/dist/i18n/translations/fr/settings.json.d.ts +358 -0
- package/dist/index-lmRk5L6z.js +2160 -0
- package/dist/index-lmRk5L6z.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/z-index.d.ts +29 -0
- package/dist/lib/z-index.d.ts.map +1 -0
- package/dist/router/router.d.ts +3 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/routes.d.ts +4 -0
- package/dist/router/routes.d.ts.map +1 -0
- package/dist/sidebar-ClIeZ2zb.js +303 -0
- package/dist/sidebar-ClIeZ2zb.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/switch-8SzUJz7Q.js +44 -0
- package/dist/switch-8SzUJz7Q.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +93 -0
- package/postcss.config.js +6 -0
- package/src/app.tsx +119 -0
- package/src/components/ContentView.tsx +258 -0
- package/src/components/HomeView.tsx +19 -0
- package/src/components/LoadingOverlay.tsx +12 -0
- package/src/components/NotFoundView.tsx +84 -0
- package/src/components/RouteErrorBoundary.tsx +95 -0
- package/src/components/ViewRoute.tsx +47 -0
- package/src/components/ui/alert-dialog.tsx +181 -0
- package/src/components/ui/breadcrumb.tsx +155 -0
- package/src/components/ui/button-group.tsx +52 -0
- package/src/components/ui/button.tsx +51 -0
- package/src/components/ui/dialog.tsx +160 -0
- package/src/components/ui/drawer.tsx +200 -0
- package/src/components/ui/select.tsx +24 -0
- package/src/components/ui/sidebar.tsx +406 -0
- package/src/components/ui/sonner.tsx +36 -0
- package/src/components/ui/switch.tsx +45 -0
- package/src/constants/urls.ts +4 -0
- package/src/features/alertDialog/DialogContext.tsx +468 -0
- package/src/features/config/ConfigProvider.ts +96 -0
- package/src/features/config/types.ts +195 -0
- package/src/features/config/useConfig.ts +15 -0
- package/src/features/cookieConsent/CookieConsentModal.tsx +122 -0
- package/src/features/cookieConsent/CookiePreferencesView.tsx +328 -0
- package/src/features/cookieConsent/cookieConsent.ts +84 -0
- package/src/features/cookieConsent/useCookieConsent.ts +39 -0
- package/src/features/drawer/DrawerContext.tsx +116 -0
- package/src/features/layouts/AppLayout.tsx +63 -0
- package/src/features/layouts/DefaultLayout.tsx +625 -0
- package/src/features/layouts/FullscreenLayout.tsx +55 -0
- package/src/features/layouts/LayoutProviders.tsx +20 -0
- package/src/features/layouts/OverlayShell.tsx +171 -0
- package/src/features/layouts/WindowsLayout.tsx +860 -0
- package/src/features/layouts/utils.ts +99 -0
- package/src/features/modal/ModalContext.tsx +112 -0
- package/src/features/sentry/initSentry.ts +72 -0
- package/src/features/settings/SettingsContext.tsx +19 -0
- package/src/features/settings/SettingsIcons.tsx +452 -0
- package/src/features/settings/SettingsProvider.tsx +341 -0
- package/src/features/settings/SettingsRoutes.tsx +66 -0
- package/src/features/settings/SettingsView.tsx +327 -0
- package/src/features/settings/components/Advanced.tsx +128 -0
- package/src/features/settings/components/Appearance.tsx +306 -0
- package/src/features/settings/components/DataPrivacy.tsx +142 -0
- package/src/features/settings/components/Develop.tsx +174 -0
- package/src/features/settings/components/LanguageAndRegion.tsx +329 -0
- package/src/features/settings/components/ServiceWorker.tsx +363 -0
- package/src/features/settings/components/UpdateApp.tsx +206 -0
- package/src/features/settings/components/develop/DialogTestButtons.tsx +137 -0
- package/src/features/settings/components/develop/DrawerTestButtons.tsx +67 -0
- package/src/features/settings/components/develop/ModalTestButtons.tsx +30 -0
- package/src/features/settings/components/develop/ToastTestButtons.tsx +179 -0
- package/src/features/settings/hooks/useSettings.tsx +10 -0
- package/src/features/sonner/SonnerContext.tsx +286 -0
- package/src/features/theme/ThemeProvider.tsx +16 -0
- package/src/features/theme/themes.ts +561 -0
- package/src/features/theme/useTheme.tsx +71 -0
- package/src/i18n/I18nProvider.tsx +32 -0
- package/src/i18n/config.ts +107 -0
- package/src/i18n/translations/en/common.json +16 -0
- package/src/i18n/translations/en/cookieConsent.json +50 -0
- package/src/i18n/translations/en/settings.json +355 -0
- package/src/i18n/translations/fr/common.json +16 -0
- package/src/i18n/translations/fr/cookieConsent.json +50 -0
- package/src/i18n/translations/fr/settings.json +355 -0
- package/src/index.css +412 -0
- package/src/index.html +100 -0
- package/src/index.ts +31 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/z-index.ts +29 -0
- package/src/main.tsx +26 -0
- package/src/router/router.tsx +8 -0
- package/src/router/routes.tsx +115 -0
- package/src/service-worker/register.ts +1199 -0
- package/src/service-worker/sw-dev.ts +87 -0
- package/src/service-worker/sw.ts +105 -0
- package/tailwind.config.js +60 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import { useSettings } from '../hooks/useSettings';
|
|
3
|
+
import { useConfig } from '@/features/config/useConfig';
|
|
4
|
+
import { getSupportedLanguages } from '@/i18n/config';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { ButtonGroup } from '@/components/ui/button-group';
|
|
7
|
+
import { Select } from '@/components/ui/select';
|
|
8
|
+
import { cn } from '@/lib/utils';
|
|
9
|
+
import { useState, useEffect } from 'react';
|
|
10
|
+
|
|
11
|
+
const GlobeIcon = () => (
|
|
12
|
+
<svg
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
width="16"
|
|
15
|
+
height="16"
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
fill="none"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
strokeWidth="2"
|
|
20
|
+
strokeLinecap="round"
|
|
21
|
+
strokeLinejoin="round"
|
|
22
|
+
>
|
|
23
|
+
<circle
|
|
24
|
+
cx="12"
|
|
25
|
+
cy="12"
|
|
26
|
+
r="10"
|
|
27
|
+
/>
|
|
28
|
+
<line
|
|
29
|
+
x1="2"
|
|
30
|
+
x2="22"
|
|
31
|
+
y1="12"
|
|
32
|
+
y2="12"
|
|
33
|
+
/>
|
|
34
|
+
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const ClockIcon = () => (
|
|
39
|
+
<svg
|
|
40
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
41
|
+
width="16"
|
|
42
|
+
height="16"
|
|
43
|
+
viewBox="0 0 24 24"
|
|
44
|
+
fill="none"
|
|
45
|
+
stroke="currentColor"
|
|
46
|
+
strokeWidth="2"
|
|
47
|
+
strokeLinecap="round"
|
|
48
|
+
strokeLinejoin="round"
|
|
49
|
+
>
|
|
50
|
+
<circle
|
|
51
|
+
cx="12"
|
|
52
|
+
cy="12"
|
|
53
|
+
r="10"
|
|
54
|
+
/>
|
|
55
|
+
<polyline points="12 6 12 12 16 14" />
|
|
56
|
+
</svg>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Timezones organized by region
|
|
60
|
+
const TIMEZONE_GROUPS = [
|
|
61
|
+
{
|
|
62
|
+
label: 'UTC',
|
|
63
|
+
timezones: [{ value: 'UTC', label: 'UTC (Coordinated Universal Time)' }],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'North America',
|
|
67
|
+
timezones: [
|
|
68
|
+
{ value: 'America/New_York', label: 'Eastern Time (US & Canada)' },
|
|
69
|
+
{ value: 'America/Chicago', label: 'Central Time (US & Canada)' },
|
|
70
|
+
{ value: 'America/Denver', label: 'Mountain Time (US & Canada)' },
|
|
71
|
+
{ value: 'America/Los_Angeles', label: 'Pacific Time (US & Canada)' },
|
|
72
|
+
{ value: 'America/Toronto', label: 'Toronto' },
|
|
73
|
+
{ value: 'America/Vancouver', label: 'Vancouver' },
|
|
74
|
+
{ value: 'America/Mexico_City', label: 'Mexico City' },
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
label: 'South America',
|
|
79
|
+
timezones: [
|
|
80
|
+
{ value: 'America/Sao_Paulo', label: 'São Paulo' },
|
|
81
|
+
{ value: 'America/Buenos_Aires', label: 'Buenos Aires' },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'Europe',
|
|
86
|
+
timezones: [
|
|
87
|
+
{ value: 'Europe/London', label: 'London' },
|
|
88
|
+
{ value: 'Europe/Paris', label: 'Paris' },
|
|
89
|
+
{ value: 'Europe/Berlin', label: 'Berlin' },
|
|
90
|
+
{ value: 'Europe/Rome', label: 'Rome' },
|
|
91
|
+
{ value: 'Europe/Madrid', label: 'Madrid' },
|
|
92
|
+
{ value: 'Europe/Amsterdam', label: 'Amsterdam' },
|
|
93
|
+
{ value: 'Europe/Stockholm', label: 'Stockholm' },
|
|
94
|
+
{ value: 'Europe/Zurich', label: 'Zurich' },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
label: 'Asia',
|
|
99
|
+
timezones: [
|
|
100
|
+
{ value: 'Asia/Tokyo', label: 'Tokyo' },
|
|
101
|
+
{ value: 'Asia/Shanghai', label: 'Shanghai' },
|
|
102
|
+
{ value: 'Asia/Hong_Kong', label: 'Hong Kong' },
|
|
103
|
+
{ value: 'Asia/Singapore', label: 'Singapore' },
|
|
104
|
+
{ value: 'Asia/Dubai', label: 'Dubai' },
|
|
105
|
+
{ value: 'Asia/Kolkata', label: 'Mumbai, New Delhi' },
|
|
106
|
+
{ value: 'Asia/Bangkok', label: 'Bangkok' },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: 'Australia & Pacific',
|
|
111
|
+
timezones: [
|
|
112
|
+
{ value: 'Australia/Sydney', label: 'Sydney' },
|
|
113
|
+
{ value: 'Australia/Melbourne', label: 'Melbourne' },
|
|
114
|
+
{ value: 'Pacific/Auckland', label: 'Auckland' },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
// Format date based on timezone and language
|
|
120
|
+
const formatDate = (date: Date, timezone: string, lang: string): string => {
|
|
121
|
+
try {
|
|
122
|
+
return new Intl.DateTimeFormat(lang, {
|
|
123
|
+
timeZone: timezone,
|
|
124
|
+
weekday: 'long',
|
|
125
|
+
year: 'numeric',
|
|
126
|
+
month: 'long',
|
|
127
|
+
day: 'numeric',
|
|
128
|
+
}).format(date);
|
|
129
|
+
} catch {
|
|
130
|
+
return date.toLocaleDateString(lang);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Format time based on timezone and language
|
|
135
|
+
const formatTime = (date: Date, timezone: string, lang: string): string => {
|
|
136
|
+
try {
|
|
137
|
+
return new Intl.DateTimeFormat(lang, {
|
|
138
|
+
timeZone: timezone,
|
|
139
|
+
hour: '2-digit',
|
|
140
|
+
minute: '2-digit',
|
|
141
|
+
second: '2-digit',
|
|
142
|
+
hour12: false,
|
|
143
|
+
}).format(date);
|
|
144
|
+
} catch {
|
|
145
|
+
return date.toLocaleTimeString(lang);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Get browser's current timezone
|
|
150
|
+
const getBrowserTimezone = (): string => {
|
|
151
|
+
if (typeof Intl !== 'undefined') {
|
|
152
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
153
|
+
}
|
|
154
|
+
return 'UTC';
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Get human-readable timezone name
|
|
158
|
+
const getTimezoneDisplayName = (timezone: string, lang = 'en'): string => {
|
|
159
|
+
try {
|
|
160
|
+
// Try to get a friendly name from Intl
|
|
161
|
+
const formatter = new Intl.DateTimeFormat(lang, {
|
|
162
|
+
timeZone: timezone,
|
|
163
|
+
timeZoneName: 'long',
|
|
164
|
+
});
|
|
165
|
+
const parts = formatter.formatToParts(new Date());
|
|
166
|
+
const timeZoneName = parts.find((part) => part.type === 'timeZoneName')?.value;
|
|
167
|
+
|
|
168
|
+
if (timeZoneName) {
|
|
169
|
+
return timeZoneName;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Fallback: try to get city name from timezone string
|
|
173
|
+
const cityName = timezone.split('/').pop()?.replace(/_/g, ' ') || timezone;
|
|
174
|
+
return cityName;
|
|
175
|
+
} catch {
|
|
176
|
+
// Final fallback: use the timezone code
|
|
177
|
+
return timezone;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const LanguageAndRegion = () => {
|
|
182
|
+
const { t } = useTranslation('settings');
|
|
183
|
+
const { settings, updateSetting } = useSettings();
|
|
184
|
+
const { config } = useConfig();
|
|
185
|
+
const currentLanguage = settings.language?.code || 'en';
|
|
186
|
+
const browserTimezone = getBrowserTimezone();
|
|
187
|
+
const currentTimezone = settings.region?.timezone || browserTimezone;
|
|
188
|
+
const isUsingBrowserTimezone = currentTimezone === browserTimezone;
|
|
189
|
+
|
|
190
|
+
// Get supported languages based on config
|
|
191
|
+
const supportedLanguages = getSupportedLanguages(config?.language);
|
|
192
|
+
|
|
193
|
+
const handleResetRegion = () => {
|
|
194
|
+
updateSetting('region', { timezone: browserTimezone });
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// State for current date/time
|
|
198
|
+
const [currentDateTime, setCurrentDateTime] = useState<{ date: string; time: string }>(() => {
|
|
199
|
+
const now = new Date();
|
|
200
|
+
return {
|
|
201
|
+
date: formatDate(now, currentTimezone, currentLanguage),
|
|
202
|
+
time: formatTime(now, currentTimezone, currentLanguage),
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Update date/time every second and when timezone/language changes
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const updateDateTime = () => {
|
|
209
|
+
const now = new Date();
|
|
210
|
+
setCurrentDateTime({
|
|
211
|
+
date: formatDate(now, currentTimezone, currentLanguage),
|
|
212
|
+
time: formatTime(now, currentTimezone, currentLanguage),
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Update immediately when timezone or language changes
|
|
217
|
+
updateDateTime();
|
|
218
|
+
|
|
219
|
+
// Then update every second
|
|
220
|
+
const interval = setInterval(updateDateTime, 1000);
|
|
221
|
+
|
|
222
|
+
return () => clearInterval(interval);
|
|
223
|
+
}, [currentTimezone, currentLanguage]);
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div className="space-y-6">
|
|
227
|
+
<div className="space-y-2">
|
|
228
|
+
<label
|
|
229
|
+
className="text-sm font-medium leading-none"
|
|
230
|
+
style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
|
|
231
|
+
>
|
|
232
|
+
{t('languageAndRegion.language')}
|
|
233
|
+
</label>
|
|
234
|
+
<div className="mt-2">
|
|
235
|
+
<ButtonGroup>
|
|
236
|
+
{supportedLanguages.map((lang) => {
|
|
237
|
+
const isSelected = currentLanguage === lang.code;
|
|
238
|
+
return (
|
|
239
|
+
<Button
|
|
240
|
+
key={lang.code}
|
|
241
|
+
variant={isSelected ? 'default' : 'outline'}
|
|
242
|
+
onClick={() => {
|
|
243
|
+
updateSetting('language', { code: lang.code });
|
|
244
|
+
}}
|
|
245
|
+
className={cn(
|
|
246
|
+
'h-10 px-4 transition-all flex items-center gap-2',
|
|
247
|
+
isSelected && ['shadow-md', 'font-semibold'],
|
|
248
|
+
!isSelected && ['bg-background hover:bg-accent/50', 'text-muted-foreground'],
|
|
249
|
+
)}
|
|
250
|
+
aria-label={lang.nativeName}
|
|
251
|
+
title={lang.nativeName}
|
|
252
|
+
>
|
|
253
|
+
<GlobeIcon />
|
|
254
|
+
<span className="text-sm font-medium">{lang.nativeName}</span>
|
|
255
|
+
</Button>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
</ButtonGroup>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<div className="space-y-2">
|
|
263
|
+
<div className="flex items-center justify-between">
|
|
264
|
+
<label
|
|
265
|
+
className="text-sm font-medium leading-none"
|
|
266
|
+
style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
|
|
267
|
+
>
|
|
268
|
+
{t('languageAndRegion.region')}
|
|
269
|
+
</label>
|
|
270
|
+
{!isUsingBrowserTimezone && (
|
|
271
|
+
<Button
|
|
272
|
+
variant="ghost"
|
|
273
|
+
size="sm"
|
|
274
|
+
onClick={handleResetRegion}
|
|
275
|
+
className="h-8 text-xs"
|
|
276
|
+
>
|
|
277
|
+
{t('languageAndRegion.resetToBrowser')}
|
|
278
|
+
</Button>
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
281
|
+
<div className="mt-2 space-y-3">
|
|
282
|
+
<Select
|
|
283
|
+
value={currentTimezone}
|
|
284
|
+
onChange={(e) => {
|
|
285
|
+
updateSetting('region', { timezone: e.target.value });
|
|
286
|
+
}}
|
|
287
|
+
className="w-full"
|
|
288
|
+
>
|
|
289
|
+
{TIMEZONE_GROUPS.map((group) => (
|
|
290
|
+
<optgroup
|
|
291
|
+
key={group.label}
|
|
292
|
+
label={group.label}
|
|
293
|
+
>
|
|
294
|
+
{group.timezones.map((tz) => {
|
|
295
|
+
const isBrowserTimezone = tz.value === browserTimezone;
|
|
296
|
+
return (
|
|
297
|
+
<option
|
|
298
|
+
key={tz.value}
|
|
299
|
+
value={tz.value}
|
|
300
|
+
>
|
|
301
|
+
{tz.label}
|
|
302
|
+
{isBrowserTimezone ? ` (${t('languageAndRegion.defaultBrowser')})` : ''}
|
|
303
|
+
</option>
|
|
304
|
+
);
|
|
305
|
+
})}
|
|
306
|
+
</optgroup>
|
|
307
|
+
))}
|
|
308
|
+
</Select>
|
|
309
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-md bg-muted/60 border border-border/50">
|
|
310
|
+
<ClockIcon />
|
|
311
|
+
<div className="flex items-baseline gap-2">
|
|
312
|
+
<span className="text-lg font-semibold tabular-nums">{currentDateTime.time}</span>
|
|
313
|
+
<span className="text-xs text-muted-foreground">{currentDateTime.date}</span>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
<div className="text-xs text-muted-foreground flex items-center gap-1">
|
|
317
|
+
<span>{t('languageAndRegion.timezoneLabel')}:</span>
|
|
318
|
+
<span className="font-medium">
|
|
319
|
+
{getTimezoneDisplayName(currentTimezone, currentLanguage)}
|
|
320
|
+
</span>
|
|
321
|
+
{isUsingBrowserTimezone && (
|
|
322
|
+
<span className="ml-1">({t('languageAndRegion.defaultBrowser')})</span>
|
|
323
|
+
)}
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
};
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import { useSettings } from '../hooks/useSettings';
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import { Switch } from '@/components/ui/switch';
|
|
5
|
+
import {
|
|
6
|
+
isServiceWorkerRegistered,
|
|
7
|
+
updateServiceWorker,
|
|
8
|
+
getServiceWorkerStatus,
|
|
9
|
+
addStatusListener,
|
|
10
|
+
serviceWorkerFileExists,
|
|
11
|
+
} from '@/service-worker/register';
|
|
12
|
+
import { shellui } from '@shellui/sdk';
|
|
13
|
+
import { useState, useEffect } from 'react';
|
|
14
|
+
|
|
15
|
+
export const ServiceWorker = () => {
|
|
16
|
+
const { t } = useTranslation('settings');
|
|
17
|
+
const { settings, updateSetting } = useSettings();
|
|
18
|
+
const [isRegistered, setIsRegistered] = useState(false);
|
|
19
|
+
const [updateAvailable, setUpdateAvailable] = useState(false);
|
|
20
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
21
|
+
const [swFileExists, setSwFileExists] = useState(true); // Track if service worker file exists
|
|
22
|
+
|
|
23
|
+
const serviceWorkerEnabled = settings?.serviceWorker?.enabled ?? true;
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// Don't check service worker status if disabled
|
|
27
|
+
if (!serviceWorkerEnabled) {
|
|
28
|
+
// Immediately clear all state and hide error messages
|
|
29
|
+
setIsRegistered(false);
|
|
30
|
+
setUpdateAvailable(false);
|
|
31
|
+
setIsLoading(false);
|
|
32
|
+
setSwFileExists(true); // Set to true to prevent error messages from showing
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Initial check with loading state
|
|
37
|
+
const initialCheck = async () => {
|
|
38
|
+
// Double-check service worker is still enabled before proceeding
|
|
39
|
+
const stillEnabled = settings?.serviceWorker?.enabled ?? true;
|
|
40
|
+
if (!stillEnabled) {
|
|
41
|
+
setIsLoading(false);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setIsLoading(true);
|
|
46
|
+
|
|
47
|
+
// First check if service worker file exists
|
|
48
|
+
const exists = await serviceWorkerFileExists();
|
|
49
|
+
|
|
50
|
+
// Check again if service worker was disabled during the async operation
|
|
51
|
+
const currentEnabled = settings?.serviceWorker?.enabled ?? true;
|
|
52
|
+
if (!currentEnabled) {
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
setSwFileExists(exists);
|
|
58
|
+
|
|
59
|
+
if (exists) {
|
|
60
|
+
// Check service worker status only if file exists
|
|
61
|
+
const status = await getServiceWorkerStatus();
|
|
62
|
+
|
|
63
|
+
// Final check before updating state
|
|
64
|
+
const finalCheck = settings?.serviceWorker?.enabled ?? true;
|
|
65
|
+
if (finalCheck) {
|
|
66
|
+
setIsRegistered(status.registered);
|
|
67
|
+
setUpdateAvailable(status.updateAvailable);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// File doesn't exist, so service worker can't be registered
|
|
71
|
+
// Only update if service worker is still enabled
|
|
72
|
+
const finalCheck = settings?.serviceWorker?.enabled ?? true;
|
|
73
|
+
if (finalCheck) {
|
|
74
|
+
setIsRegistered(false);
|
|
75
|
+
setUpdateAvailable(false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setIsLoading(false);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Background refresh without affecting loading state
|
|
83
|
+
const refreshStatus = async () => {
|
|
84
|
+
// Skip if service worker was disabled (check current setting value)
|
|
85
|
+
const currentEnabled = settings?.serviceWorker?.enabled ?? true;
|
|
86
|
+
if (!currentEnabled) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if file exists first
|
|
91
|
+
const exists = await serviceWorkerFileExists();
|
|
92
|
+
|
|
93
|
+
// Check again if service worker was disabled during the async operation
|
|
94
|
+
const currentEnabledAfter = settings?.serviceWorker?.enabled ?? true;
|
|
95
|
+
if (!currentEnabledAfter) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setSwFileExists(exists);
|
|
100
|
+
|
|
101
|
+
if (exists) {
|
|
102
|
+
// Check service worker status only if file exists
|
|
103
|
+
const status = await getServiceWorkerStatus();
|
|
104
|
+
|
|
105
|
+
// Final check before updating state
|
|
106
|
+
const finalCheck = settings?.serviceWorker?.enabled ?? true;
|
|
107
|
+
if (finalCheck) {
|
|
108
|
+
setIsRegistered(status.registered);
|
|
109
|
+
setUpdateAvailable(status.updateAvailable);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// File doesn't exist, so service worker can't be registered
|
|
113
|
+
// Only update if service worker is still enabled
|
|
114
|
+
const finalCheck = settings?.serviceWorker?.enabled ?? true;
|
|
115
|
+
if (finalCheck) {
|
|
116
|
+
setIsRegistered(false);
|
|
117
|
+
setUpdateAvailable(false);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Initial check immediately
|
|
123
|
+
initialCheck();
|
|
124
|
+
|
|
125
|
+
// Listen for status changes
|
|
126
|
+
const unsubscribe = addStatusListener((status) => {
|
|
127
|
+
// Only update if service worker is still enabled (check current setting value)
|
|
128
|
+
const currentEnabled = settings?.serviceWorker?.enabled ?? true;
|
|
129
|
+
if (currentEnabled) {
|
|
130
|
+
setIsRegistered(status.registered);
|
|
131
|
+
setUpdateAvailable(status.updateAvailable);
|
|
132
|
+
setIsLoading(false);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Also check periodically as fallback (background refresh) - less frequent to avoid excessive fetches
|
|
137
|
+
const interval = setInterval(refreshStatus, 30000); // Check every 30 seconds instead of 5
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
unsubscribe();
|
|
141
|
+
clearInterval(interval);
|
|
142
|
+
};
|
|
143
|
+
}, [serviceWorkerEnabled]);
|
|
144
|
+
|
|
145
|
+
const handleToggleServiceWorker = async (enabled: boolean) => {
|
|
146
|
+
// Immediately clear all state before updating setting to prevent error flashes
|
|
147
|
+
if (!enabled) {
|
|
148
|
+
setIsRegistered(false);
|
|
149
|
+
setUpdateAvailable(false);
|
|
150
|
+
setSwFileExists(true); // Set to true to hide error messages immediately
|
|
151
|
+
setIsLoading(false);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
updateSetting('serviceWorker', { enabled });
|
|
155
|
+
|
|
156
|
+
if (!enabled) {
|
|
157
|
+
// Already cleared above, just return
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Give it a moment to register/unregister
|
|
162
|
+
setTimeout(async () => {
|
|
163
|
+
// Double-check service worker is still enabled before updating state
|
|
164
|
+
const currentEnabled = settings?.serviceWorker?.enabled ?? true;
|
|
165
|
+
if (enabled && currentEnabled) {
|
|
166
|
+
const registered = await isServiceWorkerRegistered();
|
|
167
|
+
setIsRegistered(registered);
|
|
168
|
+
}
|
|
169
|
+
}, 1000);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const handleUpdateNow = async () => {
|
|
173
|
+
try {
|
|
174
|
+
await updateServiceWorker();
|
|
175
|
+
// Don't show toast here - updateServiceWorker() will handle the reload
|
|
176
|
+
// and the update process already provides feedback through the UI
|
|
177
|
+
} catch (_error) {
|
|
178
|
+
shellui.toast({
|
|
179
|
+
title: t('caching.updateError.title'),
|
|
180
|
+
description: t('caching.updateError.description'),
|
|
181
|
+
type: 'error',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const handleResetToLatest = async () => {
|
|
187
|
+
try {
|
|
188
|
+
// Clear all caches and reload
|
|
189
|
+
if ('caches' in window) {
|
|
190
|
+
const cacheNames = await caches.keys();
|
|
191
|
+
await Promise.all(cacheNames.map((name) => caches.delete(name)));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
shellui.toast({
|
|
195
|
+
title: t('caching.resetSuccess.title'),
|
|
196
|
+
description: t('caching.resetSuccess.description'),
|
|
197
|
+
type: 'success',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Reload the app using shellUI refresh message (refreshes entire app, not just iframe)
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
const sent = shellui.sendMessageToParent({
|
|
203
|
+
type: 'SHELLUI_REFRESH_PAGE',
|
|
204
|
+
payload: {},
|
|
205
|
+
});
|
|
206
|
+
if (!sent) {
|
|
207
|
+
// Fallback to window.location.reload if message can't be sent
|
|
208
|
+
window.location.reload();
|
|
209
|
+
}
|
|
210
|
+
}, 1000);
|
|
211
|
+
} catch (_error) {
|
|
212
|
+
shellui.toast({
|
|
213
|
+
title: t('caching.resetError.title'),
|
|
214
|
+
description: t('caching.resetError.description'),
|
|
215
|
+
type: 'error',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div className="space-y-6">
|
|
222
|
+
{isLoading ? (
|
|
223
|
+
<div className="text-sm text-muted-foreground">{t('caching.loading')}</div>
|
|
224
|
+
) : null}
|
|
225
|
+
|
|
226
|
+
{!isLoading && (
|
|
227
|
+
<div className="space-y-6">
|
|
228
|
+
{/* Enable/Disable service worker */}
|
|
229
|
+
<div className="space-y-2">
|
|
230
|
+
<div className="flex items-center justify-between">
|
|
231
|
+
<div className="space-y-0.5">
|
|
232
|
+
<label
|
|
233
|
+
className="text-sm font-medium leading-none"
|
|
234
|
+
style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
|
|
235
|
+
>
|
|
236
|
+
{t('caching.enabled.title')}
|
|
237
|
+
</label>
|
|
238
|
+
<p className="text-sm text-muted-foreground">
|
|
239
|
+
{serviceWorkerEnabled
|
|
240
|
+
? t('caching.enabled.descriptionEnabled')
|
|
241
|
+
: t('caching.enabled.descriptionDisabled')}
|
|
242
|
+
</p>
|
|
243
|
+
</div>
|
|
244
|
+
<Switch
|
|
245
|
+
checked={serviceWorkerEnabled}
|
|
246
|
+
onCheckedChange={handleToggleServiceWorker}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
{/* Status – always visible; shows Disabled when off */}
|
|
252
|
+
<div className="space-y-2">
|
|
253
|
+
<label
|
|
254
|
+
className="text-sm font-medium leading-none"
|
|
255
|
+
style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
|
|
256
|
+
>
|
|
257
|
+
{t('caching.status.title')}
|
|
258
|
+
</label>
|
|
259
|
+
<div className="flex items-center gap-2 text-sm mt-2">
|
|
260
|
+
{!serviceWorkerEnabled ? (
|
|
261
|
+
<span className="text-muted-foreground">○ {t('caching.status.disabled')}</span>
|
|
262
|
+
) : !swFileExists ? (
|
|
263
|
+
<span className="text-red-600 dark:text-red-400">
|
|
264
|
+
✗ {t('caching.status.fileNotFound')}
|
|
265
|
+
</span>
|
|
266
|
+
) : (
|
|
267
|
+
<>
|
|
268
|
+
<span
|
|
269
|
+
className={
|
|
270
|
+
isRegistered
|
|
271
|
+
? 'text-green-600 dark:text-green-400'
|
|
272
|
+
: 'text-orange-600 dark:text-orange-400'
|
|
273
|
+
}
|
|
274
|
+
>
|
|
275
|
+
{isRegistered ? '●' : '○'}{' '}
|
|
276
|
+
{isRegistered ? t('caching.status.registered') : t('caching.status.notRunning')}
|
|
277
|
+
</span>
|
|
278
|
+
{updateAvailable && (
|
|
279
|
+
<>
|
|
280
|
+
<span className="text-muted-foreground/50">|</span>
|
|
281
|
+
<span className="text-blue-600 dark:text-blue-400">
|
|
282
|
+
● {t('caching.status.updateAvailable')}
|
|
283
|
+
</span>
|
|
284
|
+
</>
|
|
285
|
+
)}
|
|
286
|
+
</>
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
{serviceWorkerEnabled && (
|
|
292
|
+
<>
|
|
293
|
+
{/* Error Message when file missing */}
|
|
294
|
+
{!swFileExists && (
|
|
295
|
+
<div className="rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-950/30 p-4 space-y-3">
|
|
296
|
+
<div className="space-y-1">
|
|
297
|
+
<h3
|
|
298
|
+
className="text-sm font-medium leading-none text-red-600 dark:text-red-400"
|
|
299
|
+
style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
|
|
300
|
+
>
|
|
301
|
+
{t('caching.status.fileNotFound')}
|
|
302
|
+
</h3>
|
|
303
|
+
<p className="text-sm text-red-700 dark:text-red-300">
|
|
304
|
+
{t('caching.status.fileNotFoundDescription')}
|
|
305
|
+
</p>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
|
|
310
|
+
{/* Update Available */}
|
|
311
|
+
{updateAvailable && (
|
|
312
|
+
<div className="space-y-2">
|
|
313
|
+
<div className="space-y-0.5">
|
|
314
|
+
<label
|
|
315
|
+
className="text-sm font-medium leading-none"
|
|
316
|
+
style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
|
|
317
|
+
>
|
|
318
|
+
{t('caching.update.title')}
|
|
319
|
+
</label>
|
|
320
|
+
<p className="text-sm text-muted-foreground">
|
|
321
|
+
{t('caching.update.description')}
|
|
322
|
+
</p>
|
|
323
|
+
</div>
|
|
324
|
+
<div className="mt-2">
|
|
325
|
+
<Button
|
|
326
|
+
variant="outline"
|
|
327
|
+
onClick={handleUpdateNow}
|
|
328
|
+
className="w-full sm:w-auto"
|
|
329
|
+
>
|
|
330
|
+
{t('caching.update.button')}
|
|
331
|
+
</Button>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
{/* Reset Cache */}
|
|
337
|
+
<div className="space-y-2">
|
|
338
|
+
<div className="space-y-0.5">
|
|
339
|
+
<label
|
|
340
|
+
className="text-sm font-medium leading-none"
|
|
341
|
+
style={{ fontFamily: 'var(--heading-font-family, inherit)' }}
|
|
342
|
+
>
|
|
343
|
+
{t('caching.reset.title')}
|
|
344
|
+
</label>
|
|
345
|
+
<p className="text-sm text-muted-foreground">{t('caching.reset.description')}</p>
|
|
346
|
+
</div>
|
|
347
|
+
<div className="mt-2">
|
|
348
|
+
<Button
|
|
349
|
+
variant="outline"
|
|
350
|
+
onClick={handleResetToLatest}
|
|
351
|
+
className="w-full sm:w-auto"
|
|
352
|
+
>
|
|
353
|
+
{t('caching.reset.button')}
|
|
354
|
+
</Button>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
};
|