diff --git a/.gitignore b/.gitignore
index bc20fec..5c005f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,4 +43,4 @@ package-lock.json
server
-*.sqlite
+db.sqlite
diff --git a/README.md b/README.md
index 8a7fa16..bedbe3d 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,26 @@
# hyperwave 🌊
-hyperwave is a server-side framework for building web applications.
+https://hyperwave.codes/
-* fast: Bun and Hono for best-in-class performance
-* lightweight: ~20kb payload, loads in < 5 seconds on slow 3G
-* productive: use the best tools for the job: Tailwind, HTMX, and TypeScript
-* portable: compile a binary to deploy anywhere
+hyperwave is a server-side framework for building web applications.
----
+- fast: Bun and Hono for best-in-class performance
+- lightweight: ~20kb payload. Demo loads in a couple seconds even while throttled to 2G.
+- productive: use the best tools for the job: Tailwind, HTMX, and TypeScript
+- portable: compile a binary to deploy anywhere
### Setup
-`bun setup`
+`bun install && bun run src/db.ts && bun dev`
Visit port 3000 and edit `server.tsx`
---
### Example
+
This is the endpoint serving our initial landing page:
+
```typescript
app.get("/", ({ html }) =>
html(
@@ -38,6 +40,7 @@ app.get("/", ({ html }) =>
),
);
```
+
- The API serves a full HTML document to the client, which includes Tailwind classes and HTMX attributes
- The response is wrapped in a `` tag, a server-rendered functional component, which takes a `title` prop
- The button, when clicked, will issue a `GET` request to `/instructions` and replace the content of its parent div with the response.
diff --git a/assets/icons/calendar.tsx b/assets/icons/calendar.tsx
new file mode 100644
index 0000000..cbe3a94
--- /dev/null
+++ b/assets/icons/calendar.tsx
@@ -0,0 +1,12 @@
+export default function Calendar() {
+ return (
+
+ );
+}
diff --git a/assets/icons/chart.tsx b/assets/icons/chart.tsx
new file mode 100644
index 0000000..b67302e
--- /dev/null
+++ b/assets/icons/chart.tsx
@@ -0,0 +1,12 @@
+export default function Chart() {
+ return (
+
+ );
+}
diff --git a/assets/icons/check.tsx b/assets/icons/check.tsx
new file mode 100644
index 0000000..f0320f6
--- /dev/null
+++ b/assets/icons/check.tsx
@@ -0,0 +1,12 @@
+export default function Check() {
+ return (
+
+ );
+}
diff --git a/assets/icons/credit_card.tsx b/assets/icons/credit_card.tsx
new file mode 100644
index 0000000..f323cd8
--- /dev/null
+++ b/assets/icons/credit_card.tsx
@@ -0,0 +1,12 @@
+export default function CreditCard() {
+ return (
+
+ );
+}
diff --git a/assets/icons/file.tsx b/assets/icons/file.tsx
new file mode 100644
index 0000000..b51da64
--- /dev/null
+++ b/assets/icons/file.tsx
@@ -0,0 +1,12 @@
+export default function File() {
+ return (
+
+ );
+}
diff --git a/assets/icons/gear.tsx b/assets/icons/gear.tsx
new file mode 100644
index 0000000..c577e21
--- /dev/null
+++ b/assets/icons/gear.tsx
@@ -0,0 +1,12 @@
+export default function Gear() {
+ return (
+
+ );
+}
diff --git a/assets/icons/house.tsx b/assets/icons/house.tsx
new file mode 100644
index 0000000..8b15499
--- /dev/null
+++ b/assets/icons/house.tsx
@@ -0,0 +1,12 @@
+export default function House() {
+ return (
+
+ );
+}
diff --git a/assets/icons/magnify.tsx b/assets/icons/magnify.tsx
new file mode 100644
index 0000000..90a56d9
--- /dev/null
+++ b/assets/icons/magnify.tsx
@@ -0,0 +1,15 @@
+export default function Magnify() {
+ return (
+
+ );
+}
diff --git a/assets/icons/question.tsx b/assets/icons/question.tsx
new file mode 100644
index 0000000..a17c4cf
--- /dev/null
+++ b/assets/icons/question.tsx
@@ -0,0 +1,12 @@
+export default function Question() {
+ return (
+
+ );
+}
diff --git a/assets/icons/user.tsx b/assets/icons/user.tsx
new file mode 100644
index 0000000..88b9e65
--- /dev/null
+++ b/assets/icons/user.tsx
@@ -0,0 +1,12 @@
+export default function User() {
+ return (
+
+ );
+}
diff --git a/bun.lockb b/bun.lockb
index 314191e..1b1906b 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index cf77241..d186173 100644
--- a/package.json
+++ b/package.json
@@ -2,23 +2,25 @@
"name": "hyperwave",
"version": "1.0.50",
"scripts": {
+ "build": "bun css && bun build --compile ./src/server.tsx",
"css": "unocss \"src/**/*.tsx\" -o public/styles/uno.css",
"css:watch": "unocss \"src/**/*.tsx\" -o public/styles/uno.css --watch",
- "server:watch": "bun --watch run src/server.tsx",
- "dev": "concurrently \"bun css:watch\" \"bun server:watch\"",
"db": "bun run src/db.ts",
- "setup": "bun install && bun db && bun dev",
- "test": "bun run test",
+ "dev": "concurrently \"bun css:watch\" \"bun server:watch\"",
"prettier": "bunx prettier --write src/ test/ --plugin prettier-plugin-tailwindcss",
- "build": "bun css && bun build --compile ./src/server.tsx"
+ "server:watch": "bun --watch run src/server.tsx",
+ "test": "bun run test"
},
"dependencies": {
- "hono": "^3.6.3"
+ "@unocss/preset-web-fonts": "^0.58.0",
+ "hono": "^3.6.3",
+ "unocss": "^0.58.0"
},
"devDependencies": {
"@unocss/cli": "^0.56.5",
"bun-types": "latest",
"concurrently": "^8.2.1",
+ "prettier": "^3.1.0",
"prettier-plugin-tailwindcss": "^0.5.9"
},
"module": "src/server.tsx"
diff --git a/public/styles/uno.css b/public/styles/uno.css
index f60b114..600e1d6 100644
--- a/public/styles/uno.css
+++ b/public/styles/uno.css
@@ -1,27 +1,128 @@
/* layer: preflights */
-*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}
+*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}
+/* latin-ext */
+@font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHjxAwXjeu.woff2) format('woff2');
+ unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHjx4wXg.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
/* layer: default */
+.fixed{position:fixed;}
+.relative{position:relative;}
+.sticky{position:sticky;}
+.left-10{left:2.5rem;}
+.right-8{right:2rem;}
+.top-0\.5{top:0.125rem;}
+.top-14{top:3.5rem;}
+.m-0{margin:0;}
.m-auto{margin:auto;}
+.my-4{margin-top:1rem;margin-bottom:1rem;}
+.me{margin-inline-end:1rem;}
+.block{display:block;}
+.hidden{display:none;}
+.h-8{height:2rem;}
+.h-full{height:100%;}
+.max-h-14{max-height:3.5rem;}
+.min-h-14{min-height:3.5rem;}
+.w-16{width:4rem;}
+.w-40{width:10rem;}
+.w-5\/6{width:83.3333333333%;}
+.w-80{width:20rem;}
+.w-96{width:24rem;}
.w-full{width:100%;}
+.hover\:w-56:hover{width:14rem;}
.flex{display:flex;}
.flex-col{flex-direction:column;}
+.cursor-pointer{cursor:pointer;}
+.list-none{list-style-type:none;}
+.items-start\!{align-items:flex-start !important;}
+.items-center{align-items:center;}
.self-start{align-self:flex-start;}
+.justify-start\!{justify-content:flex-start !important;}
.justify-center{justify-content:center;}
+.justify-between{justify-content:space-between;}
+.gap-16{gap:4rem;}
+.gap-3{gap:0.75rem;}
.gap-4{gap:1rem;}
.gap-8{gap:2rem;}
-.border-b-2{border-bottom-width:2px;}
-.border-blue-200{--un-border-opacity:1;border-color:rgba(191,219,254,var(--un-border-opacity));}
+.border{border-width:1px;}
+.border-b-1{border-bottom-width:1px;}
+.border-gray-2{--un-border-opacity:1;border-color:rgb(229 231 235 / var(--un-border-opacity));}
+.border-slate-200{--un-border-opacity:1;border-color:rgb(226 232 240 / var(--un-border-opacity));}
+.focus\:border-blue-200:focus{--un-border-opacity:1;border-color:rgb(191 219 254 / var(--un-border-opacity));}
.rounded-md{border-radius:0.375rem;}
-.bg-blue-100{--un-bg-opacity:1;background-color:rgba(219,234,254,var(--un-bg-opacity));}
-.bg-blue-50{--un-bg-opacity:1;background-color:rgba(239,246,255,var(--un-bg-opacity));}
-.p-10{padding:2.5rem;}
+.border-none{border-style:none;}
+.border-solid{border-style:solid;}
+.border-b-solid{border-bottom-style:solid;}
+.bg-blue-100{--un-bg-opacity:1;background-color:rgb(219 234 254 / var(--un-bg-opacity));}
+.bg-gray-100{--un-bg-opacity:1;background-color:rgb(243 244 246 / var(--un-bg-opacity));}
+.bg-red-400{--un-bg-opacity:1;background-color:rgb(248 113 113 / var(--un-bg-opacity));}
+.bg-slate-700{--un-bg-opacity:1;background-color:rgb(51 65 85 / var(--un-bg-opacity));}
+.bg-slate-800{--un-bg-opacity:1;background-color:rgb(30 41 59 / var(--un-bg-opacity));}
+.bg-transparent{background-color:transparent;}
+.bg-white{--un-bg-opacity:1;background-color:rgb(255 255 255 / var(--un-bg-opacity));}
+.hover\:bg-red-400:hover{--un-bg-opacity:1;background-color:rgb(248 113 113 / var(--un-bg-opacity));}
+.hover\:bg-slate-700:hover{--un-bg-opacity:1;background-color:rgb(51 65 85 / var(--un-bg-opacity));}
+.fill-neutral-500{--un-fill-opacity:1;fill:rgb(115 115 115 / var(--un-fill-opacity));}
+.fill-white{--un-fill-opacity:1;fill:rgb(255 255 255 / var(--un-fill-opacity));}
+.p-0{padding:0;}
.p-4{padding:1rem;}
-.p-8{padding:2rem;}
.px-10{padding-left:2.5rem;padding-right:2.5rem;}
+.px-4{padding-left:1rem;padding-right:1rem;}
+.py-1{padding-top:0.25rem;padding-bottom:0.25rem;}
+.py-2{padding-top:0.5rem;padding-bottom:0.5rem;}
+.py-3{padding-top:0.75rem;padding-bottom:0.75rem;}
.py-4{padding-top:1rem;padding-bottom:1rem;}
-.text-2xl{font-size:1.5rem;line-height:2rem;}
+.pl-0\.5{padding-left:0.125rem;}
+.pl-10{padding-left:2.5rem;}
+.pl-20{padding-left:5rem;}
+.pl-3{padding-left:0.75rem;}
+.pl-5{padding-left:1.25rem;}
+.pl-6{padding-left:1.5rem;}
+.pr-10{padding-right:2.5rem;}
+.pr-4{padding-right:1rem;}
+.text-left{text-align:left;}
+.text-base{font-size:1rem;line-height:1.5rem;}
.text-sm{font-size:0.875rem;line-height:1.25rem;}
+.text-neutral-500{--un-text-opacity:1;color:rgb(115 115 115 / var(--un-text-opacity));}
+.text-red-100{--un-text-opacity:1;color:rgb(254 226 226 / var(--un-text-opacity));}
+.text-slate-400{--un-text-opacity:1;color:rgb(148 163 184 / var(--un-text-opacity));}
+.text-white{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));}
+.hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));}
.font-bold{font-weight:700;}
-.shadow-md{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgba(0,0,0,0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgba(0,0,0,0.1));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
-.shadow-sm{--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color, rgba(0,0,0,0.05));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
-.outline{outline-style:solid;}
\ No newline at end of file
+.leading-5{line-height:1.25rem;}
+.font-lato{font-family:"Lato";}
+.uppercase{text-transform:uppercase;}
+.no-underline{text-decoration:none;}
+.opacity-0{opacity:0;}
+.group:hover .group-hover\:opacity-100{opacity:1;}
+.shadow-md{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color, rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
+.shadow-sm{--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color, rgb(0 0 0 / 0.05));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
+.outline{outline-style:solid;}
+.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
+.transition-width{transition-property:width;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}
+.duration-200{transition-duration:200ms;}
+.duration-300{transition-duration:300ms;}
+.duration-50{transition-duration:50ms;}
+@media (min-width: 640px){
+.sm\:ml-4{margin-left:1rem;}
+}
+@media (min-width: 768px){
+.md\:block{display:block;}
+.md\:w-56{width:14rem;}
+.md\:pl-60{padding-left:15rem;}
+.md\:opacity-100{opacity:1;}
+}
\ No newline at end of file
diff --git a/src/Layout.tsx b/src/Layout.tsx
deleted file mode 100644
index 31b84b9..0000000
--- a/src/Layout.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-export default function Layout({ title, children }) {
- return (
-
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
- );
-}
diff --git a/src/components/Input.tsx b/src/components/Input.tsx
new file mode 100644
index 0000000..84702a3
--- /dev/null
+++ b/src/components/Input.tsx
@@ -0,0 +1,15 @@
+export default function Input({ class: className, placeholder, ...rest }) {
+ return (
+
+ );
+}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
new file mode 100644
index 0000000..6b3531f
--- /dev/null
+++ b/src/components/Layout.tsx
@@ -0,0 +1,54 @@
+import Magnify from "../../assets/icons/magnify";
+import Input from "./Input";
+import Nav from "./Nav";
+
+type Props = {
+ title: string;
+ currentPath?: string;
+ children: any;
+};
+
+export default function Layout({ title, children, currentPath }: Props) {
+ return (
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
new file mode 100644
index 0000000..5b8cf41
--- /dev/null
+++ b/src/components/Nav.tsx
@@ -0,0 +1,23 @@
+import NavItem from "./NavItem";
+import PinkButton from "./PinkButton";
+import { routes } from "../routes";
+
+export default function Nav({ currentPath }: { currentPath: string }) {
+ return (
+
+ );
+}
diff --git a/src/components/NavItem.tsx b/src/components/NavItem.tsx
new file mode 100644
index 0000000..5071dd5
--- /dev/null
+++ b/src/components/NavItem.tsx
@@ -0,0 +1,26 @@
+import { Route } from "../routes";
+
+type NavItemProps = {
+ currentPath: string;
+ route: Route;
+};
+
+export default function NavItem({ currentPath, route }: NavItemProps) {
+ return (
+
+
+ {route.icon}
+
+ {route.title}
+
+
+
+ );
+}
diff --git a/src/components/PinkButton.tsx b/src/components/PinkButton.tsx
new file mode 100644
index 0000000..7f0448c
--- /dev/null
+++ b/src/components/PinkButton.tsx
@@ -0,0 +1,20 @@
+type Props = {
+ class?: string;
+ children: any;
+ [string: string]: any;
+};
+
+export default function PinkButton({
+ class: className,
+ children,
+ ...rest
+}: Props) {
+ return (
+
+ );
+}
diff --git a/src/db.ts b/src/db.ts
index 20d35d7..0ebbda4 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -1,31 +1,15 @@
import { Database } from "bun:sqlite";
-const db = new Database("db.sqlite", { create: true });
-
-const createMovies = db.query(`
- CREATE TABLE IF NOT EXISTS Movie (
- movie_id INTEGER PRIMARY KEY,
- name TEXT,
- year TEXT,
- runtime INTEGER,
- categories TEXT,
- release_date TEXT,
- director TEXT,
- writer TEXT,
- actors TEXT,
- storyline TEXT
- );
-`);
-
-const createActors = db.query(`
- CREATE TABLE IF NOT EXISTS Actor (
- actor_id INTEGER PRIMARY KEY,
- name TEXT,
- birthname TEXT,
- birthdate TEXT,
- birthplace TEXT
+export const db = new Database("db.sqlite", { create: true });
+
+export const createUsers = () =>
+ db.query(`
+ CREATE TABLE IF NOT EXISTS User (
+ first_name TEXT,
+ last_name TEXT,
+ email TEXT
);
`);
-createMovies.run();
-createActors.run();
+export const addUser = () =>
+ db.query("INSERT INTO User (first_name, last_name, email) VALUES (?, ?, ?);");
diff --git a/src/routes.ts b/src/routes.ts
new file mode 100644
index 0000000..4497ae2
--- /dev/null
+++ b/src/routes.ts
@@ -0,0 +1,128 @@
+import Calendar from "../assets/icons/calendar";
+import Chart from "../assets/icons/chart";
+import Check from "../assets/icons/check";
+import CreditCard from "../assets/icons/credit_card";
+import File from "../assets/icons/file";
+import Gear from "../assets/icons/gear";
+import House from "../assets/icons/house";
+import Question from "../assets/icons/question";
+import User from "../assets/icons/user";
+
+export type Route = {
+ title: string;
+ link?: string;
+ childRoutes?: Route[];
+ icon?: any;
+};
+
+export const routes: Route[] = [
+ {
+ title: "Dashboard",
+ link: "/dashboard",
+ icon: House(),
+ },
+ { title: "Tasks", link: "/tasks", icon: Check() },
+ { title: "Calendar", link: "/calendar", icon: Calendar() },
+ { title: "Contacts", link: "/clients", icon: User() },
+ {
+ title: "Templates",
+ icon: File(),
+ childRoutes: [
+ { title: "Confirmations", link: "/confirmation_templates" },
+ { title: "Emails", link: "/email_templates" },
+ { title: "Forms", link: "/form_templates" },
+ { title: "Invoices", link: "/invoice_item_templates" },
+ {
+ title: "Signature",
+ link: "/signature_templates/TODOREPLACEWITHREALTOKEN/edit",
+ },
+ { title: "Task Lists", link: "/task_list_templates" },
+ {
+ title: "Terms and Conditions",
+ link: "/terms_and_conditions_templates",
+ },
+ ],
+ },
+ {
+ title: "Reports",
+ icon: Chart(),
+ childRoutes: [
+ {
+ title: "Commissions and Sales",
+ link: "/reports/commissions_and_sales/summary",
+ },
+ {
+ title: "Direct Payments",
+ link: "/reports/direct_payments/details",
+ },
+ {
+ title: "Bank Transfers",
+ link: "/reports/bank_transfers",
+ },
+ {
+ title: "Funds",
+ link: "/reports/funds",
+ },
+ ],
+ },
+ {
+ title: "Gift Cards",
+ link: "/settings/gift_cards",
+ icon: CreditCard(),
+ },
+ {
+ title: "Settings",
+ icon: Gear(),
+ childRoutes: [
+ {
+ title: "Account",
+ link: "/settings/account",
+ },
+ {
+ title: "Agency Info",
+ link: "/settings/agency_info",
+ },
+ {
+ title: "Automation",
+ link: "/settings/automation",
+ },
+ {
+ title: "Direct Payments",
+ link: "/settings/direct_payments",
+ },
+ {
+ title: "Calendar",
+ link: "/settings/calendars",
+ },
+ {
+ title: "Email",
+ link: "/settings/email",
+ },
+ {
+ title: "Invoice",
+ link: "/settings/invoice",
+ },
+ {
+ title: "Membership",
+ link: "/settings/membership",
+ },
+ {
+ title: "Security",
+ link: "/settings/security",
+ },
+ {
+ title: "Team",
+ link: "/team",
+ },
+ {
+ title: "Trip Photos",
+ link: "/settings/trip_photos",
+ },
+ ],
+ },
+ {
+ title: "Help",
+ link: "/help",
+ icon: Question(),
+ },
+];
diff --git a/src/routes/editUserInfo.tsx b/src/routes/editUserInfo.tsx
new file mode 100644
index 0000000..9cea350
--- /dev/null
+++ b/src/routes/editUserInfo.tsx
@@ -0,0 +1,103 @@
+import { Hono } from "hono";
+import Input from "../components/Input.tsx";
+import PinkButton from "../components/PinkButton.tsx";
+import { addUser, createUsers, db } from "../db.ts";
+
+const app = new Hono();
+
+createUsers().run();
+
+app.get("/", async ({ html }) => {
+ const me = db.query("select * from User limit 1").get() as {
+ first_name: string;
+ last_name: string;
+ email: string;
+ };
+
+ if (!me || !me.email || !me.first_name || !me.last_name) {
+ addUser().run("hyper@wave.com", "Hyper", "Wave");
+ return html(Refresh pls, you hit a race condition :)
);
+ }
+
+ return html(
+
+
+
Hi, {me.first_name}!
+
Email: {me.email}
+
+
+ Change Info
+
+ ,
+ );
+});
+
+app.post("/:email", async (c) => {
+ const email = c.req.param("email");
+ const body = await c.req.parseBody();
+ const { email: newEmail, firstName, lastName } = body;
+
+ if (
+ typeof email !== "string" ||
+ typeof newEmail !== "string" ||
+ typeof firstName !== "string" ||
+ typeof lastName !== "string"
+ ) {
+ throw new Error("Bad Request");
+ }
+
+ db.prepare(
+ "UPDATE USER SET email = $1, first_name = $2, last_name = $3 WHERE email = $4",
+ ).run({
+ $1: newEmail,
+ $2: firstName,
+ $3: lastName,
+ $4: email,
+ });
+
+ return c.html(
+
+
Updated user info.
+
+ Get User Info
+
+
,
+ );
+});
+
+export default app;
diff --git a/src/server.tsx b/src/server.tsx
index cf84788..48cf2f5 100644
--- a/src/server.tsx
+++ b/src/server.tsx
@@ -1,48 +1,27 @@
import { Hono } from "hono";
-import { logger } from "hono/logger";
-import Layout from "./Layout.tsx";
import { serveStatic } from "hono/bun";
+import { logger } from "hono/logger";
+import Layout from "./components/Layout.tsx";
+import PinkButton from "./components/PinkButton.tsx";
+
+import editUserRoutes from "./routes/editUserInfo.tsx";
const app = new Hono();
app.use("/styles/*", serveStatic({ root: "./public/" }));
-
app.use("*", logger());
-app.onError((err, c) => c.html({err}));
-
-app.get("/instructions", ({ html }) =>
+app.get("/", (c) => c.redirect("/dashboard"));
+app.get("/dashboard", async ({ html }) =>
html(
-
-
-
- $ bun dev
-
- -
- edit
src/server.ts
-
- - profit 🚀
-
-
,
- ),
-);
-
-app.get("/", ({ html }) =>
- html(
-
-
-
-
-
-
+
+
+ Get User Info
+
,
),
);
+app.route("/editUser", editUserRoutes);
+
export default app;
diff --git a/uno.config.ts b/uno.config.ts
new file mode 100644
index 0000000..d0cba67
--- /dev/null
+++ b/uno.config.ts
@@ -0,0 +1,15 @@
+import { defineConfig, presetAttributify, presetWind } from "unocss";
+import presetWebFonts from "@unocss/preset-web-fonts";
+
+export default defineConfig({
+ presets: [
+ presetAttributify(),
+ presetWind(),
+ presetWebFonts({
+ provider: "google",
+ fonts: {
+ lato: "Lato",
+ },
+ }),
+ ],
+});