diff --git a/src/locales/nl.json b/src/locales/nl.json
index f5e6a9a..6380d11 100644
--- a/src/locales/nl.json
+++ b/src/locales/nl.json
@@ -85,6 +85,13 @@
"portraitUrl": {
"required": "Portret afbeelding vereist"
}
+ },
+ "page": {
+ "translation": {
+ "langCode": "Taalcode",
+ "body": "Tekst"
+ },
+ "slug": "Afkorting"
}
},
"form": {
@@ -121,6 +128,13 @@
},
"week": {
"add": "Voeg matchweek toe"
+ },
+ "page": {
+ "add": "Voeg pagina toe",
+ "translation": {
+ "add": "Voeg vertaling toe"
+ },
+ "remove": "Verwijder"
}
},
"modal": {
@@ -178,5 +192,6 @@
"weekWinnerPoints": "winnaar",
"statsBlockTitle": "Statistieken",
"overviewBlockTitle": "Overzicht"
- }
+ },
+ "pageTitle": "Pagina"
}
diff --git a/src/pages/Admin.tsx b/src/pages/Admin.tsx
index d2b9a61..65db92a 100644
--- a/src/pages/Admin.tsx
+++ b/src/pages/Admin.tsx
@@ -1,9 +1,8 @@
-import { useAuth } from "@/lib/stores/AuthContext";
import { useAppSelector } from "@/reducers";
-import { CalendarOutlined, HomeOutlined, SkinOutlined, StarOutlined, UserOutlined } from "@ant-design/icons";
+import { CalendarOutlined, CopyOutlined, HomeOutlined, SkinOutlined, StarOutlined, UserOutlined } from "@ant-design/icons";
import { Menu, MenuProps } from "antd";
import Title from "antd/es/typography/Title";
-import { useContext, useState } from "react";
+import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, Navigate, Outlet, useLocation } from "react-router-dom";
import secureLocalStorage from "react-secure-storage";
@@ -14,6 +13,11 @@ const items: MenuProps["items"] = [
key: "admin",
icon: ,
},
+ {
+ label: (Pages),
+ key: "pages",
+ icon: ,
+ },
{
label: (Players),
key: "players",
@@ -44,9 +48,9 @@ const items: MenuProps["items"] = [
export const Admin = (props: { redirectPath: string }) => {
const location = useLocation();
-
+
const user = useAppSelector((state) => state.userState.user);
-
+
const { t } = useTranslation();
const [current, setCurrent] = useState(location.pathname.split("/").pop() || "admin");
const onClick: MenuProps["onClick"] = (e) => {
@@ -64,7 +68,7 @@ export const Admin = (props: { redirectPath: string }) => {
return (
<>
{t("admin.welcome", { name: user?.firstName })}
-
+
>
);
diff --git a/src/pages/PageManagement/PageManagement.tsx b/src/pages/PageManagement/PageManagement.tsx
new file mode 100644
index 0000000..2f42c21
--- /dev/null
+++ b/src/pages/PageManagement/PageManagement.tsx
@@ -0,0 +1,207 @@
+import { CreateModal } from "@/components/CreateModal";
+import { EditModal } from "@/components/EditModal";
+import { Button } from "@/components/UI/Button/Button";
+import { FormItem } from "@/components/UI/Form/Form";
+import { Col, Row } from "@/components/UI/Grid/Grid";
+import { InputNumber } from "@/components/UI/InputNumber/InputNumber";
+import { useCreatePageMutation, useDeletePageMutation, useGetPagesQuery, useUpdatePageMutation } from "@/services/pagesApi";
+import { theme } from "@/styles/theme";
+import { DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons";
+import { Divider, Form, Input, Table } from "antd";
+import TextArea from "antd/lib/input/TextArea";
+import Title from "antd/lib/typography/Title";
+import React from "react";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { TranslationStyle } from "./PlayerManagementStyle";
+
+declare type PageManagementState = {
+ openEditModal: boolean
+ openCreateModal: boolean
+ editObject?: Page
+}
+
+export const PageManagement = () => {
+ const { data: pages, isLoading: pagesLoading, isError: pagesError, isSuccess: pagesSucces } = useGetPagesQuery();
+ const [updatePage] = useUpdatePageMutation();
+ const [createPage] = useCreatePageMutation();
+ const [deletePage] = useDeletePageMutation();
+
+ const [state, setState] = useState({
+ openEditModal: false,
+ openCreateModal: false,
+ });
+
+ const { t } = useTranslation();
+
+ const PageForm =
+ <>
+
+
+
+
+
+
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {
+ fields.map((field) => (
+
+
+
+
+
+
+
+ }
+ onClick={() => remove(field.name)}
+ type="dashed"
+ style={{ marginTop: "30px" }}
+ >
+ {t("management.page.remove")}
+
+
+
+
+
+
+
+
+
+ ))};
+
+ }
+ onClick={() => add()}
+ type="primary"
+ >
+ {t("management.page.add")}
+
+ >
+ )}
+
+ >;
+
+ return (
+ <>
+
+
+
+ Pages management
+
+
+ } onClick={() => setState({ ...state, openCreateModal: true })} type="primary">{t("management.page.add")}
+
+
+
+ {pages && (
+ {
+ return (
+ {txt}
+ );
+ },
+ sorter: (a: Player, b: Player) => b.id - a.id,
+ defaultSortOrder: "descend"
+ },
+ {
+ title: "Slug",
+ dataIndex: "slug",
+ width: "5%",
+ render: (txt: number, record: any) => {
+ return (
+ {txt}
+ );
+ }
+ },
+ {
+ title: "Translations",
+ dataIndex: "translation",
+ width: "5%",
+ render: (translations: PageTranslation[], record: any) => {
+ return translations.map((tl: PageTranslation) => (
+
+ {tl.langCode}
+ {tl.body.slice(0, 100)}...
+
+ ));
+ },
+ },
+ {
+ dataIndex: "operation",
+ width: "10%",
+ align: "center",
+ render: (_: any, record: any) => {
+ return (
+ <>
+ }
+ onClick={() => setState({ ...state, openEditModal: true, editObject: record })}
+ shape={"circle"}
+ type="primary"
+ />
+ }
+ onClick={() => deletePage(record)}
+ shape={"circle"}
+ type="primary"
+ />
+ >
+ );
+ }
+ }
+ ]}
+ />
+ )}
+ { updatePage(page); setState({ ...state, openEditModal: false }); }}
+ onCancel={() => setState({ ...state, openEditModal: false })}
+ type='page'
+ action='edit'
+ >
+
+
+
+
+ {PageForm}
+
+ { console.log(page); createPage(page); setState({ ...state, openCreateModal: false }); }}
+ onCancel={() => setState({ ...state, openCreateModal: false })}
+ title={t("pageTitle")}
+ >
+ {PageForm}
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/src/pages/PageManagement/PlayerManagementStyle.ts b/src/pages/PageManagement/PlayerManagementStyle.ts
new file mode 100644
index 0000000..bfa274d
--- /dev/null
+++ b/src/pages/PageManagement/PlayerManagementStyle.ts
@@ -0,0 +1,10 @@
+import styled from "@/styles/styled-components";
+import { theme } from "@/styles/theme";
+
+export const TranslationStyle = styled.div`
+ b {
+ background-color: ${theme.primaryContrast};
+ color: ${theme.primaryColor};
+ padding: 5px;
+ }
+`;
\ No newline at end of file
diff --git a/src/pages/PlayerManagement/PlayerManagement.tsx b/src/pages/PlayerManagement/PlayerManagement.tsx
index 30bec08..e2709e3 100644
--- a/src/pages/PlayerManagement/PlayerManagement.tsx
+++ b/src/pages/PlayerManagement/PlayerManagement.tsx
@@ -2,7 +2,7 @@ import { CreateModal } from "@/components/CreateModal";
import { EditModal } from "@/components/EditModal";
import { useGetClubsQuery } from "@/services/clubsApi";
import { useCreatePlayerMutation, useGetPlayersQuery, useImportPlayersMutation, useUpdatePlayerMutation } from "@/services/playersApi";
-import { EditOutlined, CheckOutlined, CloseOutlined, PlusOutlined, CopyrightCircleOutlined, PlusCircleOutlined, RocketOutlined, CloseCircleOutlined, StarOutlined, DownloadOutlined, ExclamationOutlined, ExclamationCircleOutlined } from "@ant-design/icons";
+import { EditOutlined, CheckOutlined, CloseOutlined, PlusOutlined, CopyrightCircleOutlined, PlusCircleOutlined, RocketOutlined, CloseCircleOutlined, StarOutlined, DownloadOutlined, ExclamationOutlined, ExclamationCircleOutlined, SearchOutlined } from "@ant-design/icons";
import { Table, InputNumber, Modal } from "antd";
import Title from "antd/es/typography/Title";
import { t } from "i18next";
@@ -20,10 +20,11 @@ import { toast } from "react-toastify";
import { openErrorNotification, openSuccessNotification } from "@/lib/helpers";
declare type PlayerManagementState = {
- openEditModal: boolean
- openCreateModal: boolean
- openImportModal: boolean
- editObject?: Player
+ openEditModal: boolean
+ openCreateModal: boolean
+ openImportModal: boolean
+ editObject?: Player
+ nameFilter?: string
}
@@ -38,7 +39,8 @@ export const PlayerManagement = () => {
const [state, setState] = useState({
openEditModal: false,
openCreateModal: false,
- openImportModal: false
+ openImportModal: false,
+ nameFilter: "",
});
useEffect(() => {
@@ -56,164 +58,164 @@ export const PlayerManagement = () => {
];
const PlayerForm =
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >;
@@ -246,15 +248,25 @@ export const PlayerManagement = () => {
+
+ }
+ type="text"
+ placeholder={t("general.playersListSearchInputPlaceholder").toString()}
+ name="search"
+ onChange={(event: any) => setState({...state, nameFilter: event.target.value})
+ }
+ />
+
{players && (
p.name.toLowerCase().includes(state.nameFilter))}
rowKey={"id"}
size="small"
rowClassName={"ant-table-row"}
- pagination={{ position: ["bottomCenter"], showSizeChanger: false}}
+ pagination={{ position: ["bottomCenter"], showSizeChanger: false }}
columns={[
{
title: "ID",
@@ -288,7 +300,7 @@ export const PlayerManagement = () => {
);
},
filters: positionsName.map((pos: string, id: number) => ({ text: pos, value: id + 1 })),
- onFilter: (value, record) => record.positionId === value
+ onFilter: (value, record) => record.positionId === value,
},
{
title: "Club",
@@ -317,12 +329,6 @@ export const PlayerManagement = () => {
title: "Last name",
dataIndex: "surname",
width: "20%",
- render: (txt: string, record: any) => {
- return (
- {txt}
- );
- },
- sorter: (a: Player, b: Player) => a.surname.localeCompare(b.surname)
},
{
title: "First name",
@@ -342,7 +348,7 @@ export const PlayerManagement = () => {
return (
{txt}
);
- }
+ },
},
{
title: "Specialities",
@@ -430,7 +436,7 @@ export const PlayerManagement = () => {
const data = await importPlayers().unwrap();
toast.dismiss("loading-importing-players");
openSuccessNotification({ title: "Import successfull", message: `${data.count} players imported` });
- } catch(err) {
+ } catch (err) {
toast.dismiss("loading-importing-players");
openErrorNotification({ title: "Importing players failed" });
}
@@ -439,7 +445,7 @@ export const PlayerManagement = () => {
onCancel={() => setState({ ...state, openImportModal: false })}
cancelText={t("cancelBtn")}
>
- Het importeren is een zeer kostbare operatie. Zeker dat je wilt doorgaan?
+ Het importeren is een zeer kostbare operatie. Zeker dat je wilt doorgaan?
Het importeren duurt min. 5 minuten!
>
diff --git a/src/pages/Rules/Rules.tsx b/src/pages/Rules/Rules.tsx
index e3e48df..cf5b527 100644
--- a/src/pages/Rules/Rules.tsx
+++ b/src/pages/Rules/Rules.tsx
@@ -1,18 +1,37 @@
import { Col, Row } from "@/components/UI/Grid/Grid";
+import { useGetPageQuery } from "@/services/pagesApi";
import Title from "antd/es/typography/Title";
import { t } from "i18next";
+import { useEffect, useState } from "react";
+import { RulesStyles } from "./RulesStyle";
+import { Block } from "@/components/Block/Block";
+import parseHTML from "html-react-parser";
-type RulesProps = {
- // todo
+type RulesState = {
+ text: string
}
-export const Rules = (props: RulesProps) => {
+export const Rules = () => {
+ const { data: page, isLoading: pageLoading } = useGetPageQuery("rules");
+ const [state, setState] = useState({
+ text: "",
+ });
+
+ useEffect(() => {
+ const body = page && page[0] && page[0].translation && page[0].translation[0] && page[0].translation[0].body;
+ setState({ ...state, text: body || "" });
+ }, [page]);
+
return (
-
-
- {t("general.rules")}
-
-
-
+
+
+
+ {t("general.rules")}
+
+ {parseHTML(state.text)}
+
+
+
+
);
};
\ No newline at end of file
diff --git a/src/reducers/index.tsx b/src/reducers/index.tsx
index f62e127..54e4ea5 100644
--- a/src/reducers/index.tsx
+++ b/src/reducers/index.tsx
@@ -10,6 +10,7 @@ import { userReducer } from "@/features/userSlice";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
import { playerStatsApi } from "@/services/statisticsApi";
+import { pagesApi } from "@/services/pagesApi";
const reducer = combineReducers({
[usersApi.reducerPath]: usersApi.reducer,
@@ -19,6 +20,7 @@ const reducer = combineReducers({
[matchesApi.reducerPath]: matchesApi.reducer,
[weeksApi.reducerPath]: weeksApi.reducer,
[playerStatsApi.reducerPath]: playerStatsApi.reducer,
+ [pagesApi.reducerPath]: pagesApi.reducer,
application: applicationReducer,
userState: userReducer,
});
@@ -46,6 +48,7 @@ export const store = configureStore({
matchesApi.middleware,
weeksApi.middleware,
playerStatsApi.middleware,
+ pagesApi.middleware,
]
});
diff --git a/src/routes.tsx b/src/routes.tsx
index 1a52ae2..97a007b 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -1,9 +1,8 @@
-import { Navigate, Outlet, createBrowserRouter } from "react-router-dom";
+import { Navigate, createBrowserRouter } from "react-router-dom";
import ProtectedRoute from "@/components/ProtectedRoute";
import { Header } from "@/components/Header/Header";
import { LoginCallback } from "./pages/LoginCallback";
-import secureLocalStorage from "react-secure-storage";
import { Home } from "./pages/Home/Home";
import { NewTeam } from "./pages/NewTeam/NewTeam";
import { Admin } from "./pages/Admin";
@@ -22,6 +21,7 @@ import { TransfersPage } from "./pages/Transfers/Transfers";
import { PointsPage } from "./pages/Points/Points";
import { MatchContainer } from "./pages/Match/Match";
import { Welcome } from "./pages/Welcome/Welcome";
+import { PageManagement } from "./pages/PageManagement/PageManagement";
const Layout = ({ children }: any) => {
return (
@@ -98,6 +98,10 @@ export const router = createBrowserRouter([
path: "/admin",
element: ,
children: [
+ {
+ path: "pages",
+ element:
+ },
{
path: "players",
element:
diff --git a/src/services/pagesApi.ts b/src/services/pagesApi.ts
new file mode 100644
index 0000000..4ed1e7b
--- /dev/null
+++ b/src/services/pagesApi.ts
@@ -0,0 +1,57 @@
+import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
+
+import config from "@/config";
+
+export const pagesApi = createApi({
+ reducerPath: "pagesApi",
+ baseQuery: fetchBaseQuery({ baseUrl: `${config.API_URL}/pages`, credentials: "include" }),
+ tagTypes: ["Page"],
+ endpoints: (builder) => ({
+ getPages: builder.query({
+ query: () => "",
+ providesTags: (res, err, arg) =>
+ res
+ ? [...res.map(({ id }) => ({ type: "Page" as const, id })), "Page"]
+ : ["Page"]
+ }),
+
+ getPage: builder.query({
+ query: (arg) => ({
+ url: "",
+ params: { slug: arg },
+ }),
+ providesTags: (res, err, arg) =>
+ res
+ ? [...res.map(({ id }) => ({ type: "Page" as const, id })), "Page"]
+ : ["Page"]
+ }),
+
+ createPage: builder.mutation<{ msg: string }, Page>({
+ invalidatesTags: ["Page"],
+ query: (args) => ({
+ url: "",
+ method: "POST",
+ body: args
+ }),
+ }),
+
+ updatePage: builder.mutation<{ msg: string }, Page>({
+ query: ({ id, ...data }) => ({
+ url: `${id}`,
+ method: "PUT",
+ body: data
+ }),
+ invalidatesTags: (res, err, arg) => [{ type: "Page", id: arg.id }]
+ }),
+
+ deletePage: builder.mutation<{ msg: string }, Partial & Pick>({
+ invalidatesTags: (res, err, arg) => [{ type: "Page", id: arg.id }],
+ query: ({ id }) => ({
+ url: `${id}`,
+ method: "DELETE",
+ }),
+ }),
+ })
+});
+
+export const { useGetPagesQuery, useGetPageQuery, useCreatePageMutation, useUpdatePageMutation, useDeletePageMutation } = pagesApi;
\ No newline at end of file
diff --git a/src/types/index.tsx b/src/types/index.tsx
index 5d80a67..e508336 100644
--- a/src/types/index.tsx
+++ b/src/types/index.tsx
@@ -128,11 +128,23 @@ type Week = {
deadlineDate: Date
}
-// type Players = {
-// isFetching: boolean
-// loaded: boolean
-// data: string
-// }
+type Page = {
+ translation: PageTranslation[]
+ id: number
+ slug: string
+ competitionFeed: string
+ thumbnail: any
+ created: string
+ modified: string
+}
+
+type PageTranslation = {
+ id?: number
+ langCode: string
+ body: string
+ title?: any
+ pageId?: number
+}
type ApplicationState = {
competition: Competition