Skip to content
Snippets Groups Projects
Commit dd4cb9f8 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '298-tos-is-missing' into 'main'

Resolve "ToS is missing"

See merge request !256
parents dd79fab8 1ec74d04
No related branches found
No related tags found
2 merge requests!264Resolve "add support for matomo",!256Resolve "ToS is missing"
Pipeline #96367 passed
Showing
with 153 additions and 23 deletions
minerva-front (18.0.0~beta.5) stable; urgency=medium
* Small improvements: when ToS is defined ask user to accept it (#298)
* Small improvements: there is a waiting spinner after clicking on download
button (#297)
* Small improvements: when exporting map as image provide default selections
......
// const root = 'https://minerva-dev.lcsb.uni.lu';
const root = 'https://lux1.atcomp.pl';
window.config = {
BASE_API_URL: 'https://minerva-dev.lcsb.uni.lu/minerva/api',
BASE_NEW_API_URL: 'https://minerva-dev.lcsb.uni.lu/minerva/new_api/',
BASE_MAP_IMAGES_URL: 'https://minerva-dev.lcsb.uni.lu/',
BASE_API_URL: `${root}/minerva/api`,
BASE_NEW_API_URL: `${root}/minerva/new_api/`,
BASE_MAP_IMAGES_URL: `${root}/`,
DEFAULT_PROJECT_ID: 'sample',
ADMIN_PANEL_URL: 'https://minerva-dev.lcsb.uni.lu/minerva/admin.xhtml',
ADMIN_PANEL_URL: `${root}/minerva/admin.xhtml`,
};
......@@ -8,19 +8,21 @@ interface AppWrapperProps {
children: ReactNode;
}
export const AppWrapper = ({ children }: AppWrapperProps): JSX.Element => (
<MapInstanceProvider>
<Provider store={store}>
<>
<Toaster
position="top-center"
visibleToasts={1}
style={{
width: '700px',
}}
/>
{children}
</>
</Provider>
</MapInstanceProvider>
);
export const AppWrapper = ({ children }: AppWrapperProps): JSX.Element => {
return (
<MapInstanceProvider>
<Provider store={store}>
<>
<Toaster
position="top-center"
visibleToasts={1}
style={{
width: '700px',
}}
/>
{children}
</>
</Provider>
</MapInstanceProvider>
);
};
......@@ -4,6 +4,7 @@ import dynamic from 'next/dynamic';
import { AccessDeniedModal } from '@/components/FunctionalArea/Modal/AccessDeniedModal/AccessDeniedModal.component';
import { AddCommentModal } from '@/components/FunctionalArea/Modal/AddCommentModal/AddCommentModal.component';
import { LicenseModal } from '@/components/FunctionalArea/Modal/LicenseModal';
import { ToSModal } from '@/components/FunctionalArea/Modal/ToSModal/ToSModal.component';
import { EditOverlayModal } from './EditOverlayModal';
import { LoginModal } from './LoginModal';
import { ErrorReportModal } from './ErrorReportModal';
......@@ -63,6 +64,11 @@ export const Modal = (): React.ReactNode => {
<AccessDeniedModal />
</ModalLayout>
)}
{isOpen && modalName === 'terms-of-service' && (
<ModalLayout>
<ToSModal />
</ModalLayout>
)}
{isOpen && modalName === 'select-project' && (
<ModalLayout>
<AccessDeniedModal />
......
......@@ -30,6 +30,7 @@ export const ModalLayout = ({ children }: ModalLayoutProps): JSX.Element => {
modalName === 'login' && 'h-auto w-[400px]',
modalName === 'access-denied' && 'h-auto w-[400px]',
modalName === 'select-project' && 'h-auto w-[400px]',
modalName === 'terms-of-service' && 'h-auto w-[400px]',
modalName === 'add-comment' && 'h-auto w-[400px]',
modalName === 'error-report' && 'h-auto w-[800px]',
['edit-overlay', 'logged-in-menu'].includes(modalName) && 'h-auto w-[432px]',
......@@ -46,7 +47,7 @@ export const ModalLayout = ({ children }: ModalLayoutProps): JSX.Element => {
<div> {modalTitle} </div>
)}
{modalName !== 'logged-in-menu' && (
{modalName !== 'logged-in-menu' && modalName !== 'terms-of-service' && (
<button type="button" onClick={handleCloseModal} aria-label="close button">
<Icon name="close" className="fill-font-500" />
</button>
......
import React from 'react';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { Button } from '@/shared/Button';
import { getSessionValid, logout, updateUser } from '@/redux/user/user.thunks';
import { closeModal } from '@/redux/modal/modal.slice';
import { userSelector } from '@/redux/user/user.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { termsOfServiceValSelector } from '@/redux/configuration/configuration.selectors';
export const ToSModal: React.FC = () => {
const dispatch = useAppDispatch();
const { userData } = useAppSelector(userSelector);
const termsOfService = useAppSelector(termsOfServiceValSelector);
const updateUserTosHandler = async (): Promise<void> => {
// eslint-disable-next-line no-console
console.log('update');
if (userData) {
const user = { ...userData, termsOfUseConsent: true };
await dispatch(updateUser(user));
await dispatch(getSessionValid());
dispatch(closeModal());
}
};
const logoutHandler = async (): Promise<void> => {
await dispatch(logout());
dispatch(closeModal());
};
return (
<div className="w-[400px] border border-t-[#E1E0E6] bg-white p-[24px]">
<div>
I agree to the minerva{' '}
<a href={termsOfService} target="_blank" className="underline">
Terms of Service.
</a>
</div>
<div className="mt-4 grid grid-cols-2 gap-2">
<div>
<Button
className="ring-transparent hover:ring-transparent"
variantStyles="secondary"
onClick={updateUserTosHandler}
>
OK
</Button>
</div>
<div className="text-center">
<Button className="block w-full" onClick={logoutHandler}>
I disagree
</Button>
</div>
</div>
</div>
);
};
import { useSelect } from 'downshift';
import { IconButton } from '@/shared/IconButton';
import { twMerge } from 'tailwind-merge';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { userSelector } from '@/redux/user/user.selectors';
import { openToSModal } from '@/redux/modal/modal.slice';
import { termsOfServiceValSelector } from '@/redux/configuration/configuration.selectors';
import { useUserActions } from '../hooks/useUserActions';
export const AuthenticatedUser = (): React.ReactNode => {
......@@ -10,6 +15,14 @@ export const AuthenticatedUser = (): React.ReactNode => {
items: actions,
});
const dispatch = useAppDispatch();
const { userData } = useAppSelector(userSelector);
const termsOfService = useAppSelector(termsOfServiceValSelector);
if (userData && !userData.termsOfUseConsent && termsOfService) {
dispatch(openToSModal());
}
return (
<>
<IconButton
......
......@@ -102,6 +102,7 @@ export const apiPath = {
getSubmapConnections: (): string => `projects/${PROJECT_ID}/submapConnections/`,
logout: (): string => `doLogout`,
user: (login: string): string => `users/${login}`,
updateUser: (login: string): string => `users/${login}`,
getStacktrace: (code: string): string => `stacktrace/${code}`,
submitError: (): string => `minervanet/submitError`,
getOauthProviders: (): string => `oauth/providers/`,
......
......@@ -5,6 +5,7 @@ export const NEUTRAL_COLOR_VAL_NAME_ID = 'NEUTRAL_COLOR_VAL';
export const OVERLAY_OPACITY_NAME_ID = 'OVERLAY_OPACITY';
export const SEARCH_DISTANCE_NAME_ID = 'SEARCH_DISTANCE';
export const REQUEST_ACCOUNT_EMAIL = 'REQUEST_ACCOUNT_EMAIL';
export const TERMS_OF_SERVICE_ID = 'TERMS_OF_USE';
export const LEGEND_FILE_NAMES_IDS = [
'LEGEND_FILE_1',
......
......@@ -18,6 +18,7 @@ import {
SVG_IMAGE_HANDLER_NAME_ID,
SEARCH_DISTANCE_NAME_ID,
REQUEST_ACCOUNT_EMAIL,
TERMS_OF_SERVICE_ID,
} from './configuration.constants';
import { ConfigurationHandlersIds, ConfigurationImageHandlersIds } from './configuration.types';
......@@ -63,6 +64,11 @@ export const adminEmailValSelector = createSelector(
state => configurationAdapterSelectors.selectById(state, REQUEST_ACCOUNT_EMAIL)?.value,
);
export const termsOfServiceValSelector = createSelector(
configurationOptionsSelector,
state => configurationAdapterSelectors.selectById(state, TERMS_OF_SERVICE_ID)?.value,
);
export const defaultLegendImagesSelector = createSelector(configurationOptionsSelector, state =>
LEGEND_FILE_NAMES_IDS.map(
legendNameId => configurationAdapterSelectors.selectById(state, legendNameId)?.value,
......
......@@ -109,3 +109,9 @@ export const openLicenseModalReducer = (state: ModalState, action: PayloadAction
state.modalName = 'license';
state.modalTitle = `License: ${action.payload}`;
};
export const openToSModalReducer = (state: ModalState): void => {
state.isOpen = true;
state.modalName = 'terms-of-service';
state.modalTitle = 'Terms of service!';
};
......@@ -15,6 +15,7 @@ import {
openAccessDeniedModalReducer,
openSelectProjectModalReducer,
openLicenseModalReducer,
openToSModalReducer,
} from './modal.reducers';
const modalSlice = createSlice({
......@@ -35,6 +36,7 @@ const modalSlice = createSlice({
openAccessDeniedModal: openAccessDeniedModalReducer,
openSelectProjectModal: openSelectProjectModalReducer,
openLicenseModal: openLicenseModalReducer,
openToSModal: openToSModalReducer,
},
});
......@@ -53,6 +55,7 @@ export const {
openAccessDeniedModal,
openSelectProjectModal,
openLicenseModal,
openToSModal,
} = modalSlice.actions;
export default modalSlice.reducer;
......@@ -9,9 +9,11 @@ import { getError } from '@/utils/error-report/getError';
import axios, { HttpStatusCode } from 'axios';
import { showToast } from '@/utils/showToast';
import { setLoginForOldMinerva } from '@/utils/setLoginForOldMinerva';
import { apiPath } from '../apiPath';
import { closeModal, openLoggedInMenuModal } from '../modal/modal.slice';
import { ThunkConfig } from '@/types/store';
import { userSchema } from '@/models/userSchema';
import { hasPrivilege } from './user.utils';
import { closeModal, openLoggedInMenuModal } from '../modal/modal.slice';
import { apiPath } from '../apiPath';
const getUserRole = (privileges: UserPrivilege[]): string => {
if (hasPrivilege(privileges, 'IS_ADMIN')) {
......@@ -118,3 +120,29 @@ export const logout = createAsyncThunk('user/logout', async () => {
return Promise.reject(getError({ error, prefix: 'Log out' }));
}
});
export const updateUser = createAsyncThunk<undefined, User, ThunkConfig>(
'users/updateUser',
// eslint-disable-next-line consistent-return
async user => {
try {
const newUser = await axiosInstance.patch<User>(
apiPath.updateUser(user.login),
{
user: {
termsOfUseConsent: user.termsOfUseConsent,
},
},
{
withCredentials: true,
},
);
validateDataUsingZodSchema(newUser, userSchema);
showToast({ type: 'success', message: 'ToS agreement registered' });
} catch (error) {
return Promise.reject(getError({ error }));
}
},
);
......@@ -10,4 +10,5 @@ export type ModalName =
| 'error-report'
| 'access-denied'
| 'select-project'
| 'terms-of-service'
| 'logged-in-menu';
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment