From 89aa3973beee554fbaeb454c9812ca0235dc9ac3 Mon Sep 17 00:00:00 2001 From: Adele Strysse Date: Sun, 5 Oct 2025 15:45:49 +0200 Subject: [PATCH 1/4] fix: fix validation for input fields fix validation for input fields for ReceiptPage --- src/pages/ReceiptPage.tsx | 58 ++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/src/pages/ReceiptPage.tsx b/src/pages/ReceiptPage.tsx index 0054666..43b50d8 100644 --- a/src/pages/ReceiptPage.tsx +++ b/src/pages/ReceiptPage.tsx @@ -78,6 +78,52 @@ const ReceiptPage = () => { const submitform = async () => { + + // Validate card number length to be exactly 16 numbers + if (!usedOnlineCard) { + const accountNumber = formdata.account_number || ""; + const isValidAccountNumber = /^\d{11}$/.test(accountNumber); + if (!isValidAccountNumber) { + alert("Kontonummer må være 11 sifre"); + return; + } + } + + if (usedOnlineCard) { + const cardNumber = formdata.card_number || ""; + const isValidCardInfo = cardNumber.length > 0; + if (!isValidCardInfo) { + alert("Vennligst skriv inn kortinformasjon"); + return; + } + } + + // Validate beløp + if (isNaN(formdata.amount) || formdata.amount === null || formdata.amount === undefined) { + alert("Beløp må være et tall"); + return; + } + + if (formdata.amount < 0) { + alert("Beløp kan ikke være negativt"); + return; + } + + if (formdata.name.length <= 0) { + alert("Vennligst skriv annledning"); + return; + } + + if (!formdata.committee_id) { + alert("Velg en ansvarlig enhet"); + return; + } + + if (attachments.length === 0) { + alert("Last opp minst én kviterring/ett vedlegg"); + return; + } + setDisableSubmit(true); const paymentInfo: PaymentInformation = { usedOnlineCard: usedOnlineCard, @@ -100,14 +146,12 @@ const ReceiptPage = () => { }; try { - await submitReceipt(body); - alert("Kvittering sendt inn!"); - // TODO: Fix with popup success message in home something - navigate("/?receiptsubmittedsuccess=1"); + await submitReceipt(body); + alert("Kvittering sendt inn!"); + // TODO: Fix with popup success message in home something + navigate("/?receiptsubmittedsuccess=1"); } catch (e) { - alert("Noe gikk galt, prøv igjen senere"); - } setDisableSubmit(false); @@ -267,7 +311,7 @@ const ReceiptPage = () => { }); }} > - + {data && data.length ? data.map((committee: any) => { return ( From f3ffe7d61d9dbd3e8b7c05d1f7633306c3058807 Mon Sep 17 00:00:00 2001 From: Adele Strysse Date: Sun, 5 Oct 2025 16:38:01 +0200 Subject: [PATCH 2/4] feat: add buttons for card info input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add buttons to card info input field to choose between card number and committee name to easier validate, and updated logic for "beløp" to handle wrong inputs --- src/pages/ReceiptPage.tsx | 83 ++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/src/pages/ReceiptPage.tsx b/src/pages/ReceiptPage.tsx index 43b50d8..df5552c 100644 --- a/src/pages/ReceiptPage.tsx +++ b/src/pages/ReceiptPage.tsx @@ -48,8 +48,10 @@ interface ReceiptRequestBody { const ReceiptPage = () => { const navigate = useNavigate(); + const [cardInputType, setCardInputType] = useState<"number" | "committee">("number"); const [usedOnlineCard, setUsedOnlineCard] = useState(false); const [disableSubmit, setDisableSubmit] = useState(false); + const [amountInput, setAmountInput] = useState(""); const [attachments, setAttachments] = useState([]); @@ -91,15 +93,27 @@ const ReceiptPage = () => { if (usedOnlineCard) { const cardNumber = formdata.card_number || ""; - const isValidCardInfo = cardNumber.length > 0; - if (!isValidCardInfo) { - alert("Vennligst skriv inn kortinformasjon"); - return; + if (cardInputType === "number") { + if (!(/^\d{16}$/.test(cardNumber))) { + alert("Kortnummer må være 16 sifre"); + return; + } + } + if (cardInputType === "committee") { + if (cardNumber.trim() === "") { + alert("Vennligst skriv inn navn på komité"); + return; + } } } - // Validate beløp - if (isNaN(formdata.amount) || formdata.amount === null || formdata.amount === undefined) { + // Validate beløp + if (!amountInput) { + alert("Beløp må fylles inn"); + return; + } + + if (isNaN(formdata.amount) || amountInput === null || amountInput === undefined) { alert("Beløp må være et tall"); return; } @@ -124,6 +138,9 @@ const ReceiptPage = () => { return; } + const numericAmount = parseFloat(amountInput); + const updatedFormData = { ...formdata, amount: numericAmount }; + setDisableSubmit(true); const paymentInfo: PaymentInformation = { usedOnlineCard: usedOnlineCard, @@ -138,7 +155,7 @@ const ReceiptPage = () => { id: 0, }; const body: ReceiptRequestBody = { - receipt: formdata, + receipt: updatedFormData, attachments: await Promise.all( [...attachments].map(async (file) => await fileToBase64(file)), ), @@ -215,14 +232,11 @@ const ReceiptPage = () => {

Beløp

{ - setFormdata({ - ...formdata, - amount: parseInt(e.target.value), - }); - }} + value={amountInput} + onChange={(e) => setAmountInput(e.target.value)} >
@@ -265,26 +279,49 @@ const ReceiptPage = () => {
-

Kortinformasjon

+

Kortinformasjon

+
+ + + +
{ setFormdata({ ...formdata, card_number: e.target.value }); }} - > + />
-
+

Beløp

{ - setFormdata({ - ...formdata, - amount: parseInt(e.target.value), - }); - }} + onChange={(e) => setAmountInput(e.target.value)} >
From d76ad42e7552c0ed0f09a094c4f31f56367066c1 Mon Sep 17 00:00:00 2001 From: Adele Xiao Yuan Strysse Date: Sun, 5 Oct 2025 21:09:08 +0200 Subject: [PATCH 3/4] fix: update validation (not using alerts) update validation to not use alerts but a red description of error below input field --- src/pages/ReceiptPage.tsx | 200 +++++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 77 deletions(-) diff --git a/src/pages/ReceiptPage.tsx b/src/pages/ReceiptPage.tsx index df5552c..03f8bc9 100644 --- a/src/pages/ReceiptPage.tsx +++ b/src/pages/ReceiptPage.tsx @@ -48,12 +48,14 @@ interface ReceiptRequestBody { const ReceiptPage = () => { const navigate = useNavigate(); - const [cardInputType, setCardInputType] = useState<"number" | "committee">("number"); const [usedOnlineCard, setUsedOnlineCard] = useState(false); const [disableSubmit, setDisableSubmit] = useState(false); const [amountInput, setAmountInput] = useState(""); + const [accountNumber, setAccountNumber] = useState(""); + const [cardNumber, setCardNumber] = useState(""); const [attachments, setAttachments] = useState([]); + const allowedTypes = ["application/pdf", "image/png", "image/jpeg", "image/jpg"]; const auth = useAuth(); const { user } = auth; @@ -65,7 +67,16 @@ const ReceiptPage = () => { }); const onFileChange = async (files: File[]) => { - setAttachments([...files]); + const validFiles = files.filter((file) => allowedTypes.includes(file.type)); + const invalidFiles = files.filter((file) => !allowedTypes.includes(file.type)); + + if (invalidFiles.length > 0) { + alert("Bare PDF eller bildefiler (JPG, PNG, JPEG) er tillatt. Ugyldige filer ble ignorert."); + } + + if (validFiles.length > 0) { + setAttachments(validFiles); + } }; const [formdata, setFormdata]: [FormData, any] = useState({ @@ -78,65 +89,86 @@ const ReceiptPage = () => { account_number: "", }); + const [errors, setErrors] = useState({ + amount: "", + account_number: "", + card_number: "", + name: "", + committee_id: "", + attachments: "", + }); - const submitform = async () => { + const validateForm = () => { + const newErrors: typeof errors = { + amount: "", + account_number: "", + card_number: "", + name: "", + committee_id: "", + attachments: "", + }; + + if (!amountInput) { + newErrors.amount = "Beløp må fylles inn"; + } else { + const num = Number(amountInput); + if (num <= 0) { + newErrors.amount = "Beløp må være større enn 0"; + } else if (num > 100000) { + newErrors.amount = "Beløp kan ikke overstige 100 000"; + } + } - // Validate card number length to be exactly 16 numbers if (!usedOnlineCard) { - const accountNumber = formdata.account_number || ""; - const isValidAccountNumber = /^\d{11}$/.test(accountNumber); - if (!isValidAccountNumber) { - alert("Kontonummer må være 11 sifre"); - return; + if (!/^\d{11}$/.test(formdata.account_number || "")) { + newErrors.account_number = "Kontonummer må være 11 sifre"; } } if (usedOnlineCard) { const cardNumber = formdata.card_number || ""; - if (cardInputType === "number") { - if (!(/^\d{16}$/.test(cardNumber))) { - alert("Kortnummer må være 16 sifre"); - return; - } - } - if (cardInputType === "committee") { - if (cardNumber.trim() === "") { - alert("Vennligst skriv inn navn på komité"); - return; - } + if (!/^\d{16}$/.test(cardNumber)) { + newErrors.card_number = "Kortnummer må være 16 sifre"; } } - // Validate beløp - if (!amountInput) { - alert("Beløp må fylles inn"); - return; + if (formdata.name.trim() === "") { + newErrors.name = "Vennligst skriv anledning"; } - if (isNaN(formdata.amount) || amountInput === null || amountInput === undefined) { - alert("Beløp må være et tall"); - return; + if (!formdata.committee_id) { + newErrors.committee_id = "Velg en ansvarlig enhet"; } - if (formdata.amount < 0) { - alert("Beløp kan ikke være negativt"); - return; + if (attachments.length === 0) { + newErrors.attachments = "Last opp minst én kvittering/vedlegg"; } - if (formdata.name.length <= 0) { - alert("Vennligst skriv annledning"); - return; - } + setErrors(newErrors); - if (!formdata.committee_id) { - alert("Velg en ansvarlig enhet"); - return; - } + return Object.values(newErrors).every((e) => e === ""); + }; - if (attachments.length === 0) { - alert("Last opp minst én kviterring/ett vedlegg"); - return; - } + const formatAccountNumber = (value: string) => { + const digits = value.replace(/\D/g, ""); + const parts: string[] = []; + + if (digits.length > 0) parts.push(digits.substring(0, 4)); + if (digits.length > 4) parts.push(digits.substring(4, 6)); + if (digits.length > 6) parts.push(digits.substring(6, 11)); + + return parts.join(" "); + }; + + const formatCardNumber = (value: string) => { + return value + .replace(/\D/g, "") + .replace(/(.{4})/g, "$1 ") + .trim(); + }; + + const submitform = async () => { + if (!validateForm()) return; const numericAmount = parseFloat(amountInput); const updatedFormData = { ...formdata, amount: numericAmount }; @@ -222,12 +254,20 @@ const ReceiptPage = () => {

Kontonummer

{ - setFormdata({ ...formdata, account_number: e.target.value }); + const raw = e.target.value.replace(/\D/g, ""); + setAccountNumber(raw); + setFormdata({ ...formdata, account_number: raw }); }} > +

+ {errors.account_number || " "} +

Beløp

@@ -235,9 +275,12 @@ const ReceiptPage = () => { type="number" placeholder={"530"} className="text-black p-3 rounded w-full" - value={amountInput} + max={100000} onChange={(e) => setAmountInput(e.target.value)} > +

+ {errors.amount || " "} +

@@ -250,6 +293,9 @@ const ReceiptPage = () => { setFormdata({ ...formdata, name: e.target.value }); }} > +

+ {errors.name || " "} +

Ansvarlig enhet

@@ -273,56 +319,44 @@ const ReceiptPage = () => { }) : null} +

+ {errors.committee_id || " "} +

-

Kortinformasjon

-
- - - -
+

Kortnummer

{ - setFormdata({ ...formdata, card_number: e.target.value }); + const raw = e.target.value.replace(/\D/g, ""); + setCardNumber(raw); + setFormdata({ ...formdata, card_number: raw }); }} /> +

+ {errors.card_number || " "} +

-
+

Beløp

setAmountInput(e.target.value)} > +

+ {errors.amount || " "} +

@@ -335,6 +369,9 @@ const ReceiptPage = () => { setFormdata({ ...formdata, name: e.target.value }); }} > +

+ {errors.name || " "} +

Ansvarlig enhet

@@ -359,6 +396,9 @@ const ReceiptPage = () => { }) : null} +

+ {errors.committee_id || " "} +

@@ -377,7 +417,13 @@ const ReceiptPage = () => {

Vedlegg

- + +

+ {errors.attachments || " "} +

From e9122e048b8c58c0c160e876a719ff8cd697ad4f Mon Sep 17 00:00:00 2001 From: Adele Xiao Yuan Strysse Date: Sun, 5 Oct 2025 21:10:10 +0200 Subject: [PATCH 4/4] fix: fix file upload fix file upload to handle invalid file formats and reject them --- src/components/form/FileUpload.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/form/FileUpload.tsx b/src/components/form/FileUpload.tsx index f6c79d7..5f1c531 100644 --- a/src/components/form/FileUpload.tsx +++ b/src/components/form/FileUpload.tsx @@ -1,22 +1,22 @@ import React, { useState, ChangeEvent, DragEvent } from "react"; interface FileUploadProps { + files: File[]; onFileChange: (files: File[]) => void; + multiple?: boolean; } -const FileUpload = (props: FileUploadProps) => { - const [files, setFiles] = useState([]); - +const FileUpload = ({ files, onFileChange, multiple = true }: FileUploadProps) => { const handleFileChange = (event: ChangeEvent) => { const newFiles = Array.from(event.target.files || []); - props.onFileChange([...files, ...newFiles]); - setFiles((prevFiles) => [...prevFiles, ...newFiles]); + onFileChange([...files, ...newFiles]); + event.target.value = ""; }; const handleDrop = (event: DragEvent) => { event.preventDefault(); const newFiles = Array.from(event.dataTransfer.files); - setFiles((prevFiles) => [...prevFiles, ...newFiles]); + onFileChange([...files, ...newFiles]); }; const handleDragOver = (event: DragEvent) => { @@ -24,8 +24,7 @@ const FileUpload = (props: FileUploadProps) => { }; const removeFile = (index: number) => { - props.onFileChange(files.filter((_, i) => i !== index)); - setFiles((prevFiles) => prevFiles.filter((_, i) => i !== index)); + onFileChange(files.filter((_, i) => i !== index)); }; return (