This commit is contained in:
Michael Zhang 2022-10-23 23:14:43 -05:00
commit 111cc23599
15 changed files with 8142 additions and 0 deletions

24
.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
dist
.solid
.output
.vercel
.netlify
netlify
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# Temp
gitignore
# System Files
.DS_Store
Thumbs.db

9
README.md Normal file
View file

@ -0,0 +1,9 @@
# Wisesplit
Does the one calculation I expected Splitwise to have but it didn't.
## Contact
License: AGPL-3.0
Author: Michael Zhang

7761
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "splitwise",
"scripts": {
"dev": "solid-start dev",
"build": "solid-start build",
"start": "solid-start start"
},
"type": "module",
"devDependencies": {
"solid-start-node": "^0.2.0",
"typescript": "^4.8.4",
"vite": "^3.1.8"
},
"dependencies": {
"@solidjs/meta": "^0.28.0",
"@solidjs/router": "^0.5.0",
"solid-js": "^1.6.0",
"solid-start": "^0.2.0",
"undici": "^5.11.0"
},
"engines": {
"node": ">=16"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

View file

@ -0,0 +1,20 @@
.increment {
font-family: inherit;
font-size: inherit;
padding: 1em 2em;
color: #335d92;
background-color: rgba(68, 107, 158, 0.1);
border-radius: 2em;
border: 2px solid rgba(68, 107, 158, 0);
outline: none;
width: 200px;
font-variant-numeric: tabular-nums;
}
.increment:focus {
border: 2px solid #335d92;
}
.increment:active {
background-color: rgba(68, 107, 158, 0.2);
}

View file

@ -0,0 +1,11 @@
import { createSignal } from "solid-js";
import "./Counter.css";
export default function Counter() {
const [count, setCount] = createSignal(0);
return (
<button class="increment" onClick={() => setCount(count() + 1)}>
Clicks: {count()}
</button>
);
}

3
src/entry-client.tsx Normal file
View file

@ -0,0 +1,3 @@
import { mount, StartClient } from "solid-start/entry-client";
mount(() => <StartClient />, document);

9
src/entry-server.tsx Normal file
View file

@ -0,0 +1,9 @@
import {
createHandler,
renderAsync,
StartServer,
} from "solid-start/entry-server";
export default createHandler(
renderAsync((event) => <StartServer event={event} />)
);

40
src/root.css Normal file
View file

@ -0,0 +1,40 @@
body {
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
}
a {
margin-right: 1rem;
}
main {
text-align: center;
padding: 1em;
margin: 0 auto;
}
h1 {
color: #335d92;
text-transform: uppercase;
font-size: 4rem;
font-weight: 100;
line-height: 1.1;
margin: 4rem auto;
max-width: 14rem;
}
p {
max-width: 14rem;
margin: 2rem auto;
line-height: 1.35;
}
@media (min-width: 480px) {
h1 {
max-width: none;
}
p {
max-width: none;
}
}

39
src/root.tsx Normal file
View file

@ -0,0 +1,39 @@
// @refresh reload
import { Suspense } from "solid-js";
import {
A,
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
Title,
} from "solid-start";
import "./root.css";
export default function Root() {
return (
<Html lang="en">
<Head>
<Title>SolidStart - Bare</Title>
<Meta charset="utf-8" />
<Meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Body>
<Suspense>
<ErrorBoundary>
<A href="/">Index</A>
<A href="/about">About</A>
<Routes>
<FileRoutes />
</Routes>
</ErrorBoundary>
</Suspense>
<Scripts />
</Body>
</Html>
);
}

19
src/routes/[...404].tsx Normal file
View file

@ -0,0 +1,19 @@
import { Title } from "solid-start";
import { HttpStatusCode } from "solid-start/server";
export default function NotFound() {
return (
<main>
<Title>Not Found</Title>
<HttpStatusCode code={404} />
<h1>Page Not Found</h1>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}

161
src/routes/index.tsx Normal file
View file

@ -0,0 +1,161 @@
import { createStore } from "solid-js/store";
import { batch, createComputed, createSignal, For } from "solid-js";
import { Title } from "solid-start";
export default function Home() {
const [state, setState] = createStore({
itemEdit: "",
items: [],
finalTotal: 0,
get totals() {
const totals = new Map();
let subtotalSum = 0;
for (let item of state.items) {
const price = item.price;
const numPeople = item.people.length;
if (numPeople == 0) continue;
const eachPrice = price / numPeople;
subtotalSum += price;
for (let person of item.people) {
const personName = person.name.toString();
let accum = totals.get(personName) || 0;
accum += eachPrice;
totals.set(personName, accum);
}
}
if (subtotalSum == 0) return totals;
const proportion = state.finalTotal / subtotalSum;
console.log("Subtotal sum", subtotalSum, proportion);
const newTotals = new Map();
for (let person of totals.keys()) {
const value = totals.get(person);
newTotals.set(person, value * proportion);
}
return newTotals;
},
});
const [totals, setTotals] = createSignal(new Map());
const addItem = (e) => {
e.preventDefault();
batch(() => {
setState("items", [
...state.items,
{ name: state.itemEdit, price: 0, personEdit: "", people: [] },
]);
setState("itemEdit", "");
});
return false;
};
const itemAddPerson = (e, i) => {
e.preventDefault();
batch(() => {
setState("items", i, "people", [
...state.items[i].people,
{ name: state.items[i].personEdit },
]);
setState("items", i, "personEdit", "");
});
return false;
};
const trySetNum = (target, v) => {
try {
let n = parseFloat(v);
console.log([...target, n]);
setState.apply(null, [...target, n]);
} catch (e) {
return;
}
};
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});
return (
<main>
<Title>Hello World</Title>
<h2>Items</h2>
<details>
<summary>Details for nerds</summary>
<pre>{JSON.stringify(state)}</pre>
</details>
<p>
Final total: {formatter.format(state.finalTotal)}
<input
type="text"
onInput={(e) => trySetNum(["finalTotal"], e.target.value)}
value={state.finalTotal}
/>
</p>
<ul style={{ "text-align": "left" }}>
<For each={state.items}>
{(item, i) => (
<li>
{item.name} ({formatter.format(item.price)})
<input
type="text"
onInput={(e) =>
trySetNum(["items", i(), "price"], e.target.value)
}
value={item.price}
/>
<ul>
<For each={item.people}>
{(person, j) => <li>{person.name}</li>}
</For>
<li>
<form onSubmit={(e) => itemAddPerson(e, i())}>
<input
type="text"
placeholder="Add person to split..."
onInput={(e) =>
setState("items", i(), "personEdit", e.target.value)
}
value={item.personEdit}
/>
</form>
</li>
</ul>
</li>
)}
</For>
<li>
<form onSubmit={addItem}>
<input
type="text"
placeholder="Add item..."
onInput={(e) => setState("itemEdit", e.target.value)}
value={state.itemEdit}
/>
</form>
</li>
</ul>
<ul>
<For each={Array.from(state.totals)}>
{([person, amount], i) => (
<li>
{person} : {formatter.format(amount)}
</li>
)}
</For>
</ul>
</main>
);
}

16
tsconfig.json Normal file
View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"types": ["vite/client"],
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
}
}

6
vite.config.ts Normal file
View file

@ -0,0 +1,6 @@
import solid from "solid-start/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [solid()],
});