From fcc59f0afb5f7d31eb0cfa84c75ed3120246c725 Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Sat, 30 May 2026 06:59:24 -0400 Subject: [PATCH] Add: - Initial dashboard draft - Login page - supabase magic link auth - env parsing --- deno.lock | 50 ++++++++++ src/lib/server/env.ts | 16 ++++ src/lib/server/supabase.ts | 8 ++ src/lib/styles/style.css | 143 +++++++++++++++++++++++++++-- src/routes/+layout.svelte | 8 -- src/routes/+page.svelte | 7 ++ src/routes/dashboard/+page.svelte | 51 ++++++++++ src/routes/dashboard/Config.svelte | 14 +++ src/routes/login/+page.server.ts | 29 ++++++ src/routes/login/+page.svelte | 19 ++++ 10 files changed, 329 insertions(+), 16 deletions(-) create mode 100644 src/lib/server/env.ts create mode 100644 src/lib/server/supabase.ts create mode 100644 src/routes/dashboard/+page.svelte create mode 100644 src/routes/dashboard/Config.svelte create mode 100644 src/routes/login/+page.server.ts create mode 100644 src/routes/login/+page.svelte diff --git a/deno.lock b/deno.lock index 4f41806..4077776 100644 --- a/deno.lock +++ b/deno.lock @@ -3,6 +3,7 @@ "specifiers": { "npm:@eslint/compat@^2.0.4": "2.1.0_eslint@10.4.0", "npm:@eslint/js@^10.0.1": "10.0.1_eslint@10.4.0", + "npm:@supabase/supabase-js@^2.106.2": "2.106.2", "npm:@sveltejs/adapter-auto@^7.0.1": "7.0.1_@sveltejs+kit@2.61.1__@sveltejs+vite-plugin-svelte@7.1.2___svelte@5.55.10___vite@8.0.14____@types+node@24.12.4___@types+node@24.12.4__svelte@5.55.10__typescript@6.0.3__vite@8.0.14___@types+node@24.12.4__@types+node@24.12.4_@sveltejs+vite-plugin-svelte@7.1.2__svelte@5.55.10__vite@8.0.14___@types+node@24.12.4__@types+node@24.12.4_@types+node@24.12.4_svelte@5.55.10_typescript@6.0.3_vite@8.0.14__@types+node@24.12.4", "npm:@sveltejs/kit@^2.57.0": "2.61.1_@sveltejs+vite-plugin-svelte@7.1.2__svelte@5.55.10__vite@8.0.14___@types+node@24.12.4__@types+node@24.12.4_svelte@5.55.10_typescript@6.0.3_vite@8.0.14__@types+node@24.12.4_@types+node@24.12.4", "npm:@sveltejs/vite-plugin-svelte@7": "7.1.2_svelte@5.55.10_vite@8.0.14__@types+node@24.12.4_@types+node@24.12.4", @@ -258,6 +259,51 @@ "@standard-schema/spec@1.1.0": { "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" }, + "@supabase/auth-js@2.106.2": { + "integrity": "sha512-VcAjUErkHkhC5Jaf+g/G1qbkQrFh8edaCdHa7pxJmHUjkWKjT7UnYCtPA89XV0N0GIYRkEqJZw5V62CtOxTmBQ==", + "dependencies": [ + "tslib" + ] + }, + "@supabase/functions-js@2.106.2": { + "integrity": "sha512-oRnr0QrL8H+zTO1YyQ1QjiHZU/957jvubbxSJTUm2XLAgzoGGV9Tahfyd+uvLsBLRVmXLtpU3oyCjdQIvkGMOA==", + "dependencies": [ + "tslib" + ] + }, + "@supabase/phoenix@0.4.2": { + "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==" + }, + "@supabase/postgrest-js@2.106.2": { + "integrity": "sha512-tDOzyPgp9pIRMR2x6C9+uDSJrnXSzxLtt3d7nC+Lrsy3jnJDHYfdQC/xcRyhJE/TOBJ0heSqRKR3UmejDjZxsw==", + "dependencies": [ + "tslib" + ] + }, + "@supabase/realtime-js@2.106.2": { + "integrity": "sha512-LdRGT7DNhyZkPjubUv5bSdAZ0jSEX8wTHvx7htj7+K59TOZRvz4TuQK7tL2RWxyIZVeFMRluL04SzWS61rKnUA==", + "dependencies": [ + "@supabase/phoenix", + "tslib" + ] + }, + "@supabase/storage-js@2.106.2": { + "integrity": "sha512-xgKCSYuev1YarV+iVqr+zlfgSyremnJtn8T0NCT8L4XmMv1CLtESc0Q6kNp8+mKWdX/8ND0nzm7OMKx08kwNAw==", + "dependencies": [ + "iceberg-js", + "tslib" + ] + }, + "@supabase/supabase-js@2.106.2": { + "integrity": "sha512-2/RZ/1fmJx/MRSEDG2Xk8+J4JVk5clM9V0uSI6kUTrcS32KA89DtqI5RUOC9r6mzY3WBC9qexLjssIHjbLyVJA==", + "dependencies": [ + "@supabase/auth-js", + "@supabase/functions-js", + "@supabase/postgrest-js", + "@supabase/realtime-js", + "@supabase/storage-js" + ] + }, "@sveltejs/acorn-typescript@1.0.10_acorn@8.16.0": { "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", "dependencies": [ @@ -935,6 +981,9 @@ "graceful-fs@4.2.11": { "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "iceberg-js@0.8.1": { + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==" + }, "ignore@5.3.2": { "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" }, @@ -1528,6 +1577,7 @@ "dependencies": [ "npm:@eslint/compat@^2.0.4", "npm:@eslint/js@^10.0.1", + "npm:@supabase/supabase-js@^2.106.2", "npm:@sveltejs/adapter-auto@^7.0.1", "npm:@sveltejs/kit@^2.57.0", "npm:@sveltejs/vite-plugin-svelte@7", diff --git a/src/lib/server/env.ts b/src/lib/server/env.ts new file mode 100644 index 0000000..2800600 --- /dev/null +++ b/src/lib/server/env.ts @@ -0,0 +1,16 @@ +import { env } from "$env/dynamic/public"; + +function required(name: string): string { + const value = env[name]; + + if (!value) { + throw new Error(`Missing required environment variable ${name}`); + } + return value; +} + +export const envConfig = { + PUBLIC_SUPABASE_URL: required("PUBLIC_SUPABASE_URL"), + PUBLIC_SUPABASE_PUBLISHABLE_KEY: required("PUBLIC_SUPABASE_PUBLISHABLE_KEY"), +}; + diff --git a/src/lib/server/supabase.ts b/src/lib/server/supabase.ts new file mode 100644 index 0000000..ac81b21 --- /dev/null +++ b/src/lib/server/supabase.ts @@ -0,0 +1,8 @@ +import { createClient } from '@supabase/supabase-js' +import { envConfig } from "./env.ts"; + +export const supabase = createClient( + envConfig.PUBLIC_SUPABASE_URL, + envConfig.PUBLIC_SUPABASE_PUBLISHABLE_KEY, +); + diff --git a/src/lib/styles/style.css b/src/lib/styles/style.css index c98abfb..6c1ba7c 100644 --- a/src/lib/styles/style.css +++ b/src/lib/styles/style.css @@ -1,4 +1,21 @@ +:root { + --color-bg: #fff; + --color-text: #222; + --color-accent: #1eaedb; + --color-input-bg: #f1f1f1; + color-scheme: light; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #222; + --color-text: #fff; + --color-input-bg: #333; + color-scheme: dark; + } +} + *, *::before, *::after { @@ -31,7 +48,7 @@ h6 { } .site-logo { - color: #222; + color: var(--color-text); flex: 0 0 auto; font-family: 'VT323', monospace; font-size: clamp(32px, 9vw, 40px); @@ -40,7 +57,7 @@ h6 { } .site-logo:hover { - color: #1eaedb; + color: var(--color-accent); text-decoration: underline; } @@ -56,7 +73,7 @@ h6 { } .site-nav a { - color: #222; + color: var(--color-text); font-family: 'VT323', monospace; font-size: clamp(16px, 5vw, 24px); font-weight: 400; @@ -65,21 +82,21 @@ h6 { } .site-nav a:hover { - color: #1eaedb; + color: var(--color-accent); text-decoration: underline; } .site-nav .login-link { background: transparent; - border: 1px solid #222; - color: #222; + border: 1px solid var(--color-text); + color: var(--color-text); padding: clamp(2px, 1vw, 4px) clamp(8px, 2.5vw, 14px); } .site-nav .login-link:hover { background: transparent; - border-color: #1eaedb; - color: #1eaedb; + border-color: var(--color-accent); + color: var(--color-accent); text-decoration: underline; } @@ -149,6 +166,116 @@ li::before { } body { + background: var(--color-bg); + color: var(--color-text); font-size: 18px; font-family: 'VT323', monospace; } + +textarea#text-input { + background: var(--color-input-bg); + border: 1px solid var(--color-text); + color: var(--color-text); + font-family: 'VT323', monospace; + font-size: 20px; + height: 320px; + line-height: 1.3; + min-height: 320px; + resize: vertical; + width: 100%; +} + +textarea#text-input:focus { + border-color: var(--color-accent); + outline: 0; +} + +.login-page { + align-items: center; + display: flex; + justify-content: center; + min-height: calc(100vh - 96px); + padding-bottom: 48px; + padding-top: 48px; +} + +.login-card { + border: 1px solid var(--color-text); + max-width: 520px; + padding: clamp(24px, 5vw, 48px); + width: 100%; +} + +.login-card .display-heading { + margin-bottom: 12px; +} + +.login-copy { + font-size: 24px; + line-height: 1.25; + margin-bottom: 28px; +} + +.login-form { + display: flex; + flex-direction: column; + gap: 14px; +} + +.login-form label { + font-size: 24px; + margin-bottom: 0; +} + +.login-form input[type='email'] { + background: var(--color-input-bg); + border: 1px solid var(--color-text); + color: var(--color-text); + font-family: 'VT323', monospace; + font-size: 24px; + height: auto; + margin-bottom: 8px; + padding: 12px 14px; + width: 100%; +} + +.login-form input[type='email']:focus { + border-color: var(--color-accent); + outline: 0; +} + +.login-form button { + align-self: flex-start; + margin-top: 8px; +} + +.dashboard-button-row { + display: flex; + gap: 16px; + margin-bottom: 24px; +} + +.dashboard-button-column { + display: flex; + flex: 1; + justify-content: center; +} + +.dashboard-button { + width: 220px; +} + +button { + background: transparent; + border: 1px solid var(--color-text); + color: var(--color-text); + font-size: 20px; +} + +button:active, +button:focus, +.dashboard-button.active { + border-color: var(--color-accent); + color: var(--color-accent); + outline: 0; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 452af54..e264056 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,14 +7,6 @@ - -
{@render children()}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index ea970c9..6795c90 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,3 +1,10 @@ +
diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte new file mode 100644 index 0000000..72e799f --- /dev/null +++ b/src/routes/dashboard/+page.svelte @@ -0,0 +1,51 @@ + + + + +
+

Welcome to gitKeep...

+ +
+
+ +
+ +
+ +
+ +
+ +
+
+ + {#if activePanel === "configuration"} + + {/if} + + +
diff --git a/src/routes/dashboard/Config.svelte b/src/routes/dashboard/Config.svelte new file mode 100644 index 0000000..1ac8040 --- /dev/null +++ b/src/routes/dashboard/Config.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts new file mode 100644 index 0000000..a6c4bb3 --- /dev/null +++ b/src/routes/login/+page.server.ts @@ -0,0 +1,29 @@ + import { fail } from '@sveltejs/kit'; + import { supabase } from '$lib/server/supabase.ts'; + import type { Actions } from './$types'; + +export const actions: Actions = { + default: async ({request, url}) =>{ + const form = await request.formData(); + const email = String(form.get("email") ?? ""); + const baseUrl = url.origin; + + if (!email){ + return fail(400, {message: "Missing email"}) + } + const { error } = await supabase.auth.signInWithOtp({ + email: email, + options: { + shouldCreateUser: true, + emailRedirectTo: `${baseUrl}/dashboard`, + }, + }) + + if (error){ + return fail(400, error.message); + } + + return {success: true} + + } +} diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..d9eb671 --- /dev/null +++ b/src/routes/login/+page.svelte @@ -0,0 +1,19 @@ + + +