Skip to content

Commit 3267f77

Browse files
mfranciscDevtools
andauthored
fix(sandbox): open trial after phone verification (#918)
* open trial after phone verification --------- Co-authored-by: Devtools <devtools@redhat.com>
1 parent b61f8f3 commit 3267f77

11 files changed

Lines changed: 398 additions & 193 deletions

File tree

workspaces/sandbox/make/sandbox.mk

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ RHDH_LOCAL_DIR := "$(TMPDIR)rhdh-local"
1717
clone-rhdh-local:
1818
rm -rf ${RHDH_LOCAL_DIR}; \
1919
git clone https://github.com/redhat-developer/rhdh-local $(RHDH_LOCAL_DIR) && \
20+
cd $(RHDH_LOCAL_DIR) && \
21+
git checkout 37be302480b1458c3dfd3270c25726d65f0ffe75 && \
2022
echo "cloned to $(RHDH_LOCAL_DIR)"
2123

2224

@@ -35,12 +37,11 @@ generate-env:
3537
start-rhdh-local: clone-rhdh-local generate-env
3638
rm -rf plugins/sandbox/dist-dynamic
3739
rm -rf red-hat-developer-hub-backstage-plugin-sandbox
38-
podman run -it --rm -w /home -v $(PWD):/home node:20.19.2 bash -c "yarn install && npx --yes @janus-idp/cli@3.3.1 package package-dynamic-plugins --export-to . && exit"
40+
podman run -it --rm -w /home -v $(PWD):/home node:20 bash -c "yarn install && npx --yes @janus-idp/cli@latest package package-dynamic-plugins --export-to . && exit"
3941
cp -r red-hat-developer-hub-backstage-plugin-sandbox $(RHDH_LOCAL_DIR)/local-plugins/
4042
cp deploy/base/app-config.yaml $(RHDH_LOCAL_DIR)/configs/app-config/app-config.yaml
4143
cp deploy/base/dynamic-plugins.yaml $(RHDH_LOCAL_DIR)/configs/dynamic-plugins/dynamic-plugins.override.yaml
4244
cd $(RHDH_LOCAL_DIR) && \
43-
yq e 'del(.services.rhdh.volumes[] | select(. == "./configs:/opt/app-root/src/configs:Z"))' -i compose.yaml && \
4445
yq e '.services.rhdh.ports = ["3000:3000"] | (.services.rhdh.ports) |= map(. style="double")' -i compose.yaml && \
4546
podman-compose up -d && \
4647
echo "UI is up and running at: http://localhost:3000"

workspaces/sandbox/plugins/sandbox/src/components/Modals/PhoneVerificationModal.tsx

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,34 @@ import {
2323
PhoneNumberStep,
2424
VerificationCodeStep,
2525
} from './PhoneVerificationSteps';
26-
import { useSandboxContext } from '../../hooks/useSandboxContext';
2726
import { errorMessage } from '../../utils/common';
27+
import { Product } from '../SandboxCatalog/productData';
2828

2929
type PhoneVerificationModalProps = {
30+
id: Product;
3031
modalOpen: boolean;
3132
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
33+
setAnsibleCredsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
34+
setRefetchingUserData: React.Dispatch<React.SetStateAction<boolean>>;
3235
};
3336

3437
export const PhoneVerificationModal: React.FC<PhoneVerificationModalProps> = ({
38+
id,
3539
modalOpen,
3640
setOpen,
41+
setAnsibleCredsModalOpen,
42+
setRefetchingUserData,
3743
}) => {
3844
const registerApi = useApi(registerApiRef);
3945
const [enterOTP, setEnterOTP] = useState<boolean>(false);
40-
const { refetchUserData } = useSandboxContext();
4146
const [phoneNumber, setPhoneNumber] = useState<E164Number | undefined>();
4247
const [country, setCountry] = useState<Country>('ES');
4348
const [otp, setOtp] = useState<string[]>(['', '', '', '', '', '']);
4449

4550
const [loading, setLoading] = useState<boolean>(false);
46-
const [codeResent, setCodeResent] = useState<boolean>(false);
4751
const [phoneSubmitError, setPhoneSubmitError] = React.useState<
4852
string | undefined
4953
>();
50-
const [verificationCodeError, setVerificationCodeError] = React.useState<
51-
string | undefined
52-
>();
5354

5455
const handleClose = () => {
5556
setCountry('ES');
@@ -58,8 +59,6 @@ export const PhoneVerificationModal: React.FC<PhoneVerificationModalProps> = ({
5859
setOpen(false);
5960
setEnterOTP(false);
6061
setPhoneSubmitError(undefined);
61-
setVerificationCodeError(undefined);
62-
setCodeResent(false);
6362
};
6463

6564
const handlePhoneNumberSubmit = async () => {
@@ -84,46 +83,12 @@ export const PhoneVerificationModal: React.FC<PhoneVerificationModalProps> = ({
8483
}
8584
};
8685

87-
const handleStartTrialClick = async () => {
88-
try {
89-
setVerificationCodeError(undefined);
90-
setLoading(true);
91-
await registerApi.completePhoneVerification(otp.join(''));
92-
await refetchUserData();
93-
handleClose();
94-
} catch (e) {
95-
setVerificationCodeError(errorMessage(e));
96-
} finally {
97-
setLoading(false);
98-
}
99-
};
100-
10186
const handleEditPhoneNumber = () => {
10287
setPhoneNumber(undefined);
10388
setEnterOTP(false);
10489
setOtp(['', '', '', '', '', '']);
10590
};
10691

107-
const handleResendCode = async () => {
108-
if (codeResent) return;
109-
110-
const countryCallingCode = `+${getCountryCallingCode(country)}`;
111-
setOtp(['', '', '', '', '', '']);
112-
try {
113-
setVerificationCodeError(undefined);
114-
setLoading(true);
115-
await registerApi.initiatePhoneVerification(
116-
countryCallingCode,
117-
(phoneNumber as string)?.replace(countryCallingCode, ''),
118-
);
119-
setCodeResent(true);
120-
} catch (e) {
121-
setVerificationCodeError(errorMessage(e));
122-
} finally {
123-
setLoading(false);
124-
}
125-
};
126-
12792
return (
12893
<Dialog open={modalOpen} onClose={handleClose}>
12994
{!enterOTP ? (
@@ -139,16 +104,17 @@ export const PhoneVerificationModal: React.FC<PhoneVerificationModalProps> = ({
139104
/>
140105
) : (
141106
<VerificationCodeStep
107+
id={id}
142108
otp={otp}
143109
setOtp={setOtp}
144-
handleResendCode={handleResendCode}
145-
codeResent={codeResent}
146110
phoneNumber={phoneNumber}
147111
handleEditPhoneNumber={handleEditPhoneNumber}
148-
handleStartTrialClick={handleStartTrialClick}
112+
setAnsibleCredsModalOpen={setAnsibleCredsModalOpen}
113+
setRefetchingUserData={setRefetchingUserData}
149114
handleClose={handleClose}
150115
loading={loading}
151-
error={verificationCodeError}
116+
setLoading={setLoading}
117+
country={country}
152118
/>
153119
)}
154120
</Dialog>

workspaces/sandbox/plugins/sandbox/src/components/Modals/PhoneVerificationSteps/VerificationCodeStep.tsx

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import React, { useEffect, useRef } from 'react';
16+
import React, { useEffect, useRef, useState } from 'react';
1717
import { E164Number, parsePhoneNumber } from 'libphonenumber-js/min';
1818
import { useTheme } from '@mui/material/styles';
1919
import Typography from '@mui/material/Typography';
@@ -31,35 +31,51 @@ import CloseIcon from '@mui/icons-material/Close';
3131
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
3232
import CircularProgress from '@mui/material/CircularProgress';
3333
import { isValidOTP } from '../../../utils/phone-utils';
34+
import { Product } from '../../SandboxCatalog/productData';
35+
import { signupDataToStatus } from '../../../utils/register-utils';
36+
import { productsURLMapping } from '../../../hooks/useProductURLs';
37+
import { errorMessage } from '../../../utils/common';
38+
import { useSandboxContext } from '../../../hooks/useSandboxContext';
39+
import { Country, getCountryCallingCode } from 'react-phone-number-input';
40+
import { useApi } from '@backstage/core-plugin-api';
41+
import { registerApiRef } from '../../../api';
3442

3543
type VerificationCodeProps = {
44+
id: Product;
3645
otp: string[];
3746
setOtp: React.Dispatch<React.SetStateAction<string[]>>;
38-
handleResendCode: () => void;
39-
codeResent: boolean;
47+
country: Country;
4048
phoneNumber: E164Number | undefined;
4149
handleEditPhoneNumber: () => void;
42-
handleStartTrialClick: () => void;
4350
handleClose: () => void;
51+
setAnsibleCredsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
52+
setRefetchingUserData: React.Dispatch<React.SetStateAction<boolean>>;
53+
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
4454
loading?: boolean;
45-
error?: string;
4655
};
4756

4857
export const VerificationCodeStep: React.FC<VerificationCodeProps> = ({
58+
id,
4959
otp,
5060
setOtp,
51-
handleResendCode,
52-
codeResent,
61+
country,
5362
phoneNumber,
5463
handleEditPhoneNumber,
55-
handleStartTrialClick,
64+
setAnsibleCredsModalOpen,
65+
setRefetchingUserData,
5666
handleClose,
5767
loading,
58-
error,
68+
setLoading,
5969
}) => {
6070
const theme = useTheme();
6171

6272
const inputRefs = useRef<any>([]);
73+
const { refetchUserData, handleAAPInstance } = useSandboxContext();
74+
const [verificationCodeError, setVerificationCodeError] = React.useState<
75+
string | undefined
76+
>();
77+
const [codeResent, setCodeResent] = useState<boolean>(false);
78+
const registerApi = useApi(registerApiRef);
6379

6480
useEffect(() => {
6581
// Focus on the first input box when modal opens
@@ -107,6 +123,82 @@ export const VerificationCodeStep: React.FC<VerificationCodeProps> = ({
107123
}
108124
};
109125

126+
const handleResendCode = async () => {
127+
if (codeResent) return;
128+
129+
const countryCallingCode = `+${getCountryCallingCode(country)}`;
130+
setOtp(['', '', '', '', '', '']);
131+
try {
132+
setVerificationCodeError(undefined);
133+
setLoading(true);
134+
await registerApi.initiatePhoneVerification(
135+
countryCallingCode,
136+
(phoneNumber as string)?.replace(countryCallingCode, ''),
137+
);
138+
setCodeResent(true);
139+
} catch (e) {
140+
setVerificationCodeError(errorMessage(e));
141+
} finally {
142+
setLoading(false);
143+
}
144+
};
145+
146+
const handleStartTrialClick = async (pdt: Product) => {
147+
try {
148+
setVerificationCodeError(undefined);
149+
setLoading(true);
150+
await registerApi.completePhoneVerification(otp.join(''));
151+
const maxAttempts = 5;
152+
const retryInterval = 1000; // 1 second
153+
154+
// Poll until user is found or max attempts reached
155+
let urlToOpen = '';
156+
let userFound = false;
157+
let userReady = false;
158+
for (let i = 0; i < maxAttempts; i++) {
159+
setRefetchingUserData(true);
160+
await new Promise(resolve => setTimeout(resolve, retryInterval));
161+
162+
// Fetch the latest user data and check if user is found
163+
const userData = await refetchUserData();
164+
if (userData) {
165+
userFound = true;
166+
const userStatus = signupDataToStatus(userData);
167+
userReady = userStatus === 'ready';
168+
// if user is ready we can stop fetching the data
169+
if (userReady) {
170+
const productURLs = productsURLMapping(userData);
171+
// find the link to open if any
172+
urlToOpen = productURLs.find(pu => pu.id === id)?.url || '';
173+
// User has signed up and the trial is ready and user selects the AAP Trial
174+
if (userFound && userReady) {
175+
if (pdt === Product.AAP) {
176+
if (!userData?.defaultUserNamespace) {
177+
// eslint-disable-next-line
178+
console.error(
179+
'unable to provision AAP. user namespace is not defined.',
180+
);
181+
return;
182+
}
183+
handleAAPInstance(userData.defaultUserNamespace);
184+
setAnsibleCredsModalOpen(true);
185+
} else if (urlToOpen) {
186+
window.open(urlToOpen, '_blank');
187+
}
188+
}
189+
break;
190+
}
191+
}
192+
}
193+
handleClose();
194+
} catch (error) {
195+
setVerificationCodeError(errorMessage(error));
196+
} finally {
197+
setLoading(false);
198+
setRefetchingUserData(false);
199+
}
200+
};
201+
110202
return (
111203
<>
112204
<DialogTitle
@@ -202,14 +294,14 @@ export const VerificationCodeStep: React.FC<VerificationCodeProps> = ({
202294
</div>
203295
</Typography>
204296

205-
{error && (
297+
{verificationCodeError && (
206298
<Typography
207299
color="error"
208300
style={{ fontSize: '16px', fontWeight: 400, marginTop: '16px' }}
209301
>
210302
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
211303
<ErrorIcon color="error" style={{ fontSize: '16px' }} />
212-
{error}
304+
{verificationCodeError}
213305
</div>
214306
</Typography>
215307
)}
@@ -219,7 +311,7 @@ export const VerificationCodeStep: React.FC<VerificationCodeProps> = ({
219311
data-testid="submit-opt-button"
220312
variant="contained"
221313
type="submit"
222-
onClick={handleStartTrialClick}
314+
onClick={() => handleStartTrialClick(id)}
223315
disabled={otp.some(digit => !digit) || loading}
224316
endIcon={
225317
loading && <CircularProgress size={20} sx={{ color: '#AFAFAF' }} />

0 commit comments

Comments
 (0)