diff --git a/.github/react-frontend-workflow.yml b/.github/workflows/fertiscan-frontend-workflows.yml similarity index 96% rename from .github/react-frontend-workflow.yml rename to .github/workflows/fertiscan-frontend-workflows.yml index eeac8f3d..e81413e1 100644 --- a/.github/react-frontend-workflow.yml +++ b/.github/workflows/fertiscan-frontend-workflows.yml @@ -1,4 +1,4 @@ -name: React frontend workflows +name: Fertiscan frontend workflows on: pull_request: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e37f2214 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM node:20.12.2-alpine AS build + +WORKDIR /code + +# Copy files +COPY ./src ./src +COPY ./public ./public +COPY package.json . +COPY package-lock.json . +COPY tsconfig.json . +COPY tsconfig.node.json . +COPY vite.config.ts . +COPY index.html . + +# Install npm at a specific version, dependencies, build, and run tests +RUN npm install --include=dev +RUN npm run build + +# Setup for production +FROM node:20.12.2-alpine AS runtime + +# Install serve globally +RUN npm install -g serve + +# Set working directory +WORKDIR /app + +# Copy build artifacts from the build stage +COPY --from=build /code/dist /app + +EXPOSE 3000 + +# Serve your app +ENTRYPOINT ["serve", "-s", "/app"] diff --git a/README.md b/README.md index 8b137891..e69de29b 100644 --- a/README.md +++ b/README.md @@ -1 +0,0 @@ - diff --git a/README.old.md b/README.old.md index bb156850..5b48a34f 100644 --- a/README.old.md +++ b/README.old.md @@ -1,15 +1,20 @@ # React + TypeScript + Vite -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +This template provides a minimal setup to get React working in Vite with HMR +and some ESLint rules. Currently, two official plugins are available: -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +- [@vitejs/plugin-react]( +https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) +uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) +uses [SWC](https://swc.rs/) for Fast Refresh ## Expanding the ESLint configuration -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +If you are developing a production application, we recommend updating the +configuration to enable type aware lint rules: - Configure the top-level `parserOptions` property like this: @@ -25,6 +30,10 @@ export default { }; ``` -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Replace `plugin:@typescript-eslint/recommended` to +`plugin:@typescript-eslint/recommended-type-checked` or +`plugin:@typescript-eslint/strict-type-checked` - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list +- Install [eslint-plugin-react]( +https://github.com/jsx-eslint/eslint-plugin-react) and add +`plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/package.json b/package.json index 126414be..b31f3ea7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "tsc && vite build", "lint": "eslint .", "lint:fix": "eslint --fix .", - "preview": "vite preview" + "preview": "vite preview", + "test": "echo \"No tests for now\"" }, "dependencies": { "@dts-stn/service-canada-design-system": "^1.67.3", diff --git a/public/vite.svg b/public/vite.svg index e7b8dfb1..ee9fadaf 100644 --- a/public/vite.svg +++ b/public/vite.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/App.tsx b/src/App.tsx index a72b9c43..c6b5d418 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import HomePage from "./Pages/HomePage/HomePage"; -import JsonPage from "./Pages/JsonPage/Json" +import JsonPage from "./Pages/JsonPage/Json"; import NoPage from "./Pages/NoPage/NoPage"; import "./App.css"; import Header from "./Components/Header/Header"; @@ -12,7 +12,7 @@ function App() { } /> - }/> + } /> } /> diff --git a/src/Components/DragDropFileInput/DragDropFileInput.css b/src/Components/DragDropFileInput/DragDropFileInput.css index 81f18e1d..2626a0f0 100644 --- a/src/Components/DragDropFileInput/DragDropFileInput.css +++ b/src/Components/DragDropFileInput/DragDropFileInput.css @@ -64,4 +64,4 @@ input[type="file"] { .drag-drop-container{ grid-area: a; -} \ No newline at end of file +} diff --git a/src/Components/DragDropFileInput/DragDropFileInput.tsx b/src/Components/DragDropFileInput/DragDropFileInput.tsx index ef462fc3..84d5070f 100644 --- a/src/Components/DragDropFileInput/DragDropFileInput.tsx +++ b/src/Components/DragDropFileInput/DragDropFileInput.tsx @@ -8,12 +8,12 @@ interface FileInputProps { const DragDropFileInput: React.FC = ({ sendChange, file }) => { const [dragActive, setDragActive] = useState(false); const fileInput = useRef(null); - useEffect(()=>{ - if(file==""){ + useEffect(() => { + if (file == "") { const input = fileInput!.current as HTMLInputElement; - input.value=""; + input.value = ""; } - },[file]) + }, [file]); const handleDrag = (event: React.DragEvent) => { event.preventDefault(); if (event.type === "dragover") { diff --git a/src/Components/FileList/FileElement/FileElement.css b/src/Components/FileList/FileElement/FileElement.css index b64b4935..3ac89ddd 100644 --- a/src/Components/FileList/FileElement/FileElement.css +++ b/src/Components/FileList/FileElement/FileElement.css @@ -75,4 +75,4 @@ .cross:after { transform: translate(-50%, -50%) rotate(-45deg); - } \ No newline at end of file + } diff --git a/src/Components/FileList/FileElement/FileElement.tsx b/src/Components/FileList/FileElement/FileElement.tsx index 54db4a1f..d01bebdb 100644 --- a/src/Components/FileList/FileElement/FileElement.tsx +++ b/src/Components/FileList/FileElement/FileElement.tsx @@ -1,48 +1,60 @@ import React, { useState, useEffect, useRef } from "react"; -import "./FileElement.css" +import "./FileElement.css"; interface FileElementProps { key: number; file: File; position: number; - onClick: (selected:boolean) => void; // Function to be called on click - onDelete: ()=>void; + onClick: (selected: boolean) => void; // Function to be called on click + onDelete: () => void; } -const FileElement: React.FC = ({ file, position, onClick, onDelete }) => { +const FileElement: React.FC = ({ + file, + position, + onClick, + onDelete, +}) => { const [fileUrl, setFileUrl] = useState(""); const fileCard = useRef(null); useEffect(() => { const reader = new FileReader(); - reader.onload = (e) => setFileUrl(e?.target?.result as string || ""); + reader.onload = (e) => setFileUrl((e?.target?.result as string) || ""); reader.readAsDataURL(file); }, [file]); const handleClick = (event: React.MouseEvent) => { event.preventDefault(); // Prevent default navigation const element = event.target as HTMLElement; - if(element.className !== ("cross")){ - document.querySelectorAll(".file-element").forEach(div=>(div.id!=fileCard.current?.id && div.classList.remove("selected"))) - if(fileCard.current?.classList.contains("selected")){ + if (element.className !== "cross") { + document + .querySelectorAll(".file-element") + .forEach( + (div) => + div.id != fileCard.current?.id && div.classList.remove("selected"), + ); + if (fileCard.current?.classList.contains("selected")) { onClick?.(false); fileCard.current?.classList.remove("selected"); - }else{ + } else { onClick?.(true); fileCard.current?.classList.add("selected"); } } }; - const deleteImage = (event: React.MouseEvent)=>{ - event.preventDefault() + const deleteImage = (event: React.MouseEvent) => { + event.preventDefault(); onDelete(); - } + }; return ( -
- {file.name} +
+ {file.name}

{file.name}

diff --git a/src/Components/FileList/FileList.css b/src/Components/FileList/FileList.css index f7fecb6d..bd05c158 100644 --- a/src/Components/FileList/FileList.css +++ b/src/Components/FileList/FileList.css @@ -56,4 +56,3 @@ .overlay.active { visibility: visible; /* Show overlay when an image is selected */ } - \ No newline at end of file diff --git a/src/Components/FileList/FileList.tsx b/src/Components/FileList/FileList.tsx index a1ae1f9c..8d76b123 100644 --- a/src/Components/FileList/FileList.tsx +++ b/src/Components/FileList/FileList.tsx @@ -1,43 +1,48 @@ -import React, { useState, useRef } from "react"; +import React, { useState } from "react"; import "./FileList.css"; import FileElement from "./FileElement/FileElement"; interface FileListProps { files: File[]; - onSelectedChange: (file:File|null)=>void; - propagateDelete: (file:File, wasShown:boolean)=>void; + onSelectedChange: (file: File | null) => void; + propagateDelete: (file: File, wasShown: boolean) => void; } -const FileList: React.FC = ({ files, onSelectedChange, propagateDelete}) => { +const FileList: React.FC = ({ + files, + onSelectedChange, + propagateDelete, +}) => { const [selectedFile, setSelectedFile] = useState(null); - const handleSelectFile = (file: File|null, index: number) => { + const handleSelectFile = (file: File | null) => { setSelectedFile(file); - onSelectedChange(file) + onSelectedChange(file); }; - const handleDelete = (file: File, index:number)=>{ - if(selectedFile===file){ - setSelectedFile(null) - propagateDelete(file, false) - }else{ - propagateDelete(file, files[files.length-1]===file); + const handleDelete = (file: File) => { + if (selectedFile === file) { + setSelectedFile(null); + propagateDelete(file, false); + } else { + propagateDelete(file, files[files.length - 1] === file); } - - } + }; return (
No element to show
- {[...files].map((file: File, index: number, array: File[]) => ( + {[...files].map((file: File, index: number) => ( selected ? handleSelectFile(file, index): handleSelectFile(null,-1)} - onDelete={()=>handleDelete(file, index)} + onClick={(selected) => + selected ? handleSelectFile(file) : handleSelectFile(null) + } + onDelete={() => handleDelete(file)} /> ))}
diff --git a/src/Pages/HomePage/HomePage.css b/src/Pages/HomePage/HomePage.css index 4e9cb87c..24e041d1 100644 --- a/src/Pages/HomePage/HomePage.css +++ b/src/Pages/HomePage/HomePage.css @@ -29,4 +29,4 @@ .submit-btn{ margin-top:50px; grid-area: c; -} \ No newline at end of file +} diff --git a/src/Pages/HomePage/HomePage.tsx b/src/Pages/HomePage/HomePage.tsx index 240cdeeb..6600123a 100644 --- a/src/Pages/HomePage/HomePage.tsx +++ b/src/Pages/HomePage/HomePage.tsx @@ -1,4 +1,4 @@ -import React, { useState, ChangeEvent } from "react"; +import { useState } from "react"; import "./HomePage.css"; import DragDropFileInput from "../../Components/DragDropFileInput/DragDropFileInput"; import FileList from "../../Components/FileList/FileList"; @@ -7,51 +7,7 @@ import { useNavigate } from "react-router-dom"; function HomePage() { const [files, setFiles] = useState([]); -const [form, setForm] = useState({ - company_name: "", - company_address: "", - company_website: "", - company_phone_number: "", - manufacturer_name: "", - manufacturer_address: "", - manufacturer_website: "", - manufacturer_phone_number: "", - fertiliser_name: "", - fertiliser_registration_number: "", - fertiliser_lot_number: "", - fertiliser_npk: "", - fertiliser_precautionary_fr: "", - fertiliser_precautionary_en: "", - fertiliser_instructions_fr: "", - fertiliser_instructions_en: "", - fertiliser_ingredients_fr: "", - fertiliser_ingredients_en: "", - fertiliser_specifications_fr: "", - fertiliser_specifications_en: "", - fertiliser_cautions_fr: "", - fertiliser_cautions_en: "", - fertiliser_recommendation_fr: "", - fertiliser_recommendation_en: "", - fertiliser_first_aid_fr: "", - fertiliser_first_aid_en: "", - fertiliser_warranty_fr: "", - fertiliser_warranty_en: "", - fertiliser_danger_fr: "", - fertiliser_danger_en: "", - fertiliser_guaranteed_analysis: "", - nutrient_in_guaranteed_analysis: "", - percentage_in_guaranteed_analysis: "", - fertiliser_weight: "", - fertiliser_density: "", - fertiliser_volume: "", - fertiliser_label_all_other_text_fr: "", - all_other_text_fr_1: "", - all_other_text_fr_2: "", - fertiliser_label_all_other_text_en: "", - all_other_text_en_1: "", - all_other_text_en_2: "", - }); - const [toShow, setShow] = useState("") + const [toShow, setShow] = useState(""); const reader = new FileReader(); reader.onload = (e) => { @@ -59,48 +15,53 @@ const [form, setForm] = useState({ setShow(newFile); }; - - const handleChange = (e: ChangeEvent) => { + /** + const handleChange = (e: React.ChangeEvent) => { const field = e.target! as HTMLInputElement; setForm({ ...form, [field.name]: field.value }); }; - + */ const handlePhotoChange = (newFiles: File[]) => { if (newFiles!.length > 0) { setFiles([...files, ...newFiles]); reader.readAsDataURL(newFiles[0]!); - }else{ + } else { setShow(""); } - }; - const handleSelectedChange = (selection: File|null)=>{ - if(selection){ + const handleSelectedChange = (selection: File | null) => { + if (selection) { reader.readAsDataURL(selection); - }else{ + } else { setShow(""); } - } + }; const navigate = useNavigate(); - const Submit = ()=>{ - navigate('/Json',{state:{data:files}}); - } + const Submit = () => { + navigate("/Json", { state: { data: files } }); + }; - const handleDeletion = (toDelete:File, wasShown:boolean)=>{ - setFiles(files.filter(file=>file!==toDelete)) - if(wasShown){ - setShow("") + const handleDeletion = (toDelete: File, wasShown: boolean) => { + setFiles(files.filter((file) => file !== toDelete)); + if (wasShown) { + setShow(""); } - } + }; return (
- - + +
); diff --git a/src/Pages/JsonPage/Json.css b/src/Pages/JsonPage/Json.css index 97d4184b..eab9deb0 100644 --- a/src/Pages/JsonPage/Json.css +++ b/src/Pages/JsonPage/Json.css @@ -21,4 +21,4 @@ pre{ text-align: left; -} \ No newline at end of file +} diff --git a/src/Pages/JsonPage/Json.tsx b/src/Pages/JsonPage/Json.tsx index f6fdb360..43b0e304 100644 --- a/src/Pages/JsonPage/Json.tsx +++ b/src/Pages/JsonPage/Json.tsx @@ -1,67 +1,71 @@ import { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; -import "./Json.css" +import "./Json.css"; +function JsonPage() { + const [loading, setLoading] = useState(true); + const [form, setForm] = useState({}); + const [fetchError, setError] = useState(null); + const location = useLocation(); + const files: File[] = location.state.data; + const [uploadStarted, startUpload] = useState(false); -function JsonPage(){ - const [loading, setLoading] = useState(true) - const [form, setForm]=useState({}) - const [fetchError, setError]=useState(null) - const location = useLocation(); - const files:File[] = location.state.data; - const [uploadStarted, startUpload] = useState(false); - - - const api_url = "https://shiny-goggles-75q6p5xj4wwfp6gg-5000.app.github.dev/" - const upload_all = async ()=>{ - const res = [] - for(let i=0; i { + const res = []; + for (let i = 0; i < files.length; i++) { + const formData = new FormData(); + formData.append("file", files[i]); + res.push( + await fetch(api_url + "/upload", { + method: "POST", + headers: { + // if needed, add headers here + }, + body: formData, + }), + ); } - useEffect(()=>{ - if(!uploadStarted){ - startUpload(true); - upload_all().then(()=>{ - fetch(api_url+"/analyze",{ - method:'GET', - headers:{ - - } - }).then((response:Response)=>{ - response.json().then((data)=>{ - setForm(data) - setLoading(false) - }).catch(e=>{ - setLoading(false) - setError(e) - console.log(e) - }) - }) + return res; + }; + useEffect(() => { + if (!uploadStarted) { + startUpload(true); + upload_all().then(() => { + fetch(api_url + "/analyze", { + method: "GET", + headers: {}, + }).then((response: Response) => { + response + .json() + .then((data) => { + setForm(data); + setLoading(false); }) - } - },[]) - return ( -
-
-
-

- Votre fichier est en cours d'analyse - Your file is being analyzed -

-
-
{(fetchError ? 

{fetchError.message}

: JSON.stringify(form, null, 2))}
-
- ); + .catch((e) => { + setLoading(false); + setError(e); + console.log(e); + }); + }); + }); + } + }, []); + return ( +
+
+
+

Votre fichier est en cours d'analyse Your file is being analyzed

+
+
+        {fetchError ? (
+          

{fetchError.message}

+ ) : ( + JSON.stringify(form, null, 2) + )} +
+
+ ); } -export default JsonPage \ No newline at end of file +export default JsonPage; diff --git a/src/assets/6.svg b/src/assets/6.svg index 971841cb..de98ddc9 100644 --- a/src/assets/6.svg +++ b/src/assets/6.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/assets/react.svg b/src/assets/react.svg index 6c87de9b..8e0e0f15 100644 --- a/src/assets/react.svg +++ b/src/assets/react.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/index.css b/src/index.css index aef1e4c3..36278edd 100644 --- a/src/index.css +++ b/src/index.css @@ -82,4 +82,4 @@ button:focus-visible { -moz-user-select: none; -ms-user-select: none; user-select: none; -} \ No newline at end of file +}