@addev-be/ui 0.2.19 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@addev-be/ui",
3
- "version": "0.2.19",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "watch": "tsc -b --watch",
@@ -45,6 +45,7 @@
45
45
  "io-ts": "^2.2.21",
46
46
  "lodash": "^4.17.21",
47
47
  "moment": "^2.30.1",
48
+ "rxjs": "^7.8.1",
48
49
  "uuid": "^10.0.0"
49
50
  }
50
51
  }
@@ -0,0 +1,143 @@
1
+ import {
2
+ FC,
3
+ PropsWithChildren,
4
+ createContext,
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ useState,
9
+ } from 'react';
10
+ import {
11
+ UserDTO,
12
+ WebSocketService,
13
+ useAuthenticateRequestHandler,
14
+ useLoginRequestHandler,
15
+ useLogoutRequestHandler,
16
+ } from '../../services';
17
+
18
+ export type AuthenticationContextProps = {
19
+ token?: string;
20
+ user?: UserDTO;
21
+ authenticate: () => void;
22
+ login: (username: string, password: string) => void;
23
+ logout: () => void;
24
+ };
25
+
26
+ export const AuthenticationContext = createContext<AuthenticationContextProps>({
27
+ authenticate: () => {},
28
+ login: () => {},
29
+ logout: () => {},
30
+ });
31
+
32
+ export type AuthenticationProviderProps = PropsWithChildren<{
33
+ loginComponent?: FC;
34
+ }>;
35
+
36
+ export const AuthenticationProvider: FC<AuthenticationProviderProps> = ({
37
+ loginComponent: LoginComponent,
38
+ children,
39
+ }) => {
40
+ const [, setWebSocketStatus] = useState<boolean | undefined>(false);
41
+ const [token, setToken] = useState<string | undefined>(
42
+ sessionStorage.getItem('authToken') || undefined
43
+ );
44
+ const [user, setUser] = useState<UserDTO | undefined>(undefined);
45
+
46
+ const sendAuthenticateRequest = useAuthenticateRequestHandler();
47
+ const sendLoginRequest = useLoginRequestHandler();
48
+ const sendLogoutRequest = useLogoutRequestHandler();
49
+
50
+ useEffect(() => {
51
+ if (token) {
52
+ sessionStorage.setItem('authToken', token);
53
+ } else {
54
+ sessionStorage.removeItem('authToken');
55
+ }
56
+ }, [token]);
57
+
58
+ const authenticate = useCallback(() => {
59
+ if (token && !user) {
60
+ sendAuthenticateRequest({ token }).then(
61
+ (response) => {
62
+ if (response.status === 0 && response.user) {
63
+ setUser(response.user);
64
+ } else if (response.status > 0) {
65
+ setToken(undefined);
66
+ setUser(undefined);
67
+ }
68
+ },
69
+ () => {
70
+ setToken(undefined);
71
+ setUser(undefined);
72
+ }
73
+ );
74
+ }
75
+ }, [sendAuthenticateRequest, token, user]);
76
+
77
+ const login = useCallback(
78
+ (username: string, password: string) => {
79
+ sendLoginRequest({ username, password }).then(
80
+ (response) => {
81
+ if (response.status === 0 && response.user) {
82
+ setToken(response.token);
83
+ setUser(response.user);
84
+ } else if (response.status > 0) {
85
+ setToken(undefined);
86
+ setUser(undefined);
87
+ }
88
+ },
89
+ () => {
90
+ setToken(undefined);
91
+ setUser(undefined);
92
+ }
93
+ );
94
+ },
95
+ [sendLoginRequest]
96
+ );
97
+
98
+ const logout = useCallback(() => {
99
+ sendLogoutRequest({}).finally(() => {
100
+ setToken(undefined);
101
+ setUser(undefined);
102
+ });
103
+ }, [sendLogoutRequest]);
104
+
105
+ const contextValue = useMemo(
106
+ () => ({
107
+ token,
108
+ user,
109
+ authenticate,
110
+ login,
111
+ logout,
112
+ }),
113
+ [authenticate, login, logout, token, user]
114
+ );
115
+
116
+ useEffect(() => {
117
+ const subscription = WebSocketService.getInstance()?.status$.subscribe(
118
+ (newStatus) => {
119
+ setWebSocketStatus((previousStatus) => {
120
+ if (!previousStatus && newStatus) {
121
+ authenticate();
122
+ }
123
+ return newStatus;
124
+ });
125
+ }
126
+ );
127
+ return () => subscription?.unsubscribe();
128
+ }, [authenticate]);
129
+
130
+ return (
131
+ <AuthenticationContext.Provider value={contextValue}>
132
+ {!user ? (
133
+ LoginComponent ? (
134
+ <LoginComponent />
135
+ ) : (
136
+ <div>Not authenticated</div>
137
+ )
138
+ ) : (
139
+ children
140
+ )}
141
+ </AuthenticationContext.Provider>
142
+ );
143
+ };
@@ -0,0 +1,47 @@
1
+ import {
2
+ FC,
3
+ PropsWithChildren,
4
+ createContext,
5
+ useCallback,
6
+ useMemo,
7
+ useState,
8
+ } from 'react';
9
+
10
+ import { Loading } from '../../components';
11
+
12
+ export type LoadingContextType = {
13
+ startLoading: () => void;
14
+ stopLoading: () => void;
15
+ };
16
+
17
+ export const LoadingContext = createContext<LoadingContextType>({
18
+ startLoading: () => {},
19
+ stopLoading: () => {},
20
+ });
21
+
22
+ export const LoadingProvider: FC<PropsWithChildren> = ({ children }) => {
23
+ const [loadingCount, setLoadingCount] = useState(0);
24
+
25
+ const startLoading = useCallback(() => {
26
+ setLoadingCount((count) => count + 1);
27
+ }, []);
28
+
29
+ const stopLoading = useCallback(() => {
30
+ setLoadingCount((count) => count - 1);
31
+ }, []);
32
+
33
+ const value = useMemo(
34
+ () => ({
35
+ startLoading,
36
+ stopLoading,
37
+ }),
38
+ [startLoading, stopLoading]
39
+ );
40
+
41
+ return (
42
+ <LoadingContext.Provider value={value}>
43
+ <Loading visible={loadingCount > 0}>Chargement ...</Loading>
44
+ {children}
45
+ </LoadingContext.Provider>
46
+ );
47
+ };
@@ -1,3 +1,4 @@
1
1
  export { ThemeProvider } from './ThemeProvider';
2
2
 
3
3
  export { type Theme } from './types';
4
+ export { defaultTheme } from './defaultTheme';
@@ -1,8 +1,10 @@
1
+ import { LoadingContext } from './LoadingProvider';
1
2
  import { PortalsContext } from './PortalsProvider';
2
3
  import { SettingsContext } from './SettingsProvider';
3
4
  import { UiContext } from './UiProviders';
4
5
  import { useContext } from 'react';
5
6
 
7
+ export const useLoading = () => useContext(LoadingContext);
6
8
  export const usePortals = () => useContext(PortalsContext);
7
- export const useUi = () => useContext(UiContext);
8
9
  export const useSettings = () => useContext(SettingsContext);
10
+ export const useUi = () => useContext(UiContext);
@@ -1,5 +1,6 @@
1
- export * from './UiProviders';
1
+ export * from './AuthenticationProvider';
2
2
  export * from './PortalsProvider';
3
3
  export * from './ThemeProvider';
4
+ export * from './UiProviders';
4
5
 
5
6
  export * from './hooks';
@@ -1,13 +1,16 @@
1
1
  import { Config } from '../config';
2
2
  import { Request } from './base';
3
+ import { Subject } from 'rxjs';
3
4
  import { v4 } from 'uuid';
4
5
 
5
6
  export class WebSocketService {
6
7
  private static instance: WebSocketService;
7
8
  private config: Config;
8
9
  private socket: WebSocket | null = null;
9
- public status: boolean | undefined = false;
10
- private listeners: ((status: boolean | undefined) => void)[] = [];
10
+
11
+ public status: boolean | undefined = undefined;
12
+ public status$ = new Subject<boolean | undefined>();
13
+
11
14
  private queue: Request[] = [];
12
15
  private promises: {
13
16
  [id: string]: {
@@ -71,23 +74,9 @@ export class WebSocketService {
71
74
  }
72
75
  }
73
76
 
74
- public subscribe(listener: (status: boolean | undefined) => void) {
75
- this.listeners.push(listener);
76
- listener(this.status);
77
- return () => this.listeners.splice(this.listeners.indexOf(listener), 1);
78
- }
79
-
80
- public unsubscribe(listener: (status: boolean | undefined) => void) {
81
- this.listeners.splice(this.listeners.indexOf(listener), 1);
82
- }
83
-
84
77
  private setStatus(status: boolean | undefined) {
85
- const previousStatus = this.status;
86
78
  this.status = status;
87
- this.listeners.forEach((listener) => listener(status));
88
- if (this.socket && !previousStatus && status) {
89
- this.sendQueue();
90
- }
79
+ this.status$.next(status);
91
80
  }
92
81
 
93
82
  private sendQueue() {
@@ -1,6 +1,23 @@
1
1
  import { HttpService } from './HttpService';
2
2
  import { WebSocketService } from './WebSocketService';
3
3
  import { useCallback } from 'react';
4
+ import { useLoading } from '../providers';
5
+
6
+ export const useLoadingRequestHandler = <TReq, TRes>(
7
+ name: string
8
+ ): ((data: TReq) => Promise<TRes>) => {
9
+ const { startLoading, stopLoading } = useLoading();
10
+
11
+ return useCallback(
12
+ (data: TReq) => {
13
+ startLoading();
14
+ return WebSocketService.getInstance()
15
+ .sendRequest<TReq, TRes>(name, data)
16
+ .finally(stopLoading);
17
+ },
18
+ [name, startLoading, stopLoading]
19
+ );
20
+ };
4
21
 
5
22
  export const useWebSocketRequestHandler = <TReq, TRes>(
6
23
  name: string
@@ -1,2 +1,8 @@
1
1
  export * from './WebSocketService';
2
2
  export * from './hooks';
3
+
4
+ export * from './requests/auth';
5
+
6
+ export * from './types/auth';
7
+ export * from './types/base';
8
+ export * from './types/users';
@@ -0,0 +1,44 @@
1
+ import {
2
+ AuthenticateRequestDTO,
3
+ AuthenticateResponseDTO,
4
+ CheckRecoveryKeyRequestDTO,
5
+ CheckRecoveryKeyResponseDTO,
6
+ LoginRequestDTO,
7
+ LoginResponseDTO,
8
+ LogoutRequestDTO,
9
+ LogoutResponseDTO,
10
+ ResetPasswordRequestDTO,
11
+ ResetPasswordResponseDTO,
12
+ SendRecoveryKeyRequestDTO,
13
+ SendRecoveryKeyResponseDTO,
14
+ } from '../types/auth';
15
+
16
+ import { useLoadingRequestHandler } from '../hooks';
17
+
18
+ export const useAuthenticateRequestHandler = () =>
19
+ useLoadingRequestHandler<AuthenticateRequestDTO, AuthenticateResponseDTO>(
20
+ 'Authenticate'
21
+ );
22
+
23
+ export const useLoginRequestHandler = () =>
24
+ useLoadingRequestHandler<LoginRequestDTO, LoginResponseDTO>('Login');
25
+
26
+ export const useLogoutRequestHandler = () =>
27
+ useLoadingRequestHandler<LogoutRequestDTO, LogoutResponseDTO>('Logout');
28
+
29
+ export const useSendRecoveryKeyRequestHandler = () =>
30
+ useLoadingRequestHandler<
31
+ SendRecoveryKeyRequestDTO,
32
+ SendRecoveryKeyResponseDTO
33
+ >('SendRecoveryKey');
34
+
35
+ export const useCheckRecoveryKeyRequestHandler = () =>
36
+ useLoadingRequestHandler<
37
+ CheckRecoveryKeyRequestDTO,
38
+ CheckRecoveryKeyResponseDTO
39
+ >('CheckRecoveryKey');
40
+
41
+ export const useResetPasswordRequestHandler = () =>
42
+ useLoadingRequestHandler<ResetPasswordRequestDTO, ResetPasswordResponseDTO>(
43
+ 'ResetPassword'
44
+ );
@@ -0,0 +1,131 @@
1
+ import * as t from 'io-ts';
2
+
3
+ import { userDtoCodec } from './users';
4
+
5
+ export const loginRequestDtoCodec = t.type(
6
+ {
7
+ username: t.string,
8
+ password: t.string,
9
+ },
10
+ 'LoginRequestDTO'
11
+ );
12
+
13
+ export const loginResponseDtoCodec = t.type(
14
+ {
15
+ status: t.number,
16
+ token: t.string,
17
+ user: t.union([userDtoCodec, t.null]),
18
+ },
19
+ 'LoginResponseDTO'
20
+ );
21
+
22
+ export type LoginRequestDTO = t.TypeOf<typeof loginRequestDtoCodec>;
23
+ export type LoginResponseDTO = t.TypeOf<typeof loginResponseDtoCodec>;
24
+
25
+ /*****/
26
+
27
+ export const logoutRequestDtoCodec = t.type({}, 'LogoutRequestDTO');
28
+
29
+ export const logoutResponseDtoCodec = t.type(
30
+ {
31
+ status: t.number,
32
+ },
33
+ 'LogoutResponseDTO'
34
+ );
35
+
36
+ export type LogoutRequestDTO = t.TypeOf<typeof logoutRequestDtoCodec>;
37
+ export type LogoutResponseDTO = t.TypeOf<typeof logoutResponseDtoCodec>;
38
+
39
+ /*****/
40
+
41
+ export const authenticateRequestDtoCodec = t.type(
42
+ {
43
+ token: t.string,
44
+ },
45
+ 'AuthenticateRequestDTO'
46
+ );
47
+
48
+ export const authenticateResponseDtoCodec = t.type(
49
+ {
50
+ status: t.number,
51
+ user: t.union([userDtoCodec, t.null]),
52
+ },
53
+ 'AuthenticateResponseDTO'
54
+ );
55
+
56
+ export type AuthenticateRequestDTO = t.TypeOf<
57
+ typeof authenticateRequestDtoCodec
58
+ >;
59
+ export type AuthenticateResponseDTO = t.TypeOf<
60
+ typeof authenticateResponseDtoCodec
61
+ >;
62
+
63
+ /*****/
64
+
65
+ export const sendRecoveryKeyRequestDtoCodec = t.type(
66
+ {
67
+ email: t.string,
68
+ },
69
+ 'SendRecoveryKeyRequestDTO'
70
+ );
71
+
72
+ export const sendRecoveryKeyResponseDtoCodec = t.type(
73
+ {
74
+ status: t.number,
75
+ },
76
+ 'SendRecoveryKeyResponseDTO'
77
+ );
78
+
79
+ export type SendRecoveryKeyRequestDTO = t.TypeOf<
80
+ typeof sendRecoveryKeyRequestDtoCodec
81
+ >;
82
+ export type SendRecoveryKeyResponseDTO = t.TypeOf<
83
+ typeof sendRecoveryKeyResponseDtoCodec
84
+ >;
85
+
86
+ /*****/
87
+
88
+ export const checkRecoveryKeyRequestDtoCodec = t.type(
89
+ {
90
+ key: t.string,
91
+ },
92
+ 'CheckRecoveryKeyRequestDTO'
93
+ );
94
+
95
+ export const checkRecoveryKeyResponseDtoCodec = t.type(
96
+ {
97
+ status: t.number,
98
+ },
99
+ 'CheckRecoveryKeyResponseDTO'
100
+ );
101
+
102
+ export type CheckRecoveryKeyRequestDTO = t.TypeOf<
103
+ typeof checkRecoveryKeyRequestDtoCodec
104
+ >;
105
+ export type CheckRecoveryKeyResponseDTO = t.TypeOf<
106
+ typeof checkRecoveryKeyResponseDtoCodec
107
+ >;
108
+
109
+ /*****/
110
+
111
+ export const resetPasswordRequestDtoCodec = t.type(
112
+ {
113
+ key: t.string,
114
+ password: t.string,
115
+ },
116
+ 'ResetPasswordRequestDTO'
117
+ );
118
+
119
+ export const resetPasswordResponseDtoCodec = t.type(
120
+ {
121
+ status: t.number,
122
+ },
123
+ 'ResetPasswordResponseDTO'
124
+ );
125
+
126
+ export type ResetPasswordRequestDTO = t.TypeOf<
127
+ typeof resetPasswordRequestDtoCodec
128
+ >;
129
+ export type ResetPasswordResponseDTO = t.TypeOf<
130
+ typeof resetPasswordResponseDtoCodec
131
+ >;
@@ -0,0 +1,10 @@
1
+ import * as t from 'io-ts';
2
+
3
+ export const baseModelDtoCodec = t.type({
4
+ id: t.union([t.string, t.null]),
5
+ });
6
+
7
+ export type BaseModelDTO = t.TypeOf<typeof baseModelDtoCodec>;
8
+
9
+ export const UUID_REGEX =
10
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -0,0 +1,20 @@
1
+ import * as t from 'io-ts';
2
+
3
+ import { baseModelDtoCodec } from './base';
4
+
5
+ export const userDtoCodec = t.intersection(
6
+ [
7
+ t.type({
8
+ ...baseModelDtoCodec.props,
9
+ username: t.string,
10
+ }),
11
+ t.partial({
12
+ name: t.string,
13
+ email: t.string,
14
+ lastLogin: t.union([t.string, t.null]),
15
+ }),
16
+ ],
17
+ 'UserDTO'
18
+ );
19
+
20
+ export type UserDTO = t.TypeOf<typeof userDtoCodec>;