Skip to content

Commit

Permalink
YKI(Frontend): Show user's active registrations in header
Browse files Browse the repository at this point in the history
  • Loading branch information
lket committed Oct 13, 2023
1 parent 564e432 commit 6f9e6eb
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 7 deletions.
2 changes: 2 additions & 0 deletions frontend/packages/yki/public/i18n/fi-FI/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
12 changes: 11 additions & 1 deletion frontend/packages/yki/setupProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@ 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 {
CasAuthenticatedClerkSession,
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:
Expand All @@ -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 (
<div className="session-header columns gapped">
{notOnRegistrationPage &&
userOpenRegistrations.openRegistrations &&
userOpenRegistrations.openRegistrations['open-registrations'] &&
userOpenRegistrations.openRegistrations['open-registrations'][0] && (
<div className="session-header__open-registrations columns gapped-xxs">
<Text>
{translateCommon('header.sessionState.activeRegistrations')}
</Text>
<Link
to={generatePath(AppRoutes.ExamSessionRegistration, {
examSessionId:
userOpenRegistrations.openRegistrations[
'open-registrations'
][0].exam_session_id.toString(),
})}
>
{translateCommon('header.sessionState.continueToRegistration')}
</Link>
</div>
)}
<Text className="columns gapped-xxs session-header__user-details">
<PersonIcon className="session-header__user-icon" />
{getUserName(session)}
Expand Down
3 changes: 2 additions & 1 deletion frontend/packages/yki/src/enums/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions frontend/packages/yki/src/interfaces/publicRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,10 @@ export interface PublicRegistrationFormSubmitErrorResponse {
person_creation?: boolean;
};
}

export interface UserOpenRegistration {
exam_session_id: number;
}
export interface UserOpenRegistrationsResponse {
'open-registrations': Array<UserOpenRegistration>;
}
45 changes: 45 additions & 0 deletions frontend/packages/yki/src/redux/reducers/userOpenRegistrations.ts
Original file line number Diff line number Diff line change
@@ -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<UserOpenRegistrationsResponse>
) {
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;
2 changes: 2 additions & 0 deletions frontend/packages/yki/src/redux/sagas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -19,5 +20,6 @@ export default function* rootSaga() {
watchRegistration(),
watchReservationRequest(),
watchSession(),
watchUserOpenRegistrations(),
]);
}
2 changes: 2 additions & 0 deletions frontend/packages/yki/src/redux/sagas/registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
30 changes: 30 additions & 0 deletions frontend/packages/yki/src/redux/sagas/userOpenRegistrations.ts
Original file line number Diff line number Diff line change
@@ -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<UserOpenRegistrationsResponse> = yield call(
axiosInstance.get,
APIEndpoints.OpenRegistrations
);
yield put(acceptUserOpenRegistrations(response.data));
} catch (error) {
yield put(rejectUserOpenRegistrations());
}
}

export function* watchUserOpenRegistrations() {
yield takeLatest(
loadUserOpenRegistrations.type,
loadUserOpenRegistrationsSaga
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { RootState } from 'configs/redux';

export const userOpenRegistrationsSelector = (state: RootState) =>
state.userOpenRegistrations;
2 changes: 2 additions & 0 deletions frontend/packages/yki/src/redux/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -27,6 +28,7 @@ const store = configureStore({
registration: registrationReducer,
reservation: reservationReducer,
session: sessionReducer,
userOpenRegistrations: userOpenRegistrationsReducer,
},
middleware: [saga],
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
}
}
}

0 comments on commit 6f9e6eb

Please sign in to comment.