@thecb/components 11.3.1-beta.0 → 11.3.2-beta.0

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": "@thecb/components",
3
- "version": "11.3.1-beta.0",
3
+ "version": "11.3.2-beta.0",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -6,17 +6,43 @@ import React, {
6
6
  useImperativeHandle
7
7
  } from "react";
8
8
  import { Box } from "../../atoms/layouts";
9
+ import Text from "../../atoms/text";
9
10
  import styled from "styled-components";
10
11
  import { useTurnstileScript } from "../../../hooks";
11
12
  import { noop } from "../../../util/general";
13
+ import { ERROR_COLOR } from "../../../constants/colors";
12
14
 
13
15
  const TurnstileWidgetContainer = styled(Box)`
14
16
  display: flex;
17
+ flex-direction: column;
18
+ align-items: flex-end;
15
19
  padding: ${({ padding }) => padding};
16
20
  justify-content: ${({ justify }) => justify};
21
+ align-items: ${({ justify }) => justify};
17
22
  width: 100%;
18
23
  `;
19
24
 
25
+ const FATAL_ERROR_CODES = [
26
+ // Configuration errors
27
+ /^100/,
28
+ /^105/,
29
+ /^110100$/,
30
+ /^110110$/,
31
+ /^110200$/,
32
+ /^110420$/,
33
+ /^110430$/,
34
+ /^400020$/,
35
+ /^400030$/,
36
+ /^400040$/,
37
+ // Browser/environment errors
38
+ /^110500$/,
39
+ /^110510$/,
40
+ /^200010$/,
41
+ /^200100$/,
42
+ // Internal errors
43
+ /^120/
44
+ ];
45
+
20
46
  const TurnstileWidget = forwardRef(
21
47
  (
22
48
  {
@@ -29,62 +55,140 @@ const TurnstileWidget = forwardRef(
29
55
  justify = "flex-end",
30
56
  size = "normal",
31
57
  tabindex = 0,
32
- retry = "auto",
33
- theme = "auto",
34
- extraStyles = ""
58
+ retry = "never",
59
+ theme = "light",
60
+ extraStyles = "",
61
+ maxErrorRetries = 3
35
62
  },
36
63
  ref
37
64
  ) => {
38
65
  if (!verifyURL || !siteKey) return null;
39
66
 
40
67
  const widgetRef = useRef(null);
41
- const [widgetId, setWidgetId] = useState(null);
42
- const isTurnstileLoaded = useTurnstileScript(verifyURL);
68
+ const widgetIdRef = useRef(null);
69
+ const retryCountRef = useRef(0);
70
+ const [error, setError] = useState(null);
71
+
72
+ const { scriptLoaded, scriptError } = useTurnstileScript(verifyURL);
73
+
74
+ // Clear the error state and reset retry count
75
+ const clearError = () => {
76
+ setError(null);
77
+ retryCountRef.current = 0;
78
+ };
79
+
80
+ // Handle errors based on the retry strategy
81
+ const handleError = errorCode => {
82
+ retryCountRef.current += 1;
83
+ const currentRetryCount = retryCountRef.current;
84
+
85
+ // If we haven't exceeded max retries, reset and try again
86
+ if (currentRetryCount <= maxErrorRetries) {
87
+ window.turnstile.reset(widgetIdRef.current);
88
+ setError(null);
89
+ return;
90
+ }
91
+
92
+ // Max retries exceeded - show appropriate error message
93
+ if (errorCode) {
94
+ const errorMessage = getErrorMessage(errorCode);
95
+ setError(errorMessage);
96
+ }
97
+ };
98
+
99
+ const getErrorMessage = errorCode => {
100
+ const isFatalError = FATAL_ERROR_CODES.some(pattern =>
101
+ pattern.test(errorCode)
102
+ );
103
+
104
+ if (isFatalError) {
105
+ return "Browser or system error. Please refresh the page or update your browser.";
106
+ }
107
+ return "Something went wrong. Please refresh and try again.";
108
+ };
43
109
 
44
110
  // Allow the parent component to reset the Turnstile widget
45
111
  useImperativeHandle(
46
112
  ref,
47
113
  () => ({
48
114
  reset: () => {
49
- if (widgetId && window.turnstile) {
50
- window.turnstile.reset(widgetId);
115
+ if (widgetIdRef.current && window.turnstile) {
116
+ window.turnstile.reset(widgetIdRef.current);
117
+ clearError();
51
118
  }
52
119
  }
53
120
  }),
54
- [widgetId]
121
+ []
55
122
  );
56
123
 
57
124
  useEffect(() => {
58
- if (!widgetRef.current || !window.turnstile) return;
59
- if (!widgetId) {
60
- const newWidgetId = window.turnstile.render(widgetRef.current, {
61
- sitekey: siteKey,
62
- size: size,
63
- retry: retry,
64
- tabindex: tabindex,
65
- theme: theme,
66
- callback: token => onSuccess(token),
67
- "error-callback": () => onError(),
68
- "expired-callback": () => onExpired()
69
- });
70
- setWidgetId(newWidgetId);
71
- }
125
+ if (widgetIdRef.current || !window.turnstile || !scriptLoaded) return;
126
+
127
+ widgetIdRef.current = window.turnstile.render(widgetRef.current, {
128
+ sitekey: siteKey,
129
+ size: size,
130
+ retry: retry,
131
+ tabindex: tabindex,
132
+ theme: theme,
133
+ callback: token => {
134
+ clearError();
135
+ onSuccess(token);
136
+ },
137
+ "error-callback": errorCode => {
138
+ if (retry === "never") {
139
+ handleError(errorCode);
140
+ }
141
+ onError?.(errorCode);
142
+ },
143
+ "expired-callback": () => {
144
+ clearError();
145
+ onExpired?.();
146
+ }
147
+ });
148
+
72
149
  return () => {
73
- if (widgetId && window.turnstile) {
74
- window.turnstile.remove(widgetId);
75
- setWidgetId(null);
150
+ if (widgetIdRef.current && window.turnstile) {
151
+ window.turnstile.remove(widgetIdRef.current);
152
+ widgetIdRef.current = null;
153
+ clearError();
76
154
  }
77
155
  };
78
- }, [isTurnstileLoaded, siteKey, widgetId]);
156
+ }, [scriptLoaded, siteKey]);
79
157
 
80
158
  return (
81
- <TurnstileWidgetContainer
82
- padding={padding}
83
- justify={justify}
84
- extraStyles={extraStyles}
85
- >
86
- <div ref={widgetRef} />
87
- </TurnstileWidgetContainer>
159
+ <>
160
+ <TurnstileWidgetContainer
161
+ padding={padding}
162
+ justify={justify}
163
+ extraStyles={extraStyles}
164
+ >
165
+ <div ref={widgetRef} />
166
+ {error && (
167
+ <Text
168
+ color={ERROR_COLOR}
169
+ variant="pXS"
170
+ extraStyles={`
171
+ word-break: break-word;
172
+ text-align: end;
173
+ `}
174
+ >
175
+ {error}
176
+ </Text>
177
+ )}
178
+ {scriptError && (
179
+ <Text
180
+ color={ERROR_COLOR}
181
+ variant="pXS"
182
+ extraStyles={`
183
+ word-break: break-word;
184
+ text-align: end;
185
+ `}
186
+ >
187
+ Unable to load security verification. Please refresh the page.
188
+ </Text>
189
+ )}
190
+ </TurnstileWidgetContainer>
191
+ </>
88
192
  );
89
193
  }
90
194
  );
@@ -6,31 +6,40 @@ import { useEffect, useState } from "react";
6
6
  * @param {string} verifyURL - The URL of the Turnstile verification script.
7
7
  */
8
8
  const useTurnstileScript = verifyURL => {
9
- const [isLoaded, setIsLoaded] = useState(false);
9
+ const [scriptLoaded, setScriptLoaded] = useState(false);
10
+ const [scriptError, setScriptError] = useState(null);
10
11
 
11
12
  useEffect(() => {
12
13
  if (typeof window === "undefined") {
13
- setIsLoaded(false);
14
+ setScriptLoaded(false);
14
15
  return;
15
16
  }
16
17
  if (window.turnstile && window.turnstile.render) {
17
- setIsLoaded(true);
18
+ setScriptLoaded(true);
18
19
  return;
19
20
  }
20
21
 
21
22
  const script = document.createElement("script");
22
23
  script.src = `${verifyURL}?render=explicit`;
23
24
  script.onload = () => {
24
- setIsLoaded(true);
25
+ setScriptLoaded(true);
26
+ };
27
+ script.onerror = () => {
28
+ setScriptError(true);
29
+ setScriptLoaded(false);
30
+ };
31
+ script.onabort = () => {
32
+ setScriptError(true);
33
+ setScriptLoaded(false);
25
34
  };
26
35
  script.defer = true;
27
36
  document.getElementsByTagName("head")[0].appendChild(script);
28
37
 
29
38
  return () => {
30
- setIsLoaded(false);
39
+ setScriptLoaded(false);
31
40
  };
32
41
  }, [verifyURL]);
33
42
 
34
- return isLoaded;
43
+ return { scriptLoaded, scriptError };
35
44
  };
36
45
  export default useTurnstileScript;
package/src/.DS_Store DELETED
Binary file
Binary file
Binary file