aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Ricardo Cino <ricardo@cino.io>2022-06-24 21:25:05 +0200
committerLibravatar Vijay Aravamudhan <vraravam@users.noreply.github.com>2022-06-25 05:50:00 +0530
commit2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd (patch)
tree7c0172945f962609637d03e7de885a254dbec8a4 /src
parentchore: improve todo menu behaviour on fresh install (#359) (diff)
downloadferdium-app-2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd.tar.gz
ferdium-app-2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd.tar.zst
ferdium-app-2d71e61e46394d75d9f52ba1f4c273ed6d3c9cfd.zip
chore: convert the last few stores to typescript
Diffstat (limited to 'src')
-rw-r--r--src/I18n.tsx2
-rw-r--r--src/containers/auth/ImportScreen.tsx12
-rw-r--r--src/containers/auth/LockedScreen.tsx22
-rw-r--r--src/containers/auth/PasswordScreen.tsx10
-rw-r--r--src/electron-util.ts4
-rw-r--r--src/enforce-macos-app-location.ts2
-rw-r--r--src/environment-remote.ts22
-rw-r--r--src/environment.ts16
-rw-r--r--src/routes.tsx5
-rw-r--r--src/stores.types.ts37
-rw-r--r--src/stores/SettingsStore.ts (renamed from src/stores/SettingsStore.js)23
-rw-r--r--src/stores/UIStore.ts (renamed from src/stores/UIStore.js)38
-rw-r--r--src/stores/UserStore.ts (renamed from src/stores/UserStore.js)150
-rw-r--r--src/stores/lib/Store.js54
14 files changed, 186 insertions, 211 deletions
diff --git a/src/I18n.tsx b/src/I18n.tsx
index f5e157d97..1be6ab23d 100644
--- a/src/I18n.tsx
+++ b/src/I18n.tsx
@@ -17,7 +17,7 @@ type Props = {
17}; 17};
18 18
19class I18N extends Component<Props> { 19class I18N extends Component<Props> {
20 componentDidUpdate() { 20 componentDidUpdate(): void {
21 window['ferdium'].menu.rebuild(); 21 window['ferdium'].menu.rebuild();
22 } 22 }
23 23
diff --git a/src/containers/auth/ImportScreen.tsx b/src/containers/auth/ImportScreen.tsx
index 756a2e59c..8d318cb2d 100644
--- a/src/containers/auth/ImportScreen.tsx
+++ b/src/containers/auth/ImportScreen.tsx
@@ -1,17 +1,17 @@
1import { Component } from 'react'; 1import { Component } from 'react';
2import { inject, observer } from 'mobx-react'; 2import { inject, observer } from 'mobx-react';
3import { RouterStore } from 'mobx-react-router'; 3import { RouterStore } from 'mobx-react-router';
4import { UserStore } from 'src/stores.types';
4import Import from '../../components/auth/Import'; 5import Import from '../../components/auth/Import';
5import UserStore from '../../stores/UserStore';
6 6
7interface IProps { 7interface IProps {
8 actions: { 8 actions: {
9 user: UserStore, 9 user: UserStore;
10 }, 10 };
11 stores: { 11 stores: {
12 user: UserStore, 12 user: UserStore;
13 router: RouterStore, 13 router: RouterStore;
14 } 14 };
15} 15}
16 16
17class ImportScreen extends Component<IProps> { 17class ImportScreen extends Component<IProps> {
diff --git a/src/containers/auth/LockedScreen.tsx b/src/containers/auth/LockedScreen.tsx
index e6bb5e8e4..500bff0d6 100644
--- a/src/containers/auth/LockedScreen.tsx
+++ b/src/containers/auth/LockedScreen.tsx
@@ -1,20 +1,20 @@
1import { Component } from 'react'; 1import { Component, ReactElement } from 'react';
2import { inject, observer } from 'mobx-react'; 2import { inject, observer } from 'mobx-react';
3import { SettingsStore } from 'src/stores.types';
3import Locked from '../../components/auth/Locked'; 4import Locked from '../../components/auth/Locked';
4import SettingsStore from '../../stores/SettingsStore';
5 5
6import { hash } from '../../helpers/password-helpers'; 6import { hash } from '../../helpers/password-helpers';
7import UserStore from '../../stores/UserStore'; 7import UserStore from '../../stores/UserStore';
8 8
9interface IProps { 9interface IProps {
10 actions: { 10 actions: {
11 settings: SettingsStore, 11 settings: SettingsStore;
12 }, 12 };
13 stores: { 13 stores: {
14 settings: SettingsStore, 14 settings: SettingsStore;
15 user: UserStore, 15 user: UserStore;
16 } 16 };
17}; 17}
18 18
19class LockedScreen extends Component<IProps> { 19class LockedScreen extends Component<IProps> {
20 state = { 20 state = {
@@ -28,7 +28,7 @@ class LockedScreen extends Component<IProps> {
28 this.unlock = this.unlock.bind(this); 28 this.unlock = this.unlock.bind(this);
29 } 29 }
30 30
31 onSubmit(values) { 31 onSubmit(values): void {
32 const { password } = values; 32 const { password } = values;
33 33
34 let correctPassword = this.props.stores.settings.all.app.lockedPassword; 34 let correctPassword = this.props.stores.settings.all.app.lockedPassword;
@@ -52,7 +52,7 @@ class LockedScreen extends Component<IProps> {
52 } 52 }
53 } 53 }
54 54
55 unlock() { 55 unlock(): void {
56 this.props.actions.settings.update({ 56 this.props.actions.settings.update({
57 type: 'app', 57 type: 'app',
58 data: { 58 data: {
@@ -61,7 +61,7 @@ class LockedScreen extends Component<IProps> {
61 }); 61 });
62 } 62 }
63 63
64 render() { 64 render(): ReactElement {
65 const { stores } = this.props; 65 const { stores } = this.props;
66 const { useTouchIdToUnlock } = this.props.stores.settings.all.app; 66 const { useTouchIdToUnlock } = this.props.stores.settings.all.app;
67 67
diff --git a/src/containers/auth/PasswordScreen.tsx b/src/containers/auth/PasswordScreen.tsx
index 864a11fa7..d88549712 100644
--- a/src/containers/auth/PasswordScreen.tsx
+++ b/src/containers/auth/PasswordScreen.tsx
@@ -1,15 +1,15 @@
1import { Component } from 'react'; 1import { Component } from 'react';
2import { inject, observer } from 'mobx-react'; 2import { inject, observer } from 'mobx-react';
3import { UserStore } from 'src/stores.types';
3import Password from '../../components/auth/Password'; 4import Password from '../../components/auth/Password';
4import UserStore from '../../stores/UserStore';
5 5
6interface IProps { 6interface IProps {
7 actions: { 7 actions: {
8 user: UserStore, 8 user: UserStore;
9 }, 9 };
10 stores: { 10 stores: {
11 user: UserStore, 11 user: UserStore;
12 } 12 };
13}; 13};
14 14
15class PasswordScreen extends Component<IProps> { 15class PasswordScreen extends Component<IProps> {
diff --git a/src/electron-util.ts b/src/electron-util.ts
index 4576de9a6..6250366dc 100644
--- a/src/electron-util.ts
+++ b/src/electron-util.ts
@@ -3,7 +3,7 @@
3import * as electron from 'electron'; 3import * as electron from 'electron';
4import { initialize, enable } from '@electron/remote/main'; 4import { initialize, enable } from '@electron/remote/main';
5 5
6export const initializeRemote = () => { 6export const initializeRemote = (): void => {
7 if (process.type !== 'browser') { 7 if (process.type !== 'browser') {
8 throw new Error( 8 throw new Error(
9 'The remote api must be initialized from the main process.', 9 'The remote api must be initialized from the main process.',
@@ -13,7 +13,7 @@ export const initializeRemote = () => {
13 initialize(); 13 initialize();
14}; 14};
15 15
16export const enableWebContents = (webContents: electron.WebContents) => { 16export const enableWebContents = (webContents: electron.WebContents): void => {
17 enable(webContents); 17 enable(webContents);
18}; 18};
19 19
diff --git a/src/enforce-macos-app-location.ts b/src/enforce-macos-app-location.ts
index 882a4664b..3661d08a5 100644
--- a/src/enforce-macos-app-location.ts
+++ b/src/enforce-macos-app-location.ts
@@ -4,7 +4,7 @@ import { isMac } from './environment';
4import { isDevMode } from './environment-remote'; 4import { isDevMode } from './environment-remote';
5import { api } from './electron-util'; 5import { api } from './electron-util';
6 6
7export function enforceMacOSAppLocation() { 7export function enforceMacOSAppLocation(): void {
8 if (isDevMode || !isMac || api.app.isInApplicationsFolder()) { 8 if (isDevMode || !isMac || api.app.isInApplicationsFolder()) {
9 return; 9 return;
10 } 10 }
diff --git a/src/environment-remote.ts b/src/environment-remote.ts
index 298004b90..0cd5d2ce4 100644
--- a/src/environment-remote.ts
+++ b/src/environment-remote.ts
@@ -26,8 +26,8 @@ import {
26import * as buildInfo from './buildInfo.json'; 26import * as buildInfo from './buildInfo.json';
27 27
28export const { app } = electronApi; 28export const { app } = electronApi;
29export const ferdiumVersion = app.getVersion(); 29export const ferdiumVersion: string = app.getVersion();
30export const ferdiumLocale = app.getLocale(); 30export const ferdiumLocale: string = app.getLocale();
31 31
32// Set app directory before loading user modules 32// Set app directory before loading user modules
33if (process.env.FERDIUM_APPDATA_DIR != null) { 33if (process.env.FERDIUM_APPDATA_DIR != null) {
@@ -44,7 +44,7 @@ if (process.env.FERDIUM_APPDATA_DIR != null) {
44 app.setPath('userData', join(app.getPath('appData'), app.name)); 44 app.setPath('userData', join(app.getPath('appData'), app.name));
45} 45}
46 46
47export const isDevMode = 47export const isDevMode: boolean =
48 process.env.ELECTRON_IS_DEV !== undefined 48 process.env.ELECTRON_IS_DEV !== undefined
49 ? Number.parseInt(process.env.ELECTRON_IS_DEV, 10) === 1 49 ? Number.parseInt(process.env.ELECTRON_IS_DEV, 10) === 1
50 : !app.isPackaged; 50 : !app.isPackaged;
@@ -52,11 +52,11 @@ if (isDevMode) {
52 app.setPath('userData', join(app.getPath('appData'), `${app.name}Dev`)); 52 app.setPath('userData', join(app.getPath('appData'), `${app.name}Dev`));
53} 53}
54 54
55export function userDataPath(...segments: string[]) { 55export function userDataPath(...segments: string[]): string {
56 return join(app.getPath('userData'), ...[segments].flat()); 56 return join(app.getPath('userData'), ...[segments].flat());
57} 57}
58 58
59export function userDataRecipesPath(...segments: string[]) { 59export function userDataRecipesPath(...segments: string[]): string {
60 return userDataPath('recipes', ...[segments].flat()); 60 return userDataPath('recipes', ...[segments].flat());
61} 61}
62 62
@@ -84,13 +84,13 @@ if (!isDevMode || (isDevMode && useLiveAPI)) {
84 todos = PRODUCTION_TODOS_FRONTEND_URL; 84 todos = PRODUCTION_TODOS_FRONTEND_URL;
85} 85}
86 86
87export const API = api; 87export const API: string = api;
88export const API_VERSION = 'v1'; 88export const API_VERSION: string = 'v1';
89export const WS_API = wsApi; 89export const WS_API: string = wsApi;
90export const WEBSITE = web; 90export const WEBSITE: string = web;
91export const TODOS_FRONTEND = todos; 91export const TODOS_FRONTEND: string = todos;
92 92
93export function aboutAppDetails() { 93export function aboutAppDetails(): string {
94 return [ 94 return [
95 `Version: ${ferdiumVersion}`, 95 `Version: ${ferdiumVersion}`,
96 `Electron: ${electronVersion}`, 96 `Electron: ${electronVersion}`,
diff --git a/src/environment.ts b/src/environment.ts
index 7208b16b6..271bc7571 100644
--- a/src/environment.ts
+++ b/src/environment.ts
@@ -6,18 +6,18 @@ export const isMac = process.platform === 'darwin';
6export const isWindows = process.platform === 'win32'; 6export const isWindows = process.platform === 'win32';
7export const isLinux = process.platform === 'linux'; 7export const isLinux = process.platform === 'linux';
8 8
9export const electronVersion = process.versions.electron; 9export const electronVersion: string = process.versions.electron;
10export const chromeVersion = process.versions.chrome; 10export const chromeVersion: string = process.versions.chrome;
11export const nodeVersion = process.versions.node; 11export const nodeVersion: string = process.versions.node;
12 12
13export const osArch = arch(); 13export const osArch: string = arch();
14export const osRelease = release(); 14export const osRelease: string = release();
15export const is64Bit = osArch.match(/64/); 15export const is64Bit: RegExpMatchArray | null = osArch.match(/64/);
16 16
17// for accelerator, show the shortform that electron/OS understands 17// for accelerator, show the shortform that electron/OS understands
18// for tooltip, show symbol 18// for tooltip, show symbol
19const ctrlKey = isMac ? '⌘' : 'Ctrl'; 19const ctrlKey: string = isMac ? '⌘' : 'Ctrl';
20const cmdKey = isMac ? 'Cmd' : 'Ctrl'; 20const cmdKey: string = isMac ? 'Cmd' : 'Ctrl';
21 21
22export const altKey = (isAccelerator = true) => 22export const altKey = (isAccelerator = true) =>
23 !isAccelerator && isMac ? '⌥' : 'Alt'; 23 !isAccelerator && isMac ? '⌥' : 'Alt';
diff --git a/src/routes.tsx b/src/routes.tsx
index b8f649740..490d13ee8 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -1,4 +1,4 @@
1import { Component } from 'react'; 1import { Component, ReactElement } from 'react';
2import { inject, observer } from 'mobx-react'; 2import { inject, observer } from 'mobx-react';
3import { Router, Route, IndexRedirect } from 'react-router'; 3import { Router, Route, IndexRedirect } from 'react-router';
4 4
@@ -31,8 +31,7 @@ type Props = {
31}; 31};
32 32
33class Routes extends Component<Props> { 33class Routes extends Component<Props> {
34 render() { 34 render(): ReactElement {
35
36 const { history } = this.props; 35 const { history } = this.props;
37 36
38 return ( 37 return (
diff --git a/src/stores.types.ts b/src/stores.types.ts
index 24eefc416..1899db92c 100644
--- a/src/stores.types.ts
+++ b/src/stores.types.ts
@@ -2,6 +2,7 @@ import Workspace from './features/workspaces/models/Workspace';
2import Recipe from './models/Recipe'; 2import Recipe from './models/Recipe';
3import Service from './models/Service'; 3import Service from './models/Service';
4import User from './models/User'; 4import User from './models/User';
5import { Request } from './stores/lib/Request';
5import { CachedRequest } from './stores/lib/CachedRequest'; 6import { CachedRequest } from './stores/lib/CachedRequest';
6import Reaction from './stores/lib/Reaction'; 7import Reaction from './stores/lib/Reaction';
7 8
@@ -98,6 +99,7 @@ interface AppStore extends TypedStore {
98 isSystemDarkModeEnabled: () => void; 99 isSystemDarkModeEnabled: () => void;
99 isSystemMuteOverridden: () => void; 100 isSystemMuteOverridden: () => void;
100 locale: () => void; 101 locale: () => void;
102 lockedPassword: string;
101 reloadAfterResume: boolean; 103 reloadAfterResume: boolean;
102 reloadAfterResumeTime: number; 104 reloadAfterResumeTime: number;
103 searchEngine: string; 105 searchEngine: string;
@@ -192,7 +194,7 @@ interface RouterStore {
192 194
193export interface ServicesStore extends TypedStore { 195export interface ServicesStore extends TypedStore {
194 clearCacheRequest: () => void; 196 clearCacheRequest: () => void;
195 createServiceRequest: () => void; 197 createServiceRequest: CachedRequest;
196 deleteServiceRequest: () => void; 198 deleteServiceRequest: () => void;
197 allServicesRequest: CachedRequest; 199 allServicesRequest: CachedRequest;
198 filterNeedle: string; 200 filterNeedle: string;
@@ -216,7 +218,9 @@ interface ISettings {
216 [key: string]: any; 218 [key: string]: any;
217} 219}
218 220
219interface SettingsStore extends TypedStore { 221export interface SettingsStore extends TypedStore {
222 update: (value: any) => void;
223 remove: (value: any) => void;
220 fileSystemSettingsTypes: any[]; 224 fileSystemSettingsTypes: any[];
221 loaded: boolean; 225 loaded: boolean;
222 updateAppSettingsRequest: () => void; 226 updateAppSettingsRequest: () => void;
@@ -247,6 +251,7 @@ interface TodosStore extends TypedStore {
247 isFeatureEnabledByUser: () => void; 251 isFeatureEnabledByUser: () => void;
248 isTodoUrlValid: () => void; 252 isTodoUrlValid: () => void;
249 isTodosPanelForceHidden: () => void; 253 isTodosPanelForceHidden: () => void;
254 isTodosEnabled: boolean;
250 isTodosPanelVisible: () => void; 255 isTodosPanelVisible: () => void;
251 isUsingPredefinedTodoServer: () => void; 256 isUsingPredefinedTodoServer: () => void;
252 settings: { 257 settings: {
@@ -274,7 +279,7 @@ interface UIStore extends TypedStore {
274 theme: () => void; 279 theme: () => void;
275} 280}
276 281
277interface UserStore extends TypedStore { 282export interface UserStore extends TypedStore {
278 BASE_ROUTE: '/auth'; 283 BASE_ROUTE: '/auth';
279 CHANGE_SERVER_ROUTE: '/auth/server'; 284 CHANGE_SERVER_ROUTE: '/auth/server';
280 IMPORT_ROUTE: '/auth/signup/import'; 285 IMPORT_ROUTE: '/auth/signup/import';
@@ -293,30 +298,34 @@ interface UserStore extends TypedStore {
293 getUserInfoRequest: CachedRequest; 298 getUserInfoRequest: CachedRequest;
294 hasCompletedSignup: () => void; 299 hasCompletedSignup: () => void;
295 id: () => void; 300 id: () => void;
301 importLegacyServices: () => Promise<void>;
296 inviteRequest: () => void; 302 inviteRequest: () => void;
297 isImportLegacyServicesCompleted: () => void; 303 isImportLegacyServicesCompleted: boolean;
298 isImportLegacyServicesExecuting: () => void; 304 isImportLegacyServicesExecuting: boolean;
299 isLoggingOut: () => void; 305 isLoggingOut: () => void;
300 loginRequest: () => void; 306 loginRequest: () => void;
301 logoutReason: () => void; 307 logoutReason: () => void;
302 logoutReasonTypes: { SERVER: 'SERVER' }; 308 logoutReasonTypes: { SERVER: 'SERVER' };
303 passwordRequest: () => void; 309 passwordRequest: Request;
310 retrievePassword: Promise<void>
304 signupRequest: () => void; 311 signupRequest: () => void;
305 updateUserInfoRequest: () => void; 312 updateUserInfoRequest: () => void;
306 userData: () => void; 313 userData: () => void;
307 _requireAuthenticatedUser: () => void; 314 _requireAuthenticatedUser: () => void;
315 _importLegacyServices: () => void;
316 _retrievePassword: () => void;
308 changeServerRoute: () => void; 317 changeServerRoute: () => void;
309 data: User; 318 data: User;
310 importRoute: () => void; 319 importRoute: string;
311 inviteRoute: () => void; 320 inviteRoute: string;
312 isLoggedIn: boolean; 321 isLoggedIn: boolean;
313 isTokenExpired: () => boolean; 322 isTokenExpired: boolean;
314 legacyServices: () => void; 323 legacyServices: () => void;
315 loginRoute: () => void; 324 loginRoute: string;
316 logoutRoute: () => void; 325 logoutRoute: string;
317 passwordRoute: () => void; 326 passwordRoute: string;
318 setupRoute: () => void; 327 setupRoute: string;
319 signupRoute: () => void; 328 signupRoute: string;
320 team: () => void; 329 team: () => void;
321} 330}
322 331
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.ts
index 7afecd9df..524f2e50c 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.ts
@@ -2,6 +2,9 @@ import { ipcRenderer } from 'electron';
2import { getCurrentWindow } from '@electron/remote'; 2import { getCurrentWindow } from '@electron/remote';
3import { action, computed, observable, reaction } from 'mobx'; 3import { action, computed, observable, reaction } from 'mobx';
4import localStorage from 'mobx-localstorage'; 4import localStorage from 'mobx-localstorage';
5import { Stores } from 'src/stores.types';
6import { ApiInterface } from 'src/api';
7import { Actions } from 'src/actions/lib/actions';
5import { 8import {
6 DEFAULT_APP_SETTINGS, 9 DEFAULT_APP_SETTINGS,
7 FILE_SYSTEM_SETTINGS_TYPES, 10 FILE_SYSTEM_SETTINGS_TYPES,
@@ -9,11 +12,11 @@ import {
9} from '../config'; 12} from '../config';
10import { hash } from '../helpers/password-helpers'; 13import { hash } from '../helpers/password-helpers';
11import Request from './lib/Request'; 14import Request from './lib/Request';
12import Store from './lib/Store'; 15import TypedStore from './lib/TypedStore';
13 16
14const debug = require('../preload-safe-debug')('Ferdium:SettingsStore'); 17const debug = require('../preload-safe-debug')('Ferdium:SettingsStore');
15 18
16export default class SettingsStore extends Store { 19export default class SettingsStore extends TypedStore {
17 @observable updateAppSettingsRequest = new Request( 20 @observable updateAppSettingsRequest = new Request(
18 this.api.local, 21 this.api.local,
19 'updateAppSettings', 22 'updateAppSettings',
@@ -28,15 +31,15 @@ export default class SettingsStore extends Store {
28 proxy: {}, 31 proxy: {},
29 }; 32 };
30 33
31 constructor(...args) { 34 constructor(stores: Stores, api: ApiInterface, actions: Actions) {
32 super(...args); 35 super(stores, api, actions);
33 36
34 // Register action handlers 37 // Register action handlers
35 this.actions.settings.update.listen(this._update.bind(this)); 38 this.actions.settings.update.listen(this._update.bind(this));
36 this.actions.settings.remove.listen(this._remove.bind(this)); 39 this.actions.settings.remove.listen(this._remove.bind(this));
37 } 40 }
38 41
39 async setup() { 42 async setup(): Promise<void> {
40 await this._migrate(); 43 await this._migrate();
41 44
42 reaction( 45 reaction(
@@ -81,7 +84,7 @@ export default class SettingsStore extends Store {
81 } 84 }
82 }); 85 });
83 86
84 ipcRenderer.on('appSettings', (event, resp) => { 87 ipcRenderer.on('appSettings', (_, resp) => {
85 // Lock on startup if enabled in settings 88 // Lock on startup if enabled in settings
86 if ( 89 if (
87 !this.loaded && 90 !this.loaded &&
@@ -143,7 +146,7 @@ export default class SettingsStore extends Store {
143 }; 146 };
144 } 147 }
145 148
146 @action async _update({ type, data }) { 149 @action async _update({ type, data }): Promise<void> {
147 const appSettings = this.all; 150 const appSettings = this.all;
148 if (!this.fileSystemSettingsTypes.includes(type)) { 151 if (!this.fileSystemSettingsTypes.includes(type)) {
149 debug('Update settings', type, data, this.all); 152 debug('Update settings', type, data, this.all);
@@ -159,7 +162,7 @@ export default class SettingsStore extends Store {
159 } 162 }
160 } 163 }
161 164
162 @action async _remove({ type, key }) { 165 @action async _remove({ type, key }): Promise<void> {
163 if (type === 'app') return; // app keys can't be deleted 166 if (type === 'app') return; // app keys can't be deleted
164 167
165 const appSettings = this.all[type]; 168 const appSettings = this.all[type];
@@ -173,7 +176,7 @@ export default class SettingsStore extends Store {
173 } 176 }
174 } 177 }
175 178
176 _ensureMigrationAndMarkDone(migrationName, callback) { 179 _ensureMigrationAndMarkDone(migrationName: string, callback: Function): void {
177 if (!this.all.migration[migrationName]) { 180 if (!this.all.migration[migrationName]) {
178 callback(); 181 callback();
179 182
@@ -187,7 +190,7 @@ export default class SettingsStore extends Store {
187 } 190 }
188 191
189 // Helper 192 // Helper
190 async _migrate() { 193 async _migrate(): Promise<void> {
191 this._ensureMigrationAndMarkDone('password-hashing', () => { 194 this._ensureMigrationAndMarkDone('password-hashing', () => {
192 if (this.stores.settings.app.lockedPassword !== '') { 195 if (this.stores.settings.app.lockedPassword !== '') {
193 const legacySettings = localStorage.getItem('app') || {}; 196 const legacySettings = localStorage.getItem('app') || {};
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.ts
index 091b9b8eb..306b14cb1 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.ts
@@ -1,17 +1,19 @@
1import { action, observable, computed, reaction } from 'mobx'; 1import { action, observable, computed, reaction } from 'mobx';
2import { nativeTheme } from '@electron/remote'; 2import { nativeTheme } from '@electron/remote';
3 3
4import { theme, ThemeType } from '../themes'; 4import { Stores } from 'src/stores.types';
5import Store from './lib/Store'; 5import { ApiInterface } from 'src/api';
6import { Actions } from 'src/actions/lib/actions';
7import { Theme, theme, ThemeType } from '../themes';
8import TypedStore from './lib/TypedStore';
6 9
7export default class UIStore extends Store { 10export default class UIStore extends TypedStore {
8 @observable showServicesUpdatedInfoBar = false; 11 @observable showServicesUpdatedInfoBar = false;
9 12
10 @observable isOsDarkThemeActive = nativeTheme.shouldUseDarkColors; 13 @observable isOsDarkThemeActive = nativeTheme.shouldUseDarkColors;
11 14
12 constructor(...args) { 15 constructor(stores: Stores, api: ApiInterface, actions: Actions) {
13 super(...args); 16 super(stores, api, actions);
14
15 // Register action handlers 17 // Register action handlers
16 this.actions.ui.openSettings.listen(this._openSettings.bind(this)); 18 this.actions.ui.openSettings.listen(this._openSettings.bind(this));
17 this.actions.ui.closeSettings.listen(this._closeSettings.bind(this)); 19 this.actions.ui.closeSettings.listen(this._closeSettings.bind(this));
@@ -26,7 +28,7 @@ export default class UIStore extends Store {
26 }); 28 });
27 } 29 }
28 30
29 setup() { 31 setup(): void {
30 reaction( 32 reaction(
31 () => this.isDarkThemeActive, 33 () => this.isDarkThemeActive,
32 () => { 34 () => {
@@ -74,15 +76,15 @@ export default class UIStore extends Store {
74 ); 76 );
75 } 77 }
76 78
77 @computed get isSplitModeActive() { 79 @computed get isSplitModeActive(): boolean {
78 return this.stores.settings.app.splitMode; 80 return this.stores.settings.app.splitMode;
79 } 81 }
80 82
81 @computed get splitColumnsNo() { 83 @computed get splitColumnsNo(): number {
82 return this.stores.settings.app.splitColumns; 84 return this.stores.settings.app.splitColumns;
83 } 85 }
84 86
85 @computed get theme() { 87 @computed get theme(): Theme {
86 const themeId = 88 const themeId =
87 this.isDarkThemeActive || this.stores.settings.app.darkMode 89 this.isDarkThemeActive || this.stores.settings.app.darkMode
88 ? ThemeType.dark 90 ? ThemeType.dark
@@ -92,16 +94,16 @@ export default class UIStore extends Store {
92 } 94 }
93 95
94 // Actions 96 // Actions
95 @action _openSettings({ path = '/settings' }) { 97 @action _openSettings({ path = '/settings' }): void {
96 const settingsPath = path !== '/settings' ? `/settings/${path}` : path; 98 const settingsPath = path !== '/settings' ? `/settings/${path}` : path;
97 this.stores.router.push(settingsPath); 99 this.stores.router.push(settingsPath);
98 } 100 }
99 101
100 @action _closeSettings() { 102 @action _closeSettings(): void {
101 this.stores.router.push('/'); 103 this.stores.router.push('/');
102 } 104 }
103 105
104 @action _toggleServiceUpdatedInfoBar({ visible }) { 106 @action _toggleServiceUpdatedInfoBar({ visible }): void {
105 let visibility = visible; 107 let visibility = visible;
106 if (visibility === null) { 108 if (visibility === null) {
107 visibility = !this.showServicesUpdatedInfoBar; 109 visibility = !this.showServicesUpdatedInfoBar;
@@ -110,7 +112,7 @@ export default class UIStore extends Store {
110 } 112 }
111 113
112 // Reactions 114 // Reactions
113 _setupThemeInDOM() { 115 _setupThemeInDOM(): void {
114 if (!this.isDarkThemeActive) { 116 if (!this.isDarkThemeActive) {
115 document.body.classList.remove('theme__dark'); 117 document.body.classList.remove('theme__dark');
116 } else { 118 } else {
@@ -118,16 +120,16 @@ export default class UIStore extends Store {
118 } 120 }
119 } 121 }
120 122
121 _setupModeInDOM() { 123 _setupModeInDOM(): void {
122 if (!this.isSplitModeActive) { 124 if (!this.isSplitModeActive) {
123 document.body.classList.remove('mode__split'); 125 document.body.classList.remove('mode__split');
124 } else { 126 } else {
125 document.body.classList.add('mode__split'); 127 document.body.classList.add('mode__split');
126 document.body.dataset.columns = this.splitColumnsNo; 128 document.body.dataset.columns = this.splitColumnsNo.toString();
127 } 129 }
128 } 130 }
129 131
130 _setupColumnsInDOM() { 132 _setupColumnsInDOM(): void {
131 document.body.dataset.columns = this.splitColumnsNo; 133 document.body.dataset.columns = this.splitColumnsNo.toString();
132 } 134 }
133} 135}
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.ts
index 661c03e2c..616ff29a6 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.ts
@@ -4,83 +4,94 @@ import jwt from 'jsonwebtoken';
4import localStorage from 'mobx-localstorage'; 4import localStorage from 'mobx-localstorage';
5import { ipcRenderer } from 'electron'; 5import { ipcRenderer } from 'electron';
6 6
7import { ApiInterface } from 'src/api';
8import { Actions } from 'src/actions/lib/actions';
9import { Stores } from 'src/stores.types';
7import { TODOS_PARTITION_ID } from '../config'; 10import { TODOS_PARTITION_ID } from '../config';
8import { isDevMode } from '../environment-remote'; 11import { isDevMode } from '../environment-remote';
9import Store from './lib/Store';
10import Request from './lib/Request'; 12import Request from './lib/Request';
11import CachedRequest from './lib/CachedRequest'; 13import CachedRequest from './lib/CachedRequest';
14import TypedStore from './lib/TypedStore';
12 15
13const debug = require('../preload-safe-debug')('Ferdium:UserStore'); 16const debug = require('../preload-safe-debug')('Ferdium:UserStore');
14 17
15// TODO: split stores into UserStore and AuthStore 18// TODO: split stores into UserStore and AuthStore
16export default class UserStore extends Store { 19export default class UserStore extends TypedStore {
17 BASE_ROUTE = '/auth'; 20 BASE_ROUTE: string = '/auth';
18 21
19 WELCOME_ROUTE = `${this.BASE_ROUTE}/welcome`; 22 WELCOME_ROUTE: string = `${this.BASE_ROUTE}/welcome`;
20 23
21 LOGIN_ROUTE = `${this.BASE_ROUTE}/login`; 24 LOGIN_ROUTE: string = `${this.BASE_ROUTE}/login`;
22 25
23 LOGOUT_ROUTE = `${this.BASE_ROUTE}/logout`; 26 LOGOUT_ROUTE: string = `${this.BASE_ROUTE}/logout`;
24 27
25 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`; 28 SIGNUP_ROUTE: string = `${this.BASE_ROUTE}/signup`;
26 29
27 SETUP_ROUTE = `${this.BASE_ROUTE}/signup/setup`; 30 SETUP_ROUTE: string = `${this.BASE_ROUTE}/signup/setup`;
28 31
29 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`; 32 IMPORT_ROUTE: string = `${this.BASE_ROUTE}/signup/import`;
30 33
31 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`; 34 INVITE_ROUTE: string = `${this.BASE_ROUTE}/signup/invite`;
32 35
33 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`; 36 PASSWORD_ROUTE: string = `${this.BASE_ROUTE}/password`;
34 37
35 CHANGE_SERVER_ROUTE = `${this.BASE_ROUTE}/server`; 38 CHANGE_SERVER_ROUTE: string = `${this.BASE_ROUTE}/server`;
36 39
37 @observable loginRequest = new Request(this.api.user, 'login'); 40 @observable loginRequest: Request = new Request(this.api.user, 'login');
38 41
39 @observable signupRequest = new Request(this.api.user, 'signup'); 42 @observable signupRequest: Request = new Request(this.api.user, 'signup');
40 43
41 @observable passwordRequest = new Request(this.api.user, 'password'); 44 @observable passwordRequest: Request = new Request(this.api.user, 'password');
42 45
43 @observable inviteRequest = new Request(this.api.user, 'invite'); 46 @observable inviteRequest: Request = new Request(this.api.user, 'invite');
44 47
45 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); 48 @observable getUserInfoRequest: CachedRequest = new CachedRequest(
49 this.api.user,
50 'getInfo',
51 );
46 52
47 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo'); 53 @observable updateUserInfoRequest: Request = new Request(
54 this.api.user,
55 'updateInfo',
56 );
48 57
49 @observable getLegacyServicesRequest = new CachedRequest( 58 @observable getLegacyServicesRequest: CachedRequest = new CachedRequest(
50 this.api.user, 59 this.api.user,
51 'getLegacyServices', 60 'getLegacyServices',
52 ); 61 );
53 62
54 @observable deleteAccountRequest = new CachedRequest(this.api.user, 'delete'); 63 @observable deleteAccountRequest: CachedRequest = new CachedRequest(
55 64 this.api.user,
56 @observable isImportLegacyServicesExecuting = false; 65 'delete',
66 );
57 67
58 @observable isImportLegacyServicesCompleted = false; 68 @observable isImportLegacyServicesExecuting: boolean = false;
59 69
60 @observable isLoggingOut = false; 70 @observable isImportLegacyServicesCompleted: boolean = false;
61 71
62 @observable id; 72 @observable isLoggingOut: boolean = false;
63 73
64 @observable authToken = localStorage.getItem('authToken') || null; 74 @observable id: string | null | undefined;
65 75
66 @observable accountType; 76 @observable authToken: string | null =
77 localStorage.getItem('authToken') || null;
67 78
68 @observable hasCompletedSignup = false; 79 @observable accountType: string | undefined;
69 80
70 @observable userData = {}; 81 @observable hasCompletedSignup: boolean = false;
71 82
72 @observable actionStatus = []; 83 @observable userData: object = {};
73 84
74 logoutReasonTypes = { 85 logoutReasonTypes = {
75 SERVER: 'SERVER', 86 SERVER: 'SERVER',
76 }; 87 };
77 88
78 @observable logoutReason = null; 89 @observable logoutReason: string | null = null;
79 90
80 fetchUserInfoInterval = null; 91 fetchUserInfoInterval = null;
81 92
82 constructor(...args) { 93 constructor(stores: Stores, api: ApiInterface, actions: Actions) {
83 super(...args); 94 super(stores, api, actions);
84 95
85 // Register action handlers 96 // Register action handlers
86 this.actions.user.login.listen(this._login.bind(this)); 97 this.actions.user.login.listen(this._login.bind(this));
@@ -104,54 +115,58 @@ export default class UserStore extends Store {
104 ]); 115 ]);
105 } 116 }
106 117
107 setup() { 118 setup(): void {
108 // Data migration 119 // Data migration
109 this._migrateUserLocale(); 120 this._migrateUserLocale();
110 } 121 }
111 122
112 // Routes 123 // Routes
113 get loginRoute() { 124 get loginRoute(): string {
114 return this.LOGIN_ROUTE; 125 return this.LOGIN_ROUTE;
115 } 126 }
116 127
117 get logoutRoute() { 128 get logoutRoute(): string {
118 return this.LOGOUT_ROUTE; 129 return this.LOGOUT_ROUTE;
119 } 130 }
120 131
121 get signupRoute() { 132 get signupRoute(): string {
122 return this.SIGNUP_ROUTE; 133 return this.SIGNUP_ROUTE;
123 } 134 }
124 135
125 get setupRoute() { 136 get setupRoute(): string {
126 return this.SETUP_ROUTE; 137 return this.SETUP_ROUTE;
127 } 138 }
128 139
129 get inviteRoute() { 140 get inviteRoute(): string {
130 return this.INVITE_ROUTE; 141 return this.INVITE_ROUTE;
131 } 142 }
132 143
133 get importRoute() { 144 get importRoute(): string {
134 return this.IMPORT_ROUTE; 145 return this.IMPORT_ROUTE;
135 } 146 }
136 147
137 get passwordRoute() { 148 get passwordRoute(): string {
138 return this.PASSWORD_ROUTE; 149 return this.PASSWORD_ROUTE;
139 } 150 }
140 151
141 get changeServerRoute() { 152 get changeServerRoute(): string {
142 return this.CHANGE_SERVER_ROUTE; 153 return this.CHANGE_SERVER_ROUTE;
143 } 154 }
144 155
145 // Data 156 // Data
146 @computed get isLoggedIn() { 157 @computed get isLoggedIn(): boolean {
147 return Boolean(localStorage.getItem('authToken')); 158 return Boolean(localStorage.getItem('authToken'));
148 } 159 }
149 160
150 @computed get isTokenExpired() { 161 @computed get isTokenExpired(): boolean {
151 if (!this.authToken) return false; 162 if (!this.authToken) return false;
163 const parsedToken = this._parseToken(this.authToken);
152 164
153 const { tokenExpiry } = this._parseToken(this.authToken); 165 return (
154 return this.authToken !== null && moment(tokenExpiry).isBefore(moment()); 166 parsedToken !== false &&
167 this.authToken !== null &&
168 moment(parsedToken.tokenExpiry).isBefore(moment())
169 );
155 } 170 }
156 171
157 @computed get data() { 172 @computed get data() {
@@ -160,23 +175,23 @@ export default class UserStore extends Store {
160 return this.getUserInfoRequest.execute().result || {}; 175 return this.getUserInfoRequest.execute().result || {};
161 } 176 }
162 177
163 @computed get team() { 178 @computed get team(): any {
164 return this.data.team || null; 179 return this.data.team || null;
165 } 180 }
166 181
167 @computed get legacyServices() { 182 @computed get legacyServices(): any {
168 return this.getLegacyServicesRequest.execute() || {}; 183 return this.getLegacyServicesRequest.execute() || {};
169 } 184 }
170 185
171 // Actions 186 // Actions
172 @action async _login({ email, password }) { 187 @action async _login({ email, password }): Promise<void> {
173 const authToken = await this.loginRequest.execute(email, password)._promise; 188 const authToken = await this.loginRequest.execute(email, password)._promise;
174 this._setUserData(authToken); 189 this._setUserData(authToken);
175 190
176 this.stores.router.push('/'); 191 this.stores.router.push('/');
177 } 192 }
178 193
179 @action _tokenLogin(authToken) { 194 @action _tokenLogin(authToken: string): void {
180 this._setUserData(authToken); 195 this._setUserData(authToken);
181 196
182 this.stores.router.push('/'); 197 this.stores.router.push('/');
@@ -191,7 +206,7 @@ export default class UserStore extends Store {
191 company, 206 company,
192 plan, 207 plan,
193 currency, 208 currency,
194 }) { 209 }): Promise<void> {
195 const authToken = await this.signupRequest.execute({ 210 const authToken = await this.signupRequest.execute({
196 firstname, 211 firstname,
197 lastname, 212 lastname,
@@ -211,14 +226,14 @@ export default class UserStore extends Store {
211 this.stores.router.push(this.SETUP_ROUTE); 226 this.stores.router.push(this.SETUP_ROUTE);
212 } 227 }
213 228
214 @action async _retrievePassword({ email }) { 229 @action async _retrievePassword({ email }): Promise<void> {
215 const request = this.passwordRequest.execute(email); 230 const request = this.passwordRequest.execute(email);
216 231
217 await request._promise; 232 await request._promise;
218 this.actionStatus = request.result.status || []; 233 this.actionStatus = request.result.status || [];
219 } 234 }
220 235
221 @action async _invite({ invites }) { 236 @action async _invite({ invites }): Promise<void> {
222 const data = invites.filter(invite => invite.email !== ''); 237 const data = invites.filter(invite => invite.email !== '');
223 238
224 const response = await this.inviteRequest.execute(data)._promise; 239 const response = await this.inviteRequest.execute(data)._promise;
@@ -231,7 +246,7 @@ export default class UserStore extends Store {
231 } 246 }
232 } 247 }
233 248
234 @action async _update({ userData }) { 249 @action async _update({ userData }): Promise<void> {
235 if (!this.isLoggedIn) return; 250 if (!this.isLoggedIn) return;
236 251
237 const response = await this.updateUserInfoRequest.execute(userData) 252 const response = await this.updateUserInfoRequest.execute(userData)
@@ -241,11 +256,11 @@ export default class UserStore extends Store {
241 this.actionStatus = response.status || []; 256 this.actionStatus = response.status || [];
242 } 257 }
243 258
244 @action _resetStatus() { 259 @action _resetStatus(): void {
245 this.actionStatus = []; 260 this.actionStatus = [];
246 } 261 }
247 262
248 @action _logout() { 263 @action _logout(): void {
249 // workaround mobx issue 264 // workaround mobx issue
250 localStorage.removeItem('authToken'); 265 localStorage.removeItem('authToken');
251 window.localStorage.removeItem('authToken'); 266 window.localStorage.removeItem('authToken');
@@ -260,7 +275,7 @@ export default class UserStore extends Store {
260 } 275 }
261 } 276 }
262 277
263 @action async _importLegacyServices({ services }) { 278 @action async _importLegacyServices({ services }): Promise<void> {
264 this.isImportLegacyServicesExecuting = true; 279 this.isImportLegacyServicesExecuting = true;
265 280
266 // Reduces recipe duplicates 281 // Reduces recipe duplicates
@@ -289,12 +304,12 @@ export default class UserStore extends Store {
289 this.isImportLegacyServicesCompleted = true; 304 this.isImportLegacyServicesCompleted = true;
290 } 305 }
291 306
292 @action async _delete() { 307 @action async _delete(): Promise<void> {
293 this.deleteAccountRequest.execute(); 308 this.deleteAccountRequest.execute();
294 } 309 }
295 310
296 // This is a mobx autorun which forces the user to login if not authenticated 311 // This is a mobx autorun which forces the user to login if not authenticated
297 _requireAuthenticatedUser = () => { 312 _requireAuthenticatedUser = (): void => {
298 if (this.isTokenExpired) { 313 if (this.isTokenExpired) {
299 this._logout(); 314 this._logout();
300 } 315 }
@@ -328,13 +343,13 @@ export default class UserStore extends Store {
328 }; 343 };
329 344
330 // Reactions 345 // Reactions
331 async _getUserData() { 346 async _getUserData(): Promise<void> {
332 if (this.isLoggedIn) { 347 if (this.isLoggedIn) {
333 let data; 348 let data;
334 try { 349 try {
335 data = await this.getUserInfoRequest.execute()._promise; 350 data = await this.getUserInfoRequest.execute()._promise;
336 } catch { 351 } catch {
337 return false; 352 return;
338 } 353 }
339 354
340 // We need to set the beta flag for the SettingsStore 355 // We need to set the beta flag for the SettingsStore
@@ -364,9 +379,9 @@ export default class UserStore extends Store {
364 } 379 }
365 } 380 }
366 381
367 _setUserData(authToken) { 382 _setUserData(authToken: any): void {
368 const data = this._parseToken(authToken); 383 const data = this._parseToken(authToken);
369 if (data.authToken) { 384 if (data !== false && data.authToken) {
370 localStorage.setItem('authToken', data.authToken); 385 localStorage.setItem('authToken', data.authToken);
371 386
372 this.authToken = data.authToken; 387 this.authToken = data.authToken;
@@ -377,20 +392,21 @@ export default class UserStore extends Store {
377 } 392 }
378 } 393 }
379 394
380 getAuthURL(url) { 395 getAuthURL(url: string): string {
381 const parsedUrl = new URL(url); 396 const parsedUrl = new URL(url);
382 const params = new URLSearchParams(parsedUrl.search.slice(1)); 397 const params = new URLSearchParams(parsedUrl.search.slice(1));
383 398
384 params.append('authToken', this.authToken); 399 // TODO: Remove the neccesity for `as string`
400 params.append('authToken', this.authToken as string);
385 401
386 return `${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`; 402 return `${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`;
387 } 403 }
388 404
389 async _migrateUserLocale() { 405 async _migrateUserLocale(): Promise<void> {
390 try { 406 try {
391 await this.getUserInfoRequest._promise; 407 await this.getUserInfoRequest._promise;
392 } catch { 408 } catch {
393 return false; 409 return;
394 } 410 }
395 411
396 if (!this.data.locale) { 412 if (!this.data.locale) {
diff --git a/src/stores/lib/Store.js b/src/stores/lib/Store.js
deleted file mode 100644
index 739a47729..000000000
--- a/src/stores/lib/Store.js
+++ /dev/null
@@ -1,54 +0,0 @@
1import { computed, observable } from 'mobx';
2import Reaction from './Reaction';
3
4export default class Store {
5 /** @type Stores */
6 stores;
7
8 /** @type ApiInterface */
9 api;
10
11 /** @type Actions */
12 actions;
13
14 /** @type Reaction[] */
15 _reactions = [];
16
17 // status implementation
18 @observable _status = null;
19
20 @computed get actionStatus() {
21 return this._status || [];
22 }
23
24 set actionStatus(status) {
25 this._status = status;
26 }
27
28 constructor(stores, api, actions) {
29 this.stores = stores;
30 this.api = api;
31 this.actions = actions;
32 }
33
34 registerReactions(reactions) {
35 for (const reaction of reactions) {
36 this._reactions.push(new Reaction(reaction));
37 }
38 }
39
40 setup() {}
41
42 initialize() {
43 this.setup();
44 for (const reaction of this._reactions) reaction.start();
45 }
46
47 teardown() {
48 for (const reaction of this._reactions) reaction.stop();
49 }
50
51 resetStatus() {
52 this._status = null;
53 }
54}