dce-reactkit 2.0.5 → 2.0.6

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.
@@ -0,0 +1,181 @@
1
+ /**
2
+ * A wrapper for the entire React app that adds global functionality like
3
+ * handling for fatal error messages
4
+ * @author Gabe Abrams
5
+ */
6
+
7
+ // Import React
8
+ import React, { useState, useEffect } from 'react';
9
+
10
+ // Import shared components
11
+ import ErrorBox from './ErrorBox';
12
+
13
+ // Import helpers
14
+ import { setSessionExpiryHandler } from '../helpers/visitServerEndpoint';
15
+
16
+ // Import shared types
17
+ import ReactKitErrorCode from '../types/ReactKitErrorCode';
18
+
19
+ // Import custom errors
20
+ import ErrorWithCode from '../errors/ErrorWithCode';
21
+
22
+ /*------------------------------------------------------------------------*/
23
+ /* Props */
24
+ /*------------------------------------------------------------------------*/
25
+
26
+ type Props = {
27
+ // The entire app
28
+ children: React.ReactNode,
29
+ // True if this app is a dark-themed app
30
+ dark?: boolean,
31
+ // Custom session expired message
32
+ sessionExpiredMessage?: string,
33
+ };
34
+
35
+ /*------------------------------------------------------------------------*/
36
+ /* Static Helpers */
37
+ /*------------------------------------------------------------------------*/
38
+
39
+ // Stored copies of setters
40
+ let setFatalErrorMessage: (message: string) => void;
41
+ let setFatalErrorCode: (code: string) => void;
42
+
43
+ /**
44
+ * Show a fatal error message
45
+ * @author Gabe Abrams
46
+ * @param error the error to show
47
+ */
48
+ export const showFatalError = (error: any) => {
49
+ // Determine message and code
50
+ const message: string = (
51
+ typeof error === 'string'
52
+ ? error.trim()
53
+ : String(error.message ?? 'An unknown error occurred.')
54
+ );
55
+ const code: string = (
56
+ typeof error === 'string'
57
+ ? ReactKitErrorCode.NoCode
58
+ : String(error.code ?? ReactKitErrorCode.NoCode)
59
+ );
60
+
61
+ // Handle case where app hasn't loaded
62
+ if (!setFatalErrorMessage || !setFatalErrorCode) {
63
+ return alert(`An error has occurred: ${message} (code: ${code}). Please contact support.`);
64
+ }
65
+
66
+ // Use setters
67
+ setFatalErrorMessage(message);
68
+ setFatalErrorCode(code);
69
+ };
70
+
71
+ /*------------------------------------------------------------------------*/
72
+ /* Component */
73
+ /*------------------------------------------------------------------------*/
74
+
75
+ const AppWrapper = (props: Props): React.ReactElement => {
76
+ /*------------------------------------------------------------------------*/
77
+ /* Setup */
78
+ /*------------------------------------------------------------------------*/
79
+
80
+ /* -------------- Props ------------- */
81
+
82
+ const {
83
+ children,
84
+ dark,
85
+ sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.',
86
+ } = props;
87
+
88
+ /* -------------- State ------------- */
89
+
90
+ // Fatal error
91
+ const [fatalErrorMessage, setFatalErrorMessageInner] = useState(null);
92
+ setFatalErrorMessage = setFatalErrorMessageInner;
93
+ const [fatalErrorCode, setFatalErrorCodeInner] = useState(null);
94
+ setFatalErrorCode = setFatalErrorCodeInner;
95
+
96
+ // Session expired
97
+ const [sessionHasExpired, setSessionHasExpired] = useState(false);
98
+
99
+ /*------------------------------------------------------------------------*/
100
+ /* Lifecycle Functions */
101
+ /*------------------------------------------------------------------------*/
102
+
103
+ /**
104
+ * Mount
105
+ * @author Gabe Abrams
106
+ */
107
+ useEffect(
108
+ () => {
109
+ // Add session expired handler
110
+ setSessionExpiryHandler(() => {
111
+ // Session expired!
112
+ setSessionHasExpired(true);
113
+ });
114
+ },
115
+ [],
116
+ );
117
+
118
+ /*------------------------------------------------------------------------*/
119
+ /* Render */
120
+ /*------------------------------------------------------------------------*/
121
+
122
+ /*----------------------------------------*/
123
+ /* Main UI */
124
+ /*----------------------------------------*/
125
+
126
+ // Show error
127
+ if (fatalErrorMessage || fatalErrorCode || sessionHasExpired) {
128
+ // Re-encapsulate in an error
129
+ const error = (
130
+ sessionHasExpired
131
+ ? new ErrorWithCode(
132
+ fatalErrorMessage,
133
+ fatalErrorCode,
134
+ )
135
+ : new ErrorWithCode(
136
+ sessionExpiredMessage,
137
+ ReactKitErrorCode.SessionExpired,
138
+ )
139
+ );
140
+
141
+ // Build error screen
142
+ return (
143
+ <div
144
+ style={{
145
+ display: 'block',
146
+ width: '100vw',
147
+ minHeight: '100vh',
148
+ paddingTop: '10rem',
149
+ backgroundColor: (
150
+ dark
151
+ ? '#222'
152
+ : '#fff'
153
+ ),
154
+ }}
155
+ >
156
+ <ErrorBox
157
+ title={(
158
+ sessionHasExpired
159
+ ? 'Session Expired'
160
+ : undefined
161
+ )}
162
+ error={error}
163
+ />
164
+ </div>
165
+ );
166
+ }
167
+
168
+ // Show the app itself
169
+ return (
170
+ <>
171
+ {children}
172
+ </>
173
+ );
174
+ };
175
+
176
+ /*------------------------------------------------------------------------*/
177
+ /* Wrap Up */
178
+ /*------------------------------------------------------------------------*/
179
+
180
+ // Export component
181
+ export default AppWrapper;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Displays an error
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ // Import FontAwesome Icons
10
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11
+ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
12
+
13
+ // Import shared types
14
+ import ReactKitErrorCode from '../types/ReactKitErrorCode';
15
+
16
+ /*------------------------------------------------------------------------*/
17
+ /* Types */
18
+ /*------------------------------------------------------------------------*/
19
+
20
+ type Props = {
21
+ // Error to display
22
+ error: any,
23
+ // Customized title of error box
24
+ title?: string,
25
+ // Handler for close,
26
+ onClose?: () => void,
27
+ };
28
+
29
+ /*------------------------------------------------------------------------*/
30
+ /* Component */
31
+ /*------------------------------------------------------------------------*/
32
+
33
+ const ErrorBox: React.FC<Props> = (props) => {
34
+ /*------------------------------------------------------------------------*/
35
+ /* Setup */
36
+ /*------------------------------------------------------------------------*/
37
+
38
+ /* -------------- Props ------------- */
39
+
40
+ const {
41
+ error,
42
+ title = 'An Error Occurred',
43
+ onClose,
44
+ } = props;
45
+
46
+ /*------------------------------------------------------------------------*/
47
+ /* Render */
48
+ /*------------------------------------------------------------------------*/
49
+
50
+ // Determine error text
51
+ const errorText: string = (
52
+ typeof error === 'string'
53
+ ? error.trim()
54
+ : String(error.message || 'An unknown error occurred. Please contact support.')
55
+ );
56
+
57
+ // Error code box
58
+ let errorCodeBox;
59
+ if (error && (error as any).code) {
60
+ errorCodeBox = (
61
+ <span>
62
+ {' '}
63
+ <span
64
+ style={{
65
+ backgroundColor: 'white',
66
+ borderRadius: '5px',
67
+ paddingLeft: '3px',
68
+ paddingRight: '3px',
69
+ color: '#DC4150',
70
+ fontVariant: 'small-caps',
71
+ fontSize: '80%',
72
+ whiteSpace: 'nowrap',
73
+ }}
74
+ >
75
+ code:
76
+ {' '}
77
+ {String((error as any).code ?? ReactKitErrorCode.NoCode).toUpperCase()}
78
+ </span>
79
+ </span>
80
+ );
81
+ }
82
+
83
+ // Main UI
84
+ return (
85
+ <div
86
+ className="alert alert-danger text-center"
87
+ style={{
88
+ maxWidth: '650px',
89
+ margin: 'auto',
90
+ }}
91
+ >
92
+ <h4 className="mb-1">
93
+ <FontAwesomeIcon
94
+ icon={faExclamationTriangle}
95
+ className="me-2"
96
+ />
97
+ {title}
98
+ </h4>
99
+ <div>
100
+ {errorText}
101
+ {errorCodeBox}
102
+ </div>
103
+ {onClose && (
104
+ <div className="mt-2">
105
+ <button
106
+ type="button"
107
+ className="btn btn-light"
108
+ aria-label="dismiss error and close this activity or view"
109
+ onClick={onClose}
110
+ >
111
+ Close
112
+ </button>
113
+ </div>
114
+ )}
115
+ </div>
116
+ );
117
+ };
118
+
119
+ /*------------------------------------------------------------------------*/
120
+ /* Wrap Up */
121
+ /*------------------------------------------------------------------------*/
122
+
123
+ // Export component
124
+ export default ErrorBox;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Loading spinner/indicator
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import FontAwesome Icons
7
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8
+ import { faCircle } from '@fortawesome/free-solid-svg-icons';
9
+
10
+ /*------------------------------------------------------------------------*/
11
+ /* Style */
12
+ /*------------------------------------------------------------------------*/
13
+
14
+ const style = `
15
+ /* Blips */
16
+ .LoadingSpinner-blip-1,
17
+ .LoadingSpinner-blip-2,
18
+ .LoadingSpinner-blip-3,
19
+ .LoadingSpinner-blip-4 {
20
+ font-size: 25px;
21
+ opacity: 0.6;
22
+ margin-top: 20px;
23
+ margin-bottom: 20px;
24
+ }
25
+
26
+ /* First Blip */
27
+ .LoadingSpinner-blip-1 {
28
+ animation: LoadingSpinner-pop-animation 2s infinite;
29
+ }
30
+
31
+ /* Second Blip */
32
+ .LoadingSpinner-blip-2 {
33
+ animation: LoadingSpinner-pop-animation 2s infinite;
34
+ -webkit-animation-delay: 0.1s;
35
+ animation-delay: 0.1s;
36
+ }
37
+
38
+ /* Third Blip */
39
+ .LoadingSpinner-blip-3 {
40
+ animation: LoadingSpinner-pop-animation 2s infinite;
41
+ animation-delay: 0.2s;
42
+ }
43
+
44
+ /* Fourth Blip */
45
+ .LoadingSpinner-blip-4 {
46
+ animation: LoadingSpinner-pop-animation 2s infinite;
47
+ animation-delay: 0.3s;
48
+ }
49
+
50
+ /* Pop Animation for Each Blip */
51
+ @keyframes LoadingSpinner-pop-animation {
52
+ 0% {
53
+ transform: scale(1.0);
54
+ }
55
+ 10% {
56
+ transform: scale(1.5);
57
+ }
58
+ 30% {
59
+ transform: scale(1.0);
60
+ }
61
+ 100% {
62
+ transform: scale(1.0);
63
+ }
64
+ }
65
+ `;
66
+
67
+ /*------------------------------------------------------------------------*/
68
+ /* Component */
69
+ /*------------------------------------------------------------------------*/
70
+
71
+ const LoadingSpinner = () => {
72
+ /*------------------------------------------------------------------------*/
73
+ /* Render */
74
+ /*------------------------------------------------------------------------*/
75
+
76
+ // Add all four blips to a container
77
+ return (
78
+ <div className="text-center LoadingSpinner LoadingSpinner-container">
79
+ <style>{style}</style>
80
+ <FontAwesomeIcon
81
+ icon={faCircle}
82
+ className="LoadingSpinner-blip-1 me-1"
83
+ />
84
+ <FontAwesomeIcon
85
+ icon={faCircle}
86
+ className="LoadingSpinner-blip-2 me-1"
87
+ />
88
+ <FontAwesomeIcon
89
+ icon={faCircle}
90
+ className="LoadingSpinner-blip-3 me-1"
91
+ />
92
+ <FontAwesomeIcon
93
+ icon={faCircle}
94
+ className="LoadingSpinner-blip-4"
95
+ />
96
+ </div>
97
+ );
98
+ };
99
+
100
+ /*------------------------------------------------------------------------*/
101
+ /* Wrap Up */
102
+ /*------------------------------------------------------------------------*/
103
+
104
+ // Export component
105
+ export default LoadingSpinner;