From c588f4042880adcc8c59ffa49f1dee1862fc1e50 Mon Sep 17 00:00:00 2001 From: jdubois Date: Thu, 24 Mar 2022 20:05:12 +0100 Subject: [PATCH 1/2] feat: Add new parts (Cartapapa & Run'Up) --- app/routes/index.tsx | 4 +++- .../assets/images/partnerships/cartapapa.jpeg | Bin 0 -> 26540 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 public/assets/images/partnerships/cartapapa.jpeg diff --git a/app/routes/index.tsx b/app/routes/index.tsx index c4984c6..f3547cf 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -32,7 +32,9 @@ export default function Index() { { name: "L'encas", imgSrc: "/assets/images/partnerships/l_encas.png" }, { name: "Tropic Addict", imgSrc: "/assets/images/partnerships/tropic_addict.png" }, { name: "Le 5 by La Cabane", imgSrc: "/assets/images/partnerships/le_5_by_la_cabane.png" }, - { name: "Laser Quest Comedie", imgSrc: "/assets/images/partnerships/laser_quest_comedie.jpeg" } + { name: "Laser Quest Comedie", imgSrc: "/assets/images/partnerships/laser_quest_comedie.jpeg" }, + { name: "Cartapapa", imgSrc: "/assets/images/partnerships/cartapapa.jpeg" }, + { name: "Run'Up", imgSrc: "/assets/images/partnerships/run_up.png" }, ]} /> diff --git a/public/assets/images/partnerships/cartapapa.jpeg b/public/assets/images/partnerships/cartapapa.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..accd72f3bc62aed566753d3046e26c97fd42b437 GIT binary patch literal 26540 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 Vgd;6f7&wlSfkl#vD-lA;|Jgn8xQ_q; literal 0 HcmV?d00001 From 940c36db901b342fed13101b7c1a1990d6361ead Mon Sep 17 00:00:00 2001 From: jdubois Date: Thu, 24 Mar 2022 21:52:48 +0100 Subject: [PATCH 2/2] feat: implement pagination + Add a count function in services linked with listable models + Add a Pagination type (see utils) + Use mui Pagination component to add pagination bar in some react compenents --- app/routes/challenges/index.tsx | 77 +++++++++++++++++++++++++----- app/routes/goodies/index.tsx | 84 +++++++++++++++++++++++++++------ app/routes/soutiens/index.tsx | 2 +- app/routes/users/index.tsx | 83 ++++++++++++++++++++++++++------ app/services/accomplishment.ts | 39 +++++++++++++++ app/services/challenges.ts | 31 ++++++++++++ app/services/goodies.ts | 35 +++++++++++++- app/services/purchase.ts | 39 +++++++++++++++ app/services/user.ts | 35 +++++++++++++- app/utils/pagination.tsx | 5 ++ 10 files changed, 383 insertions(+), 47 deletions(-) create mode 100644 app/utils/pagination.tsx diff --git a/app/routes/challenges/index.tsx b/app/routes/challenges/index.tsx index 7a2f648..5130141 100644 --- a/app/routes/challenges/index.tsx +++ b/app/routes/challenges/index.tsx @@ -1,11 +1,13 @@ -import { CircularProgress, Container, Typography } from "@mui/material"; +import { CircularProgress, Container, Pagination as PaginationMui, Typography } from "@mui/material"; import { + ActionFunction, json, LoaderFunction, useCatch, useLoaderData, useOutletContext, + useSubmit, useTransition, } from "remix"; @@ -19,22 +21,60 @@ import { Challenge } from "~/models/Challenge"; import { requireAuth } from "~/services/authentication"; import ChallengeGrid from "~/components/challenge/grids/challengeGrid"; -import { getManyChallenge } from "~/services/challenges"; +import { getChallengeCount, getManyChallenge } from "~/services/challenges"; import { ContextData } from "~/root"; import { blue } from "@mui/material/colors"; +import { Pagination } from "~/utils/pagination"; type LoaderData = { challengeResponse?: { + data?: Pagination; error?: string; success?: string; - challenges?: Challenge[]; }; }; -async function loadChallenges(token: string) { - const { code, ...challengeResponse } = await getManyChallenge(token, 100, 0); +const rowPerPage = 100; - return json({ challengeResponse } as LoaderData, code); +async function loadChallenges(token: string, page: number = 0) { + const challengeResponse = await getManyChallenge(token, rowPerPage, page * rowPerPage); + const countResponse = await getChallengeCount(token); + + if (challengeResponse.error) { + return json( + { challengeResponse: { error: challengeResponse.error } } as LoaderData, + challengeResponse.code + ); + } + + if (countResponse.error) { + return json( + { challengeResponse: { error: countResponse.error } } as LoaderData, + countResponse.code + ); + } + + return json({ + challengeResponse: { + pagination: { + page: page, + count: countResponse.count, + items: challengeResponse.challenges + } + } + } as LoaderData, Math.max(challengeResponse.code || 200, countResponse.code || 200)); +} + +export const action: ActionFunction = async ({ request }) => { + const token = await requireAuth(request, "/challenges"); + const formData = await request.formData(); + const page = Number(formData.get("page")); + + if (isNaN(page)) { + return json({ challengeResponse: { error: "Bad form data: page must be a number" } }, 400) + } + + return await loadChallenges(token, page); } export const loader: LoaderFunction = async ({ request }) => { @@ -46,10 +86,16 @@ export const loader: LoaderFunction = async ({ request }) => { export default function Challenges() { const loaderData = useLoaderData(); - const { API_URL } = useOutletContext(); - const transition = useTransition(); + const submit = useSubmit(); + + const handleChangePage = async (event: React.ChangeEvent, value: number) => { + const formData = new FormData(); + //Page starts from 1 instead of 0 for MUI Pagination component + formData.append("page", (value - 1).toString()); + submit(formData); + }; return ( @@ -60,16 +106,16 @@ export default function Challenges() { {generateAlert( "info", loaderData.challengeResponse?.success && - (!loaderData.challengeResponse?.challenges || - loaderData.challengeResponse.challenges.length === 0) + (!loaderData.challengeResponse?.data?.items || + loaderData.challengeResponse?.data?.items.length === 0) ? "Il n'y a aucun challenge pour l'instant" : undefined )} - {loaderData.challengeResponse?.challenges && - loaderData.challengeResponse?.challenges.length !== 0 && ( + {loaderData.challengeResponse?.data?.items && + loaderData.challengeResponse?.data?.items.length !== 0 && ( )} {transition.state === "submitting" && ( @@ -84,6 +130,11 @@ export default function Challenges() { }} /> )} + ); } diff --git a/app/routes/goodies/index.tsx b/app/routes/goodies/index.tsx index bac50d4..47988bb 100644 --- a/app/routes/goodies/index.tsx +++ b/app/routes/goodies/index.tsx @@ -1,11 +1,13 @@ -import { CircularProgress, Container, Typography } from "@mui/material"; +import { CircularProgress, Container, Pagination as PaginationMui, Typography } from "@mui/material"; import { + ActionFunction, json, LoaderFunction, useCatch, useLoaderData, useOutletContext, + useSubmit, useTransition, } from "remix"; @@ -16,23 +18,65 @@ import { } from "~/utils/error"; import { requireAuth } from "~/services/authentication"; -import { getManyGoodies } from "~/services/goodies"; +import { getGoodiesCount, getManyGoodies } from "~/services/goodies"; import GoodiesGrid from "~/components/goodies/grids/goodiesGrid"; import { Goodies } from "~/models/Goodies"; import { ContextData } from "~/root"; import { blue } from "@mui/material/colors"; +import { Pagination } from "~/utils/pagination"; type LoaderData = { - goodiesResponse?: { error?: string; goodies?: Goodies[]; success?: string }; + goodiesResponse?: { + error?: string; + data?: Pagination; + success?: string + }; }; -async function loadGoodies(token: string) { - const { code, ...goodiesResponse } = await getManyGoodies(token, 100, 0); +const rowPerPage = 100; - return json({ goodiesResponse } as LoaderData, code); +async function loadGoodies(token: string, page: number = 0) { + const goodiesResponse = await getManyGoodies(token, rowPerPage, page * rowPerPage); + const countResponse = await getGoodiesCount(token); + + if (goodiesResponse.error) { + return json( + { goodiesResponse: { error: goodiesResponse.error } } as LoaderData, + goodiesResponse.code + ); + } + + if (countResponse.error) { + return json( + { goodiesResponse: { error: countResponse.error } } as LoaderData, + countResponse.code + ); + } + + return json({ + goodiesResponse: { + pagination: { + page: page, + count: countResponse.count, + items: goodiesResponse.goodies, + } + } + } as LoaderData, Math.max(goodiesResponse.code || 200, countResponse.code || 200)); +} + +export const action: ActionFunction = async ({ request }) => { + const token = await requireAuth(request, "/goodies"); + const formData = await request.formData(); + const page = Number(formData.get("page")); + + if (isNaN(page)) { + return json({ challengeResponse: { error: "Bad form data: page must be a number" } }, 400) + } + + return await loadGoodies(token, page); } -//Function that handle GET resuests +//Function that handle GET requests export const loader: LoaderFunction = async ({ request }) => { const token = await requireAuth(request, "/goodies"); @@ -41,10 +85,17 @@ export const loader: LoaderFunction = async ({ request }) => { export default function Shop() { const loaderData = useLoaderData(); - const { API_URL } = useOutletContext(); - const transition = useTransition(); + const submit = useSubmit(); + + const handleChangePage = async (event: React.ChangeEvent, value: number) => { + const formData = new FormData(); + //Page starts from 1 instead of 0 for MUI Pagination component + formData.append("page", (value - 1).toString()); + submit(formData); + }; + return ( @@ -54,16 +105,16 @@ export default function Shop() { {generateAlert( "info", loaderData.goodiesResponse?.success && - (!loaderData.goodiesResponse?.goodies || - loaderData.goodiesResponse.goodies.length === 0) + (!loaderData.goodiesResponse?.data?.items || + loaderData.goodiesResponse?.data?.items.length === 0) ? "Il n'y a pas de goodies pour l'instant" : undefined )} - {loaderData.goodiesResponse?.goodies && - loaderData.goodiesResponse.goodies.length !== 0 && ( + {loaderData.goodiesResponse?.data?.items && + loaderData.goodiesResponse?.data?.items.length !== 0 && ( )} {transition.state === "submitting" && ( @@ -78,6 +129,11 @@ export default function Shop() { }} /> )} + ); } diff --git a/app/routes/soutiens/index.tsx b/app/routes/soutiens/index.tsx index b2291f5..532c6a2 100644 --- a/app/routes/soutiens/index.tsx +++ b/app/routes/soutiens/index.tsx @@ -44,7 +44,7 @@ export default function Soutiens() { width="560" style={{ border: "none", overflow: "hidden" }} scrolling="no" - allowFullScreen="true" + allowFullScreen={true} allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share" > diff --git a/app/routes/users/index.tsx b/app/routes/users/index.tsx index 2bb3515..c5fc221 100644 --- a/app/routes/users/index.tsx +++ b/app/routes/users/index.tsx @@ -1,11 +1,13 @@ -import { CircularProgress, Container, Typography } from "@mui/material"; +import { CircularProgress, Container, Pagination as PaginationMui, Typography } from "@mui/material"; import { + ActionFunction, json, LoaderFunction, useCatch, useLoaderData, useOutletContext, + useSubmit, useTransition, } from "remix"; @@ -18,35 +20,81 @@ import { import UserList from "~/components/user/userList"; import { requireAuth } from "~/services/authentication"; import { User } from "~/models/User"; -import { getManyUser } from "~/services/user"; +import { getManyUser, getUserCount } from "~/services/user"; import { ContextData } from "~/root"; import { blue } from "@mui/material/colors"; +import { Pagination } from "~/utils/pagination"; type LoaderData = { - userResponse?: { error?: string; users?: User[]; success?: string }; + userResponse?: { + error?: string; + data?: Pagination; + success?: string; + }; }; -async function loadUsers(token: string) { - const { code, ...userResponse } = await getManyUser(token, 1000); +const rowPerPage = 100; - console.log(userResponse.users); +async function loadUsers(token: string, page: number = 0) { + const userResponse = await getManyUser(token, rowPerPage, page * rowPerPage); + const countResponse = await getUserCount(token); - return json({ userResponse } as LoaderData, code); + if (userResponse.error) { + return json( + { userResponse: { error: userResponse.error } } as LoaderData, + userResponse.code + ); + } + + if (countResponse.error) { + return json( + { userResponse: { error: countResponse.error } } as LoaderData, + countResponse.code + ); + } + + return json({ + userResponse: { + pagination: { + page: page, + count: countResponse.count, + items: userResponse.users, + } + } + } as LoaderData, Math.max(userResponse.code || 200, countResponse.code || 200)); +} + +export const action: ActionFunction = async ({ request }) => { + const token = await requireAuth(request, "/users"); + const formData = await request.formData(); + const page = Number(formData.get("page")); + + if (isNaN(page)) { + return json({ challengeResponse: { error: "Bad form data: page must be a number" } }, 400) + } + + return await loadUsers(token, page); } //Function that handle GET resuests export const loader: LoaderFunction = async ({ request }) => { - const token = await requireAuth(request, "/goodies"); + const token = await requireAuth(request, "/users"); return await loadUsers(token); }; export default function Users() { const loaderData = useLoaderData(); - const { API_URL } = useOutletContext(); - const transition = useTransition(); + const submit = useSubmit(); + + const handleChangePage = async (event: React.ChangeEvent, value: number) => { + const formData = new FormData(); + //Page starts from 1 instead of 0 for MUI Pagination component + formData.append("page", (value - 1).toString()); + submit(formData); + }; return ( @@ -57,17 +105,17 @@ export default function Users() { {generateAlert( "info", loaderData.userResponse?.success && - (!loaderData.userResponse?.users || - loaderData.userResponse?.users.length === 0) + (!loaderData.userResponse?.data?.items || + loaderData.userResponse?.data?.items.length === 0) ? "Il n'y a aucun utilisateur à afficher" : undefined )} - {loaderData.userResponse?.users && - loaderData.userResponse?.users.length !== 0 && ( + {loaderData.userResponse?.data?.items && + loaderData.userResponse?.data?.items.length !== 0 && (
b.totalEarnedPoints - a.totalEarnedPoints )} /> @@ -85,6 +133,11 @@ export default function Users() { }} /> )} + ); } diff --git a/app/services/accomplishment.ts b/app/services/accomplishment.ts index f681c65..9f481d1 100644 --- a/app/services/accomplishment.ts +++ b/app/services/accomplishment.ts @@ -307,3 +307,42 @@ export async function deleteProof(token: string, accomplishmentId: number) { }; } } + +export async function getAccomplishmentCount( + token: string, + challengeId?: number, + userId?: number, + validation?: Validation +) { + const searchParams = buildSearchParams( + { key: "challengeId", val: challengeId?.toString() }, + { key: "userId", val: userId?.toString() }, + { key: "status", val: validation } + ); + try { + const reply = await axios.get<{ + message: string; + count: number; + }>(`/accomplishment/count${searchParams}`, { + headers: buildAxiosHeaders(token), + }); + + return { + success: reply.data.message, + code: reply.status, + count: reply.data.count, + }; + } catch (err) { + if ( + axios.isAxiosError(err) && + typeof err.response?.data.message === "string" + ) { + return { error: err.response.data.message, code: err.response.status }; + } + console.error(err); + return { + error: + "Sorry, it seems there is some problem reaching our API. Please contact and administrator.", + }; + } +} \ No newline at end of file diff --git a/app/services/challenges.ts b/app/services/challenges.ts index 2c2ec80..9db0af6 100644 --- a/app/services/challenges.ts +++ b/app/services/challenges.ts @@ -284,3 +284,34 @@ export async function deleteChallengePicture( }; } } + +export async function getChallengeCount( + token: string, +) { + try { + const reply = await axios.get<{ + message: string; + count: number; + }>("/challenge/count", { + headers: buildAxiosHeaders(token), + }); + + return { + success: reply.data.message, + code: reply.status, + count: reply.data.count, + }; + } catch (err) { + if ( + axios.isAxiosError(err) && + typeof err.response?.data.message === "string" + ) { + return { error: err.response.data.message, code: err.response.status }; + } + console.error(err); + return { + error: + "Sorry, it seems there is some problem reaching our API. Please contact and administrator.", + }; + } +} \ No newline at end of file diff --git a/app/services/goodies.ts b/app/services/goodies.ts index fb01a00..2c34056 100644 --- a/app/services/goodies.ts +++ b/app/services/goodies.ts @@ -199,8 +199,8 @@ export async function putGoodiesPicture( axios.isAxiosError(err) && typeof err.response?.data.message === "string" ) { - if(err.response.data.message.includes("request file too large")){ - return {error: "Fichier trop volumineux, veuiller envoyer un fichier plus léger"} + if (err.response.data.message.includes("request file too large")) { + return { error: "Fichier trop volumineux, veuiller envoyer un fichier plus léger" } } return { error: err.response.data.message, code: err.response.status }; } @@ -272,3 +272,34 @@ export async function deleteGoodiesPicture(token: string, goodiesId: number) { }; } } + +export async function getGoodiesCount( + token: string, +) { + try { + const reply = await axios.get<{ + message: string; + count: number; + }>("/goodies/count", { + headers: buildAxiosHeaders(token), + }); + + return { + success: reply.data.message, + code: reply.status, + count: reply.data.count, + }; + } catch (err) { + if ( + axios.isAxiosError(err) && + typeof err.response?.data.message === "string" + ) { + return { error: err.response.data.message, code: err.response.status }; + } + console.error(err); + return { + error: + "Sorry, it seems there is some problem reaching our API. Please contact and administrator.", + }; + } +} \ No newline at end of file diff --git a/app/services/purchase.ts b/app/services/purchase.ts index 6413d90..be2270a 100644 --- a/app/services/purchase.ts +++ b/app/services/purchase.ts @@ -179,3 +179,42 @@ export async function getPurchase(token: string, purchaseId: number) { }; } } + +export async function getPurchaseCount( + token: string, + goodiesId?: number, + userId?: number, + delivered?: boolean, +) { + const searchParams = buildSearchParams( + { key: "goodiesId", val: goodiesId?.toString() }, + { key: "userId", val: userId?.toString() }, + { key: "delivered", val: delivered?.toString() } + ); + try { + const reply = await axios.get<{ + message: string; + count: number; + }>(`/purchase/count${searchParams}`, { + headers: buildAxiosHeaders(token), + }); + + return { + success: reply.data.message, + code: reply.status, + count: reply.data.count, + }; + } catch (err) { + if ( + axios.isAxiosError(err) && + typeof err.response?.data.message === "string" + ) { + return { error: err.response.data.message, code: err.response.status }; + } + console.error(err); + return { + error: + "Sorry, it seems there is some problem reaching our API. Please contact and administrator.", + }; + } +} \ No newline at end of file diff --git a/app/services/user.ts b/app/services/user.ts index 8e06136..7cec6cc 100644 --- a/app/services/user.ts +++ b/app/services/user.ts @@ -262,8 +262,8 @@ export async function putAvatar(token: string, userId: number, avatar: Blob) { axios.isAxiosError(err) && typeof err.response?.data.message === "string" ) { - if(err.response.data.message.includes("request file too large")){ - return {error: "Fichier trop volumineux, veuiller envoyer un fichier plus léger"} + if (err.response.data.message.includes("request file too large")) { + return { error: "Fichier trop volumineux, veuiller envoyer un fichier plus léger" } } return { error: err.response.data.message, code: err.response.status }; } @@ -335,3 +335,34 @@ export async function deleteAvatar(token: string, userId: number) { }; } } + +export async function getUserCount( + token: string, +) { + try { + const reply = await axios.get<{ + message: string; + count: number; + }>("/purchase/count", { + headers: buildAxiosHeaders(token), + }); + + return { + success: reply.data.message, + code: reply.status, + count: reply.data.count, + }; + } catch (err) { + if ( + axios.isAxiosError(err) && + typeof err.response?.data.message === "string" + ) { + return { error: err.response.data.message, code: err.response.status }; + } + console.error(err); + return { + error: + "Sorry, it seems there is some problem reaching our API. Please contact and administrator.", + }; + } +} \ No newline at end of file diff --git a/app/utils/pagination.tsx b/app/utils/pagination.tsx new file mode 100644 index 0000000..0e2d3fd --- /dev/null +++ b/app/utils/pagination.tsx @@ -0,0 +1,5 @@ +export type Pagination = { + page: number, + count: number, + items: T[], +} \ No newline at end of file