feat(infra): Update infra and add support for teams to SST (#186)

## Description
- [x] Adds support for AWS SSO, which makes us (the team) able to use
SST and update the components independently
- [x] Splits the webpage into the landing page (Qwik), and Astro (the
console) in charge of playing. This allows us to pass in Environment
Variables to the console
- ~Migrates the docs from Nuxt to Nextjs, and connects them to SST. This
allows us to use Fumadocs _citation needed_ that's much more beautiful,
and supports OpenApi~
- Cloudflare pages with github integration is not working on our new CF
account. So we will have to push the pages deployment manually with
Github actions
- [x] Moves the current set up from my personal CF and AWS accounts to
dedicated Nestri accounts -

## Related Issues
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing
functionality)
- [x] Documentation update
- [ ] Other (please describe):

## Checklist

- [x] I have updated relevant documentation
- [x] My code follows the project's coding style
- [x] My changes generate no new warnings/errors

## Notes for Reviewers
<!-- Point out areas you'd like reviewers to focus on, questions you
have, or decisions that need discussion -->
Please approve my PR 🥹


## Screenshots/Demo
<!-- If applicable, add screenshots or a GIF demo of your changes
(especially for UI changes) -->

## Additional Context
<!-- Add any other context about the pull request here -->
This commit is contained in:
Wanjohi
2025-02-27 18:52:05 +03:00
committed by GitHub
parent 237e016b2d
commit 457aac2258
138 changed files with 4218 additions and 2579 deletions

175
packages/www/.gitignore vendored Normal file
View File

@@ -0,0 +1,175 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

15
packages/www/README.md Normal file
View File

@@ -0,0 +1,15 @@
# @console/www
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.1.34. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

43
packages/www/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="dark light" />
<meta
name="theme-color"
media="(prefers-color-scheme: light)"
content="hsla(0,0%,98%)"
/>
<meta
name="theme-color"
media="(prefers-color-scheme: dark)"
content="hsla(0,0%,0%)"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/src/assets/seo/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/src/assets/seo/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/src/assets/seo/favicon-16x16.png"
/>
<title>Nestri - Your games. Your rules.</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
</body>
</html>

32
packages/www/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@nestri/www",
"type": "module",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"typecheck": "tsc --noEmit --incremental"
},
"devDependencies": {
"@macaron-css/vite": "^1.5.1",
"@types/bun": "latest",
"vite": "5.4.10",
"vite-plugin-solid": "^2.11.2"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@fontsource-variable/geist-mono": "^5.0.1",
"@nestri/core": "*",
"@fontsource-variable/mona-sans": "^5.0.1",
"@fontsource/geist-sans": "^5.1.0",
"@macaron-css/core": "^1.5.2",
"@macaron-css/solid": "^1.5.3",
"@solid-primitives/storage": "^4.3.1",
"@solidjs/router": "^0.15.3",
"modern-normalize": "^3.0.1",
"solid-js": "^1.9.5"
}
}

147
packages/www/src/App.tsx Normal file
View File

@@ -0,0 +1,147 @@
import '@fontsource-variable/mona-sans';
import '@fontsource-variable/geist-mono';
import '@fontsource/geist-sans/400.css';
import '@fontsource/geist-sans/500.css';
import '@fontsource/geist-sans/600.css';
import '@fontsource/geist-sans/700.css';
import '@fontsource/geist-sans/800.css';
import '@fontsource/geist-sans/900.css';
import { TeamCreate } from './pages/new';
import { styled } from "@macaron-css/solid";
import { useStorage } from './providers/account';
import { darkClass, lightClass, theme } from './ui/theme';
import { AuthProvider, useAuth } from './providers/auth';
import { Navigate, Route, Router } from "@solidjs/router";
import { globalStyle, macaron$ } from "@macaron-css/core";
import { Component, createSignal, Match, onCleanup, Switch } from 'solid-js';
const Root = styled("div", {
base: {
inset: 0,
lineHeight: 1,
fontSynthesis: "none",
color: theme.color.d1000.gray,
fontFamily: theme.font.family.body,
textRendering: "optimizeLegibility",
WebkitFontSmoothing: "antialised",
backgroundColor: theme.color.background.d100,
},
});
globalStyle("html", {
fontSize: 16,
fontWeight: 400,
// Hardcode colors
"@media": {
"(prefers-color-scheme: light)": {
backgroundColor: "hsla(0,0%,98%)",
},
"(prefers-color-scheme: dark)": {
backgroundColor: "hsla(0,0%,0%)",
},
},
});
globalStyle("h1, h2, h3, h4, h5, h6, p", {
margin: 0,
});
macaron$(() =>
["::placeholder", ":-ms-input-placeholder"].forEach((selector) =>
globalStyle(selector, {
opacity: 1,
color: theme.color.d1000.gray,
}),
),
);
globalStyle("body", {
cursor: "default",
});
globalStyle("*", {
boxSizing: "border-box",
});
export const App: Component = () => {
const [theme, setTheme] = createSignal<string>(
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light",
);
const darkMode = window.matchMedia("(prefers-color-scheme: dark)");
const setColorScheme = (e: MediaQueryListEvent) => {
setTheme(e.matches ? "dark" : "light");
};
darkMode.addEventListener("change", setColorScheme);
onCleanup(() => {
darkMode.removeEventListener("change", setColorScheme);
});
const storage = useStorage();
return (
<Root class={theme() === "light" ? lightClass : darkClass} id="styled">
<Router>
<Route
path="*"
component={(props) => (
<AuthProvider>
{props.children}
</AuthProvider>
// <CommandBar>
// <ReplicacheStatusProvider>
// <DummyProvider>
// <DummyConfigProvider>
// <FlagsProvider>
// <RealtimeProvider />
// <LocalProvider>
// <LocalLogsProvider>
// <GlobalCommands />
// {props.children}
// </LocalLogsProvider>
// </LocalProvider>
// </FlagsProvider>
// </DummyConfigProvider>
// </DummyProvider>
// </ReplicacheStatusProvider>
// </AuthProvider>
// </CommandBar>
)}
>
{/* <Route path="local" component={Local} />
<Route path="debug" component={DebugRoute} />
<Route path="design" component={Design} />
<Route path="workspace" component={WorkspaceCreate} />
<Route path=":workspaceSlug">{WorkspaceRoute}</Route> */}
<Route path="new" component={TeamCreate} />
<Route
path="/"
component={() => {
const auth = useAuth();
return (
<Switch>
<Match when={auth.current.teams.length > 0}>
<Navigate
href={`/${(
auth.current.teams.find(
(w) => w.id === storage.value.team,
) || auth.current.teams[0]
).slug
}`}
/>
</Match>
<Match when={true}>
<Navigate href={`/new`} />
</Match>
</Switch>
);
}}
/>
{/* <Route path="*" component={() => <NotFound />} /> */}
</Route>
</Router>
</Root>
)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/icons/mstile-150x150.png"/>
<TileColor>#ffede5</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M398 4232 l-48 -3 -1 -42 c-2 -188 2 -861 6 -865 2 -3 997 -5 2210
-6 l2205 -1 -2 458 -3 459 -2160 1 c-1188 1 -2181 1 -2207 -1z"/>
<path d="M350 2984 c0 -19 0 -198 0 -399 0 -201 0 -391 0 -424 l0 -58 2210 0
2210 0 0 457 0 457 -2210 0 -2210 0 0 -33z"/>
<path d="M354 1799 c-3 -6 -7 -613 -5 -844 l1 -70 2197 2 c1209 1 2204 2 2211
3 18 0 18 910 0 911 -81 4 -4401 2 -4404 -2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 902 B

View File

@@ -0,0 +1,18 @@
{
"name": "Nestri",
"short_name": "Nestri",
"icons": [
{
"src": "/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#fafafa",
"background_color": "#fafafa",
"display": "standalone"}

View File

@@ -0,0 +1,26 @@
import { ParentProps, Show, createContext, useContext } from "solid-js";
export function createInitializedContext<
Name extends string,
T extends { ready: boolean }
>(name: Name, cb: () => T) {
const ctx = createContext<T>();
return {
use: () => {
const context = useContext(ctx);
if (!context) throw new Error(`No ${name} context`);
return context;
},
provider: (props: ParentProps) => {
const value = cb();
return (
<Show when={value.ready}>
<ctx.Provider value={value} {...props}>
{props.children}
</ctx.Provider>
</Show>
);
},
}
}

View File

@@ -0,0 +1,27 @@
/* @refresh reload */
import { render } from "solid-js/web";
// import posthog from "posthog-js";
// posthog.init("phc_M0b2lW4smpsGIufiTBZ22USKwCy0fyqljMOGufJc79p", {
// api_host: "https://telemetry.ion.sst.dev",
// });
import "modern-normalize/modern-normalize.css";
import { App } from "./App";
import { StorageProvider } from "./providers/account";
const root = document.getElementById("root");
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
throw new Error(
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got mispelled?"
);
}
render(
() => (
<StorageProvider>
<App />
</StorageProvider>
),
root!
);

View File

@@ -0,0 +1,7 @@
export function DefaultState() {
return (
<div>
We are logging you in
</div>
)
}

View File

@@ -0,0 +1,15 @@
import { Container, FullScreen } from "@nestri/www/ui/layout";
import { Text } from "@nestri/www/ui/text";
export function TeamCreate() {
return (
<FullScreen>
<Container flow="column" >
<Text align="center" spacing="lg" size="4xl" weight="semibold">
Your first deploy is just a sign-up away.
</Text>
</Container>
</FullScreen>
)
}

View File

@@ -0,0 +1,34 @@
import { createStore } from "solid-js/store";
import { makePersisted } from "@solid-primitives/storage";
import { ParentProps, createContext, useContext } from "solid-js";
type Context = ReturnType<typeof init>;
const context = createContext<Context>();
function init() {
const [store, setStore] = makePersisted(
createStore({
account: "",
team: "",
dummy: "",
})
);
return {
value: store,
set: setStore,
};
}
export function StorageProvider(props: ParentProps) {
const ctx = init();
return <context.Provider value={ctx}>{props.children}</context.Provider>;
}
export function useStorage() {
const ctx = useContext(context);
if (!ctx) {
throw new Error("No storage context");
}
return ctx;
}

View File

@@ -0,0 +1,222 @@
import { type Team } from "@nestri/core/team/index";
import { makePersisted } from "@solid-primitives/storage";
import { useLocation, useNavigate } from "@solidjs/router";
import { createClient } from "@openauthjs/openauth/client";
import { createInitializedContext } from "../common/context";
import { createEffect, createMemo, onMount } from "solid-js";
import { createStore, produce, reconcile } from "solid-js/store";
interface AccountInfo {
id: string;
email: string;
name: string;
access: string;
refresh: string;
avatarUrl: string;
teams: Team.Info[];
discriminator: number;
polarCustomerID: string | null;
}
interface Storage {
accounts: Record<string, AccountInfo>;
current?: string;
}
export const client = createClient({
issuer: import.meta.env.VITE_AUTH_URL,
clientID: "web",
});
export const { use: useAuth, provider: AuthProvider } =
createInitializedContext("AuthContext", () => {
const [store, setStore] = makePersisted(
createStore<Storage>({
accounts: {},
}),
{
name: "radiant.auth",
},
);
const location = useLocation();
const params = createMemo(
() => new URLSearchParams(location.hash.substring(1)),
);
const accessToken = createMemo(() => params().get("access_token"));
const refreshToken = createMemo(() => params().get("refresh_token"));
createEffect(async () => {
// if (!result.current && Object.keys(store.accounts).length) {
// result.switch(Object.keys(store.accounts)[0])
// navigate("/")
// }
})
createEffect(async () => {
if (accessToken()) return;
if (Object.keys(store.accounts).length) return;
const redirect = await client.authorize(window.location.origin, "token");
window.location.href = redirect.url
});
createEffect(async () => {
const current = store.current;
const accounts = store.accounts;
if (!current) return;
const match = accounts[current];
if (match) return;
const keys = Object.keys(accounts);
if (keys.length) {
setStore("current", keys[0]);
navigate("/");
return
}
const redirect = await client.authorize(window.location.origin, "token");
window.location.href = redirect.url
});
async function refresh() {
for (const account of [...Object.values(store.accounts)]) {
if (!account.refresh) continue;
const result = await client.refresh(account.refresh, {
access: account.access,
})
if (result.err) {
if ("id" in account)
setStore(produce((state) => {
delete state.accounts[account.id];
}))
continue
};
const tokens = result.tokens || {
access: account.access,
refresh: account.refresh,
}
fetch(import.meta.env.VITE_API_URL + "/account", {
headers: {
authorization: `Bearer ${tokens.access}`,
},
}).then(async (response) => {
await new Promise((resolve) => setTimeout(resolve, 5000));
if (response.ok) {
const result = await response.json();
const info = await result.data;
setStore(
"accounts",
info.id,
reconcile({
...info,
...tokens,
}),
);
}
if (!response.ok)
setStore(
produce((state) => {
delete state.accounts[account.id];
}),
);
})
}
}
onMount(async () => {
if (refreshToken() && accessToken()) {
const result = await fetch(import.meta.env.VITE_API_URL + "/account", {
headers: {
authorization: `Bearer ${accessToken()}`,
},
}).catch(() => { })
if (result?.ok) {
const response = await result.json();
const info = await response.data;
setStore(
"accounts",
info.id,
reconcile({
...info,
access: accessToken(),
refresh: refreshToken(),
}),
);
setStore("current", info.id);
}
window.location.hash = "";
}
await refresh();
})
const navigate = useNavigate();
// const bar = useCommandBar()
// bar.register("auth", async () => {
// return [
// {
// category: "Account",
// title: "Logout",
// icon: IconLogout,
// run: async (bar) => {
// result.logout();
// setStore("current", undefined);
// navigate("/");
// bar.hide()
// },
// },
// {
// category: "Add Account",
// title: "Add Account",
// icon: IconUserAdd,
// run: async () => {
// const redir = await client.authorize(window.location.origin, "token");
// window.location.href = redir.url
// bar.hide()
// },
// },
// ...result.all()
// .filter((item) => item.id !== result.current.id)
// .map((item) => ({
// category: "Account",
// title: "Switch to " + item.email,
// icon: IconUser,
// run: async () => {
// result.switch(item.id);
// navigate("/");
// bar.hide()
// },
// })),
// ]
// })
const result = {
get current() {
return store.accounts[store.current!]!;
},
switch(accountID: string) {
setStore("current", accountID);
},
all() {
return Object.values(store.accounts);
},
refresh,
logout() {
setStore(
produce((state) => {
if (!state.current) return;
delete state.accounts[state.current];
state.current = Object.keys(state.accounts)[0];
}),
);
},
get ready() {
return Boolean(!accessToken() && store.current);
},
};
return result;
});

12
packages/www/src/sst-env.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_AUTH_URL: string
readonly VITE_STAGE: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

View File

@@ -0,0 +1,48 @@
import { theme } from "./theme";
import { styled } from "@macaron-css/solid";
export const FullScreen = styled("div", {
base: {
inset: 0,
zIndex: 0,
display: "flex",
position: "fixed",
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.color.background.d200,
},
variants: {
inset: {
none: {},
header: {
top: theme.headerHeight.root,
},
},
},
})
export const Container = styled("div", {
base: {
backgroundColor: theme.color.background.d100,
borderColor: theme.color.gray.d400,
padding: "64px 80px 48px",
justifyContent: "center",
borderStyle: "solid",
position: "relative",
borderRadius: 12,
alignItems: "center",
maxWidth: 550,
borderWidth: 1,
display: "flex",
},
variants: {
flow: {
column: {
flexDirection: "column"
},
row: {
flexDirection: "row"
}
}
}
})

View File

@@ -0,0 +1,167 @@
import { theme } from "./theme";
import { styled } from "@macaron-css/solid";
import { utility } from "./utility";
import { CSSProperties } from "@macaron-css/core";
export const Text = styled("span", {
base: {
textWrap: "balance"
},
variants: {
leading: {
base: {
lineHeight: 1,
},
normal: {
lineHeight: "normal",
},
loose: {
lineHeight: theme.font.lineHeight,
},
},
align: {
left: {
textAlign: "left"
},
center: {
textAlign: "center"
}
},
spacing: {
none: {
letterSpacing: 0
},
xs: {
letterSpacing: -0.96
},
sm: {
letterSpacing: -0.96
},
md: {
letterSpacing: -1.28
},
lg: {
letterSpacing: -1.28
}
},
code: {
true: {
fontFamily: theme.font.family.code,
},
},
capitalize: {
true: {
textTransform: "capitalize",
},
},
uppercase: {
true: {
letterSpacing: 0.5,
textTransform: "uppercase",
},
},
weight: {
regular: {
fontWeight: theme.font.weight.regular,
},
medium: {
fontWeight: theme.font.weight.medium,
},
semibold: {
fontWeight: theme.font.weight.semibold,
},
},
center: {
true: {
textAlign: "center",
},
},
line: {
true: {
...utility.text.line,
},
},
disableSelect: {
true: {
userSelect: "none",
WebkitUserSelect: "none",
},
},
pre: {
true: {
whiteSpace: "pre-wrap",
overflowWrap: "anywhere",
},
},
underline: {
true: {
textUnderlineOffset: 2,
textDecoration: "underline",
},
},
label: {
true: {
fontWeight: 500,
letterSpacing: 0.5,
textTransform: "uppercase",
fontFamily: theme.font.family.code,
},
},
break: {
true: {
wordBreak: "break-all",
},
false: {},
},
size: (() => {
const result = {} as Record<`${keyof typeof theme.font.size}`, any>;
for (const [key, value] of Object.entries(theme.font.size)) {
result[key as keyof typeof theme.font.size] = {
fontSize: value,
};
}
return result;
})(),
color: (() => {
const record = {} as Record<keyof typeof theme.color.text, CSSProperties>;
for (const [key, _value] of Object.entries(theme.color.text)) {
record[key as keyof typeof record] = {};
}
return record;
})(),
on: (() => {
const record = {} as Record<
keyof typeof theme.color.text.primary,
CSSProperties
>;
for (const [key, _value] of Object.entries(theme.color.text.primary)) {
record[key as keyof typeof record] = {};
}
return record;
})(),
},
compoundVariants: (() => {
const result: any[] = [];
for (const [color, ons] of Object.entries(theme.color.text)) {
for (const [on, value] of Object.entries(ons)) {
result.push({
variants: {
color,
on,
},
style: {
color: value,
},
});
}
}
return result;
})(),
defaultVariants: {
on: "base",
size: "base",
color: "primary",
spacing: "none",
weight: "regular",
},
});

View File

@@ -0,0 +1,426 @@
import { createTheme } from "@macaron-css/core";
const constants = {
colorFadeDuration: "0.15s",
borderRadius: "4px",
textBoldWeight: "600",
iconOpacity: "0.85",
modalWidth: {
sm: "480px",
md: "640px",
lg: "800px",
},
headerHeight: {
root: "68px",
stage: "52px",
},
};
const space = {
px: "1px",
0: "0px",
0.5: "0.125rem",
1: "0.25rem",
1.5: "0.375rem",
2: "0.5rem",
2.5: "0.625rem",
3: "0.75rem",
3.5: "0.875rem",
4: "1rem",
5: "1.25rem",
6: "1.5rem",
7: "1.75rem",
8: "2rem",
9: "2.25rem",
10: "2.5rem",
11: "2.75rem",
12: "3rem",
14: "3.5rem",
16: "4rem",
20: "5rem",
24: "6rem",
28: "7rem",
32: "8rem",
36: "9rem",
40: "10rem",
44: "11rem",
48: "12rem",
52: "13rem",
56: "14rem",
60: "15rem",
64: "16rem",
72: "18rem",
80: "20rem",
96: "24rem",
};
const font = {
lineHeight: "1.6",
family: {
heading: '"Mona Sans Variable", sans-serif',
body: "'Geist Sans', sans-serif",
code: '"Geist Mono Variable", monospace',
},
weight: {
regular: "400",
medium: "500",
semibold: "600",
bold: "700",
extrabold: "800"
},
size: {
mono_xs: "0.6875rem",
xs: "0.75rem",
mono_sm: "0.8125rem",
sm: "0.875rem",
mono_base: "0.9375rem",
base: "1rem",
mono_lg: "1.0625rem",
lg: "1.125rem",
mono_xl: "1.1875rem",
xl: "1.25rem",
mono_2xl: "1.375rem",
"2xl": "1.5rem",
"3xl": "1.875rem",
"4xl": "2.25rem",
"5xl": "3rem",
"6xl": "3.75rem",
"7xl": "4.5rem",
"8xl": "6rem",
"9xl": "8rem",
},
};
const light = (() => {
const gray = {
d100: 'hsla(0,0%,95%)',
d200: 'hsla(0,0%,92%)',
d300: 'hsla(0,0%,90%)',
d400: 'hsla(0,0%,92%)',
d500: 'hsla(0,0%,79%)',
d600: 'hsla(0,0%,66%)',
d700: 'hsla(0,0%,56%)',
d800: 'hsla(0,0%,49%)',
d900: 'hsla(0,0%,40%)',
};
const blue = {
d100: 'hsla(212,100%,97%)',
d200: 'hsla(210,100%,96%)',
d300: 'hsla(210,100%,94%)',
d400: 'hsla(209,100%,90%)',
d500: 'hsla(209,100%,80%)',
d600: 'hsla(208,100%,66%)',
d700: 'hsla(212,100%,48%)',
d800: 'hsla(212,100%,41%)',
d900: 'hsla(211,100%,42%)',
};
const red = {
d100: "hsla(0,100%,97%)",
d200: "hsla(0,100%,96%)",
d300: "hsla(0,100%,95%)",
d400: "hsla(0,90%,92%)",
d500: "hsla(0,82%,85%)",
d600: "hsla(359,90%,71%)",
d700: "hsla(358,75%,59%)",
d800: "hsla(358,70%,52%)",
d900: "hsla(358,66%,48%)",
};
const amber = {
d100: "hsla(39,100%,95%)",
d200: "hsla(44,100%,92%)",
d300: "hsla( 43,96%,90%)",
d400: "hsla(42,100%,78%)",
d500: "hsla(38,100%,71%)",
d600: "hsla( 36,90%,62%)",
d700: "hsla(39,100%,57%)",
d800: "hsla(35,100%,52%)",
d900: "hsla(30,100%,32%)",
};
const green = {
d100: "hsla(120,60%,96%)",
d200: "hsla(120,60%,95%)",
d300: "hsla(120,60%,91%)",
d400: "hsla(122,60%,86%)",
d500: "hsla(124,60%,75%)",
d600: "hsla(125,60%,64%)",
d700: "hsla(131,41%,46%)",
d800: "hsla(132,43%,39%)",
d900: "hsla(133,50%,32%)",
};
const teal = {
d100: "hsla(169,70%,96%)",
d200: "hsla(167,70%,94%)",
d300: "hsla(168,70%,90%)",
d400: "hsla(170,70%,85%)",
d500: "hsla(170,70%,72%)",
d600: "hsla(170,70%,57%)",
d700: "hsla(173,80%,36%)",
d800: "hsla(173,83%,30%)",
d900: "hsla(174,91%,25%)",
};
const purple = {
d100: "hsla(276,100%,97%)",
d200: "hsla(277,87%,97%)",
d300: "hsla(274,78%,95%)",
d400: "hsla(276,71%,92%)",
d500: "hsla(274,70%,82%)",
d600: "hsla(273,72%,73%)",
d700: "hsla(272,51%,54%)",
d800: "hsla(272,47%,45%)",
d900: "hsla(274,71%,43%)",
};
const pink = {
d100: "hsla(330,100%,96%)",
d200: "hsla(340,90%,96%)",
d300: "hsla(340,82%,94%)",
d400: "hsla(341,76%,91%)",
d500: "hsla(340,75%,84%)",
d600: "hsla(341,75%,73%)",
d700: "hsla(336,80%,58%)",
d800: "hsla(336,74%,51%)",
d900: "hsla(336,65%,45%)",
};
const grayAlpha = {
d100: "rgba(0,0,0,0.05)",
d200: "hsla(0,0%,0%,0.08)",
d300: "hsla(0,0%,0%,0.1)",
d400: "hsla(0,0%,0%,0.08)",
d500: "hsla(0,0%,0%,0.21)",
d600: "hsla(0,0%,0%,0.34)",
d700: "hsla(0,0%,0%,0.44)",
d800: "hsla(0,0%,0%,0.51)",
d900: "hsla(0,0%,0%,0.61)",
};
const d1000 = {
gray: 'hsla(0,0%,9%)',
blue: 'hsla(211,100%,15%)',
red: "hsla(355,49%,15%)",
amber: "hsla(20,79%,17%)",
green: "hsla(128,29%,15%)",
teal: "hsla(171,80%,13%)",
purple: "hsla(276,100%,15)",
pink: "hsla(333,74%,15%)",
grayAlpha: " hsla(0,0%,0%,0.91)"
}
const background = {
d100: 'hsla(0,0%,100%)',
d200: 'hsla(0,0%,98%)'
};
const contrastFg = '#ffffff';
const focusBorder = `0 0 0 1px ${grayAlpha.d600}, 0px 0px 0px 4px rgba(0,0,0,0.16)`;
const focusColor = blue.d700
const text = {
primary: {
base: d1000.gray,
surface: gray.d900,
},
info: {
base: d1000.amber,
surface: amber.d900,
},
danger: {
base: d1000.red,
surface: red.d900,
},
};
return {
gray,
blue,
red,
amber,
green,
teal,
purple,
pink,
grayAlpha,
background,
contrastFg,
focusBorder,
focusColor,
d1000,
text
};
})()
const dark = (() => {
const gray = {
d100: "hsla(0,0%,10%)",
d200: "hsla(0,0%,12%)",
d300: "hsla(0,0%,16%)",
d400: "hsla(0,0%,18%)",
d500: "hsla(0,0%,27%)",
d600: "hsla(0,0%,53%)",
d700: "hsla(0,0%,56%)",
d800: "hsla(0,0%,49%)",
d900: "hsla(0,0%,63%)",
};
const blue = {
d100: "hsla(216,50%,12%)",
d200: "hsla(214,59%,15%)",
d300: "hsla(213,71%,20%)",
d400: "hsla(212,78%,23%)",
d500: "hsla(211,86%,27%)",
d600: "hsla(206,100%,50%)",
d700: "hsla(212,100%,48%)",
d800: "hsla(212,100%,41%)",
d900: "hsla(210,100%,66%)",
};
const red = {
d200: "hsla(357,46%,16%)",
d100: "hsla(357,37%,12%)",
d300: "hsla(356,54%,22%)",
d400: "hsla(357,55%,26%)",
d500: "hsla(357,60%,32%)",
d600: "hsla(358,75%,59%)",
d700: "hsla(358,75%,59%)",
d800: "hsla(358,69%,52%)",
d900: "hsla(358,100%,69%)",
};
const amber = {
d100: "hsla(35,100%,8%)",
d200: "hsla(32,100%,10%)",
d300: "hsla(33,100%,15%)",
d400: "hsla(35,100%,17%)",
d500: "hsla(35,91%,22%)",
d600: "hsla(39,85%,49%)",
d700: "hsla(39,100%,57%)",
d800: "hsla(35,100%,52%)",
d900: "hsla(39,90%,50%)",
};
const green = {
d100: "hsla(136,50%,9%)",
d200: "hsla(137,50%,12%)",
d300: "hsla(136,50%,14%)",
d400: "hsla(135,70%,16%)",
d500: "hsla(135,70%,23%)",
d600: "hsla(135,70%,34%)",
d700: "hsla(131,41%,46%)",
d800: "hsla(132,43%,39%)",
d900: "hsla(131,43%,57%)",
};
const teal = {
d100: "hsla(169,78%,7%)",
d200: "hsla(170,74%,9%)",
d300: "hsla(171,75%,13%)",
d400: "hsla(171,85%,13%)",
d500: "hsla(172,85%,20%)",
d600: "hsla(172,85%,32%)",
d700: "hsla(173,80%,36%)",
d800: "hsla(173,83%,30%)",
d900: "hsla(174,90%,41%)",
};
const purple = {
d100: "hsla(283,30%,12%)",
d200: "hsla(281,38%,16%)",
d300: "hsla(279,44%,23%)",
d400: "hsla(277,46%,28%)",
d500: "hsla(274,49%,35%)",
d600: "hsla(272,51%,54%)",
d700: "hsla(272,51%,54%)",
d800: "hsla(272,47%,45%)",
d900: "hsla(275,80%,71%)",
};
const pink = {
d100: "hsla(335,32%,12%)",
d200: "hsla(335,43%,16%)",
d300: "hsla(335,47%,21%)",
d400: "hsla(335,51%,22%)",
d500: "hsla(335,57%,27%)",
d600: "hsla(336,75%,40%)",
d700: "hsla(336,80%,58%)",
d800: "hsla(336,74%,51%)",
d900: "hsla(341,90%,67%)",
};
const grayAlpha = {
d100: "rgba(255,255,255,0.06)",
d200: "hsla(0,0%,100%,0.09)",
d300: "hsla(0,0%,100%,0.13)",
d400: "hsla(0,0%,100%,0.14)",
d500: "hsla(0,0%,100%,0.24)",
d600: "hsla(0,0%,100%,0.51)",
d700: "hsla(0,0%,100%,0.54)",
d800: "hsla(0,0%,100%,0.47)",
d900: "hsla(0,0%,100%,0.61)",
};
const d1000 = {
gray: 'hsla(0,0%,93%)',
blue: 'hsla( 206,100%,96%)',
red: "hsla( 353,90%,96%)",
amber: "hsla( 40,94%,93%))",
green: "hsla(136,73%,94%)",
teal: "hsla(166,71%,93%)",
purple: "hsla(281,73%,96%)",
pink: "hsla( 333,90%,96%)",
grayAlpha: "hsla(0,0%,100%,0.92)"
}
const background = {
d100: 'hsla(0,0%,4%)',
d200: 'hsla(0,0%,0%)'
};
const contrastFg = '#ffffff';
const focusBorder = `0 0 0 1px ${grayAlpha.d600}, 0px 0px 0px 4px rgba(255,255,255,0.24)`;
const focusColor = blue.d900
const text = {
primary: {
base: d1000.gray,
surface: gray.d900,
},
info: {
base: d1000.amber,
surface: amber.d900,
},
danger: {
base: d1000.red,
surface: red.d900,
},
};
return {
gray,
blue,
red,
amber,
green,
teal,
purple,
pink,
grayAlpha,
background,
contrastFg,
focusBorder,
focusColor,
d1000,
text
};
})()
export const [lightClass, theme] = createTheme({
...constants,
space,
font,
color: light,
});
export const darkClass = createTheme(theme, {
...theme,
...constants,
space,
font,
color: dark,
});

View File

@@ -0,0 +1,42 @@
import { theme } from "./theme";
export const utility = {
textLine() {
return {
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
} as any;
},
stack(space: keyof (typeof theme)["space"]) {
return {
display: "flex",
flexDirection: "column",
gap: theme.space[space],
} as any;
},
row(space: keyof (typeof theme)["space"]) {
return {
display: "flex",
gap: theme.space[space],
} as any;
},
text: {
line: {
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
} as any,
label: {
fontWeight: 500,
letterSpacing: 0.5,
textTransform: "uppercase",
fontFamily: theme.font.family.code,
} as any,
pre: {
whiteSpace: "pre-wrap",
overflowWrap: "anywhere",
} as any,
},
};

9
packages/www/sst-env.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}

View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": [
"vite/client"
],
"noEmit": true,
"isolatedModules": true,
"baseUrl": ".",
"paths": {
"@nestri/www/*": [
"./src/*"
]
}
}
}

View File

@@ -0,0 +1,21 @@
import path from "path";
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
import { macaronVitePlugin } from "@macaron-css/vite";
export default defineConfig({
//@ts-expect-error
plugins: [macaronVitePlugin(), solidPlugin()],
server: {
port: 3000,
host: "0.0.0.0",
},
build: {
target: "esnext",
},
resolve: {
alias: {
"@nestri/www": path.resolve(__dirname, "./src"),
},
},
});