feat(web-client): implement a basic web client

This commit is contained in:
M. George Hansen 2022-05-08 23:45:18 -07:00
parent 991b82d750
commit 8b8f84ede1
Signed by: mgeorgehansen
SSH key fingerprint: SHA256:JlIGiQLPyQ2RHTH3a2oVlb20Xkh9Glr8DUF4YTXHJxM
13 changed files with 12506 additions and 9 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

24
web-client/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

17
web-client/README.md Normal file
View file

@ -0,0 +1,17 @@
# csv-to-json web client
A simple Preact SPA designed to showcase the csv-to-json server in action.
## Usage
Have a recent version of node (>=v14) and npm installed, and install any dependencies needed:
```sh
$> npm i
```
Ensure that you have `csv-to-json` running and listening on port 8000, then fire up the dev server and visit the url indicated in your browser (defaults to http://localhost:3000):
```sh
$> npm run dev
```

12
web-client/index.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CSV to JSON</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

12205
web-client/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,25 @@
{
"name": "web-client",
"version": "0.1.0",
"description": "A web client for csv-to-json.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "M. George Hansen",
"license": "UNLICENSED"
"name": "web-client",
"description": "A web client for csv-to-json.",
"version": "0.1.0",
"private": true,
"author": "M. George Hansen",
"license": "UNLICENSED",
"scripts": {
"dev": "vite"
},
"dependencies": {
"preact": "^10.7.2",
"react-query": "^3.39.0"
},
"devDependencies": {
"@preact/preset-vite": "^2.1.7",
"@testing-library/preact": "^2.0.1",
"@types/jest": "^27.4.1",
"jest": "^27.5.1",
"ts-node": "^10.6.0",
"typescript": "^4.5.5",
"vite": "^2.8.4",
"vite-tsconfig-paths": "^3.4.1"
}
}

94
web-client/src/app.tsx Normal file
View file

@ -0,0 +1,94 @@
export { App };
import { QueryClient, QueryClientProvider, useMutation } from "react-query";
import { FunctionComponent } from "preact";
import { useEffect, useState } from "preact/hooks";
const queryClient = new QueryClient();
const App: FunctionComponent = () => (
<QueryClientProvider client={queryClient}>
<FileLoader />
</QueryClientProvider>
);
const FileLoader: FunctionComponent = () => (
<div
style={{
display: "flex",
alignItems: "center",
height: "100%",
justifyContent: "center",
}}
>
<form
action="http://localhost:8000"
method="POST"
encType="multipart/form-data"
>
<h1>Convert Your CSV to JSON:</h1>
<div
style={{
border: "1px solid #fff",
borderRadius: 4,
padding: "20px 10px",
}}
>
<label>
Upload CSV{" "}
<input
style={{ cursor: "pointer" }}
type="file"
name="file"
/>
</label>
<button
style={{
border: "1px solid #fff",
background: "none",
color: "inherit",
padding: "5px 10px",
borderRadius: 4,
cursor: "pointer",
}}
>
Convert
</button>
</div>
<Motto />
</form>
</div>
);
const MOTTOS = [
"It's fun AND educational!",
"Everyone's doing it, don't get left behind!",
"Do iiittt!",
];
const UPDATE_INTERVAL = 5_000;
const Motto: FunctionComponent = () => {
const [motto, setMotto] = useState(0);
useEffect(() => {
const handle = setInterval(
() =>
setMotto((i) => {
const newIndex = i + 1;
if (newIndex >= MOTTOS.length) {
return 0;
} else {
return newIndex;
}
}),
UPDATE_INTERVAL
);
return () => {
clearInterval(handle);
};
}, []);
return <div>{MOTTOS[motto]}</div>;
};

5
web-client/src/index.tsx Normal file
View file

@ -0,0 +1,5 @@
import { render } from "preact";
import { App } from "./app";
import "./reset.css";
render(<App />, document.body);

58
web-client/src/reset.css Normal file
View file

@ -0,0 +1,58 @@
/* Based on https://www.joshwcomeau.com/css/custom-css-reset/ */
html,
body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
background: #222;
color: #fff;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
a {
display: block;
text-decoration: unset;
color: unset;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}

View file

@ -0,0 +1,25 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"useDefineForClassFields": true,
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"module": "ESNext",
"moduleResolution": "Node",
"isolatedModules": true,
"jsx": "react-jsx",
"jsxImportSource": "preact",
"rootDir": "./",
"types": ["vite/client"],
"typeRoots": ["./types"],
"paths": {
"#/*": ["./*"],
"react": ["../node_modules/preact/compat"],
"react-dom": ["../node_modules/preact/compat"]
}
}
}

View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true
}
}

9
web-client/tsconfig.json Normal file
View file

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true
}
}

16
web-client/vite.config.ts Normal file
View file

@ -0,0 +1,16 @@
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";
import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact(), tsconfigPaths({ projects: ["./src/tsconfig.json"] })],
resolve: {
alias: {
react: "preact/compat",
"react-dom": "preact/compat",
"react-dom/test-utils": "preact/test-utils",
"react/jsx-runtime": "preact/jsx-runtime",
},
},
});