,
41 | nextElement: ClassicElement,
42 | container: Element,
43 | callback?: (component: ClassicComponent
) => any): ClassicComponent
;
44 | function unstable_renderSubtreeIntoContainer
(
45 | parentComponent: Component,
46 | nextElement: ReactElement,
47 | container: Element,
48 | callback?: (component: Component
) => any): Component
;
49 | }
50 |
51 | namespace __DOMServer {
52 | function renderToString(element: ReactElement): string;
53 | function renderToStaticMarkup(element: ReactElement): string;
54 | var version: string;
55 | }
56 | }
57 |
58 | declare module "react-dom" {
59 | import DOM = __React.__DOM;
60 | export = DOM;
61 | }
62 |
63 | declare module "react-dom/server" {
64 | import DOMServer = __React.__DOMServer;
65 | export = DOMServer;
66 | }
67 |
--------------------------------------------------------------------------------
/typescript/typings/react/react-helmet.d.ts:
--------------------------------------------------------------------------------
1 |
2 | ///
3 |
4 | declare module ReactHelmet {
5 | export interface ReactHelmetInterface {
6 | rewind(): any;
7 | }
8 | }
9 |
10 | // declare var reactHelmet: ReactHelmet.ReactHelmetInterface;
11 | declare var reactHelmet: any;
12 |
13 | declare module 'react-helmet' {
14 | export default reactHelmet;
15 | }
16 |
--------------------------------------------------------------------------------
/typescript/typings/redux/react-redux.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for react-redux 2.1.2
2 | // Project: https://github.com/rackt/react-redux
3 | // Definitions by: Qubo
4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped
5 |
6 | ///
7 | ///
8 |
9 | declare module "react-redux" {
10 | import { Component } from 'react';
11 | import { Store, Dispatch, ActionCreator } from 'redux';
12 |
13 | export class ElementClass extends Component { }
14 | export interface ClassDecorator {
15 | (component: T): T
16 | }
17 |
18 | /**
19 | * Connects a React component to a Redux store.
20 | * @param mapStateToProps
21 | * @param mapDispatchToProps
22 | * @param mergeProps
23 | * @param options
24 | */
25 | export function connect(mapStateToProps?: MapStateToProps,
26 | mapDispatchToProps?: MapDispatchToPropsFunction|MapDispatchToPropsObject,
27 | mergeProps?: MergeProps,
28 | options?: Options): ClassDecorator;
29 |
30 | interface MapStateToProps {
31 | (state: any, ownProps?: any): any;
32 | }
33 |
34 | interface MapDispatchToPropsFunction {
35 | (dispatch: Dispatch, ownProps?: any): any;
36 | }
37 |
38 | interface MapDispatchToPropsObject {
39 | [name: string]: ActionCreator;
40 | }
41 |
42 | interface MergeProps {
43 | (stateProps: any, dispatchProps: any, ownProps: any): any;
44 | }
45 |
46 | interface Options {
47 | /**
48 | * If true, implements shouldComponentUpdate and shallowly compares the result of mergeProps,
49 | * preventing unnecessary updates, assuming that the component is a “pure” component
50 | * and does not rely on any input or state other than its props and the selected Redux store’s state.
51 | * Defaults to true.
52 | * @default true
53 | */
54 | pure: boolean;
55 | }
56 |
57 | export interface Property {
58 | /**
59 | * The single Redux store in your application.
60 | */
61 | store?: Store;
62 | children?: Function;
63 | }
64 |
65 | /**
66 | * Makes the Redux store available to the connect() calls in the component hierarchy below.
67 | */
68 | export class Provider extends Component { }
69 | }
70 |
--------------------------------------------------------------------------------
/typescript/typings/redux/redux-async-connect.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare module "redux-async-connect" {
3 | export function ReduxAsyncConnect(): any;
4 | export function loadOnServer(props: any, store: any): any;
5 | }
6 |
--------------------------------------------------------------------------------
/typescript/typings/redux/redux-devtools-dock-monitor.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for redux-devtools-dock-monitor 1.0.1
2 | // Project: https://github.com/gaearon/redux-devtools-dock-monitor
3 | // Definitions by: Petryshyn Sergii
4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5 |
6 | ///
7 |
8 | declare module "redux-devtools-dock-monitor" {
9 | import * as React from 'react'
10 |
11 | interface IDockMonitorProps {
12 | /**
13 | * Any valid Redux DevTools monitor.
14 | */
15 | children?: React.ReactNode
16 |
17 | /**
18 | * A key or a key combination that toggles the dock visibility.
19 | * Must be recognizable by parse-key (for example, 'ctrl-h')
20 | */
21 | toggleVisibilityKey: string
22 |
23 | /**
24 | * A key or a key combination that toggles the dock position.
25 | * Must be recognizable by parse-key (for example, 'ctrl-w')
26 | */
27 | changePositionKey: string
28 |
29 | /**
30 | * When true, the dock size is a fraction of the window size, fixed otherwise.
31 | *
32 | * @default true
33 | */
34 | fluid?: boolean
35 |
36 | /**
37 | * Size of the dock. When fluid is true, a float (0.5 means half the window size).
38 | * When fluid is false, a width in pixels
39 | *
40 | * @default 0.3 (3/10th of the window size)
41 | */
42 | defaultSize?: number
43 |
44 | /**
45 | * Where the dock appears on the screen.
46 | * Valid values: 'left', 'top', 'right', 'bottom'
47 | *
48 | * @default 'right'
49 | */
50 | defaultPosition?: string
51 |
52 | /**
53 | * @default true
54 | */
55 | defaultIsVisible?: boolean
56 | }
57 |
58 | export default class DockMonitor extends React.Component {}
59 | }
60 |
--------------------------------------------------------------------------------
/typescript/typings/redux/redux-devtools-log-monitor.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for redux-devtools-log-monitor 1.0.1
2 | // Project: https://github.com/gaearon/redux-devtools-log-monitor
3 | // Definitions by: Petryshyn Sergii
4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5 |
6 | ///
7 |
8 | declare module "redux-devtools-log-monitor" {
9 | import * as React from 'react'
10 |
11 | interface ILogMonitorProps {
12 | /**
13 | * Either a string referring to one of the themes provided by
14 | * redux-devtools-themes or a custom object of the same format.
15 | *
16 | * @see https://github.com/gaearon/redux-devtools-themes
17 | */
18 | theme?: string
19 |
20 | /**
21 | * A function that selects the slice of the state for DevTools to show.
22 | *
23 | * @example state => state.thePart.iCare.about.
24 | * @default state => state.
25 | */
26 | select?: (state: any) => any
27 |
28 | /**
29 | * When true, records the current scroll top every second so it
30 | * can be restored on refresh. This only has effect when used together
31 | * with persistState() enhancer from Redux DevTools.
32 | *
33 | * @default true
34 | */
35 | preserveScrollTop?: boolean
36 | }
37 |
38 | export default class LogMonitor extends React.Component {}
39 | }
40 |
--------------------------------------------------------------------------------
/typescript/typings/redux/redux-devtools.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for redux-devtools 3.0.0
2 | // Project: https://github.com/gaearon/redux-devtools
3 | // Definitions by: Petryshyn Sergii
4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5 |
6 | ///
7 |
8 | declare module "redux-devtools" {
9 | import * as React from 'react'
10 |
11 | interface IDevTools {
12 | new (): JSX.ElementClass
13 | instrument(): Function
14 | }
15 |
16 | export function createDevTools(el: React.ReactElement): IDevTools
17 | export function persistState(debugSessionKey: string): Function
18 |
19 | var factory: { instrument(): Function }
20 |
21 | export default factory;
22 | }
23 |
--------------------------------------------------------------------------------
/typescript/typings/redux/redux-logger.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for redux-logger v2.0.0
2 | // Project: https://github.com/fcomb/redux-logger
3 | // Definitions by: Alexander Rusakov
4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped
5 |
6 | // FIXED VERSION
7 |
8 | ///
9 |
10 | declare module 'redux-logger' {
11 |
12 | function createLogger(options?: createLogger.ReduxLoggerOptions): Redux.Middleware;
13 |
14 | namespace createLogger {
15 | interface ReduxLoggerOptions {
16 | actionTransformer?: (action: any) => any;
17 | collapsed?: boolean;
18 | duration?: boolean;
19 | level?: string;
20 | logger?: any;
21 | predicate?: (getState: Function, action: any) => boolean;
22 | timestamp?: boolean;
23 | transformer?: (state:any) => any;
24 | }
25 | }
26 |
27 | export = createLogger;
28 | }
29 |
--------------------------------------------------------------------------------
/typescript/typings/redux/redux-thunk.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for redux-thunk
2 | // Project: https://github.com/gaearon/redux-thunk
3 | // Definitions by: Qubo
4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped
5 |
6 | ///
7 |
8 | declare module ReduxThunk {
9 | export interface Thunk extends Redux.Middleware {}
10 | export interface ThunkInterface {
11 | (dispatch: Redux.Dispatch, getState?: () => T): any;
12 | }
13 | }
14 |
15 | declare module "redux-thunk" {
16 | var thunk: ReduxThunk.Thunk;
17 | export = thunk;
18 | }
19 |
--------------------------------------------------------------------------------
/typescript/typings/redux/redux.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for Redux v1.0.0
2 | // Project: https://github.com/rackt/redux
3 | // Definitions by: William Buchwalter , Vincent Prouillet
4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped
5 |
6 | declare module Redux {
7 |
8 | interface ActionCreator extends Function {
9 | (...args: any[]): any;
10 | }
11 |
12 | interface Reducer extends Function {
13 | (state: any, action: any): any;
14 | }
15 |
16 | interface Dispatch extends Function {
17 | (action: any): any;
18 | }
19 |
20 | interface StoreMethods {
21 | dispatch: Dispatch;
22 | getState(): any;
23 | }
24 |
25 |
26 | interface MiddlewareArg {
27 | dispatch: Dispatch;
28 | getState: Function;
29 | }
30 |
31 | interface Middleware extends Function {
32 | (obj: MiddlewareArg): Function;
33 | }
34 |
35 | class Store {
36 | getReducer(): Reducer;
37 | replaceReducer(nextReducer: Reducer): void;
38 | dispatch(action: any): any;
39 | getState(): any;
40 | subscribe(listener: Function): Function;
41 | }
42 |
43 | function createStore(reducer: Reducer, initialState?: any): Store;
44 | function bindActionCreators(actionCreators: T, dispatch: Dispatch): T;
45 | function combineReducers(reducers: any): Reducer;
46 | function applyMiddleware(...middlewares: Middleware[]): Function;
47 | function compose(...functions: Function[]): T;
48 | }
49 |
50 | declare module "redux" {
51 | export = Redux;
52 | }
53 |
--------------------------------------------------------------------------------
/typescript/typings/socket.io-client.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for socket.io-client 1.4.4
2 | // Project: http://socket.io/
3 | // Definitions by: PROGRE , Damian Connolly , Florent Poujol
4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped
5 |
6 | declare var io: SocketIOClientStatic;
7 |
8 | declare module 'socket.io-client' {
9 | export = io;
10 | }
11 |
12 | interface SocketIOClientStatic {
13 |
14 | /**
15 | * Looks up an existing 'Manager' for multiplexing. If the user summons:
16 | * 'io( 'http://localhost/a' );'
17 | * 'io( 'http://localhost/b' );'
18 | *
19 | * We reuse the existing instance based on the same scheme/port/host, and
20 | * we initialize sockets for each namespace. If autoConnect isn't set to
21 | * false in the options, then we'll automatically connect
22 | * @param uri The uri that we'll connect to, including the namespace, where '/' is the default one (e.g. http://localhost:4000/somenamespace)
23 | * @opts Any connect options that we want to pass along
24 | * @return A Socket object
25 | */
26 | ( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket;
27 |
28 | /**
29 | * Auto-connects to the window location and defalt namespace.
30 | * E.g. window.protocol + '//' + window.host + ':80/'
31 | * @opts Any connect options that we want to pass along
32 | * @return A Socket object
33 | */
34 | ( opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket;
35 |
36 | /**
37 | * @see the default constructor (io(uri, opts))
38 | */
39 | connect( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket;
40 |
41 | /**
42 | * @see the default constructor (io(opts))
43 | */
44 | connect( opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket;
45 |
46 | /**
47 | * The socket.io protocol revision number this client works with
48 | * @default 4
49 | */
50 | protocol: number;
51 |
52 | /**
53 | * Socket constructor - exposed for the standalone build
54 | */
55 | Socket: SocketIOClient.Socket;
56 |
57 | /**
58 | * Manager constructor - exposed for the standalone build
59 | */
60 | Manager: SocketIOClient.ManagerStatic;
61 | }
62 |
63 | declare module SocketIOClient {
64 |
65 | /**
66 | * The base emiter class, used by Socket and Manager
67 | */
68 | interface Emitter {
69 | /**
70 | * Adds a listener for a particular event. Calling multiple times will add
71 | * multiple listeners
72 | * @param event The event that we're listening for
73 | * @param fn The function to call when we get the event. Parameters depend on the
74 | * event in question
75 | * @return This Emitter
76 | */
77 | on( event: string, fn: Function ):Emitter;
78 |
79 | /**
80 | * @see on( event, fn )
81 | */
82 | addEventListener( event: string, fn: Function ):Emitter;
83 |
84 | /**
85 | * Adds a listener for a particular event that will be invoked
86 | * a single time before being automatically removed
87 | * @param event The event that we're listening for
88 | * @param fn The function to call when we get the event. Parameters depend on
89 | * the event in question
90 | * @return This Emitter
91 | */
92 | once( event: string, fn: Function ):Emitter;
93 |
94 | /**
95 | * Removes a listener for a particular type of event. This will either
96 | * remove a specific listener, or all listeners for this type of event
97 | * @param event The event that we want to remove the listener of
98 | * @param fn The function to remove, or null if we want to remove all functions
99 | * @return This Emitter
100 | */
101 | off( event: string, fn?: Function ):Emitter;
102 |
103 | /**
104 | * @see off( event, fn )
105 | */
106 | removeListener( event: string, fn?: Function ):Emitter;
107 |
108 | /**
109 | * @see off( event, fn )
110 | */
111 | removeEventListener( event: string, fn?: Function ):Emitter;
112 |
113 | /**
114 | * Removes all event listeners on this object
115 | * @return This Emitter
116 | */
117 | removeAllListeners():Emitter;
118 |
119 | /**
120 | * Emits 'event' with the given args
121 | * @param event The event that we want to emit
122 | * @param args Optional arguments to emit with the event
123 | * @return Emitter
124 | */
125 | emit( event: string, ...args: any[] ):Emitter;
126 |
127 | /**
128 | * Returns all the callbacks for a particular event
129 | * @param event The event that we're looking for the callbacks of
130 | * @return An array of callback Functions, or an empty array if we don't have any
131 | */
132 | listeners( event: string ):Function[];
133 |
134 | /**
135 | * Returns if we have listeners for a particular event
136 | * @param event The event that we want to check if we've listeners for
137 | * @return True if we have listeners for this event, false otherwise
138 | */
139 | hasListeners( event: string ):boolean;
140 | }
141 |
142 | /**
143 | * The Socket static interface
144 | */
145 | interface SocketStatic {
146 |
147 | /**
148 | * Creates a new Socket, used for communicating with a specific namespace
149 | * @param io The Manager that's controlling this socket
150 | * @param nsp The namespace that this socket is for (@default '/')
151 | * @return A new Socket
152 | */
153 | ( io: SocketIOClient.Manager, nsp: string ): Socket;
154 |
155 | /**
156 | * Creates a new Socket, used for communicating with a specific namespace
157 | * @param io The Manager that's controlling this socket
158 | * @param nsp The namespace that this socket is for (@default '/')
159 | * @return A new Socket
160 | */
161 | new ( url: string, opts: any ): SocketIOClient.Manager;
162 | }
163 |
164 | /**
165 | * The Socket that we use to connect to a Namespace on the server
166 | */
167 | interface Socket extends Emitter {
168 |
169 | /**
170 | * The Manager that's controller this socket
171 | */
172 | io: SocketIOClient.Manager;
173 |
174 | /**
175 | * The namespace that this socket is for
176 | * @default '/'
177 | */
178 | nsp: string;
179 |
180 | /**
181 | * The ID of the socket; matches the server ID and is set when we're connected, and cleared
182 | * when we're disconnected
183 | */
184 | id: string;
185 |
186 | /**
187 | * Are we currently connected?
188 | * @default false
189 | */
190 | connected: boolean;
191 |
192 | /**
193 | * Are we currently disconnected?
194 | * @default true
195 | */
196 | disconnected: boolean;
197 |
198 | /**
199 | * Opens our socket so that it connects. If the 'autoConnect' option for io is
200 | * true (default), then this is called automatically when the Socket is created
201 | */
202 | open(): Socket;
203 |
204 | /**
205 | * @see open();
206 | */
207 | connect(): Socket;
208 |
209 | /**
210 | * Sends a 'message' event
211 | * @param args Any optional arguments that we want to send
212 | * @see emit
213 | * @return This Socket
214 | */
215 | send( ...args: any[] ):Socket;
216 |
217 | /**
218 | * An override of the base emit. If the event is one of:
219 | * connect
220 | * connect_error
221 | * connect_timeout
222 | * connecting
223 | * disconnect
224 | * error
225 | * reconnect
226 | * reconnect_attempt
227 | * reconnect_failed
228 | * reconnect_error
229 | * reconnecting
230 | * ping
231 | * pong
232 | * then the event is emitted normally. Otherwise, if we're connected, the
233 | * event is sent. Otherwise, it's buffered.
234 | *
235 | * If the last argument is a function, then it will be called
236 | * as an 'ack' when the response is received. The parameter(s) of the
237 | * ack will be whatever data is returned from the event
238 | * @param event The event that we're emitting
239 | * @param args Optional arguments to send with the event
240 | * @return This Socket
241 | */
242 | emit( event: string, ...args: any[] ):Socket;
243 |
244 | /**
245 | * Disconnects the socket manually
246 | * @return This Socket
247 | */
248 | close():Socket;
249 |
250 | /**
251 | * @see close()
252 | */
253 | disconnect():Socket;
254 |
255 | /**
256 | * Sets the compress flag.
257 | * @param compress If `true`, compresses the sending data
258 | * @return this Socket
259 | */
260 | compress(compress: boolean):Socket;
261 | }
262 |
263 | /**
264 | * The Manager static interface
265 | */
266 | interface ManagerStatic {
267 | /**
268 | * Creates a new Manager
269 | * @param uri The URI that we're connecting to (e.g. http://localhost:4000)
270 | * @param opts Any connection options that we want to use (and pass to engine.io)
271 | * @return A Manager
272 | */
273 | ( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Manager;
274 |
275 | /**
276 | * Creates a new Manager with the default URI (window host)
277 | * @param opts Any connection options that we want to use (and pass to engine.io)
278 | */
279 | ( opts: SocketIOClient.ConnectOpts ):SocketIOClient.Manager;
280 |
281 | /**
282 | * @see default constructor
283 | */
284 | new ( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Manager;
285 |
286 | /**
287 | * @see default constructor
288 | */
289 | new ( opts: SocketIOClient.ConnectOpts ):SocketIOClient.Manager;
290 | }
291 |
292 | /**
293 | * The Manager class handles all the Namespaces and Sockets that we're using
294 | */
295 | interface Manager extends Emitter {
296 |
297 | /**
298 | * All the namespaces currently controlled by this Manager, and the Sockets
299 | * that we're using to communicate with them
300 | */
301 | nsps: { [namespace:string]: Socket };
302 |
303 | /**
304 | * The connect options that we used when creating this Manager
305 | */
306 | opts: SocketIOClient.ConnectOpts;
307 |
308 | /**
309 | * The state of the Manager. Either 'closed', 'opening', or 'open'
310 | */
311 | readyState: string;
312 |
313 | /**
314 | * The URI that this manager is for (host + port), e.g. 'http://localhost:4000'
315 | */
316 | uri: string;
317 |
318 | /**
319 | * The currently connected sockets
320 | */
321 | connecting: Socket[];
322 |
323 | /**
324 | * If we should auto connect (also used when creating Sockets). Set via the
325 | * opts object
326 | */
327 | autoConnect: boolean;
328 |
329 | /**
330 | * Gets if we should reconnect automatically
331 | * @default true
332 | */
333 | reconnection(): boolean;
334 |
335 | /**
336 | * Sets if we should reconnect automatically
337 | * @param v True if we should reconnect automatically, false otherwise
338 | * @default true
339 | * @return This Manager
340 | */
341 | reconnection( v: boolean ): Manager;
342 |
343 | /**
344 | * Gets the number of reconnection attempts we should try before giving up
345 | * @default Infinity
346 | */
347 | reconnectionAttempts(): number;
348 |
349 | /**
350 | * Sets the number of reconnection attempts we should try before giving up
351 | * @param v The number of attempts we should do before giving up
352 | * @default Infinity
353 | * @return This Manager
354 | */
355 | reconnectionAttempts( v: number ): Manager;
356 |
357 | /**
358 | * Gets the delay in milliseconds between each reconnection attempt
359 | * @default 1000
360 | */
361 | reconnectionDelay(): number;
362 |
363 | /**
364 | * Sets the delay in milliseconds between each reconnection attempt
365 | * @param v The delay in milliseconds
366 | * @default 1000
367 | * @return This Manager
368 | */
369 | reconnectionDelay( v: number ): Manager;
370 |
371 | /**
372 | * Gets the max reconnection delay in milliseconds between each reconnection
373 | * attempt
374 | * @default 5000
375 | */
376 | reconnectionDelayMax(): number;
377 |
378 | /**
379 | * Sets the max reconnection delay in milliseconds between each reconnection
380 | * attempt
381 | * @param v The max reconnection dleay in milliseconds
382 | * @return This Manager
383 | */
384 | reconnectionDelayMax( v: number ): Manager;
385 |
386 | /**
387 | * Gets the randomisation factor used in the exponential backoff jitter
388 | * when reconnecting
389 | * @default 0.5
390 | */
391 | randomizationFactor(): number;
392 |
393 | /**
394 | * Sets the randomisation factor used in the exponential backoff jitter
395 | * when reconnecting
396 | * @param The reconnection randomisation factor
397 | * @default 0.5
398 | * @return This Manager
399 | */
400 | randomizationFactor( v: number ): Manager;
401 |
402 | /**
403 | * Gets the timeout in milliseconds for our connection attempts
404 | * @default 20000
405 | */
406 | timeout(): number;
407 |
408 | /**
409 | * Sets the timeout in milliseconds for our connection attempts
410 | * @param The connection timeout milliseconds
411 | * @return This Manager
412 | */
413 | timeout(v: boolean): Manager;
414 |
415 | /**
416 | * Sets the current transport socket and opens our connection
417 | * @param fn An optional callback to call when our socket has either opened, or
418 | * failed. It can take one optional parameter of type Error
419 | * @return This Manager
420 | */
421 | open( fn?: (err?: any) => void ): Manager;
422 |
423 | /**
424 | * @see open( fn );
425 | */
426 | connect( fn?: (err?: any) => void ): Manager;
427 |
428 | /**
429 | * Creates a new Socket for the given namespace
430 | * @param nsp The namespace that this Socket is for
431 | * @return A new Socket, or if one has already been created for this namespace,
432 | * an existing one
433 | */
434 | socket( nsp: string ): Socket;
435 | }
436 |
437 | /**
438 | * Options we can pass to the socket when connecting
439 | */
440 | interface ConnectOpts {
441 |
442 | /**
443 | * Should we force a new Manager for this connection?
444 | * @default false
445 | */
446 | forceNew?: boolean;
447 |
448 | /**
449 | * Should we multiplex our connection (reuse existing Manager) ?
450 | * @default true
451 | */
452 | multiplex?: boolean;
453 |
454 | /**
455 | * The path to get our client file from, in the case of the server
456 | * serving it
457 | * @default '/socket.io'
458 | */
459 | path?: string;
460 |
461 | /**
462 | * Should we allow reconnections?
463 | * @default true
464 | */
465 | reconnection?: boolean;
466 |
467 | /**
468 | * How many reconnection attempts should we try?
469 | * @default Infinity
470 | */
471 | reconnectionAttempts?: number;
472 |
473 | /**
474 | * The time delay in milliseconds between reconnection attempts
475 | * @default 1000
476 | */
477 | reconnectionDelay?: number;
478 |
479 | /**
480 | * The max time delay in milliseconds between reconnection attempts
481 | * @default 5000
482 | */
483 | reconnectionDelayMax?: number;
484 |
485 | /**
486 | * Used in the exponential backoff jitter when reconnecting
487 | * @default 0.5
488 | */
489 | randomizationFactor?: number;
490 |
491 | /**
492 | * The timeout in milliseconds for our connection attempt
493 | * @default 20000
494 | */
495 | timeout?: number;
496 |
497 | /**
498 | * Should we automically connect?
499 | * @default true
500 | */
501 | autoConnect?: boolean;
502 |
503 | /**
504 | * The host that we're connecting to. Set from the URI passed when connecting
505 | */
506 | host?: string;
507 |
508 | /**
509 | * The hostname for our connection. Set from the URI passed when connecting
510 | */
511 | hostname?: string;
512 |
513 | /**
514 | * If this is a secure connection. Set from the URI passed when connecting
515 | */
516 | secure?: boolean;
517 |
518 | /**
519 | * The port for our connection. Set from the URI passed when connecting
520 | */
521 | port?: string;
522 |
523 | /**
524 | * Any query parameters in our uri. Set from the URI passed when connecting
525 | */
526 | query?: Object;
527 |
528 | /**
529 | * `http.Agent` to use, defaults to `false` (NodeJS only)
530 | */
531 | agent?: string|boolean;
532 |
533 | /**
534 | * Whether the client should try to upgrade the transport from
535 | * long-polling to something better.
536 | * @default true
537 | */
538 | upgrade?: boolean;
539 |
540 | /**
541 | * Forces JSONP for polling transport.
542 | */
543 | forceJSONP?: boolean;
544 |
545 | /**
546 | * Determines whether to use JSONP when necessary for polling. If
547 | * disabled (by settings to false) an error will be emitted (saying
548 | * "No transports available") if no other transports are available.
549 | * If another transport is available for opening a connection (e.g.
550 | * WebSocket) that transport will be used instead.
551 | * @default true
552 | */
553 | jsonp?: boolean;
554 |
555 | /**
556 | * Forces base 64 encoding for polling transport even when XHR2
557 | * responseType is available and WebSocket even if the used standard
558 | * supports binary.
559 | */
560 | forceBase64?: boolean;
561 |
562 | /**
563 | * Enables XDomainRequest for IE8 to avoid loading bar flashing with
564 | * click sound. default to `false` because XDomainRequest has a flaw
565 | * of not sending cookie.
566 | * @default false
567 | */
568 | enablesXDR?: boolean;
569 |
570 | /**
571 | * The param name to use as our timestamp key
572 | * @default 't'
573 | */
574 | timestampParam?: string;
575 |
576 | /**
577 | * Whether to add the timestamp with each transport request. Note: this
578 | * is ignored if the browser is IE or Android, in which case requests
579 | * are always stamped
580 | * @default false
581 | */
582 | timestampRequests?: boolean;
583 |
584 | /**
585 | * A list of transports to try (in order). Engine.io always attempts to
586 | * connect directly with the first one, provided the feature detection test
587 | * for it passes.
588 | * @default ['polling','websocket']
589 | */
590 | transports?: string[];
591 |
592 | /**
593 | * The port the policy server listens on
594 | * @default 843
595 | */
596 | policyPost?: number;
597 |
598 | /**
599 | * If true and if the previous websocket connection to the server succeeded,
600 | * the connection attempt will bypass the normal upgrade process and will
601 | * initially try websocket. A connection attempt following a transport error
602 | * will use the normal upgrade process. It is recommended you turn this on
603 | * only when using SSL/TLS connections, or if you know that your network does
604 | * not block websockets.
605 | * @default false
606 | */
607 | rememberUpgrade?: boolean;
608 |
609 | /**
610 | * Are we only interested in transports that support binary?
611 | */
612 | onlyBinaryUpgrades?: boolean;
613 |
614 | /**
615 | * (SSL) Certificate, Private key and CA certificates to use for SSL.
616 | * Can be used in Node.js client environment to manually specify
617 | * certificate information.
618 | */
619 | pfx?: string;
620 |
621 | /**
622 | * (SSL) Private key to use for SSL. Can be used in Node.js client
623 | * environment to manually specify certificate information.
624 | */
625 | key?: string;
626 |
627 | /**
628 | * (SSL) A string or passphrase for the private key or pfx. Can be
629 | * used in Node.js client environment to manually specify certificate
630 | * information.
631 | */
632 | passphrase?: string
633 |
634 | /**
635 | * (SSL) Public x509 certificate to use. Can be used in Node.js client
636 | * environment to manually specify certificate information.
637 | */
638 | cert?: string;
639 |
640 | /**
641 | * (SSL) An authority certificate or array of authority certificates to
642 | * check the remote host against.. Can be used in Node.js client
643 | * environment to manually specify certificate information.
644 | */
645 | ca?: string|string[];
646 |
647 | /**
648 | * (SSL) A string describing the ciphers to use or exclude. Consult the
649 | * [cipher format list]
650 | * (http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) for
651 | * details on the format.. Can be used in Node.js client environment to
652 | * manually specify certificate information.
653 | */
654 | ciphers?: string;
655 |
656 | /**
657 | * (SSL) If true, the server certificate is verified against the list of
658 | * supplied CAs. An 'error' event is emitted if verification fails.
659 | * Verification happens at the connection level, before the HTTP request
660 | * is sent. Can be used in Node.js client environment to manually specify
661 | * certificate information.
662 | */
663 | rejectUnauthorized?: boolean;
664 |
665 | }
666 | }
667 |
--------------------------------------------------------------------------------
/typescript/typings/webpack.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare var __DEVELOPMENT__: any;
3 | declare var System: any;
4 |
5 | interface NodeModule {
6 | hot: any;
7 | }
8 |
--------------------------------------------------------------------------------
/typescript/universal/Routes.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IndexRoute, Route } from 'react-router';
3 | import { Store } from 'redux';
4 |
5 | import { endLoading } from './redux/modules/global';
6 | import App from './containers/App';
7 | import Main from './containers/ShopPage';
8 | import Product from './containers/ProductPage';
9 | import ReducerRegistry from './redux/ReducerRegistry';
10 | import IAppState from './interfaces/AppState';
11 |
12 | // Require ensure shim
13 | if(typeof (require as any).ensure !== "function") (require as any).ensure = function(d, c) { c(require) };
14 | if(typeof (require as any).include !== "function") (require as any).include = function() {};
15 |
16 | const ENV = typeof global !== 'undefined' ? (global as any).ENV : 'client';
17 |
18 | export default class routes {
19 |
20 | private store: Store = null;
21 | private reducerRegistry: any = null;
22 |
23 | constructor(reducerRegistry) {
24 | this.reducerRegistry = reducerRegistry;
25 | }
26 |
27 | /**
28 | * Only need to inject this on the CLIENT side for lazy loading
29 | */
30 | injectStore(store: Store) {
31 | this.store = store;
32 | }
33 |
34 | configure() {
35 | return (
36 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
44 |
45 | /**
46 | * ROUTE HANDLERS
47 | */
48 | private getShopPage(location, cb) {
49 | if (ENV === 'client') {
50 | System.import('./containers/ShopPage')
51 | .then(container => this.changeScreen(location, cb, container))
52 | .catch(err => console.log('Epic fail: ShopPage -- ', err));
53 | } else {
54 | (require as any).ensure(['./containers/ShopPage'], require => {
55 | const container = require('./containers/ShopPage').default;
56 | this.changeScreen(location, cb, container)
57 | });
58 | }
59 | }
60 | private getProductPage(location, cb) {
61 | if (ENV === 'client') {
62 | Promise.all([
63 | System.import('./containers/ProductPage'),
64 | System.import('./redux/modules/productPage')
65 | ])
66 | .then(([container, reducer]: any) => this.changeScreen(location, cb, container, { reducer }))
67 | .catch(err => console.log('Epic fail: ProductPage -- ', err));
68 | } else {
69 | (require as any).ensure(['./containers/ProductPage', './redux/modules/productPage'], require => {
70 | const reducer = { ['productPage']: require('./redux/modules/productPage').default };
71 | const container = require('./containers/ProductPage').default;
72 | this.changeScreen(location, cb, container, reducer)
73 | });
74 | }
75 | }
76 | private getCheckoutPage(location, cb) {
77 | if (ENV === 'client') {
78 | System.import('./containers/CheckoutPage')
79 | .then(container => this.changeScreen(location, cb, container))
80 | .catch(err => console.log('Epic fail: CheckoutPage -- ', err));
81 | } else {
82 | (require as any).ensure(['./containers/CheckoutPage'], require => {
83 | const container = require('./containers/CheckoutPage').default;
84 | this.changeScreen(location, cb, container)
85 | });
86 | }
87 | }
88 |
89 |
90 | private changeScreen(
91 | location: HistoryModule.Location,
92 | cb: Function,
93 | component: React.Component,
94 | reducer?: any
95 | ) {
96 | if (reducer) {
97 | this.reducerRegistry.register(reducer);
98 | }
99 |
100 | if (!this.store) {
101 | cb(null, component);
102 | } else if (this.store.getState().routing.location.pathname === location.pathname) {
103 | cb(null, component);
104 | this.store.dispatch(endLoading);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/typescript/universal/components/AddFundsForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as classNames from 'classnames';
4 | import * as ReactCSSTransitionGroup from 'react-addons-css-transition-group';
5 | import { connect } from 'react-redux';
6 | import IAppState from '../interfaces/AppState';
7 | import Finput from './Finput';
8 |
9 | interface IAddFundsFormProps {
10 | onSubmit: Function;
11 | }
12 | interface IAddFundsFormState {}
13 |
14 | export default class AddFundsForm extends React.Component {
15 | render() : React.ReactElement {
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 | );
29 | }
30 |
31 | addFunds() {
32 | console.log(this.refs['finput']);
33 | const finput = this.refs['finput'] as Finput;
34 | this.props.onSubmit(finput.getRawValue());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/typescript/universal/components/Alerts/Alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux'
3 | import { routeActions } from 'react-router-redux'
4 | import IAlert from '../../interfaces/Alert';
5 | import { alertClasses } from '../../constants/AlertTypes';
6 | const Helmet = require('react-helmet');
7 | const classNames = require('classnames');
8 |
9 | const ALERT_LIFESPAN = 4000;
10 |
11 | interface IAlertProps {
12 | key?: any;
13 | alert?: IAlert;
14 | dismissAlert?: Function;
15 | }
16 |
17 | export default class Alert extends React.Component {
18 | alertTimeout: number;
19 |
20 | componentDidMount() {
21 | this.alertTimeout = setTimeout(this.props.dismissAlert, ALERT_LIFESPAN);
22 | }
23 |
24 | render() : React.ReactElement {
25 | return (
26 | this.dismissAlert()}>
28 |
{this.props.alert.title}
29 |
30 |
31 | )
32 | }
33 |
34 | private dismissAlert() {
35 | this.props.dismissAlert();
36 | clearTimeout(this.alertTimeout);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/typescript/universal/components/Cart/Cart.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import CartProduct from '../../containers/Cart/CartProduct';
3 | import IProduct from '../../interfaces/Product';
4 |
5 |
6 | interface ICartProps {
7 | products: IProduct[];
8 | total: number;
9 | onCheckoutClicked: React.EventHandler;
10 | }
11 |
12 | export default class Cart extends React.Component {
13 | render() : React.ReactElement {
14 |
15 | const hasProducts = this.props.products.length > 0;
16 | const nodes = !hasProducts
17 | ? Please add some products to cart.
18 | : this.props.products.map(product =>
19 |
20 |
21 |
22 | );
23 |
24 | return (
25 |
26 |
Your Cart
27 |
28 |
{nodes}
29 |
30 |
31 | Total -
32 | £{this.props.total}
33 |
34 |
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/typescript/universal/components/CheckoutPage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classnames = require('classnames');
3 | import { checkout } from '../redux/modules/cart';
4 |
5 | import IProduct from '../interfaces/Product';
6 |
7 | import CartProduct from '../containers/Cart/CartProduct'
8 | import CartComponent from '../components/Cart/Cart'
9 |
10 | interface ICheckoutPageComponentProps {
11 | onCheckoutClicked?: () => any;
12 | products: IProduct[];
13 | total: number;
14 | }
15 | interface ICheckoutPageComponentState {}
16 |
17 | export default class CheckoutPageComponent extends React.Component {
18 |
19 | render() : React.ReactElement {
20 | require('../../../sass/checkoutPage.scss');
21 |
22 | return (
23 |
24 |
CHECKOUT
25 |
26 |
27 | {
28 | this.props.products.map(product =>
29 |
30 |
31 |
32 | )
33 | }
34 |
35 |
36 |
37 | Total -
38 | £{this.props.total}
39 |
40 |
41 |
46 |
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/typescript/universal/components/Finput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as finput from 'finput';
3 |
4 | interface IFinputProps {
5 | className?: string;
6 | scale?: number;
7 | range?: number;
8 | fixed?: boolean;
9 | thousands?: string;
10 | decimal?: string;
11 | shortcuts?: any;
12 | }
13 |
14 |
15 | export default class Finput extends React.Component {
16 | destroy: Function;
17 |
18 | componentDidMount() {
19 | const options = Object.assign({}, this.props);
20 | delete options.className;
21 | this.destroy = finput(this.refs['input'] as Element, options);
22 | }
23 | componentWillUnmount() {
24 | this.destroy && this.destroy();
25 | }
26 |
27 | render() : React.ReactElement {
28 | return (
29 |
30 | )
31 | }
32 |
33 | getRawValue() : number {
34 | return (this.refs['input'] as any).rawValue;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/typescript/universal/components/Modal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 | import * as ReactCSSTransitionGroup from 'react-addons-css-transition-group';
4 | import { connect } from 'react-redux';
5 | import IAppState from '../interfaces/AppState';
6 |
7 | const PAGE_LENGTH = 10;
8 |
9 | interface IModalProps {
10 | title: string;
11 | modalClasses?: string;
12 | onClose: React.EventHandler;
13 | children?: JSX.Element[];
14 | }
15 | interface IModalState {}
16 |
17 | export default class Modal extends React.Component {
18 | render() : React.ReactElement {
19 |
20 | return (
21 |
22 |
23 |
24 |
{this.props.title}
25 |
26 | close
27 |
28 |
29 | {this.props.children}
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/typescript/universal/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classNames = require('classnames');
3 | import ILink from '../interfaces/Link';
4 | import IUser from '../interfaces/User';
5 |
6 |
7 | // Import styles
8 |
9 | interface INavbarProps {
10 | onLinkClick?: (path: string) => any;
11 | onAddFundsClick?: () => any;
12 | routing?: any;
13 | user?: IUser;
14 | links: ILink[];
15 | }
16 |
17 |
18 | export default class NavbarComponent extends React.Component {
19 |
20 | render() : React.ReactElement<{}> {
21 |
22 | return (
23 |
59 | )
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/typescript/universal/components/PageLoader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class ProductContainer extends React.Component<{}, {}> {
4 |
5 | render() : React.ReactElement<{}> {
6 |
7 | return (
8 |
9 |
10 |
Simulating page load delay
11 |
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/typescript/universal/components/Product/Product.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux'
3 | import { routeActions } from 'react-router-redux';
4 |
5 | interface IProductProps {
6 | key?: any;
7 | id: number;
8 | price: number;
9 | quantity?: number;
10 | image: string;
11 | title: string;
12 | description?: string;
13 | children?: React.ReactElement[];
14 | quantityControls?: React.ReactElement;
15 | removeButton?: React.ReactElement;
16 | push?: (String) => any;
17 | }
18 |
19 | class Product extends React.Component {
20 |
21 | render() : React.ReactElement {
22 | // Import styles
23 | return (
24 |
25 |
26 |

27 |
28 |
29 |
34 |
35 | £{this.props.price}
36 | {this.props.quantity ? `x ${this.props.quantity}` : null}
37 | {this.props.quantityControls}
38 |
39 | {
40 | this.props.description &&
41 |
42 | {this.props.description}
43 |
44 | }
45 |
46 |
47 | {
48 | this.props.removeButton &&
49 |
50 | {this.props.removeButton}
51 |
52 | }
53 |
54 | )
55 | }
56 | }
57 |
58 | export default connect(
59 | null,
60 | routeActions as any
61 | )(Product)
62 |
--------------------------------------------------------------------------------
/typescript/universal/components/Product/ProductItem.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Product from './Product';
3 | import IProduct from '../../interfaces/Product';
4 |
5 | interface IProductItemProps {
6 | product: IProduct;
7 | onAddToCartClicked: React.EventHandler;
8 | key?: any;
9 | }
10 |
11 | class ProductItem extends React.Component {
12 |
13 | render() : React.ReactElement {
14 | // Import styles
15 | const { product } = this.props;
16 |
17 | return (
18 |
19 |
27 |
28 |
32 | {
33 | product.quantity > 0
34 | ? add_shopping_cart
35 | : Sold out
36 | }
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | export default ProductItem;
45 |
--------------------------------------------------------------------------------
/typescript/universal/components/Product/ProductListPagination.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classNames from 'classnames';
3 |
4 | const MAX_LINKS = 10;
5 |
6 | interface IProductsListPaginationProps {
7 | page: number;
8 | pages: number;
9 | onPageSelected: (page: number) => void;
10 | }
11 |
12 | class ProductsListPagination extends React.Component {
13 | render() : React.ReactElement {
14 |
15 | const linksBefore = this.props.page - 1;
16 | const linksAfter = this.props.pages - this.props.page;
17 |
18 | let linksStart = this.props.page;
19 | let linksEnd = this.props.page;
20 |
21 | while ((linksEnd - linksStart) < MAX_LINKS - 1) {
22 | if (linksEnd < this.props.pages) {
23 | linksEnd++;
24 | }
25 | if ((linksStart > 1) && ((linksEnd - linksStart) < MAX_LINKS - 1)) {
26 | linksStart--;
27 | }
28 | if (linksEnd === this.props.pages && linksStart === 1) {
29 | break;
30 | }
31 | }
32 |
33 | const links: JSX.Element[] = [];
34 |
35 | // BACK arrow
36 | if (this.props.page > 1) {
37 | links.push(
38 | this.props.onPageSelected(this.props.page - 1)}>
40 | keyboard_arrow_left
41 |
42 | )
43 | }
44 |
45 | // All page numbers
46 | for (let i = linksStart; i <= linksEnd; i++) {
47 | const classes = classNames(
48 | "pagination-link",
49 | i === this.props.page ? 'active' : ''
50 | )
51 | links.push(
52 | this.props.onPageSelected(i)} >
53 | {i}
54 |
55 | )
56 | }
57 |
58 | // FORWARD arrow
59 | if (this.props.page < this.props.pages) {
60 | links.push(
61 | this.props.onPageSelected(this.props.page + 1)}>
63 | keyboard_arrow_right
64 |
65 | )
66 | }
67 |
68 | return (
69 |
70 | { links }
71 |
72 | );
73 | }
74 | }
75 |
76 | export default ProductsListPagination;
77 |
--------------------------------------------------------------------------------
/typescript/universal/components/Product/ProductsList.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IProductsListProps {
4 | children?: React.ReactElement[];
5 | total: number;
6 | pageTotal: number;
7 | pageLimit: number;
8 | currentPage: number;
9 | pages: number;
10 | }
11 |
12 | class ProductsList extends React.Component {
13 | render() : React.ReactElement {
14 | const startIndex = this.props.pageLimit * (this.props.currentPage - 1) + 1;
15 |
16 | return (
17 |
18 |
19 |
Products
20 |
21 | {startIndex} - {startIndex + this.props.pageTotal - 1}
22 | of
23 | {this.props.total}
24 |
25 |
26 |
Page {this.props.currentPage} of {this.props.pages}
27 |
{this.props.children}
28 |
29 | );
30 | }
31 | }
32 |
33 | export default ProductsList;
34 |
--------------------------------------------------------------------------------
/typescript/universal/components/ProductPage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import IProduct from '../interfaces/Product';
3 | import { addToCart } from '../redux/modules/cart';
4 | import classnames = require('classnames');
5 |
6 | interface IProductComponentProps {
7 | product: IProduct;
8 | rating: number;
9 | maxRating: number;
10 | onAddToCartClicked?: () => any;
11 | onRatingClicked?: (number) => any;
12 | }
13 |
14 | export default class ProductContainer extends React.Component {
15 |
16 | render() : React.ReactElement {
17 | require('../../../sass/productPage.scss');
18 |
19 | const Rating = [];
20 | let i;
21 | for (i = 1 ; i <= this.props.maxRating ; i++) {
22 | ((rating) => {
23 | const classes = classnames(
24 | 'material-icons md-24 star',
25 | this.props.rating < i ? 'light' : 'star-gold'
26 | );
27 | Rating.push( this.props.onRatingClicked(rating)} key={rating}
28 | className={classes}>star);
29 | })(i);
30 | }
31 |
32 | return (
33 |
34 | {
35 | this.props.product &&
36 |
37 |
38 |
39 |

40 |
41 |
42 |
43 | {this.props.product.title}
44 | { Rating }
45 |
46 | £{this.props.product.price}
47 |
48 |
49 |
52 |
53 | }
54 |
55 | )
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/typescript/universal/components/SpringItem.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const rebound: any = require('rebound');
4 |
5 | interface ISpringConfig {
6 | tension?: number;
7 | friction?: number;
8 | }
9 | interface ISpringItemProps {
10 | key: any;
11 | springConfig?: ISpringConfig;
12 | children?: JSX.Element[];
13 | }
14 |
15 | export default class SpringItem extends React.Component {
16 | defaultSpringConfig: ISpringConfig = {
17 | tension: 35,
18 | friction: 12
19 | }
20 | springs: any;
21 |
22 | // APPEARING (only transition out)
23 | componentWillAppear(callback) {
24 | this.createSprings(true);
25 | callback(); // Calls the componentDidAppear function below
26 | }
27 | componentDidAppear() {
28 | this.springs.height.setEndValue(1);
29 | this.springs.opacity.setEndValue(1);
30 | }
31 |
32 | // ENTERING (transition in and out)
33 | componentWillEnter(callback) {
34 | this.getElement('wrapper').style.height = '0';
35 | this.getElement('wrapper').style.opacity = '0';
36 | this.createSprings();
37 | callback(); // Calls the componentDidEnter function below
38 | }
39 | componentDidEnter() {
40 | this.springs.height.setEndValue(1);
41 | this.springs.opacity.setEndValue(1);
42 | }
43 | componentWillUnmount() {
44 | this.springs.height.destroy();
45 | this.springs.opacity.destroy();
46 | }
47 |
48 | componentWillLeave(remove: Function) {
49 | this.setListeners(this.springs);
50 |
51 | this.springs.height.addListener({
52 | onSpringAtRest: (spring) => {
53 | this.getElement('wrapper').style.height = '0';
54 | this.springs.height.removeAllListeners();
55 | }
56 | });
57 | this.springs.opacity.addListener({
58 | onSpringAtRest: (spring) => {
59 | remove();
60 | }
61 | });
62 | this.springs.height.setEndValue(0);
63 | this.springs.opacity.setEndValue(0);
64 | }
65 |
66 | render() : React.ReactElement {
67 |
68 | return (
69 |
70 |
71 | {this.props.children}
72 |
73 |
74 | )
75 | }
76 |
77 | private createSprings(animateOutOnly?: boolean) {
78 | const springSystem = new rebound.SpringSystem();
79 | const heightConfig: ISpringConfig = Object.assign({},
80 | this.defaultSpringConfig,
81 | this.props.springConfig || {}
82 | );
83 | const opacityConfig: ISpringConfig = {
84 | tension: heightConfig.tension * 0.75,
85 | friction: heightConfig.friction * 1.2
86 | };
87 |
88 | const heightSpring = springSystem.createSpring(heightConfig.tension, heightConfig.friction);
89 | const opacitySpring = springSystem.createSpring(opacityConfig.tension, opacityConfig.friction);
90 |
91 | this.springs = {
92 | height: heightSpring,
93 | opacity: opacitySpring
94 | }
95 |
96 | this.setListeners(this.springs, animateOutOnly);
97 |
98 | return this.springs;
99 | }
100 | private setListeners(springs, animateOutOnly?) {
101 | this.springs.height.removeAllListeners();
102 | this.springs.opacity.removeAllListeners();
103 |
104 | springs.height.addListener({
105 | onSpringUpdate: (spring) => {
106 | const toHeight = this.getElement('item').offsetHeight;
107 | const fromHeight = animateOutOnly ? toHeight : 0;
108 | let val = spring.getCurrentValue();
109 | const height = rebound.MathUtil.mapValueInRange(val, 0, 1, fromHeight, toHeight);
110 | this.getElement('wrapper').style.height = height + 'px';
111 | },
112 | onSpringAtRest: (spring) => {
113 | // Required inc ase screen size is changed
114 | this.getElement('wrapper').style.height = 'auto';
115 | }
116 | });
117 | springs.opacity.addListener({
118 | onSpringUpdate: (spring) => {
119 | let val = spring.getCurrentValue();
120 | const fromOpacity = animateOutOnly ? 1 : 0;
121 | const opacity = rebound.MathUtil.mapValueInRange(val, 0, 1, fromOpacity, 1);
122 | this.getElement('wrapper').style.opacity = opacity;
123 | }
124 | });
125 | }
126 | private getElement(ref: string): HTMLElement {
127 | return this.refs[ref] as HTMLElement;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/typescript/universal/configureStore.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {applyMiddleware, createStore, combineReducers} from 'redux';
3 | import { compose } from 'redux';
4 | import * as logger from 'redux-logger';
5 | import * as thunk from 'redux-thunk';
6 | import { syncHistory } from 'react-router-redux';
7 | import { browserHistory } from 'react-router';
8 |
9 | export function configureClient(reducerRegistry, DevTools, initialState) {
10 |
11 | const historyMiddleware = syncHistory(browserHistory);
12 | const middleware = [thunk, logger(), historyMiddleware];
13 |
14 | const reducer = combineReducers(reducerRegistry.getReducers());
15 |
16 | const finalCreateStore = compose(
17 | applyMiddleware(...middleware),
18 | DevTools.instrument()
19 | )(createStore);
20 |
21 | const store = finalCreateStore(reducer, initialState);
22 |
23 | historyMiddleware.listenForReplays(store);
24 |
25 | // Reconfigure the store's reducer when the reducer registry is changed - we
26 | // depend on this for loading reducers via code splitting and for hot
27 | // reloading reducer modules.
28 | reducerRegistry.setChangeListener((reducers) => {
29 | store.replaceReducer(combineReducers(reducers))
30 | });
31 |
32 | return store;
33 | }
34 |
35 | export function configureServer(reducerRegistry, initialState) {
36 | const middleware = [thunk];
37 |
38 | const reducer = combineReducers(reducerRegistry.getReducers());
39 |
40 | const finalCreateStore = compose(
41 | applyMiddleware(...middleware)
42 | )(createStore);
43 |
44 | const store = finalCreateStore(reducer, initialState);
45 |
46 | // Reconfigure the store's reducer when the reducer registry is changed - we
47 | // depend on this for loading reducers via code splitting and for hot
48 | // reloading reducer modules.
49 | reducerRegistry.setChangeListener((reducers) => {
50 | store.replaceReducer(combineReducers(reducers))
51 | });
52 |
53 | return store;
54 | }
55 |
--------------------------------------------------------------------------------
/typescript/universal/constants/AlertTypes.ts:
--------------------------------------------------------------------------------
1 |
2 | enum AlertTypes {
3 | ERROR,
4 | SUCCESS,
5 | INFO
6 | }
7 |
8 | export const classesMap = {
9 | ERROR: 'error',
10 | SUCCESS: 'success',
11 | INFO: 'info',
12 | [AlertTypes.ERROR]: 'error',
13 | [AlertTypes.SUCCESS]: 'success',
14 | [AlertTypes.INFO]: 'info'
15 | }
16 |
17 | export const alertClasses = classesMap;
18 | export default AlertTypes;
19 |
--------------------------------------------------------------------------------
/typescript/universal/constants/Modals.ts:
--------------------------------------------------------------------------------
1 |
2 | enum Modals {
3 | ADD_FUNDS
4 | }
5 |
6 | export default Modals;
7 |
--------------------------------------------------------------------------------
/typescript/universal/containers/Alerts/GlobalAlerts.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactCSSTransitionGroup from 'react-addons-css-transition-group';
3 | import { connect } from 'react-redux'
4 | import { routeActions } from 'react-router-redux'
5 | import IAlert from '../../interfaces/Alert';
6 | import IAppState from '../../interfaces/AppState';
7 | import Alert from '../../components/Alerts/Alert';
8 | import { removeAlert } from '../../redux/modules/alertManager';
9 | const Helmet = require('react-helmet');
10 |
11 | interface IGlobalAlertsProps {
12 | alerts?: IAlert[];
13 | removeAlert?: (number) => any;
14 | }
15 |
16 | class GlobalAlerts extends React.Component {
17 |
18 | render() : React.ReactElement {
19 |
20 | return (
21 |
22 |
23 | {
24 | this.props.alerts.map((a) => (
25 | this.props.removeAlert(a.id)} key={a.id} />
26 | ))
27 | }
28 |
29 |
30 | )
31 | }
32 | }
33 |
34 | function mapStateToProps(state: IAppState) {
35 | return {
36 | alerts: state.alertManager.alerts
37 | }
38 | }
39 |
40 | export default connect(
41 | mapStateToProps,
42 | { removeAlert }
43 | )(GlobalAlerts)
44 |
--------------------------------------------------------------------------------
/typescript/universal/containers/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactCSSTransitionGroup from 'react-addons-css-transition-group';
3 | import { connect } from 'react-redux'
4 | import { routeActions } from 'react-router-redux'
5 | import Navbar from './Navbar';
6 | import Alerts from './Alerts/GlobalAlerts';
7 | import PageLoader from '../components/PageLoader';
8 | import AddFundsModal from '../containers/Modals/AddFundsModal';
9 | import IAppState from '../interfaces/AppState';
10 | import Modals from '../constants/Modals';
11 | import config from '../../config';
12 | import { ILocation } from 'history';
13 | const Helmet = require('react-helmet');
14 |
15 |
16 | interface IAppProps {
17 | children: React.ReactElement;
18 | location?: ILocation; // React router gives this to us
19 | loading?: boolean;
20 | openModal?: Modals;
21 | }
22 |
23 | class App extends React.Component {
24 |
25 | render() : React.ReactElement<{}> {
26 | // Import styles
27 | require('../../../sass/common.scss');
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 | { /* MAIN SECTION */ }
37 |
38 | { this.props.children }
39 |
40 |
41 | { /* PAGE LOADER */ }
42 | {
43 | this.props.loading &&
44 | }
45 |
46 | { /* MODALS */ }
47 |
48 |
52 | {
53 | (() => {
54 | switch (this.props.openModal) {
55 | case Modals.ADD_FUNDS:
56 | return
57 | default:
58 | return null;
59 | }
60 | })()
61 | }
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 | }
70 |
71 | function mapStateToProps(state: IAppState) {
72 | return {
73 | loading: state.global.loading,
74 | openModal: state.global.openModal
75 | }
76 | }
77 |
78 | export default connect(
79 | mapStateToProps,
80 | null
81 | )(App)
82 |
--------------------------------------------------------------------------------
/typescript/universal/containers/Cart/Cart.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { connect } from 'react-redux'
3 | import { checkout } from '../../redux/modules/cart'
4 | import { routeActions } from 'react-router-redux';
5 | import { getTotal, getProducts } from '../../redux/modules/cart'
6 | import CartComponent from '../../components/Cart/Cart'
7 | import IProduct from '../../interfaces/Product';
8 |
9 | interface ICartProps {
10 | products?: IProduct[];
11 | total?: number;
12 | push?: (String) => any;
13 | }
14 |
15 | class Cart extends React.Component {
16 | render() {
17 |
18 | return (
19 | this.props.push('/checkout')} />
23 | )
24 | }
25 | }
26 |
27 | const mapStateToProps = (state) => {
28 | return {
29 | products: getProducts(state.products, state.cart),
30 | total: getTotal(state.products, state.cart)
31 | }
32 | }
33 |
34 | export default connect(
35 | mapStateToProps,
36 | routeActions as any
37 | )(Cart)
38 |
--------------------------------------------------------------------------------
/typescript/universal/containers/Cart/CartProduct.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { connect } from 'react-redux'
3 | import { addToCart, removeFromCart, deleteFromCart } from '../../redux/modules/cart';
4 | import { getVisibleProducts, getProduct } from '../../redux/modules/products'
5 | import Product from '../../components/Product/Product'
6 | import IProduct from '../../interfaces/Product';
7 | import IAppState from '../../interfaces/AppState';
8 |
9 | interface IProductListProps {
10 | product: IProduct;
11 | leftInStock?: number;
12 | addToCart?: (id: number) => void;
13 | removeFromCart?: (id: number) => void;
14 | deleteFromCart?: (id: number) => void;
15 | }
16 |
17 | class ProductList extends React.Component {
18 |
19 | render() : React.ReactElement {
20 | const { product } = this.props;
21 |
22 | const DeleteButton = (
23 | this.props.deleteFromCart(product.id)}>
24 | close
25 |
26 | );
27 |
28 | const QuantityControls = (
29 |
30 | {
31 | (this.props.leftInStock > 0) &&
32 | this.props.addToCart(product.id)}>
35 | add_circle_outline
36 |
37 | }
38 | {
39 | (this.props.product.quantity > 1) &&
40 | this.props.removeFromCart(product.id)}>
43 | remove_circle_outline
44 |
45 | }
46 |
47 | );
48 |
49 | return (
50 |
58 |
59 | );
60 | }
61 | }
62 |
63 | function mapStateToProps(state: IAppState, ownProps: IProductListProps) {
64 | return {
65 | leftInStock: getProduct(state.products, ownProps.product.id).quantity
66 | }
67 | }
68 |
69 | export default connect(
70 | mapStateToProps,
71 | { addToCart, removeFromCart, deleteFromCart }
72 | )(ProductList)
73 |
--------------------------------------------------------------------------------
/typescript/universal/containers/CheckoutPage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classnames = require('classnames');
3 | import { connect } from 'react-redux';
4 | import { routeActions } from 'react-router-redux';
5 | import { checkout, getProducts, getTotal } from '../redux/modules/cart';
6 | import { endLoading } from '../redux/modules/global';
7 | import { ILocation } from 'history';
8 |
9 | import IProduct from '../interfaces/Product';
10 | import IAppState from '../interfaces/AppState';
11 |
12 | import Product from '../components/Product/Product';
13 | import CheckoutPageComponent from '../components/CheckoutPage';
14 |
15 | interface ICheckoutPageContainerProps {
16 | push?: (String) => any;
17 | endLoading?: Function;
18 | loading?: boolean;
19 | checkout?: (products: IProduct[]) => any;
20 | products: IProduct[];
21 | total: number;
22 | location?: ILocation; // React router gives this to us
23 | }
24 | interface ICheckoutPageContainerState {}
25 |
26 | class ProductContainer extends React.Component {
27 |
28 | componentWillMount(): void {
29 | this.stopLoading();
30 | }
31 | componentWillUpdate(nextProps: ICheckoutPageContainerProps): void {
32 | this.stopLoading(nextProps);
33 | }
34 |
35 | render() : React.ReactElement {
36 | return (
37 | this.checkout()}
41 | />
42 | );
43 | }
44 |
45 | private checkout() {
46 | this.props.checkout(this.props.products);
47 | this.props.push('/');
48 | }
49 | private stopLoading(props = this.props) : void {
50 | if (props.loading)
51 | props.endLoading(props.location.pathname);
52 | }
53 | }
54 |
55 | function mapStateToProps(state: IAppState) {
56 | return {
57 | products: getProducts(state.products, state.cart),
58 | total: getTotal(state.products, state.cart),
59 | loading: state.global.loading
60 | }
61 | }
62 |
63 | export default connect(
64 | mapStateToProps,
65 | Object.assign({},
66 | routeActions,
67 | { checkout, endLoading }
68 | ) as any
69 | )(ProductContainer)
70 |
--------------------------------------------------------------------------------
/typescript/universal/containers/Modals/AddFundsModal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactCSSTransitionGroup from 'react-addons-css-transition-group';
3 | import { connect } from 'react-redux';
4 | import IAppState from '../../interfaces/AppState';
5 | import Modal from '../../components/Modal';
6 | import AddFundsForm from '../../components/AddFundsForm';
7 | import Modals from '../../constants/Modals';
8 | import { closeModal } from '../../redux/modules/global';
9 | import { addFunds } from '../../redux/modules/user';
10 |
11 | interface IAddFundsModalProps {
12 | closeModal?: (modal: Modals) => any;
13 | addFunds?: (amount: number) => any;
14 | }
15 | interface IAddFundsModalState {}
16 |
17 | class AddFundsModal extends React.Component {
18 |
19 | render() : React.ReactElement {
20 |
21 | return (
22 | this.props.closeModal(Modals.ADD_FUNDS)}
24 | title="Add Funds"
25 | modalClasses="add-funds-modal"
26 | >
27 | this.props.addFunds(amount)} />
28 |
29 | );
30 | }
31 | }
32 |
33 | function mapStateToProps(state: IAppState) {
34 | return {
35 | balance: state.user.balance
36 | }
37 | }
38 |
39 | export default connect(
40 | mapStateToProps,
41 | { closeModal, addFunds }
42 | )(AddFundsModal)
43 |
--------------------------------------------------------------------------------
/typescript/universal/containers/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classNames = require('classnames');
3 | import { connect } from 'react-redux'
4 | import { routeActions } from 'react-router-redux';
5 | import ILink from '../interfaces/Link';
6 | import IUser from '../interfaces/User';
7 | import IAppState from '../interfaces/AppState';
8 | import Modals from '../constants/Modals';
9 | import NavbarComponent from '../components/Navbar';
10 | import { openModal } from '../redux/modules/global';
11 |
12 | // Import styles
13 |
14 | interface INavbarProps {
15 | push?: (String) => any;
16 | routing?: any;
17 | user?: IUser;
18 | openModal?: (modal: Modals) => any;
19 | }
20 |
21 | const links: ILink[] = [
22 | {
23 | title: 'Home',
24 | path: '/'
25 | }
26 | ]
27 |
28 | class NavbarContainer extends React.Component {
29 |
30 | render() : React.ReactElement<{}> {
31 | require('../../../sass/common.scss');
32 |
33 | return (
34 | this.props.push(route)}
36 | onAddFundsClick={() => this.props.openModal(Modals.ADD_FUNDS)}
37 | routing={this.props.routing}
38 | user={this.props.user}
39 | links={links}
40 | />
41 | )
42 | }
43 | }
44 |
45 | function mapStateToProps(state: IAppState) {
46 | return {
47 | routing: state.routing,
48 | user: state.user
49 | }
50 | }
51 |
52 | export default connect(
53 | mapStateToProps,
54 | Object.assign({},
55 | routeActions as any,
56 | { openModal }
57 | )
58 | )(NavbarContainer)
59 |
--------------------------------------------------------------------------------
/typescript/universal/containers/Product/ProductList.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as ReactCSSTransitionGroup from 'react-addons-css-transition-group';
4 | import * as ReactTransitionGroup from 'react-addons-transition-group';
5 | import { connect } from 'react-redux';
6 | import { addToCart } from '../../redux/modules/cart';
7 | import { getVisibleProducts } from '../../redux/modules/products';
8 | import ProductItem from '../../components/Product/ProductItem';
9 | import SpringItem from '../../components/SpringItem';
10 | import ProductsList from '../../components/Product/ProductsList';
11 | import ProductListPagination from '../../components/Product/ProductListPagination';
12 | import IProduct from '../../interfaces/Product';
13 | import IAppState from '../../interfaces/AppState';
14 |
15 |
16 | const PAGE_LENGTH = 10;
17 |
18 | interface IProductListProps {
19 | products?: IProduct[];
20 | addToCart?: React.EventHandler;
21 | }
22 | interface IProductListState {
23 | page?: number;
24 | }
25 |
26 | class ProductList extends React.Component {
27 | state = {
28 | page: 1
29 | }
30 |
31 | render() : React.ReactElement {
32 | const { products } = this.props;
33 |
34 | const pages: IProduct[][] = [];
35 | for (let i = 0; i < this.props.products.length; i += PAGE_LENGTH) {
36 | pages.push(this.props.products.slice(i, i+PAGE_LENGTH));
37 | }
38 |
39 | return (
40 |
46 |
47 | {
48 | pages.map((pageProducts, i) => {
49 |
50 | return (i === this.state.page - 1) &&
51 |
52 | {/*
56 | {
57 | pageProducts.map(product =>
58 | this.props.addToCart(product.id)} />
62 | )
63 | }
64 | */}
65 |
66 | {
67 | pageProducts.map(product =>
68 |
69 | this.props.addToCart(product.id)} />
72 |
73 | )
74 | }
75 |
76 |
;
77 | })
78 | }
79 |
80 | this.changePage(page)} />
84 |
85 |
86 | );
87 | }
88 |
89 |
90 | private changePage(page: number) {
91 | this.setState({ page });
92 | document.body.scrollTop = 0;
93 | }
94 | }
95 |
96 | function mapStateToProps(state: IAppState) {
97 | return {
98 | products: getVisibleProducts(state.products).reverse()
99 | }
100 | }
101 |
102 | export default connect(
103 | mapStateToProps,
104 | { addToCart }
105 | )(ProductList)
106 |
--------------------------------------------------------------------------------
/typescript/universal/containers/ProductPage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classnames = require('classnames');
3 | import { connect } from 'react-redux';
4 | import { routeActions } from 'react-router-redux';
5 | import { ILocation } from 'history';
6 |
7 | import { endLoading } from '../redux/modules/global';
8 | import { getProduct } from '../redux/modules/products';
9 | import { addToCart } from '../redux/modules/cart';
10 | import { changeRating } from '../redux/modules/productPage';
11 |
12 | import IProduct from '../interfaces/Product';
13 | import IAppPage from '../interfaces/AppPage';
14 | import IAppState from '../interfaces/AppState';
15 |
16 | import ProductPageComponent from '../components/ProductPage';
17 |
18 | interface ProductParams {
19 | id: number;
20 | }
21 | interface ProductPageContainerProps extends IAppPage {
22 | product: IProduct;
23 | rating: number;
24 | maxRating: number;
25 | addToCart?: Function;
26 | changeRating?: Function;
27 | loading?: boolean;
28 | endLoading?: Function;
29 | push?: (String) => any;
30 | location?: ILocation; // React router gives this to us
31 | }
32 |
33 | class ProductPageContainer extends React.Component {
34 |
35 | componentWillMount(): void {
36 | this.stopLoading();
37 | }
38 | componentWillUpdate(nextProps: ProductPageContainerProps): void {
39 | this.stopLoading(nextProps);
40 | }
41 |
42 | render() : React.ReactElement {
43 |
44 | return (
45 | this.addToCart()}
47 | onRatingClicked={(r) => this.props.changeRating(r)}
48 | maxRating={this.props.maxRating}
49 | rating={this.props.rating}
50 | product={this.props.product}
51 | />
52 | )
53 | }
54 |
55 | private addToCart() {
56 | this.props.addToCart(this.props.product.id);
57 | this.props.push('/');
58 | }
59 | private stopLoading(props = this.props) : void {
60 | if (props.loading)
61 | props.endLoading(props.location.pathname);
62 | }
63 | }
64 |
65 | function mapStateToProps(state: IAppState, ownProps) {
66 | return {
67 | product: getProduct(state.products, ownProps.params.id),
68 | rating: state.productPage.rating,
69 | maxRating: state.productPage.maxRating,
70 | loading: state.global.loading
71 | }
72 | }
73 |
74 | export default connect(
75 | mapStateToProps,
76 | Object.assign({},
77 | routeActions,
78 | { addToCart, changeRating, endLoading }
79 | ) as any
80 | )(ProductPageContainer)
81 |
--------------------------------------------------------------------------------
/typescript/universal/containers/ShopPage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 | import { routeActions } from 'react-router-redux';
4 | import { ILocation } from 'history';
5 | import { endLoading } from '../redux/modules/global';
6 | import ProductList from './Product/ProductList';
7 | import CartContainer from './Cart/Cart';
8 | import IAppState from '../interfaces/AppState';
9 |
10 | interface ShopPageProps {
11 | endLoading?: Function;
12 | loading?: boolean;
13 | location?: ILocation; // React router gives this to us
14 | }
15 |
16 | class Main extends React.Component {
17 |
18 | componentWillMount(): void {
19 | this.stopLoading();
20 | }
21 | componentWillUpdate(nextProps: ShopPageProps): void {
22 | this.stopLoading(nextProps);
23 | }
24 |
25 | render() : React.ReactElement {
26 | // Import styles
27 | require('../../../sass/common.scss');
28 | require('../../../sass/shopPage.scss');
29 |
30 | return (
31 |
32 |
SHOP
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | private stopLoading(props = this.props) : void {
42 | if (props.loading)
43 | props.endLoading(props.location.pathname);
44 | }
45 | }
46 |
47 | function mapStateToProps(state: IAppState, ownProps) {
48 | return {
49 | loading: state.global.loading
50 | }
51 | }
52 |
53 | export default connect(
54 | mapStateToProps,
55 | Object.assign({},
56 | routeActions,
57 | { endLoading }
58 | ) as any
59 | )(Main)
60 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/Action.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IAction {
3 | type: string;
4 | }
5 |
6 | export default IAction;
7 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/Alert.ts:
--------------------------------------------------------------------------------
1 | import AlertTypes from '../constants/AlertTypes';
2 |
3 | interface IAlert {
4 | id?: number;
5 | title: string;
6 | content: string;
7 | type: AlertTypes;
8 | }
9 |
10 | export default IAlert;
11 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/AppPage.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IAppPage {
3 | params: params;
4 | }
5 |
6 | export default IAppPage;
7 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/AppState.ts:
--------------------------------------------------------------------------------
1 | import { IAlertManagerState } from '../redux/modules/alertManager';
2 | import { IProductsState } from '../redux/modules/products';
3 | import { ICartState } from '../redux/modules/cart';
4 | import { IProductPageState } from '../redux/modules/productPage';
5 | import IRoutingState from './RoutingState';
6 | import IGlobalState from './GlobalState';
7 | import IUser from './User';
8 |
9 | interface IAppState {
10 | // Core reducers
11 | global: IGlobalState;
12 | alertManager: IAlertManagerState;
13 | user: IUser;
14 | products: IProductsState;
15 | cart: ICartState;
16 | routing: IRoutingState;
17 |
18 | // Additional
19 | productPage: IProductPageState;
20 | }
21 |
22 | export default IAppState;
23 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/GlobalState.ts:
--------------------------------------------------------------------------------
1 |
2 | import Modals from '../constants/Modals';
3 |
4 | interface IGlobalState {
5 | loading: boolean;
6 | openModal: Modals;
7 | }
8 |
9 | export default IGlobalState;
10 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/Link.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IPath {
3 | title: string,
4 | path: string
5 | }
6 |
7 | export default IPath;
8 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/Product.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IProduct {
3 | id: number;
4 | price: number;
5 | quantity: number;
6 | image: string;
7 | title: string;
8 | description: string;
9 | }
10 |
11 | export default IProduct;
12 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/RoutingState.ts:
--------------------------------------------------------------------------------
1 | import { ILocation } from 'history';
2 |
3 | interface IRoutingState {
4 | location: Location;
5 | }
6 |
7 | export default IRoutingState;
8 |
--------------------------------------------------------------------------------
/typescript/universal/interfaces/User.ts:
--------------------------------------------------------------------------------
1 |
2 | interface IUser {
3 | id?: number;
4 | name: string;
5 | balance: number;
6 | }
7 |
8 | export default IUser;
9 |
--------------------------------------------------------------------------------
/typescript/universal/redux/ReducerRegistry.ts:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/rackt/redux/issues/37#issue-85098222
2 |
3 | import { combineReducers } from 'redux';
4 |
5 | export default class ReducerRegistry {
6 | _reducers: Object;
7 | _emitChange: Function;
8 |
9 | constructor(initialReducers = {}) {
10 | this._reducers = initialReducers;
11 | this._emitChange = null;
12 | }
13 |
14 | register(newReducers) {
15 | this._reducers = Object.assign({},
16 | this._reducers,
17 | newReducers
18 | );
19 | if (this._emitChange != null) {
20 | this._emitChange(this.getReducers());
21 | }
22 | }
23 |
24 | setChangeListener(listener) {
25 | if (this._emitChange != null) {
26 | throw new Error('Can only set the listener for a ReducerRegistry once.')
27 | }
28 | this._emitChange = listener
29 | }
30 |
31 |
32 | //----------------------//
33 | // GETTERS //
34 | //----------------------//
35 | getReducers() {
36 | return this._reducers;
37 | }
38 |
39 |
40 | /**
41 | * Replace the given reducers and update the store
42 | * @param {object} store Redux Store
43 | * @param {object} updatedReducers Key value map of reducers to update
44 | */
45 | updateReducers(store, updatedReducers) {
46 | const currentReducers = this.getReducers();
47 | Object.keys(updatedReducers).forEach((reducer) => {
48 | if (!currentReducers[reducer])
49 | delete updatedReducers[reducer];
50 | });
51 |
52 | console.log("UPDATE REDUCERS: ", updatedReducers);
53 |
54 | store.replaceReducer(combineReducers(Object.assign({},
55 | currentReducers,
56 | updatedReducers
57 | )));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/typescript/universal/redux/core.ts:
--------------------------------------------------------------------------------
1 |
2 | import { default as global} from './modules/global';
3 | import { default as cart} from './modules/cart';
4 | import { default as products} from './modules/products';
5 | import { default as user} from './modules/user';
6 | import { default as alertManager} from './modules/alertManager';
7 | import { routeReducer } from 'react-router-redux';
8 |
9 | const reducers = {
10 | global,
11 | cart,
12 | products,
13 | user,
14 | alertManager,
15 |
16 | // Third party
17 | routing: routeReducer
18 | }
19 |
20 | // FOR REDUCER HOT RELOADING //
21 |
22 | export default reducers;
23 |
--------------------------------------------------------------------------------
/typescript/universal/redux/modules/alertManager.ts:
--------------------------------------------------------------------------------
1 |
2 | import IProduct from '../../interfaces/Product';
3 | import shop from '../../../client/api/shop';
4 | import { CHECKOUT_REQUEST, CHECKOUT_ERROR, ICheckoutAction } from './cart';
5 | import IUser from '../../interfaces/User';
6 | import IAlert from '../../interfaces/Alert';
7 | import IAction from '../../interfaces/Action';
8 | import AlertTypes from '../../constants/AlertTypes';
9 |
10 | const ALERT_LIFESPAN = 4000;
11 |
12 | export interface IAlertManagerState {
13 | alerts: IAlert[];
14 | IDTracker: number;
15 | }
16 | const initialState: IAlertManagerState = {
17 | alerts: [],
18 | IDTracker: 0
19 | };
20 |
21 | export const ADD_ALERT = 'ADD_ALERT';
22 | export const REMOVE_ALERT = 'REMOVE_ALERT';
23 |
24 | //----------------------------//
25 | // Handler //
26 | //----------------------------//
27 |
28 | export default function handle(state: IAlertManagerState = initialState, action: any) : IAlertManagerState {
29 | switch (action.type) {
30 | case ADD_ALERT:
31 | const alert = (action as IAddAlertAction);
32 | return {
33 | IDTracker: state.IDTracker + 1,
34 | alerts: state.alerts.concat({
35 | id: state.IDTracker,
36 | title: alert.title,
37 | content: alert.content,
38 | type: alert.alertType
39 | })
40 | };
41 | case REMOVE_ALERT:
42 | action as IRemoveAlertAction;
43 | const index = state.alerts.indexOf(getAlert(state.alerts, action.id));
44 | const newAlerts: IAlert[] = index > -1
45 | ? state.alerts.slice(0, index).concat(state.alerts.slice(index + 1))
46 | : state.alerts;
47 | return Object.assign({},
48 | state,
49 | { alerts: newAlerts }
50 | );
51 | default:
52 | return state;
53 | };
54 | }
55 |
56 |
57 | //----------------------------//
58 | // Actions //
59 | //----------------------------//
60 |
61 | export function addAlert(alert: IAlert) {
62 | return (dispatch, getState) => {
63 | const addAlertAction: IAddAlertAction = {
64 | type: ADD_ALERT,
65 | title: alert.title,
66 | content: alert.content,
67 | alertType: alert.type
68 | }
69 | dispatch(addAlertAction);
70 | }
71 | }
72 | export function removeAlert(id: number) {
73 | return (dispatch, getState) => {
74 | const removeAlertAction: IRemoveAlertAction = {
75 | type: REMOVE_ALERT,
76 | id
77 | }
78 | dispatch(removeAlertAction);
79 | }
80 | }
81 |
82 | //----------------------------//
83 | // Action interfaces //
84 | //----------------------------//
85 |
86 | export interface IAddAlertAction extends IAction {
87 | title: string;
88 | content: string;
89 | alertType: AlertTypes;
90 | }
91 | export interface IRemoveAlertAction extends IAction {
92 | id: number;
93 | }
94 |
95 |
96 | //----------------------------//
97 | // Helpers //
98 | //----------------------------//
99 |
100 | export function getAlert(alerts: IAlert[], id: number) {
101 | for (let alert of alerts) {
102 | if (alert.id === id)
103 | return alert;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/typescript/universal/redux/modules/cart.ts:
--------------------------------------------------------------------------------
1 |
2 | import IProduct from '../../interfaces/Product';
3 | import IAction from '../../interfaces/Action';
4 | import shop from '../../../client/api/shop';
5 | import { IProductsState, getProduct } from './products';
6 | import { ADD_ALERT, addAlert } from './alertManager';
7 | import AlertTypes, { alertClasses } from '../../constants/AlertTypes';
8 |
9 | export const ADD_TO_CART = 'ADD_TO_CART';
10 | export const REMOVE_FROM_CART = 'REMOVE_FROM_CART';
11 | export const DELETE_FROM_CART = 'DELETE_FROM_CART';
12 | export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST';
13 | export const CHECKOUT_ERROR = 'CHECKOUT_FAILURE';
14 | export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS';
15 |
16 | interface ICartItem {
17 | id: number;
18 | quantity: number;
19 | }
20 | export interface ICartState {
21 | [id: number]: ICartItem;
22 | }
23 |
24 | const initialState: ICartState = {}
25 |
26 |
27 | //----------------------------//
28 | // Handler //
29 | //----------------------------//
30 |
31 | export default function handle(cart: ICartState = initialState, action) : ICartState {
32 | switch (action.type) {
33 |
34 | //---------------------//
35 | // CHECKOUT HANDLERS //
36 | //---------------------//
37 | case CHECKOUT_REQUEST:
38 | // Empty cart
39 | return initialState;
40 | case CHECKOUT_SUCCESS:
41 | case CHECKOUT_ERROR: {
42 | const { cart } = action as ICheckoutAction;
43 | return cart;
44 | }
45 | //------------------------//
46 | // ADD NEW ITEM TO CART //
47 | //------------------------//
48 | case ADD_TO_CART: {
49 | const { productId } = action as IAddToCartAction;
50 | const newCart = Object.assign(
51 | {},
52 | cart,
53 | {
54 | [productId]: {
55 | id: productId,
56 | quantity: (cart[productId] ? cart[productId].quantity : 0) + 1
57 | }
58 | }
59 | );
60 | return newCart;
61 | }
62 | //-------------------------//
63 | // REMOVE ITEM FROM CART //
64 | //-------------------------//
65 | case REMOVE_FROM_CART: {
66 | const { productId } = action as IRemoveFromCartAction;
67 | const newCart = Object.assign(
68 | {},
69 | cart,
70 | {
71 | [productId]: {
72 | id: productId,
73 | quantity: (cart[productId] ? cart[productId].quantity : 0) - 1
74 | }
75 | }
76 | );
77 | if (newCart[productId].quantity <= 0) {
78 | delete newCart[productId];
79 | }
80 | return newCart;
81 | }
82 | //-------------------------//
83 | // DELETE ITEM FROM CART //
84 | //-------------------------//
85 | case DELETE_FROM_CART: {
86 | const { productId } = action as IDeleteFromCartAction;
87 | const newCart = Object.assign(
88 | {},
89 | cart
90 | );
91 | delete newCart[productId];
92 | return newCart;
93 | }
94 |
95 | default:
96 | return cart;
97 | };
98 | }
99 |
100 |
101 | //----------------------------//
102 | // Actions //
103 | //----------------------------//
104 |
105 | export function checkout(products: IProduct[]) {
106 | return (dispatch, getState) => {
107 | const cart: ICartState = getState().cart;
108 | const products: IProductsState = getState().products;
109 | const balance: number = getState().user.balance;
110 | const total: number = Number(getTotal(products, cart));
111 |
112 | const checkoutRequestAction: ICheckoutAction = {
113 | type: CHECKOUT_REQUEST,
114 | cart,
115 | total
116 | }
117 |
118 | // Dispatch checkout request (Optimistic UI)
119 | dispatch(checkoutRequestAction);
120 |
121 | shop.buyProducts(
122 | getProducts(products, cart),
123 | total,
124 | balance,
125 | // Success
126 | () => {
127 | dispatch({
128 | type: CHECKOUT_SUCCESS,
129 | cart: getState().cart // Return whatever CURRENT cart value is
130 | });
131 | dispatch(addAlert({
132 | title: 'Thanks for shopping!',
133 | content: `Your purchase of £${total} was successful`,
134 | type: AlertTypes.SUCCESS
135 | }));
136 | },
137 | // ERROR
138 | (errMsg) => {
139 | dispatch({
140 | type: CHECKOUT_ERROR,
141 | cart, // Return cart value before checkout action occurred
142 | total: total
143 | });
144 | dispatch(addAlert({
145 | title: 'Checkout error :(',
146 | content: errMsg || 'Sorry, a checkout error occurs 40% of the time, this application consistently doesn\'t work very well.',
147 | type: AlertTypes.ERROR
148 | }));
149 | }
150 | // Replace the line above with line below to rollback on failure:
151 | // dispatch({ type: types.CHECKOUT_FAILURE, cart })
152 | );
153 | }
154 | }
155 |
156 | export function addToCart(productId: number) {
157 | return (dispatch, getState) => {
158 | if (getState().products[productId].quantity > 0) {
159 | const addToCartAction: IAddToCartAction = {
160 | type: ADD_TO_CART,
161 | productId: productId
162 | }
163 | dispatch(addToCartAction);
164 | }
165 | }
166 | }
167 |
168 | export function removeFromCart(productId: number) {
169 | return (dispatch, getState) => {
170 | const cartProduct = getState().cart[productId];
171 | if (cartProduct && cartProduct.quantity > 0) {
172 | const removeFromCartAction: IRemoveFromCartAction = {
173 | type: REMOVE_FROM_CART,
174 | productId: productId
175 | }
176 | dispatch(removeFromCartAction);
177 | }
178 | }
179 | }
180 |
181 | export function deleteFromCart(productId: number) {
182 | return (dispatch, getState) => {
183 | const cartProduct: IProduct = getState().cart[productId];
184 | if (cartProduct) {
185 | const deleteFromCartAction: IDeleteFromCartAction = {
186 | type: DELETE_FROM_CART,
187 | productId: productId,
188 | oldQuantity: cartProduct.quantity
189 | }
190 | dispatch(deleteFromCartAction);
191 | }
192 | }
193 | }
194 |
195 | //----------------------------//
196 | // Action interfaces //
197 | //----------------------------//
198 |
199 | export interface ICheckoutAction extends IAction {
200 | cart: ICartState;
201 | total: number;
202 | }
203 | export interface IAddToCartAction extends IAction {
204 | productId: number;
205 | }
206 | export interface IRemoveFromCartAction extends IAction {
207 | productId: number;
208 | }
209 | export interface IDeleteFromCartAction extends IAction {
210 | productId: number;
211 | oldQuantity: number;
212 | }
213 |
214 |
215 | //----------------------------//
216 | // Helpers //
217 | //----------------------------//
218 |
219 | export function getProducts(products: IProductsState, cart: ICartState) : IProduct[] {
220 | return getAddedIds(cart).map(id => (
221 | Object.assign(
222 | {},
223 | getProduct(products, id),
224 | { quantity: getQuantity(cart, id) }
225 | )
226 | ))
227 | }
228 |
229 | export function getTotal(products: IProductsState, cart: ICartState) {
230 | return getAddedIds(cart).reduce((total, id) =>
231 | total + getProduct(products, id).price * getQuantity(cart, id),
232 | 0
233 | ).toFixed(2);
234 | }
235 |
236 | export function getQuantity(state: ICartState, productId: number | string) : number {
237 | return state[productId].quantity || 0;
238 | }
239 |
240 | export function getAddedIds(state: ICartState) : string[] {
241 | return Object.keys(state);
242 | }
243 |
--------------------------------------------------------------------------------
/typescript/universal/redux/modules/global.ts:
--------------------------------------------------------------------------------
1 |
2 | import IGlobalState from '../../interfaces/GlobalState';
3 | import IAppState from '../../interfaces/AppState';
4 | import IAction from '../../interfaces/Action';
5 | import Modals from '../../constants/Modals';
6 | import { UPDATE_LOCATION } from 'react-router-redux';
7 |
8 | const START_LOADING = UPDATE_LOCATION;
9 | const END_LOADING = 'END_LOADING';
10 | const OPEN_MODAL = 'OPEN_MODAL';
11 | const CLOSE_MODAL = 'CLOSE_MODAL';
12 |
13 | const initialState: IGlobalState = {
14 | loading: false,
15 | openModal: null
16 | }
17 |
18 |
19 | //----------------------------//
20 | // Handler //
21 | //----------------------------//
22 |
23 | export default function handle(state: IGlobalState = initialState, action: any) : IGlobalState {
24 | switch (action.type) {
25 | case START_LOADING:
26 | return Object.assign({},
27 | state,
28 | { loading: true }
29 | );
30 | case END_LOADING:
31 | return Object.assign({},
32 | state,
33 | { loading: false }
34 | );
35 | case OPEN_MODAL:
36 | const { modal } = action as IOpenModalAction;
37 | return Object.assign({},
38 | state,
39 | { openModal: modal }
40 | );
41 | case CLOSE_MODAL:
42 | return Object.assign({},
43 | state,
44 | { openModal: null }
45 | );
46 | default:
47 | return state;
48 | };
49 | }
50 |
51 |
52 | //----------------------------//
53 | // Actions //
54 | //----------------------------//
55 |
56 | export function startLoading() {
57 | return (dispatch) => {
58 | dispatch({
59 | type: START_LOADING
60 | });
61 | }
62 | }
63 |
64 | export function endLoading(path: string) {
65 | return (dispatch, getState) => {
66 | const state: IAppState = getState();
67 |
68 | if (state.routing.location.pathname === path) {
69 | dispatch({
70 | type: END_LOADING
71 | });
72 | }
73 | }
74 | }
75 |
76 | export function openModal(modal: Modals) {
77 | return (dispatch) => {
78 | const action: IOpenModalAction = {
79 | type: OPEN_MODAL,
80 | modal: modal
81 | }
82 | dispatch(action);
83 | }
84 | }
85 |
86 | export function closeModal(modal: Modals) {
87 | return (dispatch, getState) => {
88 | const state: IAppState = getState();
89 | if (state.global.openModal === modal) {
90 | dispatch({
91 | type: CLOSE_MODAL
92 | });
93 | }
94 | }
95 | }
96 |
97 |
98 | //-------------------------------//
99 | // Action Interfaces //
100 | //-------------------------------//
101 |
102 | interface IOpenModalAction extends IAction {
103 | modal: Modals;
104 | }
105 |
--------------------------------------------------------------------------------
/typescript/universal/redux/modules/productPage.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import IProduct from '../../interfaces/Product';
3 |
4 | // Action constants
5 | const CHANGE_RATING = 'CHANGE_RATING';
6 |
7 | export interface IProductPageState {
8 | rating?: number;
9 | maxRating?: number;
10 | }
11 | const initialState: IProductPageState = {
12 | rating: 0,
13 | maxRating: 5
14 | };
15 |
16 | //----------------------------//
17 | // Handler //
18 | //----------------------------//
19 |
20 | export default function handle(state: IProductPageState = initialState, action) : IProductPageState {
21 | switch (action.type) {
22 | case CHANGE_RATING:
23 | const rating: number = action.rating || 0;
24 | return Object.assign(
25 | {},
26 | state,
27 | { rating: rating }
28 | );
29 | default:
30 | return state;
31 | }
32 | }
33 |
34 |
35 | //----------------------------//
36 | // Actions //
37 | //----------------------------//
38 |
39 | export function changeRating(rating: number) {
40 | return (dispatch) => {
41 | const changeRatingAction: IChangeRatingAction = {
42 | type: CHANGE_RATING,
43 | rating: rating
44 | }
45 | dispatch(changeRatingAction);
46 | };
47 | }
48 |
49 |
50 | //----------------------------//
51 | // Action interfaces //
52 | //----------------------------//
53 |
54 | export interface IChangeRatingAction {
55 | type: string;
56 | rating: number;
57 | }
58 |
--------------------------------------------------------------------------------
/typescript/universal/redux/modules/products.ts:
--------------------------------------------------------------------------------
1 | import IProduct from '../../interfaces/Product';
2 | import IAction from '../../interfaces/Action';
3 | import shop from '../../../client/api/shop';
4 |
5 | // Constants
6 | import {
7 | ADD_TO_CART, IAddToCartAction,
8 | REMOVE_FROM_CART, IRemoveFromCartAction,
9 | DELETE_FROM_CART, IDeleteFromCartAction
10 | } from './cart';
11 | const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS';
12 |
13 | export interface IProductsState {
14 | [id: number]: IProduct;
15 | }
16 | const initialState: IProductsState = {};
17 |
18 |
19 | //----------------------------//
20 | // Handler //
21 | //----------------------------//
22 |
23 | export default function handle(products: IProductsState = initialState, action) : IProductsState {
24 | switch (action.type) {
25 |
26 | //--------------------------------//
27 | // RECEIVE PRODUCTS FROM SERVER //
28 | //--------------------------------//
29 | case RECEIVE_PRODUCTS: {
30 | const newProducts = (action as IReceiveProductsAction).products;
31 | const newProductsMap = {};
32 | newProducts.forEach((product) => {
33 | newProductsMap[product.id] = product;
34 | });
35 |
36 | return Object.assign(
37 | {},
38 | products,
39 | newProductsMap
40 | );
41 | }
42 | //------------------------//
43 | // ADD NEW ITEM TO CART //
44 | //------------------------//
45 | case ADD_TO_CART: {
46 | const { productId} = action as IAddToCartAction;
47 |
48 | if (productId) {
49 | const product: IProduct = products[productId];
50 | const newProduct = Object.assign(
51 | {},
52 | product,
53 | { quantity: product.quantity - 1 }
54 | );
55 | return Object.assign(
56 | {},
57 | products,
58 | { [productId]: newProduct }
59 | );
60 | }
61 | break;
62 | }
63 | //-------------------------//
64 | // REMOVE ITEM FROM CART //
65 | //-------------------------//
66 | case REMOVE_FROM_CART: {
67 | const { productId} = action as IRemoveFromCartAction;
68 |
69 | if (productId) {
70 | const product: IProduct = products[productId];
71 | const newProduct = Object.assign(
72 | {},
73 | product,
74 | { quantity: product.quantity + 1 }
75 | );
76 | return Object.assign(
77 | {},
78 | products,
79 | { [productId]: newProduct }
80 | );
81 | }
82 | break;
83 | }
84 | //-------------------------//
85 | // DELETE ITEM FROM CART //
86 | //-------------------------//
87 | case DELETE_FROM_CART: {
88 | const { productId, oldQuantity} = action as IDeleteFromCartAction;
89 |
90 | if (productId) {
91 | const product: IProduct = products[productId];
92 | const newProduct = Object.assign(
93 | {},
94 | product,
95 | { quantity: product.quantity + oldQuantity }
96 | );
97 | return Object.assign(
98 | {},
99 | products,
100 | { [productId]: newProduct }
101 | );
102 | }
103 | break;
104 | }
105 | default:
106 | return products;
107 | }
108 | }
109 |
110 |
111 | //----------------------------//
112 | // Actions //
113 | //----------------------------//
114 |
115 | export function getAllProducts() {
116 | return (dispatch) => {
117 | shop.getProducts((products: IProduct[]) => {
118 | const receiveProductsAction: IReceiveProductsAction = {
119 | type: RECEIVE_PRODUCTS,
120 | products: products
121 | }
122 | dispatch(receiveProductsAction);
123 | });
124 | };
125 | }
126 |
127 | export function newProductReceived(product: IProduct) {
128 | return (dispatch) => {
129 | const receiveProductsAction: IReceiveProductsAction = {
130 | type: RECEIVE_PRODUCTS,
131 | products: [product]
132 | }
133 | dispatch(receiveProductsAction);
134 | };
135 | }
136 |
137 |
138 | //----------------------------//
139 | // Action interfaces //
140 | //----------------------------//
141 |
142 | export interface IReceiveProductsAction extends IAction {
143 | products: IProduct[];
144 | }
145 |
146 |
147 | //----------------------------//
148 | // Helpers //
149 | //----------------------------//
150 |
151 | export function getProduct(products: IProductsState, id: string | number) : IProduct {
152 | return products[id];
153 | }
154 |
155 | export function getVisibleProducts(products: IProductsState = []) : IProduct[] {
156 | return Object.keys(products).map((id) => products[id]);
157 | }
158 |
--------------------------------------------------------------------------------
/typescript/universal/redux/modules/user.ts:
--------------------------------------------------------------------------------
1 |
2 | import IProduct from '../../interfaces/Product';
3 | import shop from '../../../client/api/shop';
4 | import { CHECKOUT_REQUEST, CHECKOUT_ERROR, ICheckoutAction } from './cart';
5 | import { closeModal } from './global';
6 | import IUser from '../../interfaces/User';
7 | import IAction from '../../interfaces/Action';
8 | import Modals from '../../constants/Modals';
9 | import { ADD_ALERT, addAlert } from './alertManager';
10 | import AlertTypes, { alertClasses } from '../../constants/AlertTypes';
11 |
12 | const ADD_FUNDS = 'ADD_FUNDS';
13 |
14 | const initialState: IUser = {
15 | id: null,
16 | balance: 2000,
17 | name: 'Guest'
18 | }
19 |
20 |
21 | //----------------------------//
22 | // Handler //
23 | //----------------------------//
24 |
25 | export default function handle(user: IUser = initialState, action: any) : IUser {
26 | switch (action.type) {
27 | case CHECKOUT_REQUEST:
28 | return Object.assign({},
29 | user,
30 | { balance: user.balance -= Number((action as ICheckoutAction).total) }
31 | );
32 | case CHECKOUT_ERROR:
33 | return Object.assign({},
34 | user,
35 | { balance: user.balance += (action as ICheckoutAction).total }
36 | );
37 | case ADD_FUNDS:
38 | const { amount } = action as IAddFundsAction;
39 | return Object.assign({},
40 | user,
41 | { balance: user.balance + amount }
42 | );
43 | default:
44 | return user;
45 | };
46 | }
47 |
48 |
49 | //----------------------------//
50 | // Actions //
51 | //----------------------------//
52 |
53 | export function addFunds(amount: number) {
54 | return (dispatch) => {
55 | if (!isNaN(Number(amount))) {
56 | const addFundsAction: IAddFundsAction = {
57 | type: ADD_FUNDS,
58 | amount: Number(amount)
59 | }
60 | dispatch(addFundsAction);
61 | dispatch(closeModal(Modals.ADD_FUNDS))
62 | dispatch(addAlert({
63 | title: 'Funds added!',
64 | content: `£${Number(amount).toFixed(2)} has been added to your account`,
65 | type: AlertTypes.SUCCESS
66 | }))
67 | }
68 | }
69 | }
70 |
71 |
72 | //-------------------------------//
73 | // Action Interfaces //
74 | //-------------------------------//
75 |
76 | interface IAddFundsAction extends IAction {
77 | amount: number;
78 | }
79 |
80 |
81 | //----------------------------//
82 | // Helpers //
83 | //----------------------------//
84 |
85 | export function getBalance(user: IUser) : string {
86 | return user.balance.toFixed(2);
87 | }
88 |
--------------------------------------------------------------------------------
/webpack/dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const autoprefixer = require('autoprefixer');
4 | const WebpackIsomorphicTools = require('webpack-isomorphic-tools');
5 |
6 | // https://github.com/halt-hammerzeit/webpack-isomorphic-tools
7 | const WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
8 | const webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'));
9 |
10 | const projectRootPath = path.resolve(__dirname, '../');
11 | const assetsPath = path.resolve(projectRootPath, './build');
12 |
13 | module.exports = {
14 | cache: true,
15 | inline: false,
16 | devtool: 'source-map',
17 | context: path.resolve(__dirname, '..'),
18 | progress: true,
19 | entry: [
20 | 'webpack-dev-server/client?http://localhost:9999', // WebpackDevServer host and port
21 | 'webpack/hot/only-dev-server', // 'only' prevents reload on syntax errors
22 | './javascript/client/app.js'
23 | ],
24 | output: {
25 | path: path.join(projectRootPath, 'build/'),
26 | // Need absolute path for sources to work - http://stackoverflow.com/questions/30762323/webpack-must-i-specify-the-domain-in-publicpath-for-url-directive-to-work-in
27 | publicPath: 'http://localhost:9999/build/',
28 | filename: '[name]-[hash].js',
29 | chunkFilename: '[name]-[chunkhash].js',
30 | },
31 | plugins: [
32 | new webpack.HotModuleReplacementPlugin(),
33 | new webpack.NoErrorsPlugin(),
34 | new webpack.IgnorePlugin(/webpack-stats\.json$/),
35 | new webpack.DefinePlugin({
36 | __CLIENT__: true,
37 | __SERVER__: false,
38 | __DEVELOPMENT__: true,
39 | __DEVTOOLS__: true
40 | }),
41 | webpackIsomorphicToolsPlugin.development()
42 | ],
43 | postcss: [
44 | autoprefixer({
45 | browsers: ['last 3 versions']
46 | })
47 | ],
48 | module: {
49 | loaders: [
50 | {
51 | test: /\.jsx?$/,
52 | loaders: ['react-hot', 'babel?presets[]=react,presets[]=es2015'],
53 | include: path.join(projectRootPath, 'javascript')
54 | },
55 | {
56 | test: /\.scss$/,
57 | loaders: ['style', 'css?sourceMap&importLoaders=1', 'sass'],
58 | include: path.join(projectRootPath, 'sass')
59 | },
60 | { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
61 | { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
62 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
63 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
64 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
65 | ]
66 | },
67 |
68 | // LOADER config
69 | cssLoader: {
70 | sourceMap: true,
71 | modules: false, // Enables local scoped css (hash-like class names specific to components)
72 | localIdentName: '[local]___[hash:base64:5]', // Name format for local scoped class names (if set)
73 | importLoaders: 1 // Which loaders should be applied to @imported resources (How many after css loader)
74 | },
75 | sassLoader: {
76 | sourceMap: true,
77 | outputStyle: 'expanded'
78 | }
79 | };
80 |
--------------------------------------------------------------------------------
/webpack/prod.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const autoprefixer = require('autoprefixer');
4 | const WebpackIsomorphicTools = require('webpack-isomorphic-tools');
5 | const CleanPlugin = require('clean-webpack-plugin');
6 | const CopyPlugin = require('copy-webpack-plugin');
7 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
8 |
9 | // https://github.com/halt-hammerzeit/webpack-isomorphic-tools
10 | const WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
11 | const webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'));
12 |
13 | const projectRootPath = path.resolve(__dirname, '../');
14 | const assetsPath = path.resolve(projectRootPath, './build');
15 |
16 | const extractSASS = new ExtractTextPlugin('[name].css', {
17 | allChunks: true
18 | });
19 |
20 | module.exports = {
21 | cache: true,
22 | devtool: false,
23 | verbose: true,
24 | colors: true,
25 | 'display-error-details': true,
26 | context: projectRootPath,
27 | progress: true,
28 | entry: ['./javascript/client/app.js'],
29 | output: {
30 | path: assetsPath,
31 | publicPath: '/',
32 | filename: '[name]-[hash].js',
33 | chunkFilename: '[name]-[chunkhash].js'
34 | },
35 | plugins: [
36 | new CleanPlugin(
37 | [assetsPath],
38 | { root: projectRootPath }
39 | ),
40 | new CopyPlugin([
41 | { from: 'assets', to: 'assets' }
42 | ]),
43 | // css files from the extract-text-plugin loader
44 | // new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: false}),
45 | extractSASS,
46 | new webpack.DefinePlugin({
47 | __CLIENT__: true,
48 | __SERVER__: false,
49 | __DEVELOPMENT__: false,
50 | __DEVTOOLS__: false,
51 | 'process.env': {
52 | // Useful to reduce the size of client-side libraries, e.g. react
53 | NODE_ENV: JSON.stringify('production')
54 | }
55 | }),
56 |
57 | new webpack.IgnorePlugin(/\.\/dev/, /\/config$/),
58 |
59 | // optimizations
60 | new webpack.optimize.DedupePlugin(),
61 | new webpack.optimize.CommonsChunkPlugin({
62 | name: 'commons',
63 | filename: 'commons.js'
64 | }),
65 | webpackIsomorphicToolsPlugin
66 | ],
67 | progress: true,
68 | module: {
69 | loaders: [
70 | {
71 | test: /\.scss$/,
72 | loader: extractSASS.extract('style', ['css', 'resolve-url', 'sass'])
73 | // loaders: ['style', 'css', 'resolve-url', 'sass'])
74 | // include: path.join(__dirname, 'sass')
75 | },
76 | { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
77 | { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" },
78 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" },
79 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" },
80 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" },
81 | { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
82 | ]
83 | },
84 |
85 | // LOADER config
86 | cssLoader: {
87 | // modules: true, // Enables local scoped css (hash-like class names specific to components)
88 | importLoaders: 1, // Which loaders should be applied to @imported resources (How many after css loader)
89 | // sourceMap: true
90 | },
91 | sassLoader: {
92 | // sourceMap: true,
93 | // outputStyle: 'expanded',
94 | // sourceMapContents: true
95 | },
96 | postcss: [
97 | autoprefixer({
98 | browsers: ['last 3 versions']
99 | })
100 | ],
101 | };
102 |
--------------------------------------------------------------------------------
/webpack/webpack-isomorphic-tools.js:
--------------------------------------------------------------------------------
1 | var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
2 |
3 | // see this link for more info on what all of this means
4 | // https://github.com/halt-hammerzeit/webpack-isomorphic-tools
5 | module.exports = {
6 |
7 | // when adding "js" extension to asset types
8 | // and then enabling debug mode, it may cause a weird error:
9 | //
10 | // [0] npm run start-prod exited with code 1
11 | // Sending SIGTERM to other processes..
12 | //
13 | // debug: true,
14 |
15 | assets: {
16 | images: {
17 | extensions: [
18 | 'jpeg',
19 | 'jpg',
20 | 'png',
21 | 'gif'
22 | ],
23 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
24 | },
25 | fonts: {
26 | extensions: [
27 | 'woff',
28 | 'woff2',
29 | 'ttf',
30 | 'eot'
31 | ],
32 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
33 | },
34 | svg: {
35 | extension: 'svg',
36 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
37 | },
38 | style_modules: {
39 | extensions: ['scss'],
40 | filter: function(module, regex, options, log) {
41 | if (options.development) {
42 | // in development mode there's webpack "style-loader",
43 | // so the module.name is not equal to module.name
44 | return WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
45 | } else {
46 | // in production mode there's no webpack "style-loader",
47 | // so the module.name will be equal to the asset path
48 | return regex.test(module.name);
49 | }
50 | },
51 | path: function(module, options, log) {
52 | if (options.development) {
53 | // in development mode there's webpack "style-loader",
54 | // so the module.name is not equal to module.name
55 | return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
56 | } else {
57 | // in production mode there's no webpack "style-loader",
58 | // so the module.name will be equal to the asset path
59 | return module.name;
60 | }
61 | },
62 | parser: function(module, options, log) {
63 | if (options.development) {
64 | return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
65 | } else {
66 | // in production mode there's Extract Text Loader which extracts CSS text away
67 | return module.source;
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------