Skip to content

Commit 6902d89

Browse files
committed
feat: add ReturnPurchaseModal component with delete functionality for purchases and localization updates
1 parent 2ac63c8 commit 6902d89

File tree

9 files changed

+203
-11
lines changed

9 files changed

+203
-11
lines changed

client/src/components/auth0.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,41 @@ export const postJsonToSecuredApi = async (
383383
}
384384
};
385385

386+
/**
387+
* Execute a DELETE request to a secured API endpoint using Auth0 token authentication.
388+
* Handles the token acquisition and authorization header setup automatically.
389+
* @param {string} url - The URL of the secured API endpoint to delete data from
390+
* @param {GetAccessTokenFunction} getAccessTokenFunction - Function to retrieve an access token, typically Auth0's getAccessTokenSilently
391+
* @returns {Promise<any>} Promise resolving to the JSON response from the API
392+
*/
393+
export const deleteJsonFromSecuredApi = async (
394+
url: string,
395+
getAccessTokenFunction: GetAccessTokenFunction,
396+
) => {
397+
try {
398+
const accessToken = await getAccessTokenFunction({
399+
authorizationParams: {
400+
audience: import.meta.env.AUTH0_AUDIENCE,
401+
scope: import.meta.env.AUTH0_SCOPE,
402+
},
403+
});
404+
const apiResponse = await fetch(url, {
405+
method: "DELETE",
406+
headers: {
407+
Authorization: `Bearer ${accessToken}`,
408+
"Content-Type": "application/json",
409+
},
410+
});
411+
412+
return await apiResponse.json();
413+
} catch (error) {
414+
// eslint-disable-next-line no-console
415+
console.error(error);
416+
throw error;
417+
}
418+
};
419+
420+
386421
/**
387422
* Checks if the user has a specific permission.
388423
* @param permission - The permission to check for
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* @copyright Copyright (c) 2024-2025 Ronan LE MEILLAT
3+
* @license AGPL-3.0-or-later
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as
7+
* published by the Free Software Foundation, either version 3 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
import { FormEvent, useState } from "react";
19+
import { useTranslation } from "react-i18next";
20+
import { useAuth0 } from "@auth0/auth0-react";
21+
import {
22+
Modal,
23+
ModalBody,
24+
ModalContent,
25+
ModalFooter,
26+
ModalHeader,
27+
ModalProps,
28+
} from "@heroui/modal";
29+
import { Form } from "@heroui/form";
30+
import { Button } from "@heroui/button";
31+
import { addToast } from "@heroui/toast";
32+
33+
import { deleteJsonFromSecuredApi } from "../auth0";
34+
35+
export default function ReturnPurchaseModal({
36+
purchaseId,
37+
onSuccess,
38+
...props
39+
}: {
40+
purchaseId: string;
41+
purchaseAmount?: number;
42+
onSuccess?: () => void;
43+
} & ModalProps) {
44+
const { t } = useTranslation();
45+
const { getAccessTokenSilently } = useAuth0();
46+
const [isSubmitting, setIsSubmitting] = useState(false);
47+
48+
const handleSubmit = async (e: FormEvent) => {
49+
e.preventDefault();
50+
51+
setIsSubmitting(true);
52+
53+
try {
54+
// Use the selected date or today
55+
56+
const response = await deleteJsonFromSecuredApi(
57+
`${import.meta.env.API_BASE_URL}/purchase/${purchaseId}`,
58+
getAccessTokenSilently,
59+
);
60+
61+
if (response.success) {
62+
addToast({
63+
title: t("success"),
64+
description: t("refund-processed-successfully"),
65+
variant: "solid",
66+
timeout: 5000,
67+
});
68+
69+
if (onSuccess) {
70+
onSuccess();
71+
}
72+
props.onClose?.();
73+
} else {
74+
addToast({
75+
title: t("error"),
76+
description: t("error-processing-refund"),
77+
variant: "solid",
78+
timeout: 5000,
79+
});
80+
}
81+
} catch (error) {
82+
// eslint-disable-next-line no-console
83+
console.error("Error processing refund:", error);
84+
addToast({
85+
title: t("error"),
86+
description: t("error-processing-refund"),
87+
variant: "solid",
88+
timeout: 5000,
89+
});
90+
} finally {
91+
setIsSubmitting(false);
92+
}
93+
};
94+
95+
return (
96+
<Modal
97+
{...props}
98+
aria-labelledby="refund-purchase-title"
99+
isDismissable={!isSubmitting}
100+
>
101+
<ModalContent>
102+
<ModalHeader>{t("return-item")}</ModalHeader>
103+
<ModalBody>
104+
<p>{t("are-you-sure-you-want-to-return-this-item")}</p>
105+
</ModalBody>
106+
<ModalFooter>
107+
<Form onSubmit={handleSubmit}>
108+
<Button color="primary" type="submit">
109+
{t("confirm")}
110+
</Button>
111+
<Button variant="light" onPress={props.onClose}>
112+
{t("cancel")}
113+
</Button>
114+
</Form>
115+
</ModalFooter>
116+
</ModalContent>
117+
</Modal>
118+
);
119+
}

client/src/locales/base/ar-SA.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,8 @@
154154
"click-to-see-the-screenshot": "انقر لرؤية لقطة الشاشة",
155155
"add-a-feedback-to-this-purchase": "أضف ملاحظات إلى هذا الشراء",
156156
"cancel-this-purchase-and-return-the-item": "إلغاء هذا الشراء وإرجاع العنصر",
157-
"return-this-item": "إرجاع هذا العنصر"
157+
"return-this-item": "إرجاع هذا العنصر",
158+
"are-you-sure-you-want-to-return-this-item": "هل أنت متأكد أنك تريد إرجاع هذا العنصر؟",
159+
"confirm": "يتأكد",
160+
"return-item": "العنصر الإرجاع"
158161
}

client/src/locales/base/en-US.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,8 @@
154154
"click-to-see-the-screenshot": "Click to see the screenshot",
155155
"return-this-item": "Return this item",
156156
"add-a-feedback-to-this-purchase": "Add a feedback to this purchase",
157-
"cancel-this-purchase-and-return-the-item": "Cancel this purchase and return the item"
157+
"cancel-this-purchase-and-return-the-item": "Cancel this purchase and return the item",
158+
"return-item": "Return item",
159+
"are-you-sure-you-want-to-return-this-item": "Are you sure you want to return this item ?",
160+
"confirm": "Confirm"
158161
}

client/src/locales/base/es-ES.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,8 @@
154154
"click-to-see-the-screenshot": "Haga clic para ver la captura de pantalla",
155155
"add-a-feedback-to-this-purchase": "Agregue un comentario a esta compra",
156156
"cancel-this-purchase-and-return-the-item": "Cancelar esta compra y devolver el artículo",
157-
"return-this-item": "Devuelve este artículo"
157+
"return-this-item": "Devuelve este artículo",
158+
"are-you-sure-you-want-to-return-this-item": "¿Estás seguro de que quieres devolver este artículo?",
159+
"confirm": "Confirmar",
160+
"return-item": "Artículo de devolución"
158161
}

client/src/locales/base/fr-FR.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,8 @@
154154
"click-to-see-the-screenshot": "Cliquez pour voir la capture d'écran",
155155
"add-a-feedback-to-this-purchase": "Ajouter une rétroaction à cet achat",
156156
"cancel-this-purchase-and-return-the-item": "Annuler cet achat et retourner l'article",
157-
"return-this-item": "Retourner cet article"
157+
"return-this-item": "Retourner cet article",
158+
"are-you-sure-you-want-to-return-this-item": "Êtes-vous sûr de vouloir retourner cet article?",
159+
"confirm": "Confirmer",
160+
"return-item": "Retourner un achat"
158161
}

client/src/locales/base/he-IL.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,8 @@
154154
"click-to-see-the-screenshot": "לחץ כדי לראות את צילום המסך",
155155
"add-a-feedback-to-this-purchase": "הוסף משוב לרכישה זו",
156156
"cancel-this-purchase-and-return-the-item": "בטל רכישה זו והחזיר את הפריט",
157-
"return-this-item": "להחזיר פריט זה"
157+
"return-this-item": "להחזיר פריט זה",
158+
"are-you-sure-you-want-to-return-this-item": "האם אתה בטוח שאתה רוצה להחזיר את הפריט הזה?",
159+
"confirm": "לְאַשֵׁר",
160+
"return-item": "פריט החזר"
158161
}

client/src/locales/base/zh-CN.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,11 @@
151151
"oauth-id-format-hint": "OAuth ID格式提示",
152152
"please-enter-valid-oauth-id": "请输入有效的OAuth ID",
153153
"api-server": "API服务器",
154-
"click-to-see-the-screenshot": "点击查看屏幕截图"
154+
"click-to-see-the-screenshot": "点击查看屏幕截图",
155+
"add-a-feedback-to-this-purchase": "在此购买中添加反馈",
156+
"are-you-sure-you-want-to-return-this-item": "您确定要退还此项目吗?",
157+
"cancel-this-purchase-and-return-the-item": "取消此购买并退货",
158+
"confirm": "确认",
159+
"return-item": "返回项目",
160+
"return-this-item": "返回此项目"
155161
}

client/src/pages/index.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import RefundPurchaseModal from "@/components/modals/refund-purchase-modal";
3636
import CreatePurchaseModal from "@/components/modals/create-purchase-modal";
3737
import { ScreenshotModal } from "@/components/modals/screenshot-modal";
3838
import ButtonAddFeedbackOrReturn from "@/components/button-add-feedback-or-return";
39+
import ReturnPurchaseModal from "@/components/modals/return-purchase";
3940

4041
/**
4142
* Main page of the application displaying purchase data in a tabular format
@@ -53,14 +54,15 @@ import ButtonAddFeedbackOrReturn from "@/components/button-add-feedback-or-retur
5354
export default function IndexPage() {
5455
const { t } = useTranslation();
5556
const { isAuthenticated } = useAuth0();
56-
const [createNewPurchase, setCreateNewPurchase] = useState(false);
57-
const [refundPurchases, setRefundPurchases] = useState(false);
5857
const [createFeedbackPurchase, setCreateFeedbackPurchase] = useState(false);
58+
const [createNewPurchase, setCreateNewPurchase] = useState(false);
5959
const [publishFeedbackPurchase, setPublishFeedbackPurchase] = useState(false);
60-
const [toggleAllPurchases, setToggleAllPurchases] = useState(false);
61-
const [refreshTrigger, setRefreshTrigger] = useState(0);
6260
const [purchase, setPurchase] = useState({ purchaseId: "", amount: 0 });
61+
const [refreshTrigger, setRefreshTrigger] = useState(0);
62+
const [refundPurchases, setRefundPurchases] = useState(false);
63+
const [returnPurchase, setReturnPurchase] = useState(false);
6364
const [screenshot, setScreenshot] = useState<string | null>(null);
65+
const [toggleAllPurchases, setToggleAllPurchases] = useState(false);
6466

6567
// Function to refresh the table
6668
const refreshTable = () => {
@@ -195,13 +197,17 @@ export default function IndexPage() {
195197

196198
/**
197199
* Handles opening the return modal for a specific purchase
200+
* Show a confirmation Modal to confirm the return
201+
* and then call the API to directly set the purchase as refunded
202+
* the refund amount is set to the purchase amount
203+
* the date is set to the current date
198204
*
199205
* @param {string} purchaseId - The ID of the purchase to return
200206
* @param {number} amount - The purchase amount
201207
*/
202208
const handleReturnItem = (purchaseId: string, amount: number) => {
203209
setPurchase({ purchaseId, amount });
204-
window.alert("This feature is not implemented yet.");
210+
setReturnPurchase(true);
205211
};
206212

207213
/**
@@ -391,6 +397,17 @@ export default function IndexPage() {
391397
onClose={() => setScreenshot(null)}
392398
/>
393399
)}
400+
401+
{/* Return Modal */}
402+
{returnPurchase && (
403+
<ReturnPurchaseModal
404+
children={undefined}
405+
isOpen={returnPurchase}
406+
purchaseId={purchase.purchaseId}
407+
onClose={() => setReturnPurchase(false)}
408+
onSuccess={refreshTable}
409+
/>
410+
)}
394411
</DefaultLayout>
395412
);
396413
}

0 commit comments

Comments
 (0)