Skip to content

Commit

Permalink
React admin csrf and other fixes (#211)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rtrembecky authored Nov 12, 2023
1 parent 4db6cea commit 1f2be85
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 122 deletions.
6 changes: 3 additions & 3 deletions src/components/Admin/Competition/CompetitionList.tsx
Original file line number Diff line number Diff line change
@@ -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<ListProps> = (props) => (
<List {...props}>
export const CompetitionList: FC = () => (
<List>
<Datagrid>
<TextField source="id" />
<DateField source="deadline" />
Expand Down
5 changes: 3 additions & 2 deletions src/components/Admin/custom/MyArrayField.tsx
Original file line number Diff line number Diff line change
@@ -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<MyArrayFieldProps> = ({record, source, formatNumber}) => {
export const MyArrayField: FC<MyArrayFieldProps> = ({source, formatNumber}) => {
const record = useRecordContext()
if (!record || !source) return null

const array = record[source] as (string | number)[] | undefined
Expand Down
8 changes: 7 additions & 1 deletion src/components/Admin/custom/MyDateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
6 changes: 4 additions & 2 deletions src/components/Admin/custom/MyEditActions.tsx
Original file line number Diff line number Diff line change
@@ -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<EditActionsProps> = () => {
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'
Expand Down
2 changes: 2 additions & 0 deletions src/components/Admin/custom/MyShowActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
95 changes: 24 additions & 71 deletions src/components/Admin/dataProvider.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -19,21 +19,6 @@ import {Cookies} from 'react-cookie'
// ordering: `${order === 'ASC' ? '' : '-'}${field}`,
// })

const cookies = new Cookies()

const authFetchJson = (url: string, options?: Record<string, unknown>) => {
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) => {
Expand All @@ -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)}
},
}
15 changes: 3 additions & 12 deletions src/components/Admin/resources/cms/post/PostCreate.tsx
Original file line number Diff line number Diff line change
@@ -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<CreateProps> = (props) => (
<Create redirect="show" {...props}>
export const PostCreate: FC = () => (
<Create redirect="show">
<TabbedForm>
<FormTab label="general">
{/* <NumberInput source="id" fullWidth disabled /> */}
Expand Down
16 changes: 3 additions & 13 deletions src/components/Admin/resources/cms/post/PostEdit.tsx
Original file line number Diff line number Diff line change
@@ -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<EditProps> = (props) => (
<Edit {...props} actions={<MyEditActions />} redirect="show" mutationMode="undoable">
export const PostEdit: FC = () => (
<Edit actions={<MyEditActions />} redirect="show" mutationMode="undoable">
<TabbedForm>
<FormTab label="general">
<NumberInput source="id" fullWidth disabled />
Expand Down
6 changes: 3 additions & 3 deletions src/components/Admin/resources/cms/post/PostList.tsx
Original file line number Diff line number Diff line change
@@ -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<ListProps> = (props) => (
<List {...props}>
export const PostList: FC = () => (
<List>
<Datagrid rowClick="show">
<NumberField source="id" />
<TextField source="caption" />
Expand Down
5 changes: 2 additions & 3 deletions src/components/Admin/resources/cms/post/PostShow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
DateField,
NumberField,
Show,
ShowProps,
SimpleShowLayout,
Tab,
TabbedShowLayout,
Expand All @@ -15,8 +14,8 @@ import {
import {MyShowActions} from '@/components/Admin/custom/MyShowActions'
import {SitesArrayField} from '@/components/Admin/custom/SitesArrayField'

export const PostShow: FC<ShowProps> = (props) => (
<Show {...props} actions={<MyShowActions />}>
export const PostShow: FC = () => (
<Show actions={<MyShowActions />}>
<TabbedShowLayout>
<Tab label="general">
<SimpleShowLayout>
Expand Down
4 changes: 0 additions & 4 deletions src/components/PageLayout/MenuMain/MenuMain.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@
padding: 2.5px;
}

.menuItems {
margin-top: 176px;
}

.loading {
position: absolute;
top: 50%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export type Styles = {
menuButton: string
menuCloseButton: string
menuItem: string
menuItems: string
menuOpenButton: string
visible: string
}
Expand Down
23 changes: 16 additions & 7 deletions src/components/PageLayout/MenuMain/MenuMain.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand All @@ -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)

Expand All @@ -49,12 +52,18 @@ export const MenuMain: FC = () => {
<Loading />
</div>
)}
<div className={styles.menuItems}>
{menuItems.map((menuItem: MenuItemInterface) => {
// `menuItem.url` je vo formate `/vysledky/` alebo `/akcie/matboj/`
return <MenuMainItem key={menuItem.id} caption={menuItem.caption} url={`/${seminar}${menuItem.url}`} />
})}
</div>
<Stack mt="176px">
{menuItems.map(({id, caption, url}) => (
// `url` je vo formate `/vysledky/` alebo `/akcie/matboj/`
<MenuMainItem key={id} caption={caption} url={`/${seminar}${url}`} />
))}
</Stack>
{hasPermissions && (
<Stack sx={{mt: 4, mx: 2, borderTop: '8px dashed white', pt: 4}}>
<MenuMainItem caption="TODO: Opravovanie" url={`/${seminar}/admin/opravovanie`} />
<MenuMainItem caption="Admin" url="/admin" />
</Stack>
)}
<Authentication />
</div>
</>
Expand Down

0 comments on commit 1f2be85

Please sign in to comment.