From 1f2be857ec9b3a81f0fc3d6b17a008c2a2adc7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Trembeck=C3=BD?= Date: Sun, 12 Nov 2023 01:25:24 +0100 Subject: [PATCH] React admin csrf and other fixes (#211) * fix react admin forbidden errors * fix undefined record on first load * refactor menuItems style * add veduckovska menu section * fix - the props are not injected after RA upgrade * adjust MyDateInput as of date format currently coming from BE * todo po BE fixe --- .../Admin/Competition/CompetitionList.tsx | 6 +- src/components/Admin/custom/MyArrayField.tsx | 5 +- src/components/Admin/custom/MyDateInput.tsx | 8 +- src/components/Admin/custom/MyEditActions.tsx | 6 +- src/components/Admin/custom/MyShowActions.tsx | 2 + src/components/Admin/dataProvider.ts | 95 +++++-------------- .../Admin/resources/cms/post/PostCreate.tsx | 15 +-- .../Admin/resources/cms/post/PostEdit.tsx | 16 +--- .../Admin/resources/cms/post/PostList.tsx | 6 +- .../Admin/resources/cms/post/PostShow.tsx | 5 +- .../PageLayout/MenuMain/MenuMain.module.scss | 4 - .../MenuMain/MenuMain.module.scss.d.ts | 1 - .../PageLayout/MenuMain/MenuMain.tsx | 23 +++-- 13 files changed, 70 insertions(+), 122 deletions(-) diff --git a/src/components/Admin/Competition/CompetitionList.tsx b/src/components/Admin/Competition/CompetitionList.tsx index a8b0c64c..9d2453ba 100644 --- a/src/components/Admin/Competition/CompetitionList.tsx +++ b/src/components/Admin/Competition/CompetitionList.tsx @@ -1,10 +1,10 @@ import {FC} from 'react' -import {ArrayField, Datagrid, DateField, List, ListProps, TextField} from 'react-admin' +import {ArrayField, Datagrid, DateField, List, TextField} from 'react-admin' // TODO: premysliet a prerobit rozhranie, mozno ako u postov - pri kliku na riadok ukazat Show, kde budu priklady // (nie ako teraz v zanorenom Datagride) -export const CompetitionList: FC = (props) => ( - +export const CompetitionList: FC = () => ( + diff --git a/src/components/Admin/custom/MyArrayField.tsx b/src/components/Admin/custom/MyArrayField.tsx index 5a1e86e1..85e64cd6 100644 --- a/src/components/Admin/custom/MyArrayField.tsx +++ b/src/components/Admin/custom/MyArrayField.tsx @@ -1,12 +1,13 @@ import {Chip} from '@mui/material' import {FC} from 'react' -import {FieldProps} from 'react-admin' +import {FieldProps, useRecordContext} from 'react-admin' type MyArrayFieldProps = FieldProps & { formatNumber?: (v: number) => string } -export const MyArrayField: FC = ({record, source, formatNumber}) => { +export const MyArrayField: FC = ({source, formatNumber}) => { + const record = useRecordContext() if (!record || !source) return null const array = record[source] as (string | number)[] | undefined diff --git a/src/components/Admin/custom/MyDateInput.tsx b/src/components/Admin/custom/MyDateInput.tsx index 63d1fc98..94a6389c 100644 --- a/src/components/Admin/custom/MyDateInput.tsx +++ b/src/components/Admin/custom/MyDateInput.tsx @@ -2,8 +2,14 @@ import {DateTime} from 'luxon' import {ComponentProps, FC} from 'react' import {DateInput} from 'react-admin' +const backendFormat = 'dd.MM.yyyy HH:mm:ss' + +// used to convert BE weird format of 01.01.2020 18:00:00 to 01-01-2020 input format // https://moment.github.io/luxon/#/formatting -const dateFormatter = (v: string) => DateTime.fromISO(v).toISODate() +// TODO: ale blbost, upravi sa to aj tak nazad na ISO vs ISODate: https://github.com/ZdruzenieSTROM/webstrom-backend/issues/257 +const dateFormatter = (v: string) => DateTime.fromFormat(v, backendFormat).toISODate() + +// used to convert input format of 01-01-2020 to 01-01-2020TT00:00:00.000+02:00 BE-expected format // https://moment.github.io/luxon/#/parsing const dateParser = (v: string) => DateTime.fromISO(v).toISO() diff --git a/src/components/Admin/custom/MyEditActions.tsx b/src/components/Admin/custom/MyEditActions.tsx index d9db9c27..cc372204 100644 --- a/src/components/Admin/custom/MyEditActions.tsx +++ b/src/components/Admin/custom/MyEditActions.tsx @@ -1,13 +1,15 @@ import {FC} from 'react' -import {EditActionsProps, ListButton, ShowButton, TopToolbar, useRecordContext, useResourceContext} from 'react-admin' +import {ListButton, ShowButton, TopToolbar, useRecordContext, useResourceContext} from 'react-admin' // eslint-disable-next-line node/no-extraneous-import import {useLocation} from 'react-router-dom' -export const MyEditActions: FC = () => { +export const MyEditActions: FC = () => { const {pathname} = useLocation() // bud '/cms/post/123' alebo '/cms/post/123/1' (prvy tab) const resource = useResourceContext() const record = useRecordContext() + // needed, undefined on first load + if (!record) return null const currentPathWithoutTab = `/${resource}/${record.id}` // '/cms/post/123' let to = `${currentPathWithoutTab}/show` // '/cms/post/123/show' diff --git a/src/components/Admin/custom/MyShowActions.tsx b/src/components/Admin/custom/MyShowActions.tsx index 60269cc7..e4abfd61 100644 --- a/src/components/Admin/custom/MyShowActions.tsx +++ b/src/components/Admin/custom/MyShowActions.tsx @@ -8,6 +8,8 @@ export const MyShowActions: FC = () => { const resource = useResourceContext() const record = useRecordContext() + // needed, undefined on first load + if (!record) return null const currentPathWithoutTab = `/${resource}/${record.id}/show` // '/cms/post/123/show' let to = `/${resource}/${record.id}` // '/cms/post/123' diff --git a/src/components/Admin/dataProvider.ts b/src/components/Admin/dataProvider.ts index 6b0e7fdf..f6abbf9f 100644 --- a/src/components/Admin/dataProvider.ts +++ b/src/components/Admin/dataProvider.ts @@ -1,6 +1,6 @@ +import axios from 'axios' import {stringify} from 'querystring' -import {DataProvider, fetchUtils, RaRecord} from 'react-admin' -import {Cookies} from 'react-cookie' +import {DataProvider, RaRecord} from 'react-admin' // potencialne TODO: ak BE bude mat pagination, filter alebo sort, upravime a pouzijeme tento kod. // zatial je pagination aj sort rieseny client-side a len pre getList, filter/search nemame. @@ -19,21 +19,6 @@ import {Cookies} from 'react-cookie' // ordering: `${order === 'ASC' ? '' : '-'}${field}`, // }) -const cookies = new Cookies() - -const authFetchJson = (url: string, options?: Record) => { - const token = cookies.get('webstrom-token') - const authOptions = token - ? { - user: { - authenticated: true, - token: 'Token ' + token, - }, - } - : {} - return fetchUtils.fetchJson(url, Object.assign(authOptions, options)) -} - const dynamicSort = (key: string, order: string) => { const orderValue = order === 'ASC' ? 1 : -1 return (a: RaRecord, b: RaRecord) => { @@ -54,99 +39,67 @@ export const dataProvider: DataProvider = { // ...getPaginationQuery(params.pagination), // ...getOrderingQuery(params.sort), } - const {json} = await authFetchJson(`${apiUrl}/${resource}/?${stringify(query)}`) + const {data} = await axios.get(`${apiUrl}/${resource}/?${stringify(query)}`) // client-side sort const {field, order} = params.sort - json.sort(dynamicSort(field, order)) + + data.sort(dynamicSort(field, order)) // client-side pagination const {page, perPage} = params.pagination - const pagedData = json.slice((page - 1) * perPage, page * perPage) + const pagedData = data.slice((page - 1) * perPage, page * perPage) return { data: pagedData, - total: json.length, + total: data.length, } }, getOne: async (resource, params) => { - const {json} = await authFetchJson(`${apiUrl}/${resource}/${params.id}`) + const {data} = await axios.get(`${apiUrl}/${resource}/${params.id}`) - return { - data: json, - } + return {data} }, getMany: async (resource, params) => { - const data = await Promise.all(params.ids.map((id) => authFetchJson(`${apiUrl}/${resource}/${id}`))) + const data = await Promise.all(params.ids.map((id) => axios.get(`${apiUrl}/${resource}/${id}`))) - return { - data: data.map(({json}) => json), - } + return {data: data.map(({data}) => data)} }, // TODO: ak budeme pouzivat tuto funkciu, upravime podla getList (pagination, sort, filter). uprimne este neviem, pri com sa pouziva getManyReference: async (resource, params) => { const query = { [params.target]: params.id, } - const {json} = await authFetchJson(`${apiUrl}/${resource}/?${stringify(query)}`) + const {data} = await axios.get(`${apiUrl}/${resource}/?${stringify(query)}`) return { - data: json, - total: json.length, + data: data, + total: data.length, } }, update: async (resource, params) => { - const {json} = await authFetchJson(`${apiUrl}/${resource}/${params.id}`, { - method: 'PATCH', - body: JSON.stringify(params.data), - }) + const {data} = await axios.patch(`${apiUrl}/${resource}/${params.id}`, params.data) - return {data: json} + return {data} }, updateMany: async (resource, params) => { - const data = await Promise.all( - params.ids.map((id) => - authFetchJson(`${apiUrl}/${resource}/${id}`, { - method: 'PATCH', - body: JSON.stringify(params.data), - }), - ), - ) + const data = await Promise.all(params.ids.map((id) => axios.patch(`${apiUrl}/${resource}/${id}`, params.data))) - return { - data: data.map(({json}) => json), - } + return {data: data.map(({data}) => data)} }, create: async (resource, params) => { - const {json} = await authFetchJson(`${apiUrl}/${resource}`, { - method: 'POST', - body: JSON.stringify(params.data), - }) + const {data} = await axios.post(`${apiUrl}/${resource}`, params.data) - return { - data: json, - } + return {data} }, delete: async (resource, params) => { - const {json} = await authFetchJson(`${apiUrl}/${resource}/${params.id}`, { - method: 'DELETE', - }) + const {data} = await axios.delete(`${apiUrl}/${resource}/${params.id}`) - return { - data: json, - } + return {data} }, deleteMany: async (resource, params) => { - const data = await Promise.all( - params.ids.map((id) => - authFetchJson(`${apiUrl}/${resource}/${id}`, { - method: 'DELETE', - }), - ), - ) + const data = await Promise.all(params.ids.map((id) => axios.delete(`${apiUrl}/${resource}/${id}`))) - return { - data: data.map(({json}) => json.id), - } + return {data: data.map(({data}) => data.id)} }, } diff --git a/src/components/Admin/resources/cms/post/PostCreate.tsx b/src/components/Admin/resources/cms/post/PostCreate.tsx index 09967412..5b8a5a12 100644 --- a/src/components/Admin/resources/cms/post/PostCreate.tsx +++ b/src/components/Admin/resources/cms/post/PostCreate.tsx @@ -1,20 +1,11 @@ import {FC} from 'react' -import { - ArrayInput, - Create, - CreateProps, - FormTab, - required, - SimpleFormIterator, - TabbedForm, - TextInput, -} from 'react-admin' +import {ArrayInput, Create, FormTab, required, SimpleFormIterator, TabbedForm, TextInput} from 'react-admin' import {MyDateInput} from '@/components/Admin/custom/MyDateInput' import {SitesCheckboxInput} from '@/components/Admin/custom/SitesCheckboxInput' -export const PostCreate: FC = (props) => ( - +export const PostCreate: FC = () => ( + {/* */} diff --git a/src/components/Admin/resources/cms/post/PostEdit.tsx b/src/components/Admin/resources/cms/post/PostEdit.tsx index 20e664b9..ce21a24e 100644 --- a/src/components/Admin/resources/cms/post/PostEdit.tsx +++ b/src/components/Admin/resources/cms/post/PostEdit.tsx @@ -1,22 +1,12 @@ import {FC} from 'react' -import { - ArrayInput, - Edit, - EditProps, - FormTab, - NumberInput, - required, - SimpleFormIterator, - TabbedForm, - TextInput, -} from 'react-admin' +import {ArrayInput, Edit, FormTab, NumberInput, required, SimpleFormIterator, TabbedForm, TextInput} from 'react-admin' import {MyDateInput} from '@/components/Admin/custom/MyDateInput' import {MyEditActions} from '@/components/Admin/custom/MyEditActions' import {SitesCheckboxInput} from '@/components/Admin/custom/SitesCheckboxInput' -export const PostEdit: FC = (props) => ( - } redirect="show" mutationMode="undoable"> +export const PostEdit: FC = () => ( + } redirect="show" mutationMode="undoable"> diff --git a/src/components/Admin/resources/cms/post/PostList.tsx b/src/components/Admin/resources/cms/post/PostList.tsx index 2ac8607d..b34fed3a 100644 --- a/src/components/Admin/resources/cms/post/PostList.tsx +++ b/src/components/Admin/resources/cms/post/PostList.tsx @@ -1,8 +1,8 @@ import {FC} from 'react' -import {Datagrid, DateField, List, ListProps, NumberField, TextField} from 'react-admin' +import {Datagrid, DateField, List, NumberField, TextField} from 'react-admin' -export const PostList: FC = (props) => ( - +export const PostList: FC = () => ( + diff --git a/src/components/Admin/resources/cms/post/PostShow.tsx b/src/components/Admin/resources/cms/post/PostShow.tsx index 2d8b9667..63148d3d 100644 --- a/src/components/Admin/resources/cms/post/PostShow.tsx +++ b/src/components/Admin/resources/cms/post/PostShow.tsx @@ -5,7 +5,6 @@ import { DateField, NumberField, Show, - ShowProps, SimpleShowLayout, Tab, TabbedShowLayout, @@ -15,8 +14,8 @@ import { import {MyShowActions} from '@/components/Admin/custom/MyShowActions' import {SitesArrayField} from '@/components/Admin/custom/SitesArrayField' -export const PostShow: FC = (props) => ( - }> +export const PostShow: FC = () => ( + }> diff --git a/src/components/PageLayout/MenuMain/MenuMain.module.scss b/src/components/PageLayout/MenuMain/MenuMain.module.scss index f337a2bc..af220445 100644 --- a/src/components/PageLayout/MenuMain/MenuMain.module.scss +++ b/src/components/PageLayout/MenuMain/MenuMain.module.scss @@ -50,10 +50,6 @@ padding: 2.5px; } -.menuItems { - margin-top: 176px; -} - .loading { position: absolute; top: 50%; diff --git a/src/components/PageLayout/MenuMain/MenuMain.module.scss.d.ts b/src/components/PageLayout/MenuMain/MenuMain.module.scss.d.ts index 874eeae3..e1d46efd 100644 --- a/src/components/PageLayout/MenuMain/MenuMain.module.scss.d.ts +++ b/src/components/PageLayout/MenuMain/MenuMain.module.scss.d.ts @@ -5,7 +5,6 @@ export type Styles = { menuButton: string menuCloseButton: string menuItem: string - menuItems: string menuOpenButton: string visible: string } diff --git a/src/components/PageLayout/MenuMain/MenuMain.tsx b/src/components/PageLayout/MenuMain/MenuMain.tsx index 513f727d..e5c53e84 100644 --- a/src/components/PageLayout/MenuMain/MenuMain.tsx +++ b/src/components/PageLayout/MenuMain/MenuMain.tsx @@ -1,4 +1,4 @@ -import {Theme, Typography, useMediaQuery} from '@mui/material' +import {Stack, Theme, Typography, useMediaQuery} from '@mui/material' import {useQuery} from '@tanstack/react-query' import axios from 'axios' import clsx from 'clsx' @@ -9,6 +9,7 @@ import {FC, useState} from 'react' import {CloseButton} from '@/components/CloseButton/CloseButton' import {Loading} from '@/components/Loading/Loading' import Menu from '@/svg/menu.svg' +import {useHasPermissions} from '@/utils/useHasPermissions' import {useSeminarInfo} from '@/utils/useSeminarInfo' import {Authentication} from '../Authentication/Authentication' @@ -23,6 +24,8 @@ interface MenuItemInterface { export const MenuMain: FC = () => { const {seminar, seminarId} = useSeminarInfo() + const {hasPermissions} = useHasPermissions() + const [isVisible, setIsVisible] = useState(true) const toggleMenu = () => setIsVisible((currentIsVisible) => !currentIsVisible) @@ -49,12 +52,18 @@ export const MenuMain: FC = () => { )} -
- {menuItems.map((menuItem: MenuItemInterface) => { - // `menuItem.url` je vo formate `/vysledky/` alebo `/akcie/matboj/` - return - })} -
+ + {menuItems.map(({id, caption, url}) => ( + // `url` je vo formate `/vysledky/` alebo `/akcie/matboj/` + + ))} + + {hasPermissions && ( + + + + + )}