Skip to content

Commit

Permalink
Add new pages.
Browse files Browse the repository at this point in the history
Signed-off-by: Aliwoto <[email protected]>
  • Loading branch information
ALiwoto committed Aug 23, 2024
1 parent 2c54a46 commit 6b2349b
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 5 deletions.
5 changes: 5 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UserInfoPage from './pages/userInfoPage';
import { Box, CircularProgress, Typography } from '@mui/material';
import CreateTopicPage from './pages/createTopicPage';
import SearchTopicPage from './pages/searchTopicPage';
import ConfirmAccountRedirectPage from './pages/confirmAccountRedirectPage';

const App: React.FC = () => {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(apiClient.isLoggedIn());
Expand Down Expand Up @@ -62,6 +63,10 @@ const App: React.FC = () => {
return (
<Router>
<Routes>
<Route
path="/confirmAccountRedirect"
element={<ConfirmAccountRedirectPage />}
/>
<Route
path="/login"
element={isLoggedIn ? <Navigate to="/dashboard" /> : <Login />}
Expand Down
12 changes: 12 additions & 0 deletions src/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CreateNewTopicResult,
SearchTopicData,
SearchTopicResult,
ConfirmAccountData,
} from './api';

class ExamSphereAPIClient extends UserApi {
Expand Down Expand Up @@ -224,6 +225,17 @@ class ExamSphereAPIClient extends UserApi {
return createUserResult;
}

public async confirmAccount(confirmData: ConfirmAccountData): Promise<boolean> {
let confirmResult = (await this.confirmAccountV1(confirmData))?.data.result;
if (confirmResult === undefined) {
// we shouldn't reach here, because if there is an error somewhere,
// it should have already been thrown by the API client
throw new Error("Failed to confirm account");
}

return confirmResult;
}

/**
* Refreshes the access token using the refresh token.
* @returns the auth result.
Expand Down
12 changes: 8 additions & 4 deletions src/components/containers/dashboardContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CurrentAppTranslation } from "../../translations/appTranslation";
interface DashboardContainerProps {
children?: React.ReactNode;
style?: React.CSSProperties;
disableSlideMenu?: boolean;
}

const DashboardContainer: React.FC<DashboardContainerProps> = ({ ...props }) => {
Expand All @@ -33,10 +34,13 @@ const DashboardContainer: React.FC<DashboardContainerProps> = ({ ...props }) =>
<TitleLabel>{CurrentAppTranslation.ExamSphereTitleText}</TitleLabel>
<MenuButton onClick={toggleMenu}></MenuButton>
</HeaderLabel>
<SideMenu isOpen={isSideMenuOpen}
toggleMenu={toggleMenu}
>
</SideMenu>
{
((props.disableSlideMenu !== true) &&
<SideMenu isOpen={isSideMenuOpen}
toggleMenu={toggleMenu}
>
</SideMenu>)
}
{props.children}
</div>
);
Expand Down
131 changes: 131 additions & 0 deletions src/pages/confirmAccountRedirectPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { useState } from 'react';
import { CircularProgress, Container, Paper, Box, Typography, Grid, TextField, Button } from '@mui/material';
import apiClient from '../apiClient';
import { ConfirmAccountData } from '../api';
import DashboardContainer from '../components/containers/dashboardContainer';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { CurrentAppTranslation } from '../translations/appTranslation';
import useAppSnackbar from '../components/snackbars/useAppSnackbars';
import { extractErrorDetails } from '../utils/errorUtils';
import { getTextInputTypeFromFieldName } from '../utils/textUtils';

interface ConfirmationRequiredFields {
user_id: string;
new_password: string;
repeat_password: string;
}

const ConfirmAccountRedirectPage = () => {
apiClient.clearTokens();
const urlSearch = new URLSearchParams(window.location.search);

const [requiredData, setRequiredData] = useState<ConfirmationRequiredFields>({
user_id: '',
new_password: '',
repeat_password: '',
});
const [confirmData, setConfirmData] = useState<ConfirmAccountData>({
user_id: '',
confirm_token: urlSearch.get('confirmToken') ?? '',
rl_token: urlSearch.get('rlToken') ?? '',
lt_token: urlSearch.get('lt') ?? '',
raw_password: '',
});
const [isConfirmed, setIsConfirmed] = useState(false);

const snackbar = useAppSnackbar();

const handleChange = (e: any) => {
setRequiredData({ ...requiredData, [e.target.name]: e.target.value });
setConfirmData({
...confirmData,
[e.target.name]: e.target.value,
});
};

const handleConfirm = async () => {
try {
confirmData.raw_password = requiredData.new_password;
const result = await apiClient.confirmAccount(confirmData);
if (!result) {
snackbar.error(CurrentAppTranslation.FailedToConfirmAccountCreationText);
return;
}

setIsConfirmed(true);
// Redirect the user to /login page in 3 seconds
setTimeout(() => {
window.location.href = '/login';
}, 3000);
} catch (error: any) {
const [errCode, errMessage] = extractErrorDetails(error);
snackbar.error(`Failed (${errCode}): ${errMessage}`);
return;
}

};

if (confirmData.confirm_token === '' ||
confirmData.rl_token === '' ||
confirmData.lt_token === '') {
window.location.href = '/login';
return (
<CircularProgress />
);
}

return (
<DashboardContainer disableSlideMenu={true}>
<Container maxWidth="sm">
<Paper elevation={3} sx={{ p: 3, mt: 6 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h4">{CurrentAppTranslation.ConfirmYourAccountText}</Typography>
</Box>
<Grid container spacing={2}>
{Object.keys(requiredData).map((field) => (
<Grid item xs={12} key={field}>
{!isConfirmed && (
<TextField
fullWidth
type={getTextInputTypeFromFieldName(field)}
name={field}
label={CurrentAppTranslation[field as keyof (typeof CurrentAppTranslation)]}
value={requiredData[field as keyof (typeof requiredData)]}
onChange={handleChange}
/>
)}
</Grid>
))}
{isConfirmed && (
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
<CheckCircleIcon color="success" sx={{ fontSize: 30 }} />
<Typography variant="body2" sx={{ color: 'green' }}>
{CurrentAppTranslation.ConfirmationSuccessText}
</Typography>
</Grid>
)}
{(requiredData.new_password !== requiredData.repeat_password) ? (
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
<Typography variant="body2" sx={{ color: 'red' }}>
{CurrentAppTranslation.PasswordsDoNotMatchText}
</Typography>
</Grid>
) : null}
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
<Button variant="contained" onClick={handleConfirm} style={
{
display: isConfirmed ? 'none' : 'block',
backgroundColor: requiredData.new_password === requiredData.repeat_password ? 'green' : 'red',
}
}>
{CurrentAppTranslation.ConfirmText}
</Button>
</Grid>
</Grid>
</Paper>
</Container>
</DashboardContainer>
);
};

export default ConfirmAccountRedirectPage;
7 changes: 7 additions & 0 deletions src/translations/appTranslation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ export class AppTranslationBase {
CreateUserButtonText: string = "Create User";
CreateTopicButtonText: string = "Create Topic";
SaveText: string = "Save";
ConfirmText: string = "Confirm";
EditText: string = "Edit";
UserInformationText: string = "User Information";
ConfirmYourAccountText = "Confirm Your Account";

// System messages
ConfirmationSuccessText: string = "Confirmation was successful!";
PasswordsDoNotMatchText: string = "Passwords do not match!";
FailedToConfirmAccountCreationText: string = "Failed to confirm account creation";
UserNotFoundText: string = "This user doesn't seem to exist...";
CreateNewUserText: string = "Create New User";
UserCreatedSuccessfullyText: string = "User created successfully";
Expand All @@ -52,6 +57,8 @@ export class AppTranslationBase {
topic_name: string = "Topic Name";
topic_id: string = "Topic ID";
user_id: string = "User ID";
new_password: string = "New Password";
repeat_password: string = "Repeat Password";
full_name: string = "Full Name";
email: string = "Email";
password: string = "Password";
Expand Down
8 changes: 7 additions & 1 deletion src/translations/faTranslation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ class FaTranslation extends AppTranslationBase {
CreateUserButtonText: string = "ایجاد کاربر";
CreateTopicButtonText: string = "ایجاد موضوع";
SaveText: string = "ذخیره";
ConfirmText: string = "تایید";
EditText: string = "ویرایش";
UserInformationText: string = "اطلاعات کاربر";
ConfirmYourAccountText = "حساب کاربری خود را تایید کنید";


// System messages

ConfirmationSuccessText: string = "تایید با موفقیت انجام شد!";
PasswordsDoNotMatchText: string = "رمز عبور ها یکسان نیستند!";
FailedToConfirmAccountCreationText: string = "تایید ایجاد حساب کاربری ناموفق بود";
UserNotFoundText: string = "این کاربر وجود ندارد...";
CreateNewUserText: string = "ایجاد کاربر جدید";
UserCreatedSuccessfullyText: string = "کاربر با موفقیت ایجاد شد";
Expand All @@ -55,6 +59,8 @@ class FaTranslation extends AppTranslationBase {
topic_name: string = "نام موضوع";
topic_id: string = "شناسه موضوع";
user_id: string = "شناسه کاربری";
new_password: string = "رمز عبور جدید";
repeat_password: string = "تکرار رمز عبور";
full_name: string = "نام کامل";
email: string = "ایمیل";
password: string = "رمز عبور";
Expand Down
18 changes: 18 additions & 0 deletions src/utils/textUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@



export function getTextInputTypeFromFieldName(fieldName: string): React.HTMLInputTypeAttribute {
if (fieldName.toLowerCase().includes("password")) {
return "password";
}

if (fieldName.toLowerCase().includes("email")) {
return "email";
}

if (fieldName.toLowerCase().includes("phone")) {
return "tel";
}

return "text";
}

0 comments on commit 6b2349b

Please sign in to comment.