feat: account created page redirect button added, navigate logic created, refresh token bug fix

This commit is contained in:
مهرزاد قدرتی
2025-08-19 16:46:58 +03:30
parent c8ffd34f15
commit 6ff25c69f2
12 changed files with 430 additions and 36 deletions

View File

@@ -6,8 +6,9 @@ import { useTranslation } from 'react-i18next';
import { Link as RouterLink, useSearchParams } from 'react-router-dom';
import Link from '@mui/material/Link';
import { AccountCreatedClubBanner } from './AccountCreatedClubBanner';
import { AccountCreatedRedirectButton } from './AccountCreatedRedirectButton';
type RequestedApplication = 'default' | 'unknown' | 'club';
export type RequestedApplication = 'default' | 'unknown' | 'club';
export const AccountCreated = () => {
const { t } = useTranslation('authentication');
@@ -19,13 +20,21 @@ export const AccountCreated = () => {
if (!returnUrl) return 'default';
if (returnUrl.includes('https://club.business-harmony.com/')) {
if (returnUrl.includes('club.business-harmony.com')) {
return 'club';
}
return 'unknown';
}, [params]);
const handleRedirectUser = () => {
const returnUrl = params.get('returnUrl');
if (returnUrl) {
location.href = returnUrl;
}
};
return (
<AuthenticationCard maxWidth="636px">
<Stack sx={{ alignItems: 'center' }}>
@@ -50,7 +59,14 @@ export const AccountCreated = () => {
<Box sx={{ mx: 7 }}>
<Divider sx={{ my: 2 }} />
{requestedApplication === 'club' && <AccountCreatedClubBanner />}
<Box sx={{ py: 2.5 }}>
{requestedApplication === 'club' && <AccountCreatedClubBanner />}
<AccountCreatedRedirectButton
onRedirectUser={handleRedirectUser}
requestedApplication={requestedApplication}
/>
</Box>
</Box>
)}
</AuthenticationCard>

View File

@@ -9,7 +9,7 @@ export const AccountCreatedClubBanner = () => {
const theme = useTheme();
return (
<Stack direction="row" sx={{ py: 2.5 }} spacing={2} alignItems="start">
<Stack direction="row" spacing={2} sx={{ pb: 1 }} alignItems="start">
<Profile2User size={80} color={theme.palette.club.main} />
<Stack spacing={0.5}>

View File

@@ -0,0 +1,51 @@
import { useMemo } from 'react';
import type { RequestedApplication } from './AccountCreated';
import { Box, Button, useTheme } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { CountDownTimer } from '@/components/CountDownTimer';
import type { PalleteColor } from '@/theme/palette';
export interface AccountCreatedRedirectButtonProps {
requestedApplication: RequestedApplication;
onRedirectUser: () => void;
}
export const AccountCreatedRedirectButton = ({
requestedApplication,
onRedirectUser,
}: AccountCreatedRedirectButtonProps) => {
const { t } = useTranslation('authentication');
const buttonText = useMemo<string>(() => {
switch (requestedApplication) {
case 'club':
return (
t('accountCreated.redirectingTo') +
' ' +
t('accountCreated.harmonyClub')
);
default:
return t('accountCreated.redirecting');
}
}, [requestedApplication, t]);
const buttonColor = useMemo<PalleteColor>(() => {
switch (requestedApplication) {
case 'club':
return 'club';
default:
return 'primary';
}
}, [requestedApplication]);
return (
<Box sx={{ m: 1 }}>
<Button color={buttonColor} variant="outlined" onClick={onRedirectUser}>
{buttonText + ' '} (
<CountDownTimer initialSeconds={5} onComplete={onRedirectUser} />)
</Button>
</Box>
);
};

View File

@@ -49,27 +49,31 @@ export const AuthenticationSteps = (): JSX.Element => {
}
};
const handleUserLoggedIn = (userId: GUID) => {
localStorage.setItem('userID', userId);
const handleUserLoggedIn = () => {
redirectToReturnUrl();
};
const handleConfrimPhoneNumber = (userId: GUID) => {
localStorage.setItem('userID', userId);
const handleConfrimPhoneNumber = () => {
setCurrentStep('addPhoneNumber');
};
const handlePhoneNumberVerified = () => {
navigate('/signup');
if (authReturnUrl) {
navigate(`/signup?returnUrl=${authReturnUrl}`);
} else {
navigate(`/signup`);
}
};
const redirectToReturnUrl = () => {
if (!authReturnUrl) {
navigate(import.meta.env.VITE_DEFUALT_AUTH_RETURN_URL);
} else {
location.href = authReturnUrl;
if (authMode === 'register') {
navigate(`/account-created?returnUrl=${authReturnUrl}`);
} else {
location.href = authReturnUrl;
}
}
};

View File

@@ -21,10 +21,13 @@ import { least8Chars } from '@/utils/regexes/least8Chars';
import { containsNumber } from '@/utils/regexes/containsNumber';
import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
import { isEmail } from '@/utils/regexes/isEmail';
import { useNavigate, useSearchParams } from 'react-router-dom';
export function UserCompletionPage() {
const [params] = useSearchParams();
const { t } = useTranslation('completionForm');
const showToast = useToast();
const navigate = useNavigate();
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
@@ -128,13 +131,23 @@ export function UserCompletionPage() {
),
severity: submitData.data.success ? 'success' : 'error',
});
if (submitData.data.success) {
const returnUrl = params.get('returnUrl');
navigate(
returnUrl
? `/account-created?returnUrl=${returnUrl}`
: '`/account-created',
);
}
} else if (submitError) {
showToast({
message: getErrorMessage(submitError) || t('completion.problem'),
severity: 'error',
});
}
}, [submitData, submitError, showToast, t]);
}, [submitData, submitError, showToast, t, navigate, params]);
useEffect(() => {
setShowPasswordValidations(password ? !validPassword : false);

View File

@@ -114,31 +114,43 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
}
try {
const result = await axios.post<GenerateTokenResponse>(
import.meta.env.VITE_IDENTITY_URL,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: import.meta.env.VITE_IDENTITY_CLIENT_ID, // from your token payload
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
const activeRefreshSession: string | null =
sessionStorage.getItem('active-refresh');
if (
!activeRefreshSession ||
(activeRefreshSession && JSON.parse(activeRefreshSession) === false)
) {
sessionStorage.setItem('active-refresh', JSON.stringify(true));
const result = await axios.post<GenerateTokenResponse>(
import.meta.env.VITE_IDENTITY_URL,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: import.meta.env.VITE_IDENTITY_CLIENT_ID, // from your token payload
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
},
);
);
if (result.data.access_token) {
inMemoryToken = result.data.access_token;
expiresAt = Date.now() + result.data.expires_in * 1000;
if (result.data.access_token) {
inMemoryToken = result.data.access_token;
expiresAt = Date.now() + result.data.expires_in * 1000;
sessionStorage.setItem(ACCESS_TOKEN_KEY, inMemoryToken as string);
sessionStorage.setItem(REFRESH_TOKEN_KEY, result.data.refresh_token);
sessionStorage.setItem(EXPIRES_IN_KEY, String(expiresAt));
sessionStorage.setItem(ACCESS_TOKEN_KEY, inMemoryToken as string);
sessionStorage.setItem(REFRESH_TOKEN_KEY, result.data.refresh_token);
sessionStorage.setItem(EXPIRES_IN_KEY, String(expiresAt));
setAccessToken(inMemoryToken);
} else {
logout();
setAccessToken(inMemoryToken);
} else {
logout();
}
sessionStorage.setItem('active-refresh', JSON.stringify(false));
}
} catch {
logout();

View File

@@ -76,6 +76,7 @@ export const appRoutes: RouteConfig[] = [
{
path: '/account-created',
element: <AccountCreatedPage />,
authorize: true,
},
{ path: '/signup', element: <UserCompletionPage />, authorize: true },
{

View File

@@ -10,6 +10,21 @@ declare module '@mui/material/styles' {
}
}
declare module '@mui/material/Button' {
interface ButtonPropsColorOverrides {
club: true;
}
}
export type PalleteColor =
| 'primary'
| 'secondary'
| 'error'
| 'success'
| 'warning'
| 'info'
| 'club';
export const lightPalette: PaletteOptions = {
mode: 'light',
primary: PALETTE.primary.light,