From 6f9e6eb7d3b0061a34c0ccc042546945a5f94ef9 Mon Sep 17 00:00:00 2001 From: Laura Ketola Date: Fri, 13 Oct 2023 15:32:46 +0300 Subject: [PATCH] YKI(Frontend): Show user's active registrations in header --- .../yki/public/i18n/fi-FI/common.json | 2 + frontend/packages/yki/setupProxy.js | 12 ++++- .../components/layouts/SessionStateHeader.tsx | 49 +++++++++++++++++-- frontend/packages/yki/src/enums/api.ts | 3 +- .../yki/src/interfaces/publicRegistration.ts | 7 +++ .../redux/reducers/userOpenRegistrations.ts | 45 +++++++++++++++++ .../packages/yki/src/redux/sagas/index.ts | 2 + .../yki/src/redux/sagas/registration.ts | 2 + .../src/redux/sagas/userOpenRegistrations.ts | 30 ++++++++++++ .../redux/selectors/userOpenRegistrations.ts | 4 ++ .../packages/yki/src/redux/store/index.ts | 2 + .../components/layouts/_session-header.scss | 21 +++++++- 12 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 frontend/packages/yki/src/redux/reducers/userOpenRegistrations.ts create mode 100644 frontend/packages/yki/src/redux/sagas/userOpenRegistrations.ts create mode 100644 frontend/packages/yki/src/redux/selectors/userOpenRegistrations.ts diff --git a/frontend/packages/yki/public/i18n/fi-FI/common.json b/frontend/packages/yki/public/i18n/fi-FI/common.json index 8ad2482e5d..7b655255b2 100644 --- a/frontend/packages/yki/public/i18n/fi-FI/common.json +++ b/frontend/packages/yki/public/i18n/fi-FI/common.json @@ -70,6 +70,8 @@ "langSelectorAriaLabel": "Kieli / Språk / Language" }, "sessionState": { + "activeRegistrations": "Sinulla on 1 käynnissä oleva ilmoittautuminen.", + "continueToRegistration": "Siirry ilmoittautumissivulle.", "logOut": "Kirjaudu ulos" }, "lang": { diff --git a/frontend/packages/yki/setupProxy.js b/frontend/packages/yki/setupProxy.js index f976102595..65d5132b80 100644 --- a/frontend/packages/yki/setupProxy.js +++ b/frontend/packages/yki/setupProxy.js @@ -1019,7 +1019,7 @@ module.exports = function (app) { } ); - app.get('/yki/auth/user', (req, res) => { + app.get('/yki/api/user/identity', (req, res) => { try { res.set('Content-Type', 'application/json; charset=utf-8'); res.send(adminUser); @@ -1029,6 +1029,16 @@ module.exports = function (app) { } }); + app.get('/yki/api/user/open-registrations', (req, res) => { + try { + res.set('Content-Type', 'application/json; charset=utf-8'); + res.send({ 'open-registrations': [ { exam_session_id: 25 }]}); + } catch (err) { + printError(req, err); + res.status(404).send(err.message); + } + }); + app.post('/yki/api/login-link', (req, res) => { const mockCall = () => { try { diff --git a/frontend/packages/yki/src/components/layouts/SessionStateHeader.tsx b/frontend/packages/yki/src/components/layouts/SessionStateHeader.tsx index 34919efb3c..4b7b9782a9 100644 --- a/frontend/packages/yki/src/components/layouts/SessionStateHeader.tsx +++ b/frontend/packages/yki/src/components/layouts/SessionStateHeader.tsx @@ -3,12 +3,13 @@ import { Person as PersonIcon, } from '@mui/icons-material'; import Button from '@mui/material/Button'; -import { FC } from 'react'; +import { FC, useEffect } from 'react'; +import { generatePath, Link, matchPath, useLocation } from 'react-router-dom'; import { Text } from 'shared/components'; -import { Color, Variant } from 'shared/enums'; +import { APIResponseStatus, Color, Variant } from 'shared/enums'; import { useCommonTranslation } from 'configs/i18n'; -import { useAppSelector } from 'configs/redux'; +import { useAppDispatch, useAppSelector } from 'configs/redux'; import { APIEndpoints } from 'enums/api'; import { AppRoutes } from 'enums/app'; import { @@ -16,7 +17,9 @@ import { EmailAuthenticatedSession, SuomiFiAuthenticatedSession, } from 'interfaces/session'; +import { loadUserOpenRegistrations } from 'redux/reducers/userOpenRegistrations'; import { sessionSelector } from 'redux/selectors/session'; +import { userOpenRegistrationsSelector } from 'redux/selectors/userOpenRegistrations'; const getUserName = ( user: @@ -39,12 +42,50 @@ const generateLogoutURL = () => { }; export const SessionStateHeader: FC = () => { + const location = useLocation(); const translateCommon = useCommonTranslation(); + const userOpenRegistrations = useAppSelector(userOpenRegistrationsSelector); const session = useAppSelector(sessionSelector).loggedInSession; - if (!session || session.identity === null) return <>; + const dispatch = useAppDispatch(); + const hasNullIdentity = !session || session.identity === null; + const notOnRegistrationPage = + matchPath(AppRoutes.ExamSessionRegistration, location.pathname) === null; + + useEffect(() => { + const needsUserOpenRegistrations = + !hasNullIdentity && + notOnRegistrationPage && + userOpenRegistrations.status === APIResponseStatus.NotStarted; + + if (needsUserOpenRegistrations) { + dispatch(loadUserOpenRegistrations()); + } + }, [hasNullIdentity, notOnRegistrationPage, userOpenRegistrations, dispatch]); + + if (hasNullIdentity) return <>; return (
+ {notOnRegistrationPage && + userOpenRegistrations.openRegistrations && + userOpenRegistrations.openRegistrations['open-registrations'] && + userOpenRegistrations.openRegistrations['open-registrations'][0] && ( +
+ + {translateCommon('header.sessionState.activeRegistrations')} + + + {translateCommon('header.sessionState.continueToRegistration')} + +
+ )} {getUserName(session)} diff --git a/frontend/packages/yki/src/enums/api.ts b/frontend/packages/yki/src/enums/api.ts index 9e948076e4..c0c821eb6d 100644 --- a/frontend/packages/yki/src/enums/api.ts +++ b/frontend/packages/yki/src/enums/api.ts @@ -13,7 +13,8 @@ export enum APIEndpoints { Registration = '/yki/api/registration/:registrationId', SubmitRegistration = '/yki/api/registration/:registrationId/submit', SuomiFiAuthRedirect = '/yki/auth/', - User = '/yki/auth/user', + User = '/yki/api/user/identity', + OpenRegistrations = '/yki/api/user/open-registrations', } export enum PaymentStatus { diff --git a/frontend/packages/yki/src/interfaces/publicRegistration.ts b/frontend/packages/yki/src/interfaces/publicRegistration.ts index 92b1c35dd4..4b96fd1cb3 100644 --- a/frontend/packages/yki/src/interfaces/publicRegistration.ts +++ b/frontend/packages/yki/src/interfaces/publicRegistration.ts @@ -73,3 +73,10 @@ export interface PublicRegistrationFormSubmitErrorResponse { person_creation?: boolean; }; } + +export interface UserOpenRegistration { + exam_session_id: number; +} +export interface UserOpenRegistrationsResponse { + 'open-registrations': Array; +} diff --git a/frontend/packages/yki/src/redux/reducers/userOpenRegistrations.ts b/frontend/packages/yki/src/redux/reducers/userOpenRegistrations.ts new file mode 100644 index 0000000000..6e04c87627 --- /dev/null +++ b/frontend/packages/yki/src/redux/reducers/userOpenRegistrations.ts @@ -0,0 +1,45 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { APIResponseStatus } from 'shared/enums'; + +import { UserOpenRegistrationsResponse } from 'interfaces/publicRegistration'; + +interface UserOpenRegistrationsState { + status: APIResponseStatus; + openRegistrations?: UserOpenRegistrationsResponse; +} + +const initialState: UserOpenRegistrationsState = { + status: APIResponseStatus.NotStarted, +}; + +const userOpenRegistrationsSlice = createSlice({ + name: 'userOpenRegistrations', + initialState, + reducers: { + acceptUserOpenRegistrations( + state, + action: PayloadAction + ) { + state.status = APIResponseStatus.Success; + state.openRegistrations = action.payload; + }, + loadUserOpenRegistrations(state) { + state.status = APIResponseStatus.InProgress; + }, + rejectUserOpenRegistrations(state) { + state.status = APIResponseStatus.Error; + }, + resetUserOpenRegistrations(state) { + state.status = APIResponseStatus.NotStarted; + state.openRegistrations = undefined; + }, + }, +}); + +export const userOpenRegistrationsReducer = userOpenRegistrationsSlice.reducer; +export const { + acceptUserOpenRegistrations, + loadUserOpenRegistrations, + rejectUserOpenRegistrations, + resetUserOpenRegistrations, +} = userOpenRegistrationsSlice.actions; diff --git a/frontend/packages/yki/src/redux/sagas/index.ts b/frontend/packages/yki/src/redux/sagas/index.ts index 5419b59c67..4519b239c0 100644 --- a/frontend/packages/yki/src/redux/sagas/index.ts +++ b/frontend/packages/yki/src/redux/sagas/index.ts @@ -8,6 +8,7 @@ import { watchPublicIdentification } from 'redux/sagas/publicIdentification'; import { watchRegistration } from 'redux/sagas/registration'; import { watchReservationRequest } from 'redux/sagas/reservation'; import { watchSession } from 'redux/sagas/session'; +import { watchUserOpenRegistrations } from 'redux/sagas/userOpenRegistrations'; export default function* rootSaga() { yield all([ @@ -19,5 +20,6 @@ export default function* rootSaga() { watchRegistration(), watchReservationRequest(), watchSession(), + watchUserOpenRegistrations(), ]); } diff --git a/frontend/packages/yki/src/redux/sagas/registration.ts b/frontend/packages/yki/src/redux/sagas/registration.ts index ed2a0a3ba8..610c73a26d 100644 --- a/frontend/packages/yki/src/redux/sagas/registration.ts +++ b/frontend/packages/yki/src/redux/sagas/registration.ts @@ -24,6 +24,7 @@ import { resetPublicRegistration, submitPublicRegistration, } from 'redux/reducers/registration'; +import { resetUserOpenRegistrations } from 'redux/reducers/userOpenRegistrations'; import { nationalitiesSelector } from 'redux/selectors/nationalities'; import { registrationSelector } from 'redux/selectors/registration'; import { SerializationUtils } from 'utils/serialization'; @@ -80,6 +81,7 @@ function* submitRegistrationFormSaga() { } ); yield put(acceptPublicRegistrationSubmission()); + yield put(resetUserOpenRegistrations()); } catch (error) { // eslint-disable-next-line no-console console.error('caught error!', error); diff --git a/frontend/packages/yki/src/redux/sagas/userOpenRegistrations.ts b/frontend/packages/yki/src/redux/sagas/userOpenRegistrations.ts new file mode 100644 index 0000000000..6edf55dcef --- /dev/null +++ b/frontend/packages/yki/src/redux/sagas/userOpenRegistrations.ts @@ -0,0 +1,30 @@ +import { call, put, takeLatest } from '@redux-saga/core/effects'; +import { AxiosResponse } from 'axios'; + +import axiosInstance from 'configs/axios'; +import { APIEndpoints } from 'enums/api'; +import { UserOpenRegistrationsResponse } from 'interfaces/publicRegistration'; +import { + acceptUserOpenRegistrations, + loadUserOpenRegistrations, + rejectUserOpenRegistrations, +} from 'redux/reducers/userOpenRegistrations'; + +function* loadUserOpenRegistrationsSaga() { + try { + const response: AxiosResponse = yield call( + axiosInstance.get, + APIEndpoints.OpenRegistrations + ); + yield put(acceptUserOpenRegistrations(response.data)); + } catch (error) { + yield put(rejectUserOpenRegistrations()); + } +} + +export function* watchUserOpenRegistrations() { + yield takeLatest( + loadUserOpenRegistrations.type, + loadUserOpenRegistrationsSaga + ); +} diff --git a/frontend/packages/yki/src/redux/selectors/userOpenRegistrations.ts b/frontend/packages/yki/src/redux/selectors/userOpenRegistrations.ts new file mode 100644 index 0000000000..5b5ac0a432 --- /dev/null +++ b/frontend/packages/yki/src/redux/selectors/userOpenRegistrations.ts @@ -0,0 +1,4 @@ +import { RootState } from 'configs/redux'; + +export const userOpenRegistrationsSelector = (state: RootState) => + state.userOpenRegistrations; diff --git a/frontend/packages/yki/src/redux/store/index.ts b/frontend/packages/yki/src/redux/store/index.ts index ab22491206..5e1b374b75 100644 --- a/frontend/packages/yki/src/redux/store/index.ts +++ b/frontend/packages/yki/src/redux/store/index.ts @@ -11,6 +11,7 @@ import { publicIdentificationReducer } from 'redux/reducers/publicIdentification import { registrationReducer } from 'redux/reducers/registration'; import { reservationReducer } from 'redux/reducers/reservation'; import { sessionReducer } from 'redux/reducers/session'; +import { userOpenRegistrationsReducer } from 'redux/reducers/userOpenRegistrations'; import rootSaga from 'redux/sagas/index'; const saga = createSagaMiddleware(); @@ -27,6 +28,7 @@ const store = configureStore({ registration: registrationReducer, reservation: reservationReducer, session: sessionReducer, + userOpenRegistrations: userOpenRegistrationsReducer, }, middleware: [saga], }); diff --git a/frontend/packages/yki/src/styles/components/layouts/_session-header.scss b/frontend/packages/yki/src/styles/components/layouts/_session-header.scss index 2209bebedb..b85d950ed0 100644 --- a/frontend/packages/yki/src/styles/components/layouts/_session-header.scss +++ b/frontend/packages/yki/src/styles/components/layouts/_session-header.scss @@ -1,7 +1,7 @@ .session-header { background: #f0f3f7; justify-content: flex-end; - padding: 2rem 6rem 2rem 2rem; + padding: 2rem 6rem; @include phone { align-items: flex-end; @@ -15,4 +15,23 @@ height: 2rem; width: 2rem; } + + & &__open-registrations { + margin-right: auto; + + & a { + color: $color-secondary-dark; + font-size: 1.6rem; + font-weight: normal; + letter-spacing: 0; + line-height: 1.9rem; + margin: 0; + text-decoration: underline; + text-transform: initial; + + @include phone { + font-size: 1.5rem; + } + } + } }