From 3a61b72642d6deb87f779acebb91767f54894ca8 Mon Sep 17 00:00:00 2001 From: RobinB27 Date: Sat, 15 Nov 2025 22:41:26 +0100 Subject: [PATCH] Task 3: React Frontend --- task_3_react/.gitignore | 25 ++++ task_3_react/.gitlab-ci.yml | 73 +++++++++ task_3_react/Dockerfile | 8 + task_3_react/README.md | 29 ++++ task_3_react/helm/.helmignore | 23 +++ task_3_react/helm/Chart.yaml | 6 + task_3_react/helm/charts/.gitkeep | 0 task_3_react/helm/files/.gitkeep | 0 task_3_react/helm/templates/.gitkeep | 0 task_3_react/helm/templates/deployment.yaml | 56 +++++++ task_3_react/helm/templates/ingress.yaml | 24 +++ task_3_react/helm/templates/service.yaml | 10 ++ task_3_react/helm/values.yaml | 10 ++ task_3_react/index.html | 12 ++ task_3_react/package.json | 35 +++++ task_3_react/src/client/App.css | 138 ++++++++++++++++++ task_3_react/src/client/App.tsx | 47 ++++++ task_3_react/src/client/Layout.css | 29 ++++ task_3_react/src/client/Layout.tsx | 21 +++ .../src/client/components/CSVCard.tsx | 23 +++ .../src/client/components/DataTable.tsx | 84 +++++++++++ .../src/client/components/InfoCard.tsx | 35 +++++ task_3_react/src/client/csv.ts | 30 ++++ task_3_react/src/client/index.css | 20 +++ task_3_react/src/client/main.tsx | 13 ++ task_3_react/src/client/types.d.ts | 11 ++ task_3_react/src/client/vite-env.d.ts | 1 + task_3_react/src/server/main.ts | 17 +++ task_3_react/tsconfig.json | 33 +++++ task_3_react/vite.config.ts | 7 + 30 files changed, 820 insertions(+) create mode 100644 task_3_react/.gitignore create mode 100644 task_3_react/.gitlab-ci.yml create mode 100644 task_3_react/Dockerfile create mode 100644 task_3_react/README.md create mode 100644 task_3_react/helm/.helmignore create mode 100644 task_3_react/helm/Chart.yaml create mode 100644 task_3_react/helm/charts/.gitkeep create mode 100644 task_3_react/helm/files/.gitkeep create mode 100644 task_3_react/helm/templates/.gitkeep create mode 100644 task_3_react/helm/templates/deployment.yaml create mode 100644 task_3_react/helm/templates/ingress.yaml create mode 100644 task_3_react/helm/templates/service.yaml create mode 100644 task_3_react/helm/values.yaml create mode 100644 task_3_react/index.html create mode 100644 task_3_react/package.json create mode 100644 task_3_react/src/client/App.css create mode 100644 task_3_react/src/client/App.tsx create mode 100644 task_3_react/src/client/Layout.css create mode 100644 task_3_react/src/client/Layout.tsx create mode 100644 task_3_react/src/client/components/CSVCard.tsx create mode 100644 task_3_react/src/client/components/DataTable.tsx create mode 100644 task_3_react/src/client/components/InfoCard.tsx create mode 100644 task_3_react/src/client/csv.ts create mode 100644 task_3_react/src/client/index.css create mode 100644 task_3_react/src/client/main.tsx create mode 100644 task_3_react/src/client/types.d.ts create mode 100644 task_3_react/src/client/vite-env.d.ts create mode 100644 task_3_react/src/server/main.ts create mode 100644 task_3_react/tsconfig.json create mode 100644 task_3_react/vite.config.ts diff --git a/task_3_react/.gitignore b/task_3_react/.gitignore new file mode 100644 index 0000000..513496c --- /dev/null +++ b/task_3_react/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +public + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/task_3_react/.gitlab-ci.yml b/task_3_react/.gitlab-ci.yml new file mode 100644 index 0000000..db9fb6b --- /dev/null +++ b/task_3_react/.gitlab-ci.yml @@ -0,0 +1,73 @@ +stages: + - lint + - build + - deploy + +workflow: + rules: + - if: $CI_COMMIT_BRANCH =~ /^(master|main)$/ + - if: $FORCE_DEPLOY + when: always + - when: never + +variables: + IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH + BASE_URI: "a3.webdev-25.ivia.isginf.ch" + FORCE_DEPLOY: + value: "false" + description: "Force deploy the application" + options: + - "true" + - "false" + +default: + before_script: + - IMAGE_NAME=$(echo $IMAGE_NAME | tr '[:upper:]' '[:lower:]') + - URI_NAME=$(echo $CI_PROJECT_NAME | tr '[:upper:]' '[:lower:]' | tr _ -) + +lint_helm: + stage: lint + image: matthiasgabathuler/my-runner:ubuntu-20.04 + script: + - >- + helm lint ${CI_PROJECT_DIR}/helm + --set image.name=${IMAGE_NAME} + --set image.tag=${CI_COMMIT_REF_NAME} + --set build.job_id=${CI_JOB_ID} + --set build.commit=${CI_COMMIT_SHA} + +build_webapp: + stage: build + rules: + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + script: + - echo "${DOCKER_REGISTRY_AUTH}" > /kaniko/.docker/config.json + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/Dockerfile" + --destination "${IMAGE_NAME}-webapp:latest" + +deploy_app: + stage: deploy + rules: + image: + name: alpine/helm:3.11.1 + entrypoint: [""] + script: + - >- + helm --namespace $K8S_NAMESPACE + --kube-context $K8S_CONTEXT + upgrade a3-$(echo ${CI_PROJECT_NAME} | tr _ -) ${CI_PROJECT_DIR}/helm + --install + --history-max 5 + --set image.host=${CI_REGISTRY} + --set image.name=${IMAGE_NAME} + --set image.tag=latest + --set url.hostname.uri=${URI_NAME} + --set url.hostname.base_uri=${BASE_URI} + --set build.job_id=${CI_JOB_ID} + --set build.commit=${CI_COMMIT_SHA} + - >- + echo "webapp URL: http://${URI_NAME}.${BASE_URI}" \ No newline at end of file diff --git a/task_3_react/Dockerfile b/task_3_react/Dockerfile new file mode 100644 index 0000000..711677a --- /dev/null +++ b/task_3_react/Dockerfile @@ -0,0 +1,8 @@ +FROM node +WORKDIR /app +COPY package.json . +RUN npm i +COPY . . +RUN npm run build +EXPOSE 5173 +CMD ["npm", "run", "start"] diff --git a/task_3_react/README.md b/task_3_react/README.md new file mode 100644 index 0000000..d590cad --- /dev/null +++ b/task_3_react/README.md @@ -0,0 +1,29 @@ +# Local Development + +Only change files inside the `src` directory. + +## Client side + +All client side files are located in the `src/client` directory. + +## Server side + +All server side files are located in the `src/server` directory. + +# Local Testing + +## run container for local testing + +```bash +docker build -t my-webapp . + +docker run -it --rm -p 5173:5173 my-webapp +``` +Open a browser and connect to http://localhost:5173 + +## run bash in interactive container +```bash +docker build -t my-webapp src/. + +docker run -it --rm -p 5173:5173 my-webapp bash +``` \ No newline at end of file diff --git a/task_3_react/helm/.helmignore b/task_3_react/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/task_3_react/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/task_3_react/helm/Chart.yaml b/task_3_react/helm/Chart.yaml new file mode 100644 index 0000000..5af4328 --- /dev/null +++ b/task_3_react/helm/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: a3-group-g3-rbacher-jahutz #name of the app, this should be the project name, spaces should be substituted with - +description: My Example App #description for the app +type: application +version: 0.0.1 #for versioning of the helm chart, increase it when new release is built/deployed +appVersion: "0.0.1" \ No newline at end of file diff --git a/task_3_react/helm/charts/.gitkeep b/task_3_react/helm/charts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/task_3_react/helm/files/.gitkeep b/task_3_react/helm/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/task_3_react/helm/templates/.gitkeep b/task_3_react/helm/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/task_3_react/helm/templates/deployment.yaml b/task_3_react/helm/templates/deployment.yaml new file mode 100644 index 0000000..06838e0 --- /dev/null +++ b/task_3_react/helm/templates/deployment.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-webapp +spec: + selector: + matchLabels: + app: {{ .Release.Name }}-webapp + replicas: 1 + template: + metadata: + labels: + app: {{ .Release.Name }}-webapp + annotations: + rollme: {{ randAlphaNum 5 | quote }} + spec: + imagePullSecrets: + - name: regcred + containers: + - name: {{ .Release.Name }}-webapp + image: {{ .Values.image.name }}-webapp:{{ .Values.image.tag }} + imagePullPolicy: Always + ports: + - containerPort: 5173 + resources: + limits: + memory: "250M" + requests: + memory: "100M" + commnand: ["npm", "run", "dev"] + volumeMounts: + - name: config-volume + mountPath: /app/vite.config.js + subPath: vite.config.js + volumes: + - name: config-volume + configMap: + name: {{ .Release.Name }}-webapp-config +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-webapp-config +data: + vite.config.js: | + export default { + server: { + host: '0.0.0.0', + port: 5173, + allowedHosts: true, + watch: { + usePolling: true, + interval: 1000, + }, + }, + } \ No newline at end of file diff --git a/task_3_react/helm/templates/ingress.yaml b/task_3_react/helm/templates/ingress.yaml new file mode 100644 index 0000000..c5e2480 --- /dev/null +++ b/task_3_react/helm/templates/ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-ingress + annotations: + nginx.ingress.kubernetes.io/enable-cors: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: nginx + tls: + - hosts: + - {{ .Values.url.hostname.uri }}.{{ .Values.url.hostname.base_uri }} + secretName: {{ .Release.Name }}-tls + rules: + - host: {{ .Values.url.hostname.uri }}.{{ .Values.url.hostname.base_uri }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-webapp + port: + number: 80 \ No newline at end of file diff --git a/task_3_react/helm/templates/service.yaml b/task_3_react/helm/templates/service.yaml new file mode 100644 index 0000000..10e8164 --- /dev/null +++ b/task_3_react/helm/templates/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-webapp +spec: + ports: + - port: 80 + targetPort: 5173 + selector: + app: {{ .Release.Name }}-webapp diff --git a/task_3_react/helm/values.yaml b/task_3_react/helm/values.yaml new file mode 100644 index 0000000..d752e2b --- /dev/null +++ b/task_3_react/helm/values.yaml @@ -0,0 +1,10 @@ +image: + name: "" + tag: "" +build: + job_id: "" + commit: "" +url: + hostname: + base_uri: "webdev-25.ivia.isginf.ch" + uri: "" \ No newline at end of file diff --git a/task_3_react/index.html b/task_3_react/index.html new file mode 100644 index 0000000..fe60a05 --- /dev/null +++ b/task_3_react/index.html @@ -0,0 +1,12 @@ + + + + + + Open data explorer + + +
+ + + diff --git a/task_3_react/package.json b/task_3_react/package.json new file mode 100644 index 0000000..952e573 --- /dev/null +++ b/task_3_react/package.json @@ -0,0 +1,35 @@ +{ + "name": "vite-react-typescript-starter", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "nodemon src/server/main.ts -w src/server", + "build": "vite build", + "start": "NODE_ENV=production ts-node --transpile-only src/server/main.ts", + "format": "prettier . --write", + "tsc": "tsc" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.6.0", + "@picocss/pico": "^2.0.6", + "express": "^4.21.1", + "json-2-csv": "^5.5.6", + "multer": "^1.4.5-lts.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3", + "vite-express": "*" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/multer": "^1.4.12", + "@types/node": "^22.7.6", + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.2", + "nodemon": "^3.1.7", + "prettier": "^3.3.3", + "vite": "^5.4.9" + } +} diff --git a/task_3_react/src/client/App.css b/task_3_react/src/client/App.css new file mode 100644 index 0000000..09d81a6 --- /dev/null +++ b/task_3_react/src/client/App.css @@ -0,0 +1,138 @@ +/*some style for app component*/ +:root { + --spacing: 0.25rem; + --border-color: #a0a0a0; +} + +/* Style for definition list */ +dl { + margin-top: 0; + margin-bottom: 20px; +} + +dt, +dd { + line-height: 1.42857143; +} + +dt { + font-weight: 700; +} + +dd { + margin-left: 0; +} + +body h1, +body h2 { + margin-bottom: 0; +} + +nav { + border-bottom: 1px solid var(--border-color); +} + +article { + width: 400px; +} + +body>main { + max-height: calc(100vh - 100px); + overflow-y: auto; + + padding: var(--spacing) !important; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: start; + gap: var(--spacing); + + & article { + margin: 0; + padding: var(--spacing); + + &>header { + margin: calc(var(--spacing) * -1) calc(var(--spacing) * -1) var(--spacing); + padding: var(--spacing); + } + } +} + +.info { + h4 { + margin-bottom: 2px; + margin-top: 10px; + } + + p { + margin: 0; + } +} + +.table-container { + width: 100%; +} + +.table-scroll-wrapper { + width: 100%; + max-height: 70vh; + overflow: scroll; +} + +/* Set a fixed scrollable wrapper */ +#table-content { + max-height: 400px; + overflow: auto; + + /* Set header to stick to the top of the container. */ + & thead tr th { + position: sticky; + top: 0; + } + + /* If we use border, we must use table-collapse to avoid a slight movement of the header row */ + & table { + border-collapse: collapse; + border-top: 0; + } + + /* Because we must set sticky on th, we have to apply background styles here rather than on thead */ + & th { + cursor: pointer; + border-left: 1px dotted rgba(200, 209, 224, 0.6); + border-bottom: 1px solid #ddd; + + background: #eee; + text-align: left; + box-shadow: 0px 0px 0 2px #e8e8e8; + + &.active { + background: #ddd; + } + + &.sortable::after { + font-family: FontAwesome; + content: "\f0dc"; + position: absolute; + right: 8px; + color: #999; + } + + &.active::after { + position: absolute; + right: 8px; + color: #999; + } + + &.active.sorting.asc::after { + font-family: FontAwesome; + content: "\f0d8"; + } + + &.active.sorting.desc::after { + font-family: FontAwesome; + content: "\f0d7"; + } + } +} \ No newline at end of file diff --git a/task_3_react/src/client/App.tsx b/task_3_react/src/client/App.tsx new file mode 100644 index 0000000..07b5854 --- /dev/null +++ b/task_3_react/src/client/App.tsx @@ -0,0 +1,47 @@ +import "./App.css"; +import '@fortawesome/fontawesome-free/css/all.css'; +import { readCSV } from './csv'; +import { CSV_Data, fileInfo } from './types'; + +import React, { useState } from "react"; +import Layout from "./Layout"; +import CSVCard from "./components/CSVCard"; +import InfoCard from "./components/InfoCard"; +import DataTable from "./components/DataTable"; + +function App() { + const [data, setData] = useState([] as CSV_Data); + const [info, setInfo] = useState({ + filename: "None", + filetype: "None", + filesize: "None", + rowcount: 0 + }); + + // This is triggered in CSVCard + const handleFileChange = async (e: React.ChangeEvent): Promise => { + const file = e.target.files?.[0]; + if (!file) throw new Error("No file received"); + + const data = await readCSV(e); + + const newFileInfo: fileInfo = { + filename: file.name, + filetype: file.type, + filesize: String(file.size) + "B", + rowcount: data.length + } + setInfo(newFileInfo); + setData(data); + } + + return ( + + + + + + ); +} + +export default App; diff --git a/task_3_react/src/client/Layout.css b/task_3_react/src/client/Layout.css new file mode 100644 index 0000000..43f0010 --- /dev/null +++ b/task_3_react/src/client/Layout.css @@ -0,0 +1,29 @@ +body h1, +body h2 { + margin-bottom: 0; +} + +nav { + border-bottom: 1px solid var(--border-color); +} + +body main { + max-height: calc(100vh - 100px); + overflow-y: auto; + + padding: var(--spacing) !important; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: start; + gap: var(--spacing); + + & article { + margin: 0; + padding: var(--spacing); + & > header { + margin: calc(var(--spacing) * -1) calc(var(--spacing) * -1) var(--spacing); + padding: var(--spacing); + } + } +} diff --git a/task_3_react/src/client/Layout.tsx b/task_3_react/src/client/Layout.tsx new file mode 100644 index 0000000..55dfead --- /dev/null +++ b/task_3_react/src/client/Layout.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import "./Layout.css"; + +const Layout = (props: { children: React.ReactNode }) => { + return ( + <> + +
+ {props.children} +
+ + ); +}; + +export default Layout; diff --git a/task_3_react/src/client/components/CSVCard.tsx b/task_3_react/src/client/components/CSVCard.tsx new file mode 100644 index 0000000..53d0788 --- /dev/null +++ b/task_3_react/src/client/components/CSVCard.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import "../Layout.css"; + +const CSVCard = (props: { + handleChange: (e: React.ChangeEvent) => void +}) => { + return ( +
+
+

Select CSV data

+
+
+ + + Please upload a CSV file, where the first row is the header. +
+
+ ); +} + +export default CSVCard; \ No newline at end of file diff --git a/task_3_react/src/client/components/DataTable.tsx b/task_3_react/src/client/components/DataTable.tsx new file mode 100644 index 0000000..70f09c0 --- /dev/null +++ b/task_3_react/src/client/components/DataTable.tsx @@ -0,0 +1,84 @@ +import { Key, SetStateAction, useState } from "react"; +import { CSV_Data } from "../types"; + +const DataTable = (props: {data: CSV_Data}) => { + if (props.data.length == 0) return <>; + + const header = Object.keys(props.data[0]!); + const [sortCol, setSortCol] = useState("None"); + const [sortType, setSortType] = useState("asc"); + + const sortingHandler = (col: String) => { + if (sortCol !== col) { + setSortCol(col as SetStateAction); + setSortType("asc"); + } else if (sortType === "asc") { + setSortType("desc"); + } else { + setSortCol("None"); + setSortType("None"); + } + } + + if (sortCol !== "None" && sortType === "asc") { + props.data.sort( (a, b) => { + if (a[sortCol]! < b[sortCol]!) return -1; + if (a[sortCol]! > b[sortCol]!) return 1; + return 0; + }); + } else if (sortCol !== "None" && sortType === "desc") { + props.data.sort( (a, b) => { + if (a[sortCol]! > b[sortCol]!) return -1; + if (a[sortCol]! < b[sortCol]!) return 1; + return 0; + }); + } + + return ( + + + + { + header.map( (col) => ( + + )) + } + + + + { + props.data.map( (row, i) => ( + + { + header.map( (col) => ( + + )) + } + + )) + } + +
+ ) +} + +const ColHeader = (props: {col: String, sortingHandle: (s: String) => void, isSelected: boolean, sortType: String}) => { + return ( + {props.sortingHandle!(props.col)}} + key={props.col as Key}> + {props.col} + + ); +} + +const Row = (props: {col: String, content: String}) => { + return {props.content}; +} + +export default DataTable; \ No newline at end of file diff --git a/task_3_react/src/client/components/InfoCard.tsx b/task_3_react/src/client/components/InfoCard.tsx new file mode 100644 index 0000000..684086f --- /dev/null +++ b/task_3_react/src/client/components/InfoCard.tsx @@ -0,0 +1,35 @@ +import "../Layout.css"; +import { fileInfo } from "../types"; + +const InfoCard = (props: { + info: fileInfo +}) => { + + let noFileMessage =
+ if (props.info.filename === "None") + noFileMessage =
No file selected
; + + return ( +
+
+

Data infos

+
+
+

Filename

+

{props.info.filename}

+ +

File type

+

{props.info.filetype}

+ +

File size

+

{props.info.filesize}

+ +

Number of rows

+

{props.info.rowcount}

+
+ {noFileMessage} +
+ ); +} + +export default InfoCard; \ No newline at end of file diff --git a/task_3_react/src/client/csv.ts b/task_3_react/src/client/csv.ts new file mode 100644 index 0000000..bc29697 --- /dev/null +++ b/task_3_react/src/client/csv.ts @@ -0,0 +1,30 @@ +import { CSV_Data } from './types'; +import { csv2json } from 'json-2-csv'; + +const convertCSVtoJSON = async ( csvText: string ) => { + // Type cast OK, as the typing of the external library is not perfect -> Actually it is. + // NOTE: On transpilation to JS, it will be (more or less) disregarded anyway. + // If you claim it isn't good typing, it's the same as expecting it to guess the typing, + // which is literally impossible in TS. But sure... + return ( await csv2json( csvText ) ) as CSV_Data; +}; + +/** + * Reads a CSV file and returns the data as JSON. + * @param event The change event of the file input. + */ +export const readCSV = async ( event: React.ChangeEvent ): Promise => { + if ( !( event.target instanceof HTMLInputElement ) ) { + throw new Error( 'Not an HTMLInputElement' ); + } + + const file = event.target.files?.[0]; + + if ( file == null ) { + throw new Error( 'No file selected' ); + } + + const result = await file.text(); + + return await convertCSVtoJSON( result ); +}; diff --git a/task_3_react/src/client/index.css b/task_3_react/src/client/index.css new file mode 100644 index 0000000..1818ca7 --- /dev/null +++ b/task_3_react/src/client/index.css @@ -0,0 +1,20 @@ +:root { + --spacing: 1rem; + --border-color: #a0a0a0; +} + +/* Style for definition list */ +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: 700; +} +dd { + margin-left: 0; +} diff --git a/task_3_react/src/client/main.tsx b/task_3_react/src/client/main.tsx new file mode 100644 index 0000000..4a35729 --- /dev/null +++ b/task_3_react/src/client/main.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import "@fortawesome/fontawesome-free/css/all.css"; +import App from "./App"; +import "@picocss/pico/css/pico.min.css"; + +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/task_3_react/src/client/types.d.ts b/task_3_react/src/client/types.d.ts new file mode 100644 index 0000000..e949b5d --- /dev/null +++ b/task_3_react/src/client/types.d.ts @@ -0,0 +1,11 @@ +export type CSVRecord = Record; + +export type CSV_Data = CSVRecord[]; + +// InfoCard receives this via props +export type fileInfo = { + filename: string; + filetype: string; + filesize: string; + rowcount: number; +} diff --git a/task_3_react/src/client/vite-env.d.ts b/task_3_react/src/client/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/task_3_react/src/client/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/task_3_react/src/server/main.ts b/task_3_react/src/server/main.ts new file mode 100644 index 0000000..05a5ded --- /dev/null +++ b/task_3_react/src/server/main.ts @@ -0,0 +1,17 @@ +import express from "express"; +import ViteExpress from "vite-express"; + +// creates the expres app do not change +const app = express(); + +// add your routes here + +// example route which returns a message +app.get("/hello", async function (_req, res) { + res.status(200).json({ message: "Hello World!" }); +}); + +// Do not change below this line +ViteExpress.listen(app, 5173, () => + console.log("Server is listening on http://localhost:5173"), +); diff --git a/task_3_react/tsconfig.json b/task_3_react/tsconfig.json new file mode 100644 index 0000000..fc3018c --- /dev/null +++ b/task_3_react/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "CommonJS", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + "strict": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "allowJs": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"], + "exclude": ["tests/**/*", "public/**/*", "build/**/*"] +} diff --git a/task_3_react/vite.config.ts b/task_3_react/vite.config.ts new file mode 100644 index 0000000..9cc50ea --- /dev/null +++ b/task_3_react/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +});