Skip to content

Commit

Permalink
Add support for re-rendering pages when language changes.
Browse files Browse the repository at this point in the history
Signed-off-by: Aliwoto <[email protected]>
  • Loading branch information
ALiwoto committed Aug 24, 2024
1 parent 7cd53d8 commit 73a6daa
Show file tree
Hide file tree
Showing 20 changed files with 229 additions and 51 deletions.
58 changes: 55 additions & 3 deletions src/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,80 @@ import { Box } from '@mui/material';
import AppFooter from './components/footers/AppFooter';
import { SupportedTranslations, switchAppTranslation } from './translations/translationSwitcher';
import apiClient from './apiClient';
import { CurrentAppTranslation } from './translations/appTranslation';
import { forceUpdateDashboardContainer } from './components/containers/dashboardContainer';
import { forceUpdateConfirmAccountRedirectPage } from './pages/confirmAccountRedirectPage';
import { forceUpdateCourseInfoPage } from './pages/courseInfoPage';
import { forceUpdateCreateCoursePage } from './pages/createCoursePage';
import { forceUpdateCreateExamPage } from './pages/createExamPage';
import { forceUpdateCreateTopicPage } from './pages/createTopicPage';
import { forceUpdateCreateUserPage } from './pages/createUserPage';
import { forceUpdateDashboardPage } from './pages/dashboardPage';
import { forceUpdateExamInfoPage } from './pages/examInfoPage';
import { forceUpdateLoginPage } from './pages/loginPage';
import { forceUpdateSearchCoursePage } from './pages/searchCoursePage';
import { forceUpdateSearchTopicPage } from './pages/searchTopicPage';
import { forceUpdateSearchUserPage } from './pages/searchUserPage';
import { forceUpdateUserInfoPage } from './pages/userInfoPage';

/**
* This function forces a re-render of all the pages in the app.
* This is useful when the app language is changed.
* (I wish there was a better way to do this but I couldn't find one 😭)
*/
const forceUpdateWholePage = () => {
try {
forceUpdateDashboardContainer();
forceUpdateConfirmAccountRedirectPage();
forceUpdateCourseInfoPage();
forceUpdateCreateCoursePage();
forceUpdateCreateExamPage();
forceUpdateCreateTopicPage();
forceUpdateCreateUserPage();
forceUpdateDashboardPage();
forceUpdateExamInfoPage();
forceUpdateLoginPage();
forceUpdateSearchCoursePage();
forceUpdateSearchTopicPage();
forceUpdateSearchUserPage();
forceUpdateUserInfoPage();
} catch (error: any) {
console.log(`forceUpdateWholePage: failed to force update: ${error}`);
}
};

const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [currentAppLanguage, setCurrentAppLanguage] = React.useState<string>('en');
const [fontFamily, setFontFamily] = React.useState<string>(`${CurrentAppTranslation.fontFamily}`);
const [, forceUpdate] = React.useReducer(x => x + 1, 0);

return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
minHeight: '110vh', // Ensure the layout takes at least the full viewport height
fontFamily: `${fontFamily}`,
}}
fontFamily={
`${fontFamily}`
}
>
<Box sx={{ flexGrow: 1 }} key={`ExamSphere-app-${currentAppLanguage}-box`}>
<Box sx={{ flexGrow: 1 }} about={`ExamSphere-app-${currentAppLanguage}-box`}>
{children}
</Box>
<AppFooter setAppLanguage={async (lang) => {
const userLang = lang as SupportedTranslations;
console.log(`Switching from ${currentAppLanguage} to ${lang}`);
// doing this, so react re-renders the app with the new language

// doing this so react re-renders the app with the new language
setCurrentAppLanguage(lang);
const userLang = lang as SupportedTranslations;
switchAppTranslation(userLang);
await apiClient.setAppLanguage(userLang);
setFontFamily(`${CurrentAppTranslation.fontFamily}`);

forceUpdate();
forceUpdateWholePage();
}} />
</Box>
);
Expand Down
12 changes: 8 additions & 4 deletions src/components/containers/dashboardContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import React from "react";
import React, { useReducer } from "react";
import { useState } from "react";
import SideMenu from '../menus/sideMenu';
import HeaderLabel from "../labels/headerLabel";
import TitleLabel from "../labels/titleLabel";
import MenuButton from "../menus/menuButton";
import { CurrentAppTranslation } from "../../translations/appTranslation";


interface DashboardContainerProps {
children?: React.ReactNode;
style?: React.CSSProperties;
disableSlideMenu?: boolean;
}

const DashboardContainer: React.FC<DashboardContainerProps> = ({ ...props }) => {
export var forceUpdateDashboardContainer: () => void = () => { };

export const DashboardContainer: React.FC<DashboardContainerProps> = ({ ...props }) => {
const [isSideMenuOpen, setIsSideMenuOpen] = useState(false);
const toggleMenu = () => setIsSideMenuOpen(!isSideMenuOpen);
const [, setForceUpdate] = useReducer(x => x + 1, 0);

forceUpdateDashboardContainer = () => setForceUpdate();

return (
<div style={
Expand Down Expand Up @@ -45,5 +51,3 @@ const DashboardContainer: React.FC<DashboardContainerProps> = ({ ...props }) =>
</div>
);
}

export default DashboardContainer;
24 changes: 19 additions & 5 deletions src/components/labels/titleLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import styled from "styled-components";
import React from "react";
import { CurrentAppTranslation } from "../../translations/appTranslation";

const TitleLabel = styled.h1`
font-size: 24px;
margin-inline: auto;
`;
interface TitleLabelProps {
children?: React.ReactNode;
style?: React.CSSProperties;
}

const TitleLabel: React.FC<TitleLabelProps> = ({ ...props }) => {
return <h1 style={
{
fontSize: '24px',
marginInline: 'auto',
direction: `${CurrentAppTranslation.direction}`,
...props.style
}
}>
{props.children}
</h1>;
};

export default TitleLabel;
17 changes: 14 additions & 3 deletions src/components/rendering/RenderAllFields.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

import apiClient from '../../apiClient';
import { CurrentAppTranslation } from '../../translations/appTranslation';
import { Checkbox, FormControlLabel, Grid, TextField, Typography } from '@mui/material';
import { Box, Checkbox, FormControlLabel, Grid, TextField, Typography } from '@mui/material';
import SelectMenu from '../../components/menus/selectMenu';
import ModernDateTimePicker from '../date/ModernDatePicker';

Expand Down Expand Up @@ -81,10 +81,20 @@ const RenderAllFields = (props: RenderAllFieldsProps) => {
}

return (
<Box key={`${field}-box-key`}
sx={{
display: 'flex',
justifyContent: `${CurrentAppTranslation.justifyContent}`,
width: '100%', // Ensure the Box takes full width
}}
>
<FormControlLabel key={`${field}-form-control-label-key`}
style={{
direction: `${CurrentAppTranslation.direction}`,
}}
control={
<Checkbox
checked={data[field as keyof (typeof data)] ?? false}
checked={data[field] ?? false}
disabled={!isEditing}
onChange={(e: any) => {
handleInputChange({
Expand All @@ -99,6 +109,7 @@ const RenderAllFields = (props: RenderAllFieldsProps) => {
}
label={CurrentAppTranslation[field as keyof (typeof CurrentAppTranslation)]}
/>
</Box>
);
}

Expand Down Expand Up @@ -162,7 +173,7 @@ const RenderAllFields = (props: RenderAllFieldsProps) => {
disabled={!isEditing}
type={'text'}
label={CurrentAppTranslation[field as keyof (typeof CurrentAppTranslation)]}
value={data[field as keyof (typeof data)] ?? ''}
value={data[field] ?? ''}
onChange={(e) => { handleInputChange(e as any) }}
required />
);
Expand Down
3 changes: 2 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ root.render(
<React.StrictMode>
<AppSnackBarProvider>
<AppLayout>
<App />
<App />
<div></div>
</AppLayout>
</AppSnackBarProvider>
</React.StrictMode>
Expand Down
9 changes: 7 additions & 2 deletions src/pages/confirmAccountRedirectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useEffect, useState } from 'react';
import { useEffect, useReducer, 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 {DashboardContainer} from '../components/containers/dashboardContainer';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import { CurrentAppTranslation } from '../translations/appTranslation';
import useAppSnackbar from '../components/snackbars/useAppSnackbars';
Expand All @@ -16,9 +16,14 @@ interface ConfirmationRequiredFields {
repeat_password: string;
}

export var forceUpdateConfirmAccountRedirectPage = () => {};

const ConfirmAccountRedirectPage = () => {
apiClient.clearTokens();
const urlSearch = new URLSearchParams(window.location.search);
const [, forceUpdate] = useReducer(x => x + 1, 0);

forceUpdateConfirmAccountRedirectPage = () => forceUpdate();

const [requiredData, setRequiredData] = useState<ConfirmationRequiredFields>({
user_id: '',
Expand Down
10 changes: 8 additions & 2 deletions src/pages/courseInfoPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useReducer } from 'react';
import { CircularProgress, Container, Paper, Box, Typography, Grid, TextField, Button } from '@mui/material';
import apiClient from '../apiClient';
import { EditCourseData } from '../api';
import DashboardContainer from '../components/containers/dashboardContainer';
import { DashboardContainer } from '../components/containers/dashboardContainer';
import { CurrentAppTranslation } from '../translations/appTranslation';
import useAppSnackbar from '../components/snackbars/useAppSnackbars';
import { extractErrorDetails } from '../utils/errorUtils';

export var forceUpdateCourseInfoPage = () => {};

const CourseInfoPage = () => {
const [courseData, setCourseData] = useState<EditCourseData>({
course_id: 0,
Expand All @@ -16,8 +18,12 @@ const CourseInfoPage = () => {
});
const [isEditing, setIsEditing] = useState(false);
const [isCourseNotFound, setIsCourseNotFound] = useState(false);
const [, forceUpdate] = useReducer(x => x + 1, 0);
const snackbar = useAppSnackbar();

forceUpdateCourseInfoPage = () => {
forceUpdate();
};

const fetchCourseInfo = async () => {
// the user id is passed like /courseInfo?userId=123
Expand Down
14 changes: 11 additions & 3 deletions src/pages/createCoursePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useReducer, useState } from 'react';
import SubmitButton from '../components/buttons/submitButton';
import DashboardContainer from '../components/containers/dashboardContainer';
import { DashboardContainer } from '../components/containers/dashboardContainer';
import TitleLabel from '../components/labels/titleLabel';
import CreateUserForm from '../components/forms/createUserForm';
import CreateUserContainer from '../components/containers/createUserContainer';
Expand All @@ -12,15 +12,21 @@ import { extractErrorDetails } from '../utils/errorUtils';
import RenderAllFields from '../components/rendering/RenderAllFields';
import { autoSetWindowTitle, getFieldOf } from '../utils/commonUtils';

export var forceUpdateCreateCoursePage = () => {};

const CreateCoursePage: React.FC = () => {
const [createCourseData, setCourseData] = useState<CreateCourseData>({
topic_id: 0,
course_name: '',
course_description: '',
});
const [, forceUpdate] = useReducer(x => x + 1, 0);
const snackbar = useAppSnackbar();

forceUpdateCreateCoursePage = () => {
forceUpdate();
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
setCourseData({
...createCourseData,
Expand Down Expand Up @@ -50,7 +56,9 @@ const CreateCoursePage: React.FC = () => {
<DashboardContainer>
<CreateUserContainer key={'create-course-page-create-container'}>
<CreateUserForm onSubmit={handleSubmit}>
<TitleLabel>{CurrentAppTranslation.CreateNewCourseText}</TitleLabel>
<TitleLabel>
{CurrentAppTranslation.CreateNewCourseText}
</TitleLabel>
{RenderAllFields({
data: createCourseData,
handleInputChange :handleInputChange,
Expand Down
10 changes: 8 additions & 2 deletions src/pages/createExamPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useReducer, useState } from 'react';
import SubmitButton from '../components/buttons/submitButton';
import DashboardContainer from '../components/containers/dashboardContainer';
import {DashboardContainer} from '../components/containers/dashboardContainer';
import TitleLabel from '../components/labels/titleLabel';
import CreateUserForm from '../components/forms/createUserForm';
import CreateUserContainer from '../components/containers/createUserContainer';
Expand All @@ -13,6 +13,7 @@ import RenderAllFields from '../components/rendering/RenderAllFields';
import { getUTCUnixTimestamp } from '../utils/timeUtils';
import { autoSetWindowTitle, getFieldOf } from '../utils/commonUtils';

export var forceUpdateCreateExamPage = () => {};

const CreateExamPage: React.FC = () => {
const [createExamData, setCreateExamData] = useState<CreateExamData>({
Expand All @@ -24,8 +25,13 @@ const CreateExamPage: React.FC = () => {
exam_date: 0,
is_public: false,
});
const [, forceUpdate] = useReducer(x => x + 1, 0);
const snackbar = useAppSnackbar();

forceUpdateCreateExamPage = () => {
forceUpdate();
};

useEffect(() => {
autoSetWindowTitle();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
Expand Down
11 changes: 9 additions & 2 deletions src/pages/createTopicPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useReducer, useState } from 'react';
import SubmitButton from '../components/buttons/submitButton';
import DashboardContainer from '../components/containers/dashboardContainer';
import { DashboardContainer } from '../components/containers/dashboardContainer';
import TitleLabel from '../components/labels/titleLabel';
import CreateUserForm from '../components/forms/createUserForm';
import CreateUserContainer from '../components/containers/createUserContainer';
Expand All @@ -12,12 +12,19 @@ import useAppSnackbar from '../components/snackbars/useAppSnackbars';
import { extractErrorDetails } from '../utils/errorUtils';
import { autoSetWindowTitle } from '../utils/commonUtils';

export var forceUpdateCreateTopicPage = () => {};

const CreateTopicPage: React.FC = () => {
const [createTopicData, setUserInfo] = useState<CreateNewTopicData>({
topic_name: '',
});
const [, forceUpdate] = useReducer(x => x + 1, 0);
const snackbar = useAppSnackbar();

forceUpdateCreateTopicPage = () => {
forceUpdate();
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
setUserInfo({ ...createTopicData, [e.target.name]: e.target.value });
};
Expand Down
11 changes: 9 additions & 2 deletions src/pages/createUserPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import React, { useReducer, useState } from 'react';
import SubmitButton from '../components/buttons/submitButton';
import DashboardContainer from '../components/containers/dashboardContainer';
import { DashboardContainer } from '../components/containers/dashboardContainer';
import TitleLabel from '../components/labels/titleLabel';
import CreateUserForm from '../components/forms/createUserForm';
import CreateUserContainer from '../components/containers/createUserContainer';
Expand All @@ -12,15 +12,22 @@ import { Checkbox, FormControlLabel, TextField } from '@mui/material';
import useAppSnackbar from '../components/snackbars/useAppSnackbars';
import { extractErrorDetails } from '../utils/errorUtils';

export var forceUpdateCreateUserPage = () => {};

const CreateUserPage: React.FC = () => {
const [createUserData, setUserInfo] = useState<CreateUserData>({
user_id: '',
email: '',
password: '',
role: UserRole.UserRoleStudent,
});
const [, forceUpdate] = useReducer(x => x + 1, 0);
const snackbar = useAppSnackbar();

forceUpdateCreateUserPage = () => {
forceUpdate();
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
setUserInfo({ ...createUserData, [e.target.name]: e.target.value });
};
Expand Down
Loading

0 comments on commit 73a6daa

Please sign in to comment.