diff options
-rw-r--r-- | docs/example-feature/actions.js | 10 | ||||
-rw-r--r-- | docs/example-feature/api.js | 5 | ||||
-rw-r--r-- | docs/example-feature/index.js | 36 | ||||
-rw-r--r-- | docs/example-feature/state.js | 14 | ||||
-rw-r--r-- | docs/example-feature/store.js | 32 | ||||
-rw-r--r-- | packages/forms/package.json | 4 | ||||
-rw-r--r-- | packages/forms/src/input/index.tsx | 6 | ||||
-rw-r--r-- | packages/forms/src/label/index.tsx | 2 | ||||
-rw-r--r-- | src/actions/lib/actions.js | 29 | ||||
-rw-r--r-- | src/api/server/ServerApi.js | 185 | ||||
-rw-r--r-- | src/api/utils/auth.js | 28 | ||||
-rw-r--r-- | src/styles/auth.scss | 2 | ||||
-rw-r--r-- | uidev/src/stories/input.stories.tsx | 8 |
13 files changed, 217 insertions, 144 deletions
diff --git a/docs/example-feature/actions.js b/docs/example-feature/actions.js new file mode 100644 index 000000000..c4d49b708 --- /dev/null +++ b/docs/example-feature/actions.js | |||
@@ -0,0 +1,10 @@ | |||
1 | import PropTypes from 'prop-types'; | ||
2 | import { createActionsFromDefinitions } from '../../src/actions/lib/actions'; | ||
3 | |||
4 | export const exampleFeatureActions = createActionsFromDefinitions({ | ||
5 | greet: { | ||
6 | name: PropTypes.string.isRequired, | ||
7 | }, | ||
8 | }, PropTypes.checkPropTypes); | ||
9 | |||
10 | export default exampleFeatureActions; | ||
diff --git a/docs/example-feature/api.js b/docs/example-feature/api.js new file mode 100644 index 000000000..65207e877 --- /dev/null +++ b/docs/example-feature/api.js | |||
@@ -0,0 +1,5 @@ | |||
1 | export default { | ||
2 | async getName() { | ||
3 | return Promise.resolve('Franz'); | ||
4 | }, | ||
5 | }; | ||
diff --git a/docs/example-feature/index.js b/docs/example-feature/index.js new file mode 100644 index 000000000..af859af26 --- /dev/null +++ b/docs/example-feature/index.js | |||
@@ -0,0 +1,36 @@ | |||
1 | import { reaction, runInAction } from 'mobx'; | ||
2 | import { ExampleFeatureStore } from './store'; | ||
3 | import state, { resetState } from './state'; | ||
4 | import api from './api'; | ||
5 | |||
6 | const debug = require('debug')('Franz:feature:EXAMPLE_FEATURE'); | ||
7 | |||
8 | let store = null; | ||
9 | |||
10 | export default function initAnnouncements(stores, actions) { | ||
11 | const { features } = stores; | ||
12 | |||
13 | // Toggle workspace feature | ||
14 | reaction( | ||
15 | () => ( | ||
16 | features.features.isExampleFeatureEnabled | ||
17 | ), | ||
18 | (isEnabled) => { | ||
19 | if (isEnabled) { | ||
20 | debug('Initializing `EXAMPLE_FEATURE` feature'); | ||
21 | store = new ExampleFeatureStore(stores, api, actions, state); | ||
22 | store.initialize(); | ||
23 | runInAction(() => { state.isFeatureActive = true; }); | ||
24 | } else if (store) { | ||
25 | debug('Disabling `EXAMPLE_FEATURE` feature'); | ||
26 | runInAction(() => { state.isFeatureActive = false; }); | ||
27 | store.teardown(); | ||
28 | store = null; | ||
29 | resetState(); // Reset state to default | ||
30 | } | ||
31 | }, | ||
32 | { | ||
33 | fireImmediately: true, | ||
34 | }, | ||
35 | ); | ||
36 | } | ||
diff --git a/docs/example-feature/state.js b/docs/example-feature/state.js new file mode 100644 index 000000000..676717da7 --- /dev/null +++ b/docs/example-feature/state.js | |||
@@ -0,0 +1,14 @@ | |||
1 | import { observable } from 'mobx'; | ||
2 | |||
3 | const defaultState = { | ||
4 | name: null, | ||
5 | isFeatureActive: false, | ||
6 | }; | ||
7 | |||
8 | export const exampleFeatureState = observable(defaultState); | ||
9 | |||
10 | export function resetState() { | ||
11 | Object.assign(exampleFeatureState, defaultState); | ||
12 | } | ||
13 | |||
14 | export default exampleFeatureState; | ||
diff --git a/docs/example-feature/store.js b/docs/example-feature/store.js new file mode 100644 index 000000000..d8acfdca3 --- /dev/null +++ b/docs/example-feature/store.js | |||
@@ -0,0 +1,32 @@ | |||
1 | import { action, observable, reaction } from 'mobx'; | ||
2 | import Store from '../../src/stores/lib/Store'; | ||
3 | import Request from '../../src/stores/lib/Request'; | ||
4 | |||
5 | const debug = require('debug')('Franz:feature:EXAMPLE_FEATURE:store'); | ||
6 | |||
7 | export class ExampleFeatureStore extends Store { | ||
8 | @observable getNameRequest = new Request(this.api, 'getName'); | ||
9 | |||
10 | constructor(stores, api, actions, state) { | ||
11 | super(stores, api, actions); | ||
12 | this.state = state; | ||
13 | } | ||
14 | |||
15 | setup() { | ||
16 | debug('fetching name from api'); | ||
17 | this.getNameRequest.execute(); | ||
18 | |||
19 | // Update the name on the state when the request resolved | ||
20 | reaction( | ||
21 | () => this.getNameRequest.result, | ||
22 | name => this._setName(name), | ||
23 | ); | ||
24 | } | ||
25 | |||
26 | @action _setName = (name) => { | ||
27 | debug('setting name', name); | ||
28 | this.state.name = name; | ||
29 | }; | ||
30 | } | ||
31 | |||
32 | export default ExampleFeatureStore; | ||
diff --git a/packages/forms/package.json b/packages/forms/package.json index e78929777..2cd6f4d43 100644 --- a/packages/forms/package.json +++ b/packages/forms/package.json | |||
@@ -1,6 +1,6 @@ | |||
1 | { | 1 | { |
2 | "name": "@meetfranz/forms", | 2 | "name": "@meetfranz/forms", |
3 | "version": "1.0.9", | 3 | "version": "1.0.11", |
4 | "description": "React form components for Franz", | 4 | "description": "React form components for Franz", |
5 | "main": "lib/index.js", | 5 | "main": "lib/index.js", |
6 | "scripts": { | 6 | "scripts": { |
@@ -35,5 +35,5 @@ | |||
35 | "react-dom": "16.7.0", | 35 | "react-dom": "16.7.0", |
36 | "react-jss": "^8.6.1" | 36 | "react-jss": "^8.6.1" |
37 | }, | 37 | }, |
38 | "gitHead": "14b151cad6a5a849bb476aaa3fc53bf1eead7f4b" | 38 | "gitHead": "0aad40e6afcdf5e5598137f974f7fd25abb37290" |
39 | } | 39 | } |
diff --git a/packages/forms/src/input/index.tsx b/packages/forms/src/input/index.tsx index 478738cad..ab1c33315 100644 --- a/packages/forms/src/input/index.tsx +++ b/packages/forms/src/input/index.tsx | |||
@@ -102,6 +102,9 @@ class InputComponent extends Component<IProps, IState> { | |||
102 | spellCheck, | 102 | spellCheck, |
103 | onBlur, | 103 | onBlur, |
104 | onFocus, | 104 | onFocus, |
105 | min, | ||
106 | max, | ||
107 | step, | ||
105 | } = this.props; | 108 | } = this.props; |
106 | 109 | ||
107 | const { | 110 | const { |
@@ -147,6 +150,9 @@ class InputComponent extends Component<IProps, IState> { | |||
147 | onFocus={onFocus} | 150 | onFocus={onFocus} |
148 | onBlur={onBlur} | 151 | onBlur={onBlur} |
149 | disabled={disabled} | 152 | disabled={disabled} |
153 | min={min} | ||
154 | max={max} | ||
155 | step={step} | ||
150 | /> | 156 | /> |
151 | {suffix && ( | 157 | {suffix && ( |
152 | <span className={classes.suffix}> | 158 | <span className={classes.suffix}> |
diff --git a/packages/forms/src/label/index.tsx b/packages/forms/src/label/index.tsx index 36fcfbedf..590270a06 100644 --- a/packages/forms/src/label/index.tsx +++ b/packages/forms/src/label/index.tsx | |||
@@ -26,6 +26,8 @@ class LabelComponent extends Component<ILabel> { | |||
26 | htmlFor, | 26 | htmlFor, |
27 | } = this.props; | 27 | } = this.props; |
28 | 28 | ||
29 | if (!showLabel) return children; | ||
30 | |||
29 | return ( | 31 | return ( |
30 | <label | 32 | <label |
31 | className={classnames({ | 33 | className={classnames({ |
diff --git a/src/actions/lib/actions.js b/src/actions/lib/actions.js index 499018d70..6571e9441 100644 --- a/src/actions/lib/actions.js +++ b/src/actions/lib/actions.js | |||
@@ -1,18 +1,23 @@ | |||
1 | export const createActionsFromDefinitions = (actionDefinitions, validate) => { | ||
2 | const actions = {}; | ||
3 | Object.keys(actionDefinitions).forEach((actionName) => { | ||
4 | const action = (params) => { | ||
5 | const schema = actionDefinitions[actionName]; | ||
6 | validate(schema, params, actionName); | ||
7 | action.notify(params); | ||
8 | }; | ||
9 | actions[actionName] = action; | ||
10 | action.listeners = []; | ||
11 | action.listen = listener => action.listeners.push(listener); | ||
12 | action.notify = params => action.listeners.forEach(listener => listener(params)); | ||
13 | }); | ||
14 | return actions; | ||
15 | }; | ||
16 | |||
1 | export default (definitions, validate) => { | 17 | export default (definitions, validate) => { |
2 | const newActions = {}; | 18 | const newActions = {}; |
3 | Object.keys(definitions).forEach((scopeName) => { | 19 | Object.keys(definitions).forEach((scopeName) => { |
4 | newActions[scopeName] = {}; | 20 | newActions[scopeName] = createActionsFromDefinitions(definitions[scopeName], validate); |
5 | Object.keys(definitions[scopeName]).forEach((actionName) => { | ||
6 | const action = (params) => { | ||
7 | const schema = definitions[scopeName][actionName]; | ||
8 | validate(schema, params, actionName); | ||
9 | action.notify(params); | ||
10 | }; | ||
11 | newActions[scopeName][actionName] = action; | ||
12 | action.listeners = []; | ||
13 | action.listen = listener => action.listeners.push(listener); | ||
14 | action.notify = params => action.listeners.forEach(listener => listener(params)); | ||
15 | }); | ||
16 | }); | 21 | }); |
17 | return newActions; | 22 | return newActions; |
18 | }; | 23 | }; |
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index 2871769a9..bafeef005 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -3,7 +3,6 @@ import path from 'path'; | |||
3 | import tar from 'tar'; | 3 | import tar from 'tar'; |
4 | import fs from 'fs-extra'; | 4 | import fs from 'fs-extra'; |
5 | import { remote } from 'electron'; | 5 | import { remote } from 'electron'; |
6 | import localStorage from 'mobx-localstorage'; | ||
7 | 6 | ||
8 | import ServiceModel from '../../models/Service'; | 7 | import ServiceModel from '../../models/Service'; |
9 | import RecipePreviewModel from '../../models/RecipePreview'; | 8 | import RecipePreviewModel from '../../models/RecipePreview'; |
@@ -16,6 +15,7 @@ import OrderModel from '../../models/Order'; | |||
16 | import { sleep } from '../../helpers/async-helpers'; | 15 | import { sleep } from '../../helpers/async-helpers'; |
17 | 16 | ||
18 | import { API } from '../../environment'; | 17 | import { API } from '../../environment'; |
18 | import { prepareAuthRequest, sendAuthRequest } from '../utils/auth'; | ||
19 | 19 | ||
20 | import { | 20 | import { |
21 | getRecipeDirectory, | 21 | getRecipeDirectory, |
@@ -39,6 +39,7 @@ const { default: fetch } = remote.require('electron-fetch'); | |||
39 | 39 | ||
40 | const SERVER_URL = API; | 40 | const SERVER_URL = API; |
41 | const API_VERSION = 'v1'; | 41 | const API_VERSION = 'v1'; |
42 | const API_URL = `${SERVER_URL}/${API_VERSION}`; | ||
42 | 43 | ||
43 | export default class ServerApi { | 44 | export default class ServerApi { |
44 | recipePreviews = []; | 45 | recipePreviews = []; |
@@ -47,12 +48,12 @@ export default class ServerApi { | |||
47 | 48 | ||
48 | // User | 49 | // User |
49 | async login(email, passwordHash) { | 50 | async login(email, passwordHash) { |
50 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/auth/login`, this._prepareAuthRequest({ | 51 | const request = await sendAuthRequest(`${API_URL}/auth/login`, { |
51 | method: 'POST', | 52 | method: 'POST', |
52 | headers: { | 53 | headers: { |
53 | Authorization: `Basic ${window.btoa(`${email}:${passwordHash}`)}`, | 54 | Authorization: `Basic ${window.btoa(`${email}:${passwordHash}`)}`, |
54 | }, | 55 | }, |
55 | }, false)); | 56 | }, false); |
56 | if (!request.ok) { | 57 | if (!request.ok) { |
57 | throw request; | 58 | throw request; |
58 | } | 59 | } |
@@ -63,10 +64,10 @@ export default class ServerApi { | |||
63 | } | 64 | } |
64 | 65 | ||
65 | async signup(data) { | 66 | async signup(data) { |
66 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/auth/signup`, this._prepareAuthRequest({ | 67 | const request = await sendAuthRequest(`${API_URL}/auth/signup`, { |
67 | method: 'POST', | 68 | method: 'POST', |
68 | body: JSON.stringify(data), | 69 | body: JSON.stringify(data), |
69 | }, false)); | 70 | }, false); |
70 | if (!request.ok) { | 71 | if (!request.ok) { |
71 | throw request; | 72 | throw request; |
72 | } | 73 | } |
@@ -77,10 +78,10 @@ export default class ServerApi { | |||
77 | } | 78 | } |
78 | 79 | ||
79 | async inviteUser(data) { | 80 | async inviteUser(data) { |
80 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/invite`, this._prepareAuthRequest({ | 81 | const request = await sendAuthRequest(`${API_URL}/invite`, { |
81 | method: 'POST', | 82 | method: 'POST', |
82 | body: JSON.stringify(data), | 83 | body: JSON.stringify(data), |
83 | })); | 84 | }); |
84 | if (!request.ok) { | 85 | if (!request.ok) { |
85 | throw request; | 86 | throw request; |
86 | } | 87 | } |
@@ -90,12 +91,12 @@ export default class ServerApi { | |||
90 | } | 91 | } |
91 | 92 | ||
92 | async retrievePassword(email) { | 93 | async retrievePassword(email) { |
93 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/auth/password`, this._prepareAuthRequest({ | 94 | const request = await sendAuthRequest(`${API_URL}/auth/password`, { |
94 | method: 'POST', | 95 | method: 'POST', |
95 | body: JSON.stringify({ | 96 | body: JSON.stringify({ |
96 | email, | 97 | email, |
97 | }), | 98 | }), |
98 | }, false)); | 99 | }, false); |
99 | if (!request.ok) { | 100 | if (!request.ok) { |
100 | throw request; | 101 | throw request; |
101 | } | 102 | } |
@@ -106,9 +107,7 @@ export default class ServerApi { | |||
106 | } | 107 | } |
107 | 108 | ||
108 | async userInfo() { | 109 | async userInfo() { |
109 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({ | 110 | const request = await sendAuthRequest(`${API_URL}/me`); |
110 | method: 'GET', | ||
111 | })); | ||
112 | if (!request.ok) { | 111 | if (!request.ok) { |
113 | throw request; | 112 | throw request; |
114 | } | 113 | } |
@@ -121,10 +120,10 @@ export default class ServerApi { | |||
121 | } | 120 | } |
122 | 121 | ||
123 | async updateUserInfo(data) { | 122 | async updateUserInfo(data) { |
124 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({ | 123 | const request = await sendAuthRequest(`${API_URL}/me`, { |
125 | method: 'PUT', | 124 | method: 'PUT', |
126 | body: JSON.stringify(data), | 125 | body: JSON.stringify(data), |
127 | })); | 126 | }); |
128 | if (!request.ok) { | 127 | if (!request.ok) { |
129 | throw request; | 128 | throw request; |
130 | } | 129 | } |
@@ -136,9 +135,9 @@ export default class ServerApi { | |||
136 | } | 135 | } |
137 | 136 | ||
138 | async deleteAccount() { | 137 | async deleteAccount() { |
139 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me`, this._prepareAuthRequest({ | 138 | const request = await sendAuthRequest(`${API_URL}/me`, { |
140 | method: 'DELETE', | 139 | method: 'DELETE', |
141 | })); | 140 | }); |
142 | if (!request.ok) { | 141 | if (!request.ok) { |
143 | throw request; | 142 | throw request; |
144 | } | 143 | } |
@@ -150,9 +149,7 @@ export default class ServerApi { | |||
150 | 149 | ||
151 | // Services | 150 | // Services |
152 | async getServices() { | 151 | async getServices() { |
153 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/services`, this._prepareAuthRequest({ | 152 | const request = await sendAuthRequest(`${API_URL}/me/services`); |
154 | method: 'GET', | ||
155 | })); | ||
156 | if (!request.ok) { | 153 | if (!request.ok) { |
157 | throw request; | 154 | throw request; |
158 | } | 155 | } |
@@ -165,12 +162,12 @@ export default class ServerApi { | |||
165 | } | 162 | } |
166 | 163 | ||
167 | async createService(recipeId, data) { | 164 | async createService(recipeId, data) { |
168 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service`, this._prepareAuthRequest({ | 165 | const request = await sendAuthRequest(`${API_URL}/service`, { |
169 | method: 'POST', | 166 | method: 'POST', |
170 | body: JSON.stringify(Object.assign({ | 167 | body: JSON.stringify(Object.assign({ |
171 | recipeId, | 168 | recipeId, |
172 | }, data)), | 169 | }, data)), |
173 | })); | 170 | }); |
174 | if (!request.ok) { | 171 | if (!request.ok) { |
175 | throw request; | 172 | throw request; |
176 | } | 173 | } |
@@ -195,10 +192,10 @@ export default class ServerApi { | |||
195 | await this.uploadServiceIcon(serviceId, data.iconFile); | 192 | await this.uploadServiceIcon(serviceId, data.iconFile); |
196 | } | 193 | } |
197 | 194 | ||
198 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${serviceId}`, this._prepareAuthRequest({ | 195 | const request = await sendAuthRequest(`${API_URL}/service/${serviceId}`, { |
199 | method: 'PUT', | 196 | method: 'PUT', |
200 | body: JSON.stringify(data), | 197 | body: JSON.stringify(data), |
201 | })); | 198 | }); |
202 | 199 | ||
203 | if (!request.ok) { | 200 | if (!request.ok) { |
204 | throw request; | 201 | throw request; |
@@ -216,14 +213,14 @@ export default class ServerApi { | |||
216 | const formData = new FormData(); | 213 | const formData = new FormData(); |
217 | formData.append('icon', icon); | 214 | formData.append('icon', icon); |
218 | 215 | ||
219 | const requestData = this._prepareAuthRequest({ | 216 | const requestData = prepareAuthRequest({ |
220 | method: 'PUT', | 217 | method: 'PUT', |
221 | body: formData, | 218 | body: formData, |
222 | }); | 219 | }); |
223 | 220 | ||
224 | delete requestData.headers['Content-Type']; | 221 | delete requestData.headers['Content-Type']; |
225 | 222 | ||
226 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${serviceId}`, requestData); | 223 | const request = await window.fetch(`${API_URL}/service/${serviceId}`, requestData); |
227 | 224 | ||
228 | if (!request.ok) { | 225 | if (!request.ok) { |
229 | throw request; | 226 | throw request; |
@@ -235,10 +232,10 @@ export default class ServerApi { | |||
235 | } | 232 | } |
236 | 233 | ||
237 | async reorderService(data) { | 234 | async reorderService(data) { |
238 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/reorder`, this._prepareAuthRequest({ | 235 | const request = await sendAuthRequest(`${API_URL}/service/reorder`, { |
239 | method: 'PUT', | 236 | method: 'PUT', |
240 | body: JSON.stringify(data), | 237 | body: JSON.stringify(data), |
241 | })); | 238 | }); |
242 | if (!request.ok) { | 239 | if (!request.ok) { |
243 | throw request; | 240 | throw request; |
244 | } | 241 | } |
@@ -248,9 +245,9 @@ export default class ServerApi { | |||
248 | } | 245 | } |
249 | 246 | ||
250 | async deleteService(id) { | 247 | async deleteService(id) { |
251 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${id}`, this._prepareAuthRequest({ | 248 | const request = await sendAuthRequest(`${API_URL}/service/${id}`, { |
252 | method: 'DELETE', | 249 | method: 'DELETE', |
253 | })); | 250 | }); |
254 | if (!request.ok) { | 251 | if (!request.ok) { |
255 | throw request; | 252 | throw request; |
256 | } | 253 | } |
@@ -264,9 +261,7 @@ export default class ServerApi { | |||
264 | 261 | ||
265 | // Features | 262 | // Features |
266 | async getDefaultFeatures() { | 263 | async getDefaultFeatures() { |
267 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/features/default`, this._prepareAuthRequest({ | 264 | const request = await sendAuthRequest(`${API_URL}/features/default`); |
268 | method: 'GET', | ||
269 | })); | ||
270 | if (!request.ok) { | 265 | if (!request.ok) { |
271 | throw request; | 266 | throw request; |
272 | } | 267 | } |
@@ -278,9 +273,7 @@ export default class ServerApi { | |||
278 | } | 273 | } |
279 | 274 | ||
280 | async getFeatures() { | 275 | async getFeatures() { |
281 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/features`, this._prepareAuthRequest({ | 276 | const request = await sendAuthRequest(`${API_URL}/features`); |
282 | method: 'GET', | ||
283 | })); | ||
284 | if (!request.ok) { | 277 | if (!request.ok) { |
285 | throw request; | 278 | throw request; |
286 | } | 279 | } |
@@ -314,10 +307,10 @@ export default class ServerApi { | |||
314 | } | 307 | } |
315 | 308 | ||
316 | async getRecipeUpdates(recipeVersions) { | 309 | async getRecipeUpdates(recipeVersions) { |
317 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/update`, this._prepareAuthRequest({ | 310 | const request = await sendAuthRequest(`${API_URL}/recipes/update`, { |
318 | method: 'POST', | 311 | method: 'POST', |
319 | body: JSON.stringify(recipeVersions), | 312 | body: JSON.stringify(recipeVersions), |
320 | })); | 313 | }); |
321 | if (!request.ok) { | 314 | if (!request.ok) { |
322 | throw request; | 315 | throw request; |
323 | } | 316 | } |
@@ -328,29 +321,19 @@ export default class ServerApi { | |||
328 | 321 | ||
329 | // Recipes Previews | 322 | // Recipes Previews |
330 | async getRecipePreviews() { | 323 | async getRecipePreviews() { |
331 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes`, this._prepareAuthRequest({ | 324 | const request = await sendAuthRequest(`${API_URL}/recipes`); |
332 | method: 'GET', | 325 | if (!request.ok) throw request; |
333 | })); | ||
334 | if (!request.ok) { | ||
335 | throw request; | ||
336 | } | ||
337 | const data = await request.json(); | 326 | const data = await request.json(); |
338 | |||
339 | const recipePreviews = this._mapRecipePreviewModel(data); | 327 | const recipePreviews = this._mapRecipePreviewModel(data); |
340 | debug('ServerApi::getRecipes resolves', recipePreviews); | 328 | debug('ServerApi::getRecipes resolves', recipePreviews); |
341 | |||
342 | return recipePreviews; | 329 | return recipePreviews; |
343 | } | 330 | } |
344 | 331 | ||
345 | async getFeaturedRecipePreviews() { | 332 | async getFeaturedRecipePreviews() { |
346 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/popular`, this._prepareAuthRequest({ | 333 | const request = await sendAuthRequest(`${API_URL}/recipes/popular`); |
347 | method: 'GET', | 334 | if (!request.ok) throw request; |
348 | })); | ||
349 | if (!request.ok) { | ||
350 | throw request; | ||
351 | } | ||
352 | const data = await request.json(); | ||
353 | 335 | ||
336 | const data = await request.json(); | ||
354 | // data = this._addLocalRecipesToPreviews(data); | 337 | // data = this._addLocalRecipesToPreviews(data); |
355 | 338 | ||
356 | const recipePreviews = this._mapRecipePreviewModel(data); | 339 | const recipePreviews = this._mapRecipePreviewModel(data); |
@@ -359,14 +342,11 @@ export default class ServerApi { | |||
359 | } | 342 | } |
360 | 343 | ||
361 | async searchRecipePreviews(needle) { | 344 | async searchRecipePreviews(needle) { |
362 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/search?needle=${needle}`, this._prepareAuthRequest({ | 345 | const url = `${API_URL}/recipes/search?needle=${needle}`; |
363 | method: 'GET', | 346 | const request = await sendAuthRequest(url); |
364 | })); | 347 | if (!request.ok) throw request; |
365 | if (!request.ok) { | ||
366 | throw request; | ||
367 | } | ||
368 | const data = await request.json(); | ||
369 | 348 | ||
349 | const data = await request.json(); | ||
370 | const recipePreviews = this._mapRecipePreviewModel(data); | 350 | const recipePreviews = this._mapRecipePreviewModel(data); |
371 | debug('ServerApi::searchRecipePreviews resolves', recipePreviews); | 351 | debug('ServerApi::searchRecipePreviews resolves', recipePreviews); |
372 | return recipePreviews; | 352 | return recipePreviews; |
@@ -375,10 +355,9 @@ export default class ServerApi { | |||
375 | async getRecipePackage(recipeId) { | 355 | async getRecipePackage(recipeId) { |
376 | try { | 356 | try { |
377 | const recipesDirectory = path.join(app.getPath('userData'), 'recipes'); | 357 | const recipesDirectory = path.join(app.getPath('userData'), 'recipes'); |
378 | |||
379 | const recipeTempDirectory = path.join(recipesDirectory, 'temp', recipeId); | 358 | const recipeTempDirectory = path.join(recipesDirectory, 'temp', recipeId); |
380 | const archivePath = path.join(recipeTempDirectory, 'recipe.tar.gz'); | 359 | const archivePath = path.join(recipeTempDirectory, 'recipe.tar.gz'); |
381 | const packageUrl = `${SERVER_URL}/${API_VERSION}/recipes/download/${recipeId}`; | 360 | const packageUrl = `${API_URL}/recipes/download/${recipeId}`; |
382 | 361 | ||
383 | fs.ensureDirSync(recipeTempDirectory); | 362 | fs.ensureDirSync(recipeTempDirectory); |
384 | const res = await fetch(packageUrl); | 363 | const res = await fetch(packageUrl); |
@@ -415,26 +394,21 @@ export default class ServerApi { | |||
415 | 394 | ||
416 | // Payment | 395 | // Payment |
417 | async getPlans() { | 396 | async getPlans() { |
418 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/payment/plans`, this._prepareAuthRequest({ | 397 | const request = await sendAuthRequest(`${API_URL}/payment/plans`); |
419 | method: 'GET', | 398 | if (!request.ok) throw request; |
420 | })); | ||
421 | if (!request.ok) { | ||
422 | throw request; | ||
423 | } | ||
424 | const data = await request.json(); | 399 | const data = await request.json(); |
425 | |||
426 | const plan = new PlanModel(data); | 400 | const plan = new PlanModel(data); |
427 | debug('ServerApi::getPlans resolves', plan); | 401 | debug('ServerApi::getPlans resolves', plan); |
428 | return plan; | 402 | return plan; |
429 | } | 403 | } |
430 | 404 | ||
431 | async getHostedPage(planId) { | 405 | async getHostedPage(planId) { |
432 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/payment/init`, this._prepareAuthRequest({ | 406 | const request = await sendAuthRequest(`${API_URL}/payment/init`, { |
433 | method: 'POST', | 407 | method: 'POST', |
434 | body: JSON.stringify({ | 408 | body: JSON.stringify({ |
435 | planId, | 409 | planId, |
436 | }), | 410 | }), |
437 | })); | 411 | }); |
438 | if (!request.ok) { | 412 | if (!request.ok) { |
439 | throw request; | 413 | throw request; |
440 | } | 414 | } |
@@ -445,25 +419,16 @@ export default class ServerApi { | |||
445 | } | 419 | } |
446 | 420 | ||
447 | async getPaymentDashboardUrl() { | 421 | async getPaymentDashboardUrl() { |
448 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/billing`, this._prepareAuthRequest({ | 422 | const request = await sendAuthRequest(`${API_URL}/me/billing`); |
449 | method: 'GET', | 423 | if (!request.ok) throw request; |
450 | })); | ||
451 | if (!request.ok) { | ||
452 | throw request; | ||
453 | } | ||
454 | const data = await request.json(); | 424 | const data = await request.json(); |
455 | |||
456 | debug('ServerApi::getPaymentDashboardUrl resolves', data); | 425 | debug('ServerApi::getPaymentDashboardUrl resolves', data); |
457 | return data; | 426 | return data; |
458 | } | 427 | } |
459 | 428 | ||
460 | async getSubscriptionOrders() { | 429 | async getSubscriptionOrders() { |
461 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/me/subscription`, this._prepareAuthRequest({ | 430 | const request = await sendAuthRequest(`${API_URL}/me/subscription`); |
462 | method: 'GET', | 431 | if (!request.ok) throw request; |
463 | })); | ||
464 | if (!request.ok) { | ||
465 | throw request; | ||
466 | } | ||
467 | const data = await request.json(); | 432 | const data = await request.json(); |
468 | const orders = this._mapOrderModels(data); | 433 | const orders = this._mapOrderModels(data); |
469 | debug('ServerApi::getSubscriptionOrders resolves', orders); | 434 | debug('ServerApi::getSubscriptionOrders resolves', orders); |
@@ -472,15 +437,9 @@ export default class ServerApi { | |||
472 | 437 | ||
473 | // News | 438 | // News |
474 | async getLatestNews() { | 439 | async getLatestNews() { |
475 | // eslint-disable-next-line | 440 | const url = `${API_URL}/news?platform=${os.platform()}&arch=${os.arch()}&version=${app.getVersion()}`; |
476 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/news?platform=${os.platform()}&arch=${os.arch()}&version=${app.getVersion()}`, | 441 | const request = await sendAuthRequest(url); |
477 | this._prepareAuthRequest({ | 442 | if (!request.ok) throw request; |
478 | method: 'GET', | ||
479 | })); | ||
480 | |||
481 | if (!request.ok) { | ||
482 | throw request; | ||
483 | } | ||
484 | const data = await request.json(); | 443 | const data = await request.json(); |
485 | const news = this._mapNewsModels(data); | 444 | const news = this._mapNewsModels(data); |
486 | debug('ServerApi::getLatestNews resolves', news); | 445 | debug('ServerApi::getLatestNews resolves', news); |
@@ -488,23 +447,16 @@ export default class ServerApi { | |||
488 | } | 447 | } |
489 | 448 | ||
490 | async hideNews(id) { | 449 | async hideNews(id) { |
491 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/news/${id}/read`, | 450 | const request = await sendAuthRequest(`${API_URL}/news/${id}/read`); |
492 | this._prepareAuthRequest({ | 451 | if (!request.ok) throw request; |
493 | method: 'GET', | ||
494 | })); | ||
495 | |||
496 | if (!request.ok) { | ||
497 | throw request; | ||
498 | } | ||
499 | |||
500 | debug('ServerApi::hideNews resolves', id); | 452 | debug('ServerApi::hideNews resolves', id); |
501 | } | 453 | } |
502 | 454 | ||
503 | // Health Check | 455 | // Health Check |
504 | async healthCheck() { | 456 | async healthCheck() { |
505 | const request = await window.fetch(`${SERVER_URL}/health`, this._prepareAuthRequest({ | 457 | const request = await sendAuthRequest(`${SERVER_URL}/health`, { |
506 | method: 'GET', | 458 | method: 'GET', |
507 | }, false)); | 459 | }, false); |
508 | if (!request.ok) { | 460 | if (!request.ok) { |
509 | throw request; | 461 | throw request; |
510 | } | 462 | } |
@@ -520,10 +472,7 @@ export default class ServerApi { | |||
520 | if (Object.prototype.hasOwnProperty.call(config, 'services')) { | 472 | if (Object.prototype.hasOwnProperty.call(config, 'services')) { |
521 | const services = await Promise.all(config.services.map(async (s) => { | 473 | const services = await Promise.all(config.services.map(async (s) => { |
522 | const service = s; | 474 | const service = s; |
523 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/recipes/${s.service}`, | 475 | const request = await sendAuthRequest(`${API_URL}/recipes/${s.service}`); |
524 | this._prepareAuthRequest({ | ||
525 | method: 'GET', | ||
526 | })); | ||
527 | 476 | ||
528 | if (request.status === 200) { | 477 | if (request.status === 200) { |
529 | const data = await request.json(); | 478 | const data = await request.json(); |
@@ -546,9 +495,7 @@ export default class ServerApi { | |||
546 | // Helper | 495 | // Helper |
547 | async _mapServiceModels(services) { | 496 | async _mapServiceModels(services) { |
548 | const recipes = services.map(s => s.recipeId); | 497 | const recipes = services.map(s => s.recipeId); |
549 | |||
550 | await this._bulkRecipeCheck(recipes); | 498 | await this._bulkRecipeCheck(recipes); |
551 | |||
552 | /* eslint-disable no-return-await */ | 499 | /* eslint-disable no-return-await */ |
553 | return Promise.all(services.map(async service => await this._prepareServiceModel(service))); | 500 | return Promise.all(services.map(async service => await this._prepareServiceModel(service))); |
554 | /* eslint-enable no-return-await */ | 501 | /* eslint-enable no-return-await */ |
@@ -632,26 +579,6 @@ export default class ServerApi { | |||
632 | }).filter(orderItem => orderItem !== null); | 579 | }).filter(orderItem => orderItem !== null); |
633 | } | 580 | } |
634 | 581 | ||
635 | _prepareAuthRequest(options, auth = true) { | ||
636 | const request = Object.assign(options, { | ||
637 | mode: 'cors', | ||
638 | headers: Object.assign({ | ||
639 | 'Content-Type': 'application/json', | ||
640 | 'X-Franz-Source': 'desktop', | ||
641 | 'X-Franz-Version': app.getVersion(), | ||
642 | 'X-Franz-platform': process.platform, | ||
643 | 'X-Franz-Timezone-Offset': new Date().getTimezoneOffset(), | ||
644 | 'X-Franz-System-Locale': app.getLocale(), | ||
645 | }, options.headers), | ||
646 | }); | ||
647 | |||
648 | if (auth) { | ||
649 | request.headers.Authorization = `Bearer ${localStorage.getItem('authToken')}`; | ||
650 | } | ||
651 | |||
652 | return request; | ||
653 | } | ||
654 | |||
655 | _getDevRecipes() { | 582 | _getDevRecipes() { |
656 | const recipesDirectory = getDevRecipeDirectory(); | 583 | const recipesDirectory = getDevRecipeDirectory(); |
657 | try { | 584 | try { |
diff --git a/src/api/utils/auth.js b/src/api/utils/auth.js new file mode 100644 index 000000000..6dbdeaa7f --- /dev/null +++ b/src/api/utils/auth.js | |||
@@ -0,0 +1,28 @@ | |||
1 | import { remote } from 'electron'; | ||
2 | import localStorage from 'mobx-localstorage'; | ||
3 | |||
4 | const { app } = remote; | ||
5 | |||
6 | export const prepareAuthRequest = (options = { method: 'GET' }, auth = true) => { | ||
7 | const request = Object.assign(options, { | ||
8 | mode: 'cors', | ||
9 | headers: Object.assign({ | ||
10 | 'Content-Type': 'application/json', | ||
11 | 'X-Franz-Source': 'desktop', | ||
12 | 'X-Franz-Version': app.getVersion(), | ||
13 | 'X-Franz-platform': process.platform, | ||
14 | 'X-Franz-Timezone-Offset': new Date().getTimezoneOffset(), | ||
15 | 'X-Franz-System-Locale': app.getLocale(), | ||
16 | }, options.headers), | ||
17 | }); | ||
18 | |||
19 | if (auth) { | ||
20 | request.headers.Authorization = `Bearer ${localStorage.getItem('authToken')}`; | ||
21 | } | ||
22 | |||
23 | return request; | ||
24 | }; | ||
25 | |||
26 | export const sendAuthRequest = (url, options, auth) => ( | ||
27 | window.fetch(url, prepareAuthRequest(options, auth)) | ||
28 | ); | ||
diff --git a/src/styles/auth.scss b/src/styles/auth.scss index 817801982..0a075036a 100644 --- a/src/styles/auth.scss +++ b/src/styles/auth.scss | |||
@@ -107,7 +107,7 @@ | |||
107 | &__scroll-container { | 107 | &__scroll-container { |
108 | max-height: 100vh; | 108 | max-height: 100vh; |
109 | padding: 80px 0; | 109 | padding: 80px 0; |
110 | overflow: scroll; | 110 | overflow: auto; |
111 | width: 100%; | 111 | width: 100%; |
112 | } | 112 | } |
113 | 113 | ||
diff --git a/uidev/src/stories/input.stories.tsx b/uidev/src/stories/input.stories.tsx index c522a10c7..af5e791d0 100644 --- a/uidev/src/stories/input.stories.tsx +++ b/uidev/src/stories/input.stories.tsx | |||
@@ -66,6 +66,14 @@ storiesOf('Input') | |||
66 | value="faulty input" | 66 | value="faulty input" |
67 | error="This is a generic error message." | 67 | error="This is a generic error message." |
68 | /> | 68 | /> |
69 | )) | ||
70 | .add('Type number with min & max', () => ( | ||
71 | <Input | ||
72 | {...defaultProps()} | ||
73 | type="number" | ||
74 | min={1} | ||
75 | max={10} | ||
76 | /> | ||
69 | )); | 77 | )); |
70 | 78 | ||
71 | storiesOf('Password') | 79 | storiesOf('Password') |