diff --git a/src/apps/main/interface.d.ts b/src/apps/main/interface.d.ts index e8c662943e..5e149d92b8 100644 --- a/src/apps/main/interface.d.ts +++ b/src/apps/main/interface.d.ts @@ -131,6 +131,10 @@ export interface IElectronAPI { getBackupFatalErrors(): Promise>; onBackupProgress(func: (value: number) => void): () => void; startRemoteSync(): Promise; + + pathChanged(path: string): void; + isUserLoggedIn(): Promise; + onUserLoggedInChanged(func: (value: boolean) => void): void; } declare global { diff --git a/src/apps/renderer/App.css b/src/apps/renderer/App.css index fb59708419..faf5954822 100644 --- a/src/apps/renderer/App.css +++ b/src/apps/renderer/App.css @@ -9,76 +9,71 @@ --color-white: 255 255 255; --color-black: 0 0 0; -} -@media (prefers-color-scheme: light) { - :root { - --color-highlight: 17 17 17; - --color-surface: 255 255 255; + /* Light theme (default) */ + --color-highlight: 17 17 17; + --color-surface: 255 255 255; - --color-gray-1: 249 249 252; - --color-gray-5: 243 243 248; - --color-gray-10: 229 229 235; - --color-gray-20: 209 209 215; - --color-gray-30: 199 199 205; - --color-gray-40: 174 174 179; - --color-gray-50: 142 142 148; - --color-gray-60: 99 99 103; - --color-gray-70: 72 72 75; - --color-gray-80: 58 58 59; - --color-gray-90: 44 44 48; - --color-gray-100: 24 24 27; + --color-gray-1: 249 249 252; + --color-gray-5: 243 243 248; + --color-gray-10: 229 229 235; + --color-gray-20: 209 209 215; + --color-gray-30: 199 199 205; + --color-gray-40: 174 174 179; + --color-gray-50: 142 142 148; + --color-gray-60: 99 99 103; + --color-gray-70: 72 72 75; + --color-gray-80: 58 58 59; + --color-gray-90: 44 44 48; + --color-gray-100: 24 24 27; - --color-primary: 0 102 255; - --color-primary-dark: 0 88 219; + --color-primary: 0 102 255; + --color-primary-dark: 0 88 219; - --color-red: 255 13 0; - --color-red-dark: 230 11 0; + --color-red: 255 13 0; + --color-red-dark: 230 11 0; - --color-orange: 255 149 0; - --color-orange-dark: 230 134 0; + --color-orange: 255 149 0; + --color-orange-dark: 230 134 0; - --color-yellow: 255 204 0; - --color-yellow-dark: 230 184 0; + --color-yellow: 255 204 0; + --color-yellow-dark: 230 184 0; - --color-green: 50 195 86; - --color-green-dark: 46 173 78; - } + --color-green: 50 195 86; + --color-green-dark: 46 173 78; } -@media (prefers-color-scheme: dark) { - :root { - --color-highlight: 255 255 255; - --color-surface: 17 17 17; +.dark { + --color-highlight: 255 255 255; + --color-surface: 17 17 17; - --color-gray-1: 24 24 27; - --color-gray-5: 44 44 48; - --color-gray-10: 58 58 59; - --color-gray-20: 72 72 75; - --color-gray-30: 99 99 103; - --color-gray-40: 142 142 148; - --color-gray-50: 174 174 179; - --color-gray-60: 199 199 205; - --color-gray-70: 209 209 215; - --color-gray-80: 229 229 235; - --color-gray-90: 243 243 248; - --color-gray-100: 249 249 252; + --color-gray-1: 24 24 27; + --color-gray-5: 44 44 48; + --color-gray-10: 58 58 59; + --color-gray-20: 72 72 75; + --color-gray-30: 99 99 103; + --color-gray-40: 142 142 148; + --color-gray-50: 174 174 179; + --color-gray-60: 199 199 205; + --color-gray-70: 209 209 215; + --color-gray-80: 229 229 235; + --color-gray-90: 243 243 248; + --color-gray-100: 249 249 252; - --color-primary: 20 114 255; - --color-primary-dark: 0 96 240; + --color-primary: 20 114 255; + --color-primary-dark: 0 96 240; - --color-red: 255 61 51; - --color-red-dark: 255 36 26; + --color-red: 255 61 51; + --color-red-dark: 255 36 26; - --color-orange: 255 164 36; - --color-orange-dark: 255 153 10; + --color-orange: 255 164 36; + --color-orange-dark: 255 153 10; - --color-yellow: 255 214 51; - --color-yellow-dark: 255 209 26; + --color-yellow: 255 214 51; + --color-yellow-dark: 255 209 26; - --color-green: 72 208 106; - --color-green-dark: 52 203 90; - } + --color-green: 72 208 106; + --color-green-dark: 52 203 90; } body, @@ -109,18 +104,16 @@ html { background: #555; } -@media (prefers-color-scheme: dark) { - ::-webkit-scrollbar-track { - background: #555; - } +.dark ::-webkit-scrollbar-track { + background: #555; +} - ::-webkit-scrollbar-thumb { - background: #888; - } +.dark ::-webkit-scrollbar-thumb { + background: #888; +} - ::-webkit-scrollbar-thumb:hover { - background: #f1f1f1; - } +.dark ::-webkit-scrollbar-thumb:hover { + background: #f1f1f1; } /* end-scrollbar */ diff --git a/src/apps/renderer/App.tsx b/src/apps/renderer/App.tsx index 7d8777b97f..3c8bec8bbb 100644 --- a/src/apps/renderer/App.tsx +++ b/src/apps/renderer/App.tsx @@ -3,14 +3,17 @@ import './localize/i18n.service'; import { Suspense, useEffect, useRef } from 'react'; import { HashRouter as Router, Route, Routes, useLocation, useNavigate } from 'react-router-dom'; import { TranslationProvider } from './context/LocalContext'; -import useLanguageChangedListener from './hooks/useLanguage'; import Login from './pages/Login'; import Onboarding from './pages/Onboarding'; import IssuesPage from './pages/Issues/IssuesPage'; import Settings from './pages/Settings'; import Widget from './pages/Widget'; import { useBackupNotifications } from './hooks/useBackupNotifications'; +import { useTheme } from './hooks/useTheme'; +import i18next from 'i18next'; +import { isLanguage } from '../shared/Locale/Language'; import { UsageProvider } from './context/UsageContext/usage-provider'; + function LocationWrapper({ children }: { children: JSX.Element }) { const { pathname } = useLocation(); useEffect(() => { @@ -47,8 +50,26 @@ function Loader() { } export default function App() { - useLanguageChangedListener(); useBackupNotifications(); + useTheme(); + + // Global IPC → i18next bridge for language changes + useEffect(() => { + window.electron.getConfigKey('preferedLanguage').then((value) => { + const lang = value as string; + if (lang && isLanguage(lang) && i18next.language !== lang) { + i18next.changeLanguage(lang); + } + }); + + const cleanup = window.electron.listenToConfigKeyChange('preferedLanguage', (lang) => { + if (isLanguage(lang)) { + i18next.changeLanguage(lang); + } + }); + + return cleanup; + }, []); return ( diff --git a/src/apps/renderer/assets/onboarding/antivirus/EN-Dark.svg b/src/apps/renderer/assets/onboarding/antivirus/EN-Dark.svg new file mode 100644 index 0000000000..ec68bd5c23 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/antivirus/EN-Dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/antivirus/EN-Light.svg b/src/apps/renderer/assets/onboarding/antivirus/EN-Light.svg new file mode 100644 index 0000000000..af25c4794f --- /dev/null +++ b/src/apps/renderer/assets/onboarding/antivirus/EN-Light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/antivirus/ES-Dark.svg b/src/apps/renderer/assets/onboarding/antivirus/ES-Dark.svg new file mode 100644 index 0000000000..b63704eb4e --- /dev/null +++ b/src/apps/renderer/assets/onboarding/antivirus/ES-Dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/antivirus/ES-Light.svg b/src/apps/renderer/assets/onboarding/antivirus/ES-Light.svg new file mode 100644 index 0000000000..d9e351559e --- /dev/null +++ b/src/apps/renderer/assets/onboarding/antivirus/ES-Light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/antivirus/FR-Dark.svg b/src/apps/renderer/assets/onboarding/antivirus/FR-Dark.svg new file mode 100644 index 0000000000..1b9ba8417e --- /dev/null +++ b/src/apps/renderer/assets/onboarding/antivirus/FR-Dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/antivirus/FR-Light.svg b/src/apps/renderer/assets/onboarding/antivirus/FR-Light.svg new file mode 100644 index 0000000000..9568c05458 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/antivirus/FR-Light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/antivirus/en.svg b/src/apps/renderer/assets/onboarding/antivirus/en.svg deleted file mode 100644 index 046a959fcf..0000000000 --- a/src/apps/renderer/assets/onboarding/antivirus/en.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/antivirus/es.svg b/src/apps/renderer/assets/onboarding/antivirus/es.svg deleted file mode 100644 index 046a959fcf..0000000000 --- a/src/apps/renderer/assets/onboarding/antivirus/es.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/antivirus/fr.svg b/src/apps/renderer/assets/onboarding/antivirus/fr.svg deleted file mode 100644 index 046a959fcf..0000000000 --- a/src/apps/renderer/assets/onboarding/antivirus/fr.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/backups/backups-dark.svg b/src/apps/renderer/assets/onboarding/backups/backups-dark.svg new file mode 100644 index 0000000000..fedc34493b --- /dev/null +++ b/src/apps/renderer/assets/onboarding/backups/backups-dark.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/backups/backups-light.svg b/src/apps/renderer/assets/onboarding/backups/backups-light.svg new file mode 100644 index 0000000000..b0600c7dce --- /dev/null +++ b/src/apps/renderer/assets/onboarding/backups/backups-light.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/cleaner/EN-Dark.svg b/src/apps/renderer/assets/onboarding/cleaner/EN-Dark.svg new file mode 100644 index 0000000000..fbe23eeda0 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/cleaner/EN-Dark.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/cleaner/EN-Light.svg b/src/apps/renderer/assets/onboarding/cleaner/EN-Light.svg new file mode 100644 index 0000000000..852c91f010 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/cleaner/EN-Light.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/cleaner/ES-Dark.svg b/src/apps/renderer/assets/onboarding/cleaner/ES-Dark.svg new file mode 100644 index 0000000000..abddefefca --- /dev/null +++ b/src/apps/renderer/assets/onboarding/cleaner/ES-Dark.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/cleaner/ES-Light.svg b/src/apps/renderer/assets/onboarding/cleaner/ES-Light.svg new file mode 100644 index 0000000000..b6ed834b31 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/cleaner/ES-Light.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/cleaner/FR-Dark.svg b/src/apps/renderer/assets/onboarding/cleaner/FR-Dark.svg new file mode 100644 index 0000000000..04a4c7d2bf --- /dev/null +++ b/src/apps/renderer/assets/onboarding/cleaner/FR-Dark.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/cleaner/FR-Light.svg b/src/apps/renderer/assets/onboarding/cleaner/FR-Light.svg new file mode 100644 index 0000000000..32a1728410 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/cleaner/FR-Light.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/cleaner/en.svg b/src/apps/renderer/assets/onboarding/cleaner/en.svg deleted file mode 100644 index 00a26f0c37..0000000000 --- a/src/apps/renderer/assets/onboarding/cleaner/en.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/renderer/assets/onboarding/cleaner/es.svg b/src/apps/renderer/assets/onboarding/cleaner/es.svg deleted file mode 100644 index 6edd6ad011..0000000000 --- a/src/apps/renderer/assets/onboarding/cleaner/es.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/renderer/assets/onboarding/cleaner/fr.svg b/src/apps/renderer/assets/onboarding/cleaner/fr.svg deleted file mode 100644 index f7f051c4d1..0000000000 --- a/src/apps/renderer/assets/onboarding/cleaner/fr.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/renderer/assets/onboarding/context-menu.svg b/src/apps/renderer/assets/onboarding/context-menu.svg deleted file mode 100644 index f2e044b91d..0000000000 --- a/src/apps/renderer/assets/onboarding/context-menu.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/renderer/assets/onboarding/drive/EN-Dark.svg b/src/apps/renderer/assets/onboarding/drive/EN-Dark.svg new file mode 100644 index 0000000000..516e98abbb --- /dev/null +++ b/src/apps/renderer/assets/onboarding/drive/EN-Dark.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/drive/EN-Light.svg b/src/apps/renderer/assets/onboarding/drive/EN-Light.svg new file mode 100644 index 0000000000..0d3e6a8004 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/drive/EN-Light.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/drive/ES-Dark.svg b/src/apps/renderer/assets/onboarding/drive/ES-Dark.svg new file mode 100644 index 0000000000..e5daff9809 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/drive/ES-Dark.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/drive/ES-Light.svg b/src/apps/renderer/assets/onboarding/drive/ES-Light.svg new file mode 100644 index 0000000000..fc22a59fa5 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/drive/ES-Light.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/drive/FR-Dark.svg b/src/apps/renderer/assets/onboarding/drive/FR-Dark.svg new file mode 100644 index 0000000000..ffe5b7287f --- /dev/null +++ b/src/apps/renderer/assets/onboarding/drive/FR-Dark.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/drive/FR-Light.svg b/src/apps/renderer/assets/onboarding/drive/FR-Light.svg new file mode 100644 index 0000000000..8a97c5d2e6 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/drive/FR-Light.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/finder/linux-dark.svg b/src/apps/renderer/assets/onboarding/finder/linux-dark.svg new file mode 100644 index 0000000000..b0fd8c8e29 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/finder/linux-dark.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/finder/linux-ligth.svg b/src/apps/renderer/assets/onboarding/finder/linux-ligth.svg new file mode 100644 index 0000000000..c2348fd6a5 --- /dev/null +++ b/src/apps/renderer/assets/onboarding/finder/linux-ligth.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/apps/renderer/assets/onboarding/finder/linux.svg b/src/apps/renderer/assets/onboarding/finder/linux.svg deleted file mode 100644 index bd4d1d0d05..0000000000 --- a/src/apps/renderer/assets/onboarding/finder/linux.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/en.svg b/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/en.svg deleted file mode 100644 index c9eea26820..0000000000 --- a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/en.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/es.svg b/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/es.svg deleted file mode 100644 index d947790358..0000000000 --- a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/es.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/fr.svg b/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/fr.svg deleted file mode 100644 index 04b1eed033..0000000000 --- a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/offline/fr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/en.svg b/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/en.svg deleted file mode 100644 index 0104496f3b..0000000000 --- a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/en.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/es.svg b/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/es.svg deleted file mode 100644 index 8a966728c0..0000000000 --- a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/es.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/fr.svg b/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/fr.svg deleted file mode 100644 index 68678d1683..0000000000 --- a/src/apps/renderer/assets/onboarding/folder-with-overlay-icons/online/fr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/renderer/assets/onboarding/mac-finder-widget.png b/src/apps/renderer/assets/onboarding/mac-finder-widget.png deleted file mode 100644 index e992e0e3ac..0000000000 Binary files a/src/apps/renderer/assets/onboarding/mac-finder-widget.png and /dev/null differ diff --git a/src/apps/renderer/assets/onboarding/widget.png b/src/apps/renderer/assets/onboarding/widget.png deleted file mode 100644 index 4c9712ce41..0000000000 Binary files a/src/apps/renderer/assets/onboarding/widget.png and /dev/null differ diff --git a/src/apps/renderer/components/Button.test.tsx b/src/apps/renderer/components/Button.test.tsx new file mode 100644 index 0000000000..285b0645e5 --- /dev/null +++ b/src/apps/renderer/components/Button.test.tsx @@ -0,0 +1,37 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import Button from './Button'; + +describe('Button', () => { + it('should render with type button by default and trigger onClick', () => { + const onClick = vi.fn(); + + render(); + + const button = screen.getByRole('button', { name: 'Submit' }); + + expect(button).toHaveAttribute('type', 'button'); + + fireEvent.click(button); + + expect(onClick).toBeCalledTimes(1); + }); + + it('should be disabled and use disabled styles when disabled', () => { + render(); + + const button = screen.getByRole('button', { name: 'Processing' }); + + expect(button).toBeDisabled(); + expect(button).toHaveClass('bg-gray-30'); + }); + + it('should render the outline variant styles', () => { + render(); + + const button = screen.getByRole('button', { name: 'Cancel' }); + + expect(button).toHaveClass('border-2'); + expect(button).toHaveClass('border-primary'); + expect(button).toHaveClass('text-primary'); + }); +}); diff --git a/src/apps/renderer/components/Button.tsx b/src/apps/renderer/components/Button.tsx index d471550cf2..dd6836d4e3 100644 --- a/src/apps/renderer/components/Button.tsx +++ b/src/apps/renderer/components/Button.tsx @@ -1,30 +1,35 @@ import { ReactNode } from 'react'; interface ButtonProps extends React.ButtonHTMLAttributes { - variant?: 'primary' | 'danger' | 'secondary' | 'primaryLight' | 'dangerLight'; + variant?: 'primary' | 'danger' | 'secondary' | 'outline'; size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'; children: ReactNode; customClassName?: string; - disabled?: boolean; } -export default function Button(props: ButtonProps) { +export default function Button({ + variant = 'primary', + size = 'md', + children, + customClassName, + className, + type = 'button', + onClick, + ...buttonProps +}: ButtonProps) { const variants = { - primary: props.disabled + primary: buttonProps.disabled ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30' : 'bg-primary active:bg-primary-dark text-white', - primaryLight: props.disabled - ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30' - : 'border border-primary bg-surface text-primary hover:cursor-pointer', - secondary: props.disabled + secondary: buttonProps.disabled ? 'bg-surface text-gray-40 border border-gray-5 dark:bg-gray-5 dark:text-gray-40' : 'bg-surface active:bg-gray-1 text-highlight border border-gray-20 dark:bg-gray-5 dark:active:bg-gray-10 dark:active:border-gray-30', - danger: props.disabled + danger: buttonProps.disabled ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30' : 'bg-red active:bg-red-dark text-white', - dangerLight: props.disabled - ? 'bg-gray-30 dark:bg-gray-5 text-white dark:text-gray-30' - : 'border border-red-dark bg-surface text-red-dark hover:cursor-pointer', + outline: buttonProps.disabled + ? 'bg-transparent border-2 border-gray-30 text-gray-30 dark:border-gray-40 dark:text-gray-40 font-bold' + : 'bg-transparent border-2 border-primary text-primary active:bg-primary/10 dark:border-primary dark:text-primary font-bold', }; const sizes = { @@ -35,19 +40,13 @@ export default function Button(props: ButtonProps) { '2xl': 'h-12 px-5 rounded-lg text-lg', }; - const { className, ...propsWithOutClassName } = props; - const styles = `whitespace-nowrap shadow-sm outline-none transition-all duration-75 ease-in-out focus-visible:outline-none ${ - variants[props.variant ?? 'primary'] - } ${sizes[props.size ?? 'md']} ${props.customClassName ?? ''} ${className}`; + variants[variant] + } ${sizes[size]} ${customClassName ?? ''} ${className ?? ''}`; return ( - ); } diff --git a/src/apps/renderer/hooks/useLanguage.tsx b/src/apps/renderer/hooks/useLanguage.tsx index e1e0c0b884..04fc69a1be 100644 --- a/src/apps/renderer/hooks/useLanguage.tsx +++ b/src/apps/renderer/hooks/useLanguage.tsx @@ -1,19 +1,28 @@ -import i18next from 'i18next'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; -import { DEFAULT_LANGUAGE, Language } from '../../shared/Locale/Language'; +import { DEFAULT_LANGUAGE, Language, isLanguage } from '../../shared/Locale/Language'; -export default function useLanguage() { - const [lang, setLang] = useState(DEFAULT_LANGUAGE); - - const updated = (l: Language) => { - i18next.changeLanguage(l); - setLang(l); - }; +export function useLanguage(): Language { + const { i18n } = useTranslation(); useEffect(() => { - return window.electron.listenToConfigKeyChange('preferedLanguage', updated); + window.electron.getConfigKey('preferedLanguage').then((value) => { + const lang = value; + if (lang && isLanguage(lang) && i18n.language !== lang) { + i18n.changeLanguage(lang); + } + }); + + const cleanup = window.electron.listenToConfigKeyChange('preferedLanguage', (lang) => { + if (isLanguage(lang)) { + i18n.changeLanguage(lang); + } + }); + + return cleanup; }, []); - return lang; + const currentLang = i18n.language; + return isLanguage(currentLang) ? currentLang : DEFAULT_LANGUAGE; } diff --git a/src/apps/renderer/hooks/useTheme/index.ts b/src/apps/renderer/hooks/useTheme/index.ts new file mode 100644 index 0000000000..f637b7a30f --- /dev/null +++ b/src/apps/renderer/hooks/useTheme/index.ts @@ -0,0 +1 @@ +export { useTheme } from './useTheme'; diff --git a/src/apps/renderer/hooks/useTheme/theme-helpers.test.ts b/src/apps/renderer/hooks/useTheme/theme-helpers.test.ts new file mode 100644 index 0000000000..ac8a7c068c --- /dev/null +++ b/src/apps/renderer/hooks/useTheme/theme-helpers.test.ts @@ -0,0 +1,34 @@ +import { applyThemeClass, resolveTheme } from './theme-helpers'; + +describe('theme-helpers', () => { + describe('applyThemeClass', () => { + beforeEach(() => { + document.documentElement.classList.remove('dark'); + }); + + it('should add "dark" class when theme is dark', () => { + applyThemeClass('dark'); + expect(document.documentElement.classList.contains('dark')).toBe(true); + }); + + it('should remove "dark" class when theme is light', () => { + document.documentElement.classList.add('dark'); + applyThemeClass('light'); + expect(document.documentElement.classList.contains('dark')).toBe(false); + }); + }); + + describe('resolveTheme', () => { + it('should return "light" when config is "light"', () => { + expect(resolveTheme('light')).toBe('light'); + }); + + it('should return "dark" when config is "dark"', () => { + expect(resolveTheme('dark')).toBe('dark'); + }); + + it('should resolve to "light" when config is "system" and prefers-color-scheme is light', () => { + expect(resolveTheme('system')).toBe('light'); + }); + }); +}); diff --git a/src/apps/renderer/hooks/useTheme/theme-helpers.ts b/src/apps/renderer/hooks/useTheme/theme-helpers.ts new file mode 100644 index 0000000000..cfed6ae438 --- /dev/null +++ b/src/apps/renderer/hooks/useTheme/theme-helpers.ts @@ -0,0 +1,17 @@ +import { ConfigTheme, Theme } from '../../../shared/types/Theme'; + +export function applyThemeClass(theme: Theme): void { + if (theme === 'dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } +} + +export function resolveTheme(configTheme: ConfigTheme): Theme { + if (configTheme === 'system') { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + return prefersDark ? 'dark' : 'light'; + } + return configTheme; +} diff --git a/src/apps/renderer/hooks/useTheme/useTheme.tsx b/src/apps/renderer/hooks/useTheme/useTheme.tsx new file mode 100644 index 0000000000..d18df49bc4 --- /dev/null +++ b/src/apps/renderer/hooks/useTheme/useTheme.tsx @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; +import { ConfigTheme, Theme, ThemeData } from '../../../shared/types/Theme'; +import { applyThemeClass, resolveTheme } from './theme-helpers'; + +export function useTheme() { + const [theme, setTheme] = useState(() => { + return document.documentElement.classList.contains('dark') ? 'dark' : 'light'; + }); + + function updateTheme(newTheme: Theme) { + setTheme(newTheme); + applyThemeClass(newTheme); + } + + useEffect(() => { + window.electron.getConfigKey('preferedTheme').then((value) => { + updateTheme(resolveTheme(value ?? 'system')); + }); + + const unsubscribe = window.electron.listenToConfigKeyChange('preferedTheme', (value) => { + if (typeof value === 'object' && value !== null && 'theme' in value) { + updateTheme(value.theme); + } else { + updateTheme(resolveTheme((value as ConfigTheme) ?? 'system')); + } + }); + + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleSystemChange = () => { + window.electron.getConfigKey('preferedTheme').then((value) => { + if (!value || value === 'system') { + updateTheme(mediaQuery.matches ? 'dark' : 'light'); + } + }); + }; + mediaQuery.addEventListener('change', handleSystemChange); + + return () => { + unsubscribe(); + mediaQuery.removeEventListener('change', handleSystemChange); + }; + }, []); + + return { theme }; +} diff --git a/src/apps/renderer/localize/locales/en.json b/src/apps/renderer/localize/locales/en.json index b4275fb3dd..956ee6c45d 100644 --- a/src/apps/renderer/localize/locales/en.json +++ b/src/apps/renderer/localize/locales/en.json @@ -34,54 +34,28 @@ "onboarding": { "slides": { "welcome": { - "title": "Welcome to Internxt Drive!", - "description": "Let's start by backing up all of your important files and folders in your finder's sidebar.", + "title": "Internxt Desktop", + "description": "Welcome to Internxt!\n\nBack up your files with Drive, secure against malware with Antivirus, and optimize performance with Cleaner — all while keeping your privacy protected.", "take-tour": "Take a tour" }, - "files-organization": { - "title": "Access and organize all of your stored files from the sidebar", - "description": "Your personal Internxt Drive folder is located in your {{platform_app}} sidebar" - }, - "available-online": { - "title": "Save space on your device with smart download", - "description": "Store non-essential files as online only to save local drive space", - "description-2": "By saving as online only, files are automatically removed from your device and uploaded to the cloud" - }, - "available-offline": { - "title": "Sync across devices and access files offline", - "description": "Store essential files as available offline to always have them on hand.", - "description-2": "By saving as available offline, files are stored locally and automatically sync across your devices." - }, - "context-menu": { - "title": "Organize your files with just one click", - "description": "Simply right-click one or more files and select how you want them stored.", - "list-1": "Label files available offline to access them anytime, with or without internet", - "list-2": "Label files online only to save physical hard drive space" - }, - "antivirus": { - "title": "Keep your computer safe from virus", - "description": "Internxt's Desktop app now also features the brand new Internxt Antivirus, with which you can scan and remove any potential malware from your device." + "drive": { + "title": "Drive", + "description": "Access all your files from the Internxt Drive folder in your {{platform_app}} sidebar.\n\nChoose to save space with online-only files, or keep essentials available offline — everything stays secure and in sync across your devices." }, "backups": { - "title": "Back up and secure your files with couple clicks", - "description": "Back up your files by simply selecting the folders you want to protect.", - "description-2": "Schedule backups and keep your files safe and up-to-date across your devices.", - "setup-backups": "Set up backups" + "title": "Backup", + "description": "With Internxt's upgraded backup feature, you can now safely backup folders on the cloud in order to free up space locally. You can also adjust the backup frequency as you need." }, "antivirus": { - "title": "Keep your computer safe from virus", - "description": "Internxt's Desktop app now also features the brand new Internxt Antivirus, with which you can scan and remove any potential malware from your device." + "title": "Antivirus", + "description": "Protect your device from malware and online threats.\n\nInternxt Antivirus keeps you safe with real-time scans and privacy-first security." }, "cleaner": { - "title": "Free up space locally and optimize your device's performance.", - "description": "Our cleaner finds and removes unnecessary files, so your device runs smoothly." + "title": "Cleaner", + "description": "Free up space locally and optimize your device’s performance.\n\nOur cleaner finds and removes unnecessary files, so your device runs smoothly." }, "onboarding-completed": { "title": "You're all set, enjoy your privacy!", - "backups-completed": { - "title": "{{folders}} folders added to backups", - "description": "Add more folders and schedule automatic backups in Settings" - }, "desktop-ready": { "title": "Internxt Drive is ready", "description": "Access your stored files from your {{platform_phrase}}’s sidebar" @@ -93,6 +67,7 @@ "continue": "Continue", "skip": "Skip", "open-drive": "Open Internxt Drive", + "new": "New", "platform-phrase": { "windows": "file explorer", "linux": "file browser", diff --git a/src/apps/renderer/localize/locales/es.json b/src/apps/renderer/localize/locales/es.json index 02f3924545..8e2a088653 100644 --- a/src/apps/renderer/localize/locales/es.json +++ b/src/apps/renderer/localize/locales/es.json @@ -34,57 +34,30 @@ "onboarding": { "slides": { "welcome": { - "title": "¡Bienvenido a Internxt Drive!", - "description": "Empieza haciendo una copia de seguridad de todos tus archivos y carpetas importantes en la barra lateral del buscador.", + "title": "Internxt Desktop", + "description": "¡Bienvenido a Internxt!\n\nHaz copias de seguridad de tus archivos con Drive, protégelos contra malware con Antivirus y optimiza el rendimiento con Cleaner — todo mientras mantienes tu privacidad protegida.", "take-tour": "Hacer recorrido" }, - "files-organization": { - "title": "Accede y organiza todos tus archivos almacenados desde la barra lateral", - "description": "Tu carpeta personal de Internxt Drive se encuentra en tu barra lateral de {{platform_app}}." - }, - "available-online": { - "title": "Ahorra espacio en tu dispositivo con la descarga inteligente", - "description": "Guarda los archivos no esenciales como solo online para ahorrar espacio en la unidad local.", - "description-2": "Al guardarlos de esta forma, los archivos se eliminan automáticamente de tu dispositivo y se suben a la nube." - }, - "available-offline": { - "title": "Sincronización entre dispositivos y acceso a archivos sin conexión", - "description": "Almacena tus archivos esenciales como disponibles sin conexión para tenerlos siempre a mano.", - "description-2": "Al guardar como disponibles sin conexión, los archivos se almacenan localmente y se sincronizan automáticamente en todos tus dispositivos." - }, - "context-menu": { - "title": "Organiza tus archivos con un solo clic", - "description": "Basta con hacer clic con el botón derecho en uno o varios archivos y seleccionar cómo deseas almacenarlos.", - "list-1": "Etiqueta los archivos disponibles sin conexión para acceder a ellos en cualquier momento, con o sin internet", - "list-2": "Etiqueta los archivos sólo en línea para ahorrar espacio físico en el disco duro" + "drive": { + "title": "Drive", + "description": "Accede a todos tus archivos desde la carpeta de Internxt Drive en la barra lateral de {{platform_app}}.\n\nElige ahorrar espacio con archivos disponibles solo en línea o mantener los esenciales sin conexión — todo permanece seguro y sincronizado en todos tus dispositivos." }, "antivirus": { - "title": "Mantén tu ordenador protegido contra virus", - "description": "La aplicación de escritorio de Internxt ahora también cuenta con el nuevo Internxt Antivirus, con el que puedes escanear y eliminar cualquier posible malware de tu dispositivo." + "title": "Antivirus", + "description": "Protege tu dispositivo contra el malware y las amenazas en línea.\n\nInternxt Antivirus te mantiene seguro con análisis en tiempo real y una seguridad que prioriza tu privacidad." }, "backups": { - "title": "Haz copias de seguridad y protege tus archivos con un par de clics", - "description": "Realiza copias de seguridad de tus archivos con solo seleccionar las carpetas que desea proteger.", - "description-2": "Programa tus copias de seguridad y mantén tus archivos seguros y actualizados en todos tus dispositivos.", - "setup-backups": "Configurar" - }, - "antivirus": { - "title": "Protege tus archivos con un antivirus", - "description": "Protege tus archivos con un antivirus para evitar cualquier amenaza de virus o malware.", - "description-2": "El antivirus de Internxt Drive te permite proteger tus archivos y carpetas contra cualquier amenaza de virus o malware." + "title": "Backup", + "description": "Con la función de copia de seguridad mejorada de Internxt, ahora puedes hacer copias seguras de tus carpetas en la nube para liberar espacio localmente.\n\nTambién puedes ajustar la frecuencia de las copias de seguridad según tus necesidades." }, "cleaner": { - "title": "Libera espacio localmente y optimiza el rendimiento de tu dispositivo.", - "description": "Nuestro Cleaner encuentra y elimina archivos innecesarios para que tu dispositivo funcione sin problemas." + "title": "Cleaner", + "description": "Libera espacio localmente y optimiza el rendimiento de tu dispositivo.\n\nNuestro limpiador detecta y elimina archivos innecesarios para que tu dispositivo funcione sin problemas." }, "onboarding-completed": { "title": "Ya está todo listo, ¡disfruta de tu privacidad!", - "backups-completed": { - "title": "{{folders}} carpetas añadidas a las copias de seguridad", - "description": "Añade más carpetas y programa copias de seguridad automáticas en Configuración" - }, "desktop-ready": { - "title": "Internxt Drive está listo", + "title": "Internxt está listo", "description": "Accede a tus archivos almacenados desde la barra lateral de tu {{platform_phrase}}." } } @@ -94,6 +67,7 @@ "continue": "Continuar", "open-drive": "Abrir Internxt Drive", "skip": "Saltar", + "new": "Nuevo", "platform-phrase": { "windows": "explorador de archivos", "linux": "buscador de archivos", diff --git a/src/apps/renderer/localize/locales/fr.json b/src/apps/renderer/localize/locales/fr.json index 61a6b621c7..50f9b3ab57 100644 --- a/src/apps/renderer/localize/locales/fr.json +++ b/src/apps/renderer/localize/locales/fr.json @@ -34,57 +34,30 @@ "onboarding": { "slides": { "welcome": { - "title": "Bienvenue dans Internxt Drive !", - "description": "Commençons par sauvegarder tous vos fichiers et dossiers importants dans la barre latérale de votre finder.", + "title": "Internxt Desktop", + "description": "Bienvenue dans Internxt!\n\nSauvegardez vos fichiers avec Drive, protégez-les contre les logiciels malveillants avec Antivirus et optimisez les performances avec Cleaner — tout en préservant votre vie privée.", "take-tour": "Visite guidée" }, - "files-organization": { - "title": "Accédez à tous vos fichiers sauvegardés et organisez-les à partir de la barre latérale", - "description": "Votre dossier personnel Internxt Drive est situé dans votre barre latérale {{platform_app}}." - }, - "available-online": { - "title": "Économisez de l'espace sur votre appareil grâce au téléchargement intelligent", - "description": "Stockez les fichiers non essentiels comme uniquement en ligne pour économiser de l'espace sur le disque local.", - "description-2": "En enregistrant les fichiers uniquement en ligne, ils sont automatiquement supprimés de votre appareil et téléchargés sur le cloud." - }, - "available-offline": { - "title": "Synchronisation entre appareils et accès aux fichiers hors ligne", - "description": "Stockez les fichiers essentiels en tant que disponibles hors ligne pour les avoir toujours à portée de main.", - "description-2": "En enregistrant les fichiers comme étant disponibles hors ligne, ils sont stockés localement et automatiquement synchronisés sur tous vos appareils." - }, - "context-menu": { - "title": "Organisez vos fichiers en un seul clic", - "description": "Cliquez simplement avec le bouton droit sur un ou plusieurs fichiers et sélectionnez la manière dont vous souhaitez les stocker.", - "list-1": "Identifiez les fichiers disponibles hors ligne pour y accéder à tout moment, avec ou sans internet", - "list-2": "Étiqueter les fichiers uniquement en ligne pour économiser de l'espace sur le disque dur" + "drive": { + "title": "Drive", + "description": "Accédez à tous vos fichiers depuis le dossier Internxt Drive dans la barre latérale de {{platform_app}}.\n\nChoisissez d’économiser de l’espace avec des fichiers disponibles uniquement en ligne, ou gardez l’essentiel accessible hors ligne — tout reste sécurisé et synchronisé sur tous vos appareils." }, "antivirus": { - "title": "Protégez votre ordinateur contre les virus", - "description": "L'application de bureau d'Internxt intègre désormais le tout nouveau Internxt Antivirus, qui vous permet d'analyser et de supprimer tout malware potentiel de votre appareil." + "title": "Antivirus", + "description": "Protégez votre appareil contre les logiciels malveillants et les menaces en ligne.\n\nInternxt Antivirus vous protège grâce à des analyses en temps réel et une sécurité axée sur la confidentialité." }, "backups": { - "title": "Sauvegardez et sécurisez vos fichiers en quelques clics", - "description": "Sauvegardez vos fichiers en sélectionnant simplement les dossiers que vous souhaitez protéger.", - "description-2": "Planifiez des sauvegardes et gardez vos fichiers en sécurité et à jour sur tous vos appareils.", - "setup-backups": "Configurer les sauvegardes" - }, - "antivirus": { - "title": "Protégez vos fichiers avec un antivirus", - "description": "Protégez vos fichiers avec un antivirus pour éviter toute menace de virus ou de malware.", - "description-2": "L'antivirus d'Internxt Drive vous permet de protéger vos fichiers et dossiers contre toute menace de virus ou de malware." + "title": "Backup", + "description": "Avec la fonction de sauvegarde améliorée d’Internxt, vous pouvez désormais sauvegarder vos dossiers en toute sécurité sur le cloud afin de libérer de l’espace localement.\n\nVous pouvez également ajuster la fréquence des sauvegardes selon vos besoins." }, "cleaner": { - "title": "Libérez de l'espace localement et optimisez les performances de votre appareil.", - "description": "Notre nettoyeur trouve et supprime les fichiers inutiles afin que votre appareil fonctionne correctement." + "title": "Cleaner", + "description": "Libérez de l’espace localement et optimisez les performances de votre appareil.\n\nNotre nettoyeur détecte et supprime les fichiers inutiles afin que votre appareil fonctionne sans encombre." }, "onboarding-completed": { "title": "Vous êtes prêt, profitez de votre vie privée !", - "backups-completed": { - "title": "{{folders}} dossiers ajoutés aux sauvegardes", - "description": "Ajoutez d'autres dossiers et planifiez des sauvegardes automatiques dans les Paramètres" - }, "desktop-ready": { - "title": "Internxt Drive est prêt", + "title": "Internxt est prêt", "description": "Accédez à vos fichiers stockés depuis la barre latérale de votre {{platform_phrase}}." } } @@ -94,6 +67,7 @@ "open-drive": "Ouvrir Internxt Drive", "continue": "Continuer", "skip": "Sauter", + "new": "Nouveau", "platform-phrase": { "windows": "navigateur de fichiers", "linux": "navigateur de fichiers", diff --git a/src/apps/renderer/pages/Onboarding/config.test.tsx b/src/apps/renderer/pages/Onboarding/config.test.tsx index fc6a7d6b90..48379b43c6 100644 --- a/src/apps/renderer/pages/Onboarding/config.test.tsx +++ b/src/apps/renderer/pages/Onboarding/config.test.tsx @@ -1,4 +1,4 @@ -import { SLIDES, getOnboardingSlideByName } from './config'; +import { SLIDES } from './config'; // Mock the translation context vi.mock('../../context/LocalContext', () => ({ @@ -14,10 +14,8 @@ describe('Onboarding Config', () => { const slideNames = SLIDES.map((slide) => slide.name); expect(slideNames).toEqual([ 'Welcome Slide', - 'Files Organization', - 'Available for Online usage Slide', - 'Available for Offline usage Slide', - 'Context Menu Slide', + 'Drive Slide', + 'Backups Slide', 'Antivirus Slide', 'Cleaner Slide', 'Onboarding Completed', @@ -33,17 +31,4 @@ describe('Onboarding Config', () => { }); }); }); - - describe('getOnboardingSlideByName', () => { - it('returns correct slide for valid name', () => { - const slide = getOnboardingSlideByName('Antivirus Slide'); - expect(slide).toBeDefined(); - expect(slide?.name).toBe('Antivirus Slide'); - }); - - it('returns undefined for invalid slide name', () => { - const slide = getOnboardingSlideByName('Invalid Slide'); - expect(slide).toBeUndefined(); - }); - }); }); diff --git a/src/apps/renderer/pages/Onboarding/config.tsx b/src/apps/renderer/pages/Onboarding/config.tsx index d4790dc8dd..8bfb059ebb 100644 --- a/src/apps/renderer/pages/Onboarding/config.tsx +++ b/src/apps/renderer/pages/Onboarding/config.tsx @@ -1,29 +1,23 @@ -// Slides -import { AvailableOnlineSlide } from './slides/AvailableOnlineSlide'; -import { AvailableOfflineSlide } from './slides/AvailableOfflineSlide'; -import { ContextMenuSlide } from './slides/ContextMenuSlide'; -// import { BackupsSlide } from './slides/BackupsSlide'; -import { WelcomeSlide } from './slides/WelcomeSlide'; -import { FilesOrganizationSlide } from './slides/FilesOrganizationSlide'; +import { OnboardingSlide, SideImageAnimation, SideTextAnimation } from './helpers'; +import { OnboardingCompletedSlide } from './slides/onboarding-completed-slide'; +import { BackupsSlide } from './slides/backups-slide'; +import { AntivirusSlide } from './slides/antivirus-slide'; +import { WelcomeSlide } from './slides/welcome-slide'; +import { DriveSlide } from './slides/drive-slide'; +import { CleanerSlide } from './slides/cleaner-slide'; +import Button from '../../components/Button'; +import { useTheme } from '../../hooks/useTheme'; +import { useLanguage } from '../../hooks/useLanguage'; +import { useTranslationContext } from '../../context/LocalContext'; import { - // BackupsSVG, - OnboardingSlide, - SideImageAnimation, - SideTextAnimation, + getDriveImageSvg, getAntivirusImageSvg, getCleanerImageSvg, - getFinderImage, - getOfflineImageSvg, - getOnlineImageSvg, -} from './helpers'; + getLinuxFileExplorerImage, + getBackupsImageSvg, +} from './image-helpers'; -import ContextMenuSvg from '../../assets/onboarding/context-menu.svg'; -import { OnboardingCompletedSlide } from './slides/OnboardingCompletedSlide'; -import Button from '../../components/Button'; -import { useTranslationContext } from '../../context/LocalContext'; -import { AntivirusSlide } from './slides/AntivirusSlide'; -import { CleanerSlide } from './slides/CleanerSlide'; export const SLIDES: OnboardingSlide[] = [ { name: 'Welcome Slide', @@ -43,103 +37,24 @@ export const SLIDES: OnboardingSlide[] = [ - ); }, - image: (props) => { - const FinderImage = getFinderImage(props.platform); - return ( -
- -
- ); - }, - }, - { - name: 'Files Organization', - component: (props) => { - return ( -
- - - -
- ); - }, - footer: (props) => { - const { translate } = useTranslationContext(); - return ( -
- - - {translate('onboarding.common.onboarding-progress', { - current_slide: props.currentSlide, - total_slides: props.totalSlides, - })} - -
- ); - }, - image: (props) => { - const FinderImage = getFinderImage(props.platform); - return ( -
- -
- ); - }, - }, - { - name: 'Available for Online usage Slide', - component: (props) => { - return ( -
- - - -
- ); - }, - footer: (props) => { - const { translate } = useTranslationContext(); - return ( -
- - - {translate('onboarding.common.onboarding-progress', { - current_slide: props.currentSlide, - total_slides: props.totalSlides, - })} - -
- ); - }, image: () => { - const { language } = useTranslationContext(); - const Image = getOnlineImageSvg(language); - return ( -
- - - -
- ); + const { theme } = useTheme(); + return
{getLinuxFileExplorerImage(theme)}
; }, }, { - name: 'Available for Offline usage Slide', + name: 'Drive Slide', component: (props) => { return (
- +
); @@ -161,25 +76,28 @@ export const SLIDES: OnboardingSlide[] = [ ); }, image: () => { - const { language } = useTranslationContext(); - const Image = getOfflineImageSvg(language); + const DriveImage = () => { + const language = useLanguage(); + const { theme } = useTheme(); + const DriveImage = getDriveImageSvg(language, theme); + if (!DriveImage) return null; + + return ; + }; return ( -
- - - +
+
); }, }, - { - name: 'Context Menu Slide', + name: 'Backups Slide', component: (props) => { return (
- +
); @@ -201,13 +119,8 @@ export const SLIDES: OnboardingSlide[] = [ ); }, image: () => { - return ( -
- - - -
- ); + const { theme } = useTheme(); + return
{getBackupsImageSvg(theme)}
; }, }, { @@ -238,24 +151,29 @@ export const SLIDES: OnboardingSlide[] = [ ); }, image: () => { - const { language } = useTranslationContext(); - const Image = getAntivirusImageSvg(language); + const AntivirusImage = () => { + const language = useLanguage(); + const { theme } = useTheme(); + const AntivirusImage = getAntivirusImageSvg(language, theme); + if (!AntivirusImage) return null; + + return ; + }; + return ( -
- - - +
+
); }, }, { name: 'Cleaner Slide', - component: (props) => { + component: () => { return (
- +
); @@ -277,12 +195,19 @@ export const SLIDES: OnboardingSlide[] = [ ); }, image: () => { - const { language } = useTranslationContext(); - const Image = getCleanerImageSvg(language); + const CleanerImage = () => { + const language = useLanguage(); + const { theme } = useTheme(); + const CleanerImage = getCleanerImageSvg(language, theme); + if (!CleanerImage) return null; + + return ; + }; + return (
- +
); @@ -309,19 +234,13 @@ export const SLIDES: OnboardingSlide[] = [
); }, - image: (props) => { - const Image = getFinderImage(props.platform); + image: () => { + const { theme } = useTheme(); return ( -
- - - +
+ {getLinuxFileExplorerImage(theme)}
); }, }, ]; - -export const getOnboardingSlideByName = (name: string) => { - return SLIDES.find((slide) => slide.name === name); -}; diff --git a/src/apps/renderer/pages/Onboarding/helpers.tsx b/src/apps/renderer/pages/Onboarding/helpers.tsx index 8b7566a2d2..6bf237459d 100644 --- a/src/apps/renderer/pages/Onboarding/helpers.tsx +++ b/src/apps/renderer/pages/Onboarding/helpers.tsx @@ -1,27 +1,4 @@ import { Transition } from '@headlessui/react'; -// Available Online Slide images -import AvailableOnlineImageSpanish from '../../assets/onboarding/folder-with-overlay-icons/online/es.svg'; -import AvailableOnlineImageEnglish from '../../assets/onboarding/folder-with-overlay-icons/online/en.svg'; -import AvailableOnlineImageFrench from '../../assets/onboarding/folder-with-overlay-icons/online/fr.svg'; - -// Available Offline Slide images -import AvailableOfflineImageSpanish from '../../assets/onboarding/folder-with-overlay-icons/offline/es.svg'; -import AvailableOfflineImageEnglish from '../../assets/onboarding/folder-with-overlay-icons/offline/en.svg'; -import AvailableOfflineImageFrench from '../../assets/onboarding/folder-with-overlay-icons/offline/fr.svg'; - -// Finder images -import LinuxFinderImage from '../../assets/onboarding/finder/linux.svg'; - -// Antivirus images -import AntivirusImageSpanish from '../../assets/onboarding/antivirus/es.svg'; -import AntivirusImageEnglish from '../../assets/onboarding/antivirus/en.svg'; -import AntivirusImageFrench from '../../assets/onboarding/antivirus/fr.svg'; - -// Cleaner images -import CleanerImageSpanish from '../../assets/onboarding/cleaner/es.svg'; -import CleanerImageEnglish from '../../assets/onboarding/cleaner/en.svg'; -import CleanerImageFrench from '../../assets/onboarding/cleaner/fr.svg'; - import { BackupFolder } from '../../components/Backups/BackupsFoldersSelector'; export type OnboardingSlideProps = { @@ -35,50 +12,6 @@ export type OnboardingSlideProps = { platform: string; }; -export const getOnlineImageSvg = (language: string) => { - if (language === 'es') return AvailableOnlineImageSpanish; - if (language === 'fr') return AvailableOnlineImageFrench; - return AvailableOnlineImageEnglish; -}; - -export const getAntivirusImageSvg = (language: string) => { - if (language === 'es') return AntivirusImageSpanish; - if (language === 'fr') return AntivirusImageFrench; - return AntivirusImageEnglish; -}; - -export const getCleanerImageSvg = (language: string) => { - if (language === 'es') return CleanerImageSpanish; - if (language === 'fr') return CleanerImageFrench; - return CleanerImageEnglish; -}; - -export const getOfflineImageSvg = (language: string) => { - if (language === 'es') return AvailableOfflineImageSpanish; - if (language === 'fr') return AvailableOfflineImageFrench; - return AvailableOfflineImageEnglish; -}; - -export const getFinderImage = (_platform: string) => { - return LinuxFinderImage; -}; - -export const getPlatformName = (platform: string) => { - if (platform === 'win32') return 'Windows'; - if (platform === 'linux') return 'Linux'; - if (platform === 'darwin') return 'MacOS'; - - return ''; -}; - -export const getPlatformPhraseTranslationKey = (platform: string) => { - if (platform === 'win32') return 'onboarding.common.platform-phrase.windows'; - if (platform === 'linux') return 'onboarding.common.platform-phrase.linux'; - if (platform === 'darwin') return 'onboarding.common.platform-phrase.macos'; - - return 'onboarding.common.platform-phrase.macos'; -}; - export type OnboardingSlide = { name: string; component: React.FC; diff --git a/src/apps/renderer/pages/Onboarding/image-helpers.tsx b/src/apps/renderer/pages/Onboarding/image-helpers.tsx new file mode 100644 index 0000000000..fcc6b3a736 --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/image-helpers.tsx @@ -0,0 +1,101 @@ +import { OnboardingImages } from '../types'; +import { Language } from '../../../shared/Locale/Language'; + +import CleanerLightImageSpanish from '../../assets/onboarding/cleaner/ES-Light.svg'; +import CleanerLightImageFrench from '../../assets/onboarding/cleaner/FR-Light.svg'; +import CleanerLightImageEnglish from '../../assets/onboarding/cleaner/EN-Light.svg'; +import CleanerDarkImageSpanish from '../../assets/onboarding/cleaner/ES-Dark.svg'; +import CleanerDarkImageFrench from '../../assets/onboarding/cleaner/FR-Dark.svg'; +import CleanerDarkImageEnglish from '../../assets/onboarding/cleaner/EN-Dark.svg'; + +import DriveLightImageSpanish from '../../assets/onboarding/drive/ES-Light.svg'; +import DriveLightImageFrench from '../../assets/onboarding/drive/FR-Light.svg'; +import DriveLightImageEnglish from '../../assets/onboarding/drive/EN-Light.svg'; +import DriveDarkImageSpanish from '../../assets/onboarding/drive/ES-Dark.svg'; +import DriveDarkImageFrench from '../../assets/onboarding/drive/FR-Dark.svg'; +import DriveDarkImageEnglish from '../../assets/onboarding/drive/EN-Dark.svg'; + +import AntivirusLightImageSpanish from '../../assets/onboarding/antivirus/ES-Light.svg'; +import AntivirusLightImageFrench from '../../assets/onboarding/antivirus/FR-Light.svg'; +import AntivirusLightImageEnglish from '../../assets/onboarding/antivirus/EN-Light.svg'; +import AntivirusDarkImageSpanish from '../../assets/onboarding/antivirus/ES-Dark.svg'; +import AntivirusDarkImageFrench from '../../assets/onboarding/antivirus/FR-Dark.svg'; +import AntivirusDarkImageEnglish from '../../assets/onboarding/antivirus/EN-Dark.svg'; + +import FileExplorerLightImage from '../../assets/onboarding/finder/linux-ligth.svg'; +import FileExplorerDarkImage from '../../assets/onboarding/finder/linux-dark.svg'; + +import BackupsLightSvg from '../../assets/onboarding/backups/backups-light.svg'; +import BackupsDarkSvg from '../../assets/onboarding/backups/backups-dark.svg'; + +type ClearTheme = 'light' | 'dark'; + +export function getLinuxFileExplorerImage(theme: ClearTheme) { + const FileExplorer = theme === 'light' ? FileExplorerLightImage : FileExplorerDarkImage; + return ; +} + +export const getCleanerImageSvg = (language: Language, theme: ClearTheme) => { + const images: OnboardingImages = { + es: { + light: CleanerLightImageSpanish, + dark: CleanerDarkImageSpanish, + }, + fr: { + light: CleanerLightImageFrench, + dark: CleanerDarkImageFrench, + }, + en: { + light: CleanerLightImageEnglish, + dark: CleanerDarkImageEnglish, + }, + }; + + const lang = images[language] || images.en; + return lang[theme]; +}; + +export const getDriveImageSvg = (language: Language, theme: ClearTheme) => { + const images: OnboardingImages = { + es: { + light: DriveLightImageSpanish, + dark: DriveDarkImageSpanish, + }, + fr: { + light: DriveLightImageFrench, + dark: DriveDarkImageFrench, + }, + en: { + light: DriveLightImageEnglish, + dark: DriveDarkImageEnglish, + }, + }; + + const lang = images[language] || images.en; + return lang[theme]; +}; + +export const getAntivirusImageSvg = (language: Language, theme: ClearTheme) => { + const images: OnboardingImages = { + es: { + light: AntivirusLightImageSpanish, + dark: AntivirusDarkImageSpanish, + }, + fr: { + light: AntivirusLightImageFrench, + dark: AntivirusDarkImageFrench, + }, + en: { + light: AntivirusLightImageEnglish, + dark: AntivirusDarkImageEnglish, + }, + }; + + const lang = images[language] || images.en; + return lang[theme]; +}; + +export const getBackupsImageSvg = (theme: ClearTheme) => { + const BackupsImage = theme === 'light' ? BackupsLightSvg : BackupsDarkSvg; + return ; +}; diff --git a/src/apps/renderer/pages/Onboarding/index.test.tsx b/src/apps/renderer/pages/Onboarding/index.test.tsx index 1ceae50bcb..f0bf7c72c2 100644 --- a/src/apps/renderer/pages/Onboarding/index.test.tsx +++ b/src/apps/renderer/pages/Onboarding/index.test.tsx @@ -17,7 +17,8 @@ vi.mock('../../context/LocalContext', () => ({ describe('Onboarding', () => { beforeEach(() => { - vi.clearAllMocks(); + (window.electron.getConfigKey as ReturnType).mockResolvedValue(undefined); + (window.electron.listenToConfigKeyChange as ReturnType).mockReturnValue(() => {}); }); it('renders the first slide (Welcome) by default', () => { @@ -31,8 +32,8 @@ describe('Onboarding', () => { // Click the "Take Tour" button on Welcome slide fireEvent.click(screen.getByText('onboarding.slides.welcome.take-tour')); - // Should show the Files Organization slide - expect(screen.getByText('onboarding.slides.files-organization.title')).toBeInTheDocument(); + // Should show the Drive slide + expect(screen.getByText('onboarding.slides.drive.title')).toBeInTheDocument(); }); it('finishes onboarding when clicking skip', () => { diff --git a/src/apps/renderer/pages/Onboarding/slides/AntivirusSlide.test.tsx b/src/apps/renderer/pages/Onboarding/slides/AntivirusSlide.test.tsx deleted file mode 100644 index 0534475a46..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/AntivirusSlide.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import { AntivirusSlide } from './AntivirusSlide'; - -vi.mock('../../../context/LocalContext', () => ({ - useTranslationContext: () => ({ - translate: (key: string) => key, - language: 'en', - }), - LocalizationProvider: ({ children }: { children: React.ReactNode }) => <>{children}, -})); - -describe('AntivirusSlide', () => { - const mockProps = { - onGoNextSlide: vi.fn(), - onSkipOnboarding: vi.fn(), - onSetupBackups: vi.fn(), - onFinish: vi.fn(), - platform: 'windows', - currentSlide: 1, - totalSlides: 7, - backupFolders: [], - }; - - it('renders with correct structure', () => { - render(); - - // Check if title and description are rendered with correct translation keys - const title = screen.getByText('onboarding.slides.antivirus.title'); - const description = screen.getByText('onboarding.slides.antivirus.description'); - - expect(title).toBeInTheDocument(); - expect(title).toHaveClass('text-3xl', 'font-semibold', 'text-gray-100'); - - expect(description).toBeInTheDocument(); - expect(description).toHaveClass('text-lg', 'text-gray-100'); - }); -}); diff --git a/src/apps/renderer/pages/Onboarding/slides/AntivirusSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/AntivirusSlide.tsx deleted file mode 100644 index 75c7d4da75..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/AntivirusSlide.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { OnboardingSlideProps } from '../helpers'; -import { useTranslationContext } from '../../../context/LocalContext'; - -export type AntivirusSlideProps = OnboardingSlideProps; - -export const AntivirusSlide: React.FC = () => { - const { translate } = useTranslationContext(); - - return ( -
-

{translate('onboarding.slides.antivirus.title')}

-

- {translate('onboarding.slides.antivirus.description')} -

-
- ); -}; diff --git a/src/apps/renderer/pages/Onboarding/slides/AvailableOfflineSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/AvailableOfflineSlide.tsx deleted file mode 100644 index edab94808b..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/AvailableOfflineSlide.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { useTranslationContext } from '../../../context/LocalContext'; - -export const AvailableOfflineSlide: React.FC = () => { - const { translate } = useTranslationContext(); - - return ( -
-

- {translate('onboarding.slides.available-offline.title')} -

-

- {translate('onboarding.slides.available-offline.description')} -

-

- {translate('onboarding.slides.available-offline.description-2')} -

-
- ); -}; diff --git a/src/apps/renderer/pages/Onboarding/slides/AvailableOnlineSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/AvailableOnlineSlide.tsx deleted file mode 100644 index d91c5ba6dc..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/AvailableOnlineSlide.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { useTranslationContext } from '../../../context/LocalContext'; - -export const AvailableOnlineSlide: React.FC = () => { - const { translate } = useTranslationContext(); - - return ( -
-

- {translate('onboarding.slides.available-online.title')} -

-

- {translate('onboarding.slides.available-online.description')} -

-

- {translate('onboarding.slides.available-online.description-2')} -

-
- ); -}; diff --git a/src/apps/renderer/pages/Onboarding/slides/CleanerSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/CleanerSlide.tsx deleted file mode 100644 index d771bbd944..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/CleanerSlide.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useTranslationContext } from '../../../../renderer/context/LocalContext'; -import { OnboardingSlideProps } from '../helpers'; - -export function CleanerSlide(props: OnboardingSlideProps) { - const { translate } = useTranslationContext(); - - return ( -
-

{translate('onboarding.slides.cleaner.title')}

-

- {translate('onboarding.slides.cleaner.description')} -

-
- ); -} diff --git a/src/apps/renderer/pages/Onboarding/slides/ContextMenuSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/ContextMenuSlide.tsx deleted file mode 100644 index 20f890ff54..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/ContextMenuSlide.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { useTranslationContext } from '../../../context/LocalContext'; - -export const ContextMenuSlide: React.FC = () => { - const { translate } = useTranslationContext(); - - return ( -
-

{translate('onboarding.slides.context-menu.title')}

-

- {translate('onboarding.slides.context-menu.description')} -

-
    -
  • - {translate('onboarding.slides.context-menu.list-1')} -
  • -
  • - {translate('onboarding.slides.context-menu.list-2')} -
  • -
-
- ); -}; diff --git a/src/apps/renderer/pages/Onboarding/slides/FilesOrganizationSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/FilesOrganizationSlide.tsx deleted file mode 100644 index f0625ed0bb..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/FilesOrganizationSlide.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { OnboardingSlideProps, getPlatformName } from '../helpers'; -import { useTranslationContext } from '../../../context/LocalContext'; - -export type FilesOrganizationSlideProps = OnboardingSlideProps; - -export const FilesOrganizationSlide: React.FC = (props) => { - const { translate } = useTranslationContext(); - - return ( -
-

- {translate('onboarding.slides.files-organization.title')} -

-

- {translate('onboarding.slides.files-organization.description', { - platform_app: getPlatformName(props.platform), - })} -

-
- ); -}; diff --git a/src/apps/renderer/pages/Onboarding/slides/OnboardingCompletedSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/OnboardingCompletedSlide.tsx deleted file mode 100644 index 125d69c4d5..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/OnboardingCompletedSlide.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { CheckCircle } from 'phosphor-react'; -import React from 'react'; -import { OnboardingSlideProps, getPlatformPhraseTranslationKey } from '../helpers'; -import { useTranslationContext } from '../../../context/LocalContext'; - -export type OnboardingCompletedSlideProps = OnboardingSlideProps; - -export const OnboardingCompletedSlide: React.FC = (props) => { - const { translate } = useTranslationContext(); - - return ( -
-

- {translate('onboarding.slides.onboarding-completed.title')} -

- {props.backupFolders.length ? ( - <> -
-
- -
-
-

- {translate('onboarding.slides.onboarding-completed.backups-completed.title', { - folders: props.backupFolders.length, - })} -

-

- {translate('onboarding.slides.onboarding-completed.backups-completed.description')} -

-
-
- - ) : null} -
-
- -
-
-

- {translate('onboarding.slides.onboarding-completed.desktop-ready.title')} -

-

- {translate('onboarding.slides.onboarding-completed.desktop-ready.description', { - platform_phrase: translate(getPlatformPhraseTranslationKey(props.platform)), - })} -

-
-
-
- ); -}; diff --git a/src/apps/renderer/pages/Onboarding/slides/WelcomeSlide.tsx b/src/apps/renderer/pages/Onboarding/slides/WelcomeSlide.tsx deleted file mode 100644 index 68b966b532..0000000000 --- a/src/apps/renderer/pages/Onboarding/slides/WelcomeSlide.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { OnboardingSlideProps } from '../helpers'; -import { useTranslationContext } from '../../../context/LocalContext'; - -export type WelcomeSlideProps = OnboardingSlideProps; - -export const WelcomeSlide: React.FC = () => { - const { translate } = useTranslationContext(); - - return ( -
-

Internxt Desktop

-

- {translate('onboarding.slides.welcome.title')} -

-

- {translate('onboarding.slides.welcome.description')} -

-
- ); -}; diff --git a/src/apps/renderer/pages/Onboarding/slides/antivirus-slide.test.tsx b/src/apps/renderer/pages/Onboarding/slides/antivirus-slide.test.tsx new file mode 100644 index 0000000000..6bb2cb637b --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/antivirus-slide.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from '@testing-library/react'; +import { AntivirusSlide } from './antivirus-slide'; +import { OnboardingSlideProps } from '../helpers'; + +vi.mock('../../../hooks/useTheme', () => ({ + useTheme: () => ({ theme: 'light' }), +})); + +const defaultProps: OnboardingSlideProps = { + onGoNextSlide: vi.fn(), + onSkipOnboarding: vi.fn(), + onSetupBackups: vi.fn(), + onFinish: vi.fn(), + backupFolders: [], + currentSlide: 0, + totalSlides: 6, + platform: 'linux', +}; + +describe('AntivirusSlide', () => { + it('should render the title', () => { + render(); + + expect(screen.getByText('onboarding.slides.antivirus.title')).toBeInTheDocument(); + }); + + it('should render the "new" badge', () => { + render(); + + expect(screen.getByText('onboarding.common.new')).toBeInTheDocument(); + }); + + it('should render the description', () => { + render(); + + expect(screen.getByText('onboarding.slides.antivirus.description')).toBeInTheDocument(); + }); +}); diff --git a/src/apps/renderer/pages/Onboarding/slides/antivirus-slide.tsx b/src/apps/renderer/pages/Onboarding/slides/antivirus-slide.tsx new file mode 100644 index 0000000000..24582105e8 --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/antivirus-slide.tsx @@ -0,0 +1,23 @@ +import { OnboardingSlideProps } from '../helpers'; +import { useTheme } from '../../../hooks/useTheme'; +import { useTranslationContext } from '../../../context/LocalContext'; + +export const AntivirusSlide: React.FC = () => { + const { translate } = useTranslationContext(); + const { theme } = useTheme(); + + return ( +
+
+

{translate('onboarding.slides.antivirus.title')}

+ + {translate('onboarding.common.new')} + +
+

+ {translate('onboarding.slides.antivirus.description')} +

+
+ ); +}; diff --git a/src/apps/renderer/pages/Onboarding/slides/backups-slide.test.tsx b/src/apps/renderer/pages/Onboarding/slides/backups-slide.test.tsx new file mode 100644 index 0000000000..503617bdcc --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/backups-slide.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/react'; +import { BackupsSlide } from './backups-slide'; +import { OnboardingSlideProps } from '../helpers'; + +vi.mock('../../../hooks/useTheme', () => ({ + useTheme: () => ({ theme: 'light' }), +})); + +const defaultProps: OnboardingSlideProps = { + onGoNextSlide: vi.fn(), + onSkipOnboarding: vi.fn(), + onSetupBackups: vi.fn(), + onFinish: vi.fn(), + backupFolders: [], + currentSlide: 0, + totalSlides: 6, + platform: 'linux', +}; + +describe('BackupsSlide', () => { + it('should render the title', () => { + render(); + + expect(screen.getByText('onboarding.slides.backups.title')).toBeInTheDocument(); + }); + + it('should render the description', () => { + render(); + + expect(screen.getByText('onboarding.slides.backups.description')).toBeInTheDocument(); + }); +}); diff --git a/src/apps/renderer/pages/Onboarding/slides/backups-slide.tsx b/src/apps/renderer/pages/Onboarding/slides/backups-slide.tsx new file mode 100644 index 0000000000..b604843fd1 --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/backups-slide.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { OnboardingSlideProps } from '../helpers'; +import { useTheme } from '../../../hooks/useTheme'; +import { useTranslationContext } from '../../../context/LocalContext'; + +export const BackupsSlide: React.FC = () => { + const { translate } = useTranslationContext(); + const { theme } = useTheme(); + + return ( +
+

{translate('onboarding.slides.backups.title')}

+

+ {translate('onboarding.slides.backups.description')} +

+
+ ); +}; diff --git a/src/apps/renderer/pages/Onboarding/slides/cleaner-slide.test.tsx b/src/apps/renderer/pages/Onboarding/slides/cleaner-slide.test.tsx new file mode 100644 index 0000000000..44ff2ab863 --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/cleaner-slide.test.tsx @@ -0,0 +1,26 @@ +import { render, screen } from '@testing-library/react'; +import { CleanerSlide } from './cleaner-slide'; + +vi.mock('../../../hooks/useTheme', () => ({ + useTheme: () => ({ theme: 'light' }), +})); + +describe('CleanerSlide', () => { + it('should render the title', () => { + render(); + + expect(screen.getByText('onboarding.slides.cleaner.title')).toBeInTheDocument(); + }); + + it('should render the "new" badge', () => { + render(); + + expect(screen.getByText('onboarding.common.new')).toBeInTheDocument(); + }); + + it('should render the description', () => { + render(); + + expect(screen.getByText('onboarding.slides.cleaner.description')).toBeInTheDocument(); + }); +}); diff --git a/src/apps/renderer/pages/Onboarding/slides/cleaner-slide.tsx b/src/apps/renderer/pages/Onboarding/slides/cleaner-slide.tsx new file mode 100644 index 0000000000..93ea3db56f --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/cleaner-slide.tsx @@ -0,0 +1,22 @@ +import { useTheme } from '../../../hooks/useTheme'; +import { useTranslationContext } from '../../../context/LocalContext'; + +export function CleanerSlide() { + const { translate } = useTranslationContext(); + const { theme } = useTheme(); + + return ( +
+
+

{translate('onboarding.slides.cleaner.title')}

+ + {translate('onboarding.common.new')} + +
+

+ {translate('onboarding.slides.cleaner.description')} +

+
+ ); +} diff --git a/src/apps/renderer/pages/Onboarding/slides/drive-slide.test.tsx b/src/apps/renderer/pages/Onboarding/slides/drive-slide.test.tsx new file mode 100644 index 0000000000..a516d704fa --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/drive-slide.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/react'; +import { DriveSlide } from './drive-slide'; +import { OnboardingSlideProps } from '../helpers'; + +vi.mock('../../../hooks/useTheme', () => ({ + useTheme: () => ({ theme: 'light' }), +})); + +const defaultProps: OnboardingSlideProps = { + onGoNextSlide: vi.fn(), + onSkipOnboarding: vi.fn(), + onSetupBackups: vi.fn(), + onFinish: vi.fn(), + backupFolders: [], + currentSlide: 0, + totalSlides: 6, + platform: 'linux', +}; + +describe('DriveSlide', () => { + it('should render the title', () => { + render(); + + expect(screen.getByText('onboarding.slides.drive.title')).toBeInTheDocument(); + }); + + it('should render the description with platform interpolation', () => { + render(); + + expect(screen.getByText('onboarding.slides.drive.description')).toBeInTheDocument(); + }); +}); diff --git a/src/apps/renderer/pages/Onboarding/slides/drive-slide.tsx b/src/apps/renderer/pages/Onboarding/slides/drive-slide.tsx new file mode 100644 index 0000000000..cbea430221 --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/drive-slide.tsx @@ -0,0 +1,20 @@ +import { OnboardingSlideProps } from '../helpers'; +import { useTheme } from '../../../hooks/useTheme'; +import { useTranslationContext } from '../../../context/LocalContext'; + +export const DriveSlide: React.FC = () => { + const { translate } = useTranslationContext(); + const { theme } = useTheme(); + + return ( +
+

{translate('onboarding.slides.drive.title')}

+

+ {translate('onboarding.slides.drive.description', { + platform_app: translate('onboarding.common.platform-phrase.windows'), + })} +

+
+ ); +}; diff --git a/src/apps/renderer/pages/Onboarding/slides/onboarding-completed-slide.test.tsx b/src/apps/renderer/pages/Onboarding/slides/onboarding-completed-slide.test.tsx new file mode 100644 index 0000000000..f8e5ac1a60 --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/onboarding-completed-slide.test.tsx @@ -0,0 +1,45 @@ +import { render, screen } from '@testing-library/react'; +import { OnboardingCompletedSlide } from './onboarding-completed-slide'; +import { OnboardingSlideProps } from '../helpers'; + +vi.mock('../../../hooks/useTheme', () => ({ + useTheme: () => ({ theme: 'light' }), +})); + +const defaultProps: OnboardingSlideProps = { + onGoNextSlide: vi.fn(), + onSkipOnboarding: vi.fn(), + onSetupBackups: vi.fn(), + onFinish: vi.fn(), + backupFolders: [], + currentSlide: 0, + totalSlides: 6, + platform: 'linux', +}; + +describe('OnboardingCompletedSlide', () => { + it('should render the title', () => { + render(); + + expect(screen.getByText('onboarding.slides.onboarding-completed.title')).toBeInTheDocument(); + }); + + it('should render the desktop-ready section title', () => { + render(); + + expect(screen.getByText('onboarding.slides.onboarding-completed.desktop-ready.title')).toBeInTheDocument(); + }); + + it('should render the desktop-ready description', () => { + render(); + + expect(screen.getByText('onboarding.slides.onboarding-completed.desktop-ready.description')).toBeInTheDocument(); + }); + + it('should render the check circle icon', () => { + const { container } = render(); + + const svg = container.querySelector('svg'); + expect(svg).toBeInTheDocument(); + }); +}); diff --git a/src/apps/renderer/pages/Onboarding/slides/onboarding-completed-slide.tsx b/src/apps/renderer/pages/Onboarding/slides/onboarding-completed-slide.tsx new file mode 100644 index 0000000000..e916aea62a --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/onboarding-completed-slide.tsx @@ -0,0 +1,33 @@ +import { CheckCircle } from 'phosphor-react'; +import { OnboardingSlideProps } from '../helpers'; +import { useTheme } from '../../../hooks/useTheme'; +import { useTranslationContext } from '../../../context/LocalContext'; + +export const OnboardingCompletedSlide: React.FC = () => { + const { translate } = useTranslationContext(); + const { theme } = useTheme(); + + return ( +
+

+ {translate('onboarding.slides.onboarding-completed.title')} +

+
+
+ +
+
+

+ {translate('onboarding.slides.onboarding-completed.desktop-ready.title')} +

+

+ {translate('onboarding.slides.onboarding-completed.desktop-ready.description', { + platform_phrase: translate('onboarding.common.platform-phrase.windows'), + })} +

+
+
+
+ ); +}; diff --git a/src/apps/renderer/pages/Onboarding/slides/welcome-slide.test.tsx b/src/apps/renderer/pages/Onboarding/slides/welcome-slide.test.tsx new file mode 100644 index 0000000000..a36b4d210a --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/welcome-slide.test.tsx @@ -0,0 +1,20 @@ +import { render, screen } from '@testing-library/react'; +import { WelcomeSlide } from './welcome-slide'; + +vi.mock('../../../hooks/useTheme', () => ({ + useTheme: () => ({ theme: 'light' }), +})); + +describe('WelcomeSlide', () => { + it('should render the title', () => { + render(); + + expect(screen.getByText('onboarding.slides.welcome.title')).toBeInTheDocument(); + }); + + it('should render the description', () => { + render(); + + expect(screen.getByText('onboarding.slides.welcome.description')).toBeInTheDocument(); + }); +}); diff --git a/src/apps/renderer/pages/Onboarding/slides/welcome-slide.tsx b/src/apps/renderer/pages/Onboarding/slides/welcome-slide.tsx new file mode 100644 index 0000000000..e48a221bd5 --- /dev/null +++ b/src/apps/renderer/pages/Onboarding/slides/welcome-slide.tsx @@ -0,0 +1,18 @@ +import { OnboardingSlideProps } from '../helpers'; +import { useTheme } from '../../../hooks/useTheme'; +import { useTranslationContext } from '../../../context/LocalContext'; + +export const WelcomeSlide: React.FC = () => { + const { translate } = useTranslationContext(); + const { theme } = useTheme(); + + return ( +
+

{translate('onboarding.slides.welcome.title')}

+

+ {translate('onboarding.slides.welcome.description')} +

+
+ ); +}; diff --git a/src/apps/renderer/pages/types.ts b/src/apps/renderer/pages/types.ts new file mode 100644 index 0000000000..2bb71aec0c --- /dev/null +++ b/src/apps/renderer/pages/types.ts @@ -0,0 +1,10 @@ +type OnboardingImagesItem = { + light: React.FC>; + dark: React.FC>; +}; + +export type OnboardingImages = { + es: OnboardingImagesItem; + fr: OnboardingImagesItem; + en: OnboardingImagesItem; +}; diff --git a/src/core/theme/theme.ts b/src/core/theme/theme.ts index 65dea118cd..ec02389628 100644 --- a/src/core/theme/theme.ts +++ b/src/core/theme/theme.ts @@ -6,7 +6,7 @@ import { electronStore } from '../../apps/main/config'; import { Theme, ThemeData } from './theme.types'; export function getTheme(): ThemeData { - const configTheme = electronStore.get('preferedTheme'); + const configTheme = electronStore.get('preferedTheme') ?? 'system'; nativeTheme.themeSource = configTheme; diff --git a/tailwind.config.js b/tailwind.config.js index b15b97d3e2..ec404ebed6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,7 @@ const colors = require('tailwindcss/colors'); module.exports = { - darkMode: 'media', + darkMode: 'class', content: ['./src/**/*.tsx'], theme: { colors: { diff --git a/vitest.setup.renderer.ts b/vitest.setup.renderer.ts index dac69fa918..66118f1691 100644 --- a/vitest.setup.renderer.ts +++ b/vitest.setup.renderer.ts @@ -58,6 +58,21 @@ function createTypedMock(): DeepMock { global.window = global.window || {}; global.window.electron = createTypedMock(); +// Mock window.matchMedia (not available in jsdom) +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}); + // Mock LocalContext vi.mock('./src/apps/renderer/context/LocalContext', () => ({ useTranslationContext: () => ({