Commit e3f1ecba by Arjun Jhukal

minor changes

parent a15facb5
...@@ -37,6 +37,12 @@ export interface PaymentModalProps { ...@@ -37,6 +37,12 @@ export interface PaymentModalProps {
successMessage?: string; successMessage?: string;
/** /**
* HTTP path segments / URLs that indicate the provider redirected to app success route
* Useful for redirect flows like Acuity /login on success.
*/
successRedirectPaths?: string[];
/**
* Title shown while loading * Title shown while loading
*/ */
title?: string; title?: string;
...@@ -97,6 +103,7 @@ export default function PaymentModal({ ...@@ -97,6 +103,7 @@ export default function PaymentModal({
onSuccess, onSuccess,
onError, onError,
successMessage = 'success', successMessage = 'success',
successRedirectPaths = ['/login', '/success'],
title = 'Processing Payment', title = 'Processing Payment',
maxWidth = 'sm', maxWidth = 'sm',
height = 600 height = 600
...@@ -104,19 +111,55 @@ export default function PaymentModal({ ...@@ -104,19 +111,55 @@ export default function PaymentModal({
const iframeRef = useRef<HTMLIFrameElement>(null); const iframeRef = useRef<HTMLIFrameElement>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<PaymentError | null>(null); const [error, setError] = useState<PaymentError | null>(null);
const [hasDetectedSuccess, setHasDetectedSuccess] = useState(false);
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
setHasDetectedSuccess(false);
return; return;
} }
const checkRedirectSuccess = (): void => {
if (hasDetectedSuccess || !iframeRef.current?.contentWindow) return;
try {
const currentUrl = iframeRef.current.contentWindow.location.href;
if (!currentUrl) return;
const normalized = currentUrl.toLowerCase();
const found = successRedirectPaths.some((pattern) => {
const normalizedPattern = pattern.toLowerCase();
if (normalizedPattern.startsWith('http://') || normalizedPattern.startsWith('https://')) {
return normalized === normalizedPattern;
}
return normalized.includes(normalizedPattern);
});
if (found) {
console.log('[PaymentModal] Detected success redirect URL:', currentUrl);
setHasDetectedSuccess(true);
setIsLoading(false);
onSuccess?.();
setTimeout(() => onClose(), 300);
}
} catch {
// cross-origin iframe URL access will throw during external provider flow, ignore until same-origin
}
};
const handleMessage = (event: MessageEvent): void => { const handleMessage = (event: MessageEvent): void => {
try { try {
// Security: Verify message origin // Ignore messages from React DevTools and other injected frames
if (event.source !== iframeRef.current?.contentWindow) {
console.debug('[PaymentModal] Ignoring message from non-iframe source', event.origin);
return;
}
// Security: Verify message origin; allow provider origin and same-host fallback
const paymentOrigin = new URL(url).origin; const paymentOrigin = new URL(url).origin;
if (event.origin !== paymentOrigin) { const trustedOrigins = new Set([paymentOrigin, window.location.origin]);
if (!trustedOrigins.has(event.origin)) {
console.warn( console.warn(
`[PaymentModal] Ignoring message from untrusted origin: ${event.origin}` `[PaymentModal] Ignoring message from untrusted origin: ${event.origin}`
); );
...@@ -144,6 +187,8 @@ export default function PaymentModal({ ...@@ -144,6 +187,8 @@ export default function PaymentModal({
if (isSuccess) { if (isSuccess) {
console.log('[PaymentModal] Payment successful! Closing modal...'); console.log('[PaymentModal] Payment successful! Closing modal...');
setHasDetectedSuccess(true);
setIsLoading(false);
onSuccess?.(); onSuccess?.();
setTimeout(() => onClose(), 500); // Small delay for animation setTimeout(() => onClose(), 500); // Small delay for animation
return; return;
...@@ -165,16 +210,17 @@ export default function PaymentModal({ ...@@ -165,16 +210,17 @@ export default function PaymentModal({
setError(paymentError); setError(paymentError);
onError?.(paymentError); onError?.(paymentError);
} }
} catch (error) { } catch {
console.error('[PaymentModal] Error handling message:', error); console.error('[PaymentModal] Error handling message: unknown error');
} }
}; };
// Add message listener const redirectInterval = window.setInterval(checkRedirectSuccess, 450);
window.addEventListener('message', handleMessage); window.addEventListener('message', handleMessage);
// Notify iframe that parent is ready (for some providers) // Notify iframe that parent is ready (for some providers)
setTimeout(() => { const readyTimeout = window.setTimeout(() => {
if (iframeRef.current?.contentWindow) { if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage( iframeRef.current.contentWindow.postMessage(
{ type: 'parent:ready' }, { type: 'parent:ready' },
...@@ -184,9 +230,11 @@ export default function PaymentModal({ ...@@ -184,9 +230,11 @@ export default function PaymentModal({
}, 1000); }, 1000);
return () => { return () => {
window.clearInterval(redirectInterval);
window.clearTimeout(readyTimeout);
window.removeEventListener('message', handleMessage); window.removeEventListener('message', handleMessage);
}; };
}, [isOpen, url, onClose, onSuccess, onError, successMessage]); }, [isOpen, url, onClose, onSuccess, onError, successMessage, successRedirectPaths, hasDetectedSuccess]);
const handleIframeLoad = (): void => { const handleIframeLoad = (): void => {
console.log('[PaymentModal] iframe loaded'); console.log('[PaymentModal] iframe loaded');
......
'use client'; 'use client';
import PasswordField from '@/components/molecules/PasswordField'; import PasswordField from '@/components/molecules/PasswordField';
import PaymentModal from '@/components/molecules/PaymentModal';
import { US_STATES } from '@/constants/state'; import { US_STATES } from '@/constants/state';
import { useAppDispatch } from '@/hooks/hook'; import { useAppDispatch } from '@/hooks/hook';
import { PATH } from '@/routes/PATH'; import { PATH } from '@/routes/PATH';
...@@ -14,6 +15,8 @@ import { ArrowLeft } from '@wandersonalwes/iconsax-react'; ...@@ -14,6 +15,8 @@ import { ArrowLeft } from '@wandersonalwes/iconsax-react';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import AuthMessageBlock from '../authMessageBlock'; import AuthMessageBlock from '../authMessageBlock';
...@@ -104,6 +107,9 @@ const validationSchema = Yup.object().shape({ ...@@ -104,6 +107,9 @@ const validationSchema = Yup.object().shape({
export default function RegisterPage() { export default function RegisterPage() {
const [registerUser, { isLoading }] = useRegisterUserMutation(); const [registerUser, { isLoading }] = useRegisterUserMutation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [isAcuityModalOpen, setIsAcuityModalOpen] = useState(false);
const [acuityUrl, setAcuityUrl] = useState('');
const route = useRouter();
const initialValues = { const initialValues = {
first_name: '', first_name: '',
middle_name: '', middle_name: '',
...@@ -157,9 +163,12 @@ export default function RegisterPage() { ...@@ -157,9 +163,12 @@ export default function RegisterPage() {
autoTime: true, autoTime: true,
}), }),
); );
console.log("Register response:", response?.data?.redirect_url); console.log("Register response:", response?.data?.redirection_url);
if (response?.data?.redirect_url) { if (response?.data?.redirection_url) {
window.open(response?.data?.redirect_url, '_blank'); window.open(response?.data?.redirection_url, "_blank");
setAcuityUrl(response.data.redirection_url);
// setIsAcuityModalOpen(true);
route.replace(PATH.AUTH.LOGIN.ROOT)
} }
} }
...@@ -487,6 +496,25 @@ export default function RegisterPage() { ...@@ -487,6 +496,25 @@ export default function RegisterPage() {
</form> </form>
</Box> </Box>
<PaymentModal
url={acuityUrl}
isOpen={isAcuityModalOpen}
onClose={() => setIsAcuityModalOpen(false)}
onSuccess={() => {
setIsAcuityModalOpen(false);
dispatch(showToast({ message: 'Verification complete!', variant: ToastVariant.SUCCESS, autoTime: true }));
}}
onError={(error: { message: string }) => {
console.error('Acuity verification error', error);
dispatch(showToast({ message: error.message || 'Verification failed', variant: ToastVariant.ERROR, autoTime: true }));
}}
successMessage="verified"
title="Acuity identity verification"
maxWidth="md"
height={700}
/>
</> </>
) )
} }
...@@ -29,7 +29,7 @@ export interface LoginResponse { ...@@ -29,7 +29,7 @@ export interface LoginResponse {
access_token: string, access_token: string,
// expires_in: 3600, // expires_in: 3600,
user: User, user: User,
redirect_url: string; redirection_url: string;
} }
message: string message: string
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment