Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ jobs:

- name: Build
run: bun run build
env:
PUBLIC_MAPBOX_ACCESS_TOKEN: ${{ secrets.PUBLIC_MAPBOX_ACCESS_TOKEN }}
9 changes: 8 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
"python.defaultInterpreterPath": "${workspaceFolder}/scraping-service/.venv/bin/python3",
"python.analysis.extraPaths": ["${workspaceFolder}/server/src"],
"python.terminal.activateEnvironment": true,
"cSpell.words": ["pgvector", "sqlc", "sqlcgen", "uuidv", "workos"],
"cSpell.words": [
"pgvector",
"sqlc",
"sqlcgen",
"Timestamptz",
"uuidv",
"workos"
],
"python-envs.pythonProjects": []
}
3 changes: 3 additions & 0 deletions client/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@vitest/browser-playwright": "^4.1.3",
"bits-ui": "^2.16.3",
"clsx": "^2.1.1",
"daisyui": "^5.5.20",
"embla-carousel-svelte": "^8.6.0",
"eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8",
Expand Down
4 changes: 0 additions & 4 deletions client/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
<meta name="text-scale" content="scale" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
Expand Down
3 changes: 2 additions & 1 deletion client/src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const handle: Handle = async ({ event, resolve }) => {
const isAuthRoute = routeId.startsWith('/auth');
const isOnboardingRoute = routeId === '/onboarding';
const isLoginRoute = routeId === '/login';
const isLogoutRoute = routeId === '/logout';

// 1. Check for forced onboarding from query param (e.g. from callback)
const hasOnboardedParam = event.url.searchParams.get('onboarded');
Expand Down Expand Up @@ -69,7 +70,7 @@ export const handle: Handle = async ({ event, resolve }) => {
throw redirect(303, user.onboarded ? '/plans' : '/onboarding');
}

if (!user.onboarded && !isOnboardingRoute && !isAuthRoute) {
if (!user.onboarded && !isOnboardingRoute && !isAuthRoute && !isLogoutRoute) {
throw redirect(303, '/onboarding');
}

Expand Down
62 changes: 29 additions & 33 deletions client/src/lib/components/navbar.svelte
Original file line number Diff line number Diff line change
@@ -1,70 +1,66 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { page } from '$app/state';
import { Button } from '$lib/components/ui/button';
import { scrollState } from '$lib/hooks/scroll.svelte';
import { mode, toggleMode } from 'mode-watcher';
import { Moon, SignOut, Sun } from 'phosphor-svelte';
import { MoonIcon, SignOutIcon, SunIcon } from 'phosphor-svelte';

let { isScrolled = false } = $props();
let scrollY = $state(0);

$effect(() => {
scrollState.y = scrollY;
});

console.log(page.data.user);
const loggedIn = $derived(page.data.user != null);
const onboarded = $derived(page.data.user?.onboarded);
const path = $derived(page.data.path ?? page.url.pathname);
</script>

<svelte:window bind:scrollY />

<nav
class="fixed top-0 z-50 w-full transition-all duration-300 {isScrolled
class="fixed top-0 z-50 w-full transition-all duration-300 {scrollState.isScrolled
? 'bg-[#f5f5f0]/90 backdrop-blur-sm dark:bg-zinc-950/90'
: 'bg-transparent'}"
>
<div class="mx-auto flex max-w-7xl items-center justify-between px-6 py-5 md:px-12">
<a
href="/"
class="font-geist-pixel text-sm font-bold tracking-[0.3em] uppercase transition-colors hover:text-zinc-600 dark:text-zinc-100 dark:hover:text-zinc-400"
href={resolve('/')}
class="text-sm font-bold tracking-[0.3em] uppercase transition-colors hover:text-zinc-600 dark:text-zinc-100 dark:hover:text-zinc-400"
>
PathWeave
</a>
<div class="flex items-center gap-3">
{#if page.url.pathname !== '/login' && !path.includes('plans')}
{#if loggedIn}
<a
href="/plans"
class="text-xs font-bold tracking-[0.2em] text-zinc-500 uppercase transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
>
Plans
</a>
{#if onboarded}
<a href={resolve('/plans')} class="link"> Your Plans </a>
{/if}
{:else}
<a
href="/login"
class="text-xs font-bold tracking-[0.2em] text-zinc-500 uppercase transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
>
Login
</a>
<a href={resolve('/login')} class="link"> Login </a>
{/if}
{/if}
{#if loggedIn}
<form method="POST" action="/logout">
<button
type="submit"
class="inline-flex h-9 w-9 items-center justify-center border border-zinc-300 bg-[#f5f5f0]/90 text-zinc-900 transition-colors hover:border-zinc-500 hover:text-zinc-900 dark:border-zinc-900 dark:bg-zinc-900/80 dark:text-zinc-200 dark:hover:border-zinc-400"
aria-label="Log out"
title="Log out"
>
<SignOut class="h-3.5 w-3.5" weight="bold" />
</button>
<form method="POST" action="/logout" class="leading-none">
<Button type="submit" variant="ghost" size="icon" aria-label="Log out" title="Log out">
<SignOutIcon weight="bold" />
</Button>
</form>
{/if}
<button
type="button"
class="inline-flex h-9 w-9 items-center justify-center border border-zinc-300 bg-[#f5f5f0]/90 text-zinc-900 transition-colors hover:border-zinc-500 hover:text-zinc-900 dark:border-zinc-900 dark:bg-zinc-900/80 dark:text-zinc-200 dark:hover:border-zinc-400"
<Button
variant="ghost"
size="icon"
aria-label="Toggle light and dark mode"
aria-pressed={mode.current === 'dark'}
onclick={toggleMode}
>
{#if mode.current === 'dark'}
<Sun class="h-3.5 w-3.5" weight="bold" />
<SunIcon weight="bold" />
{:else}
<Moon class="h-3.5 w-3.5" weight="bold" />
<MoonIcon weight="bold" />
{/if}
</button>
</Button>
</div>
</div>
</nav>
2 changes: 1 addition & 1 deletion client/src/lib/components/onboarding/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import coursesRaw from '$lib/data/courses.json';
import programsRaw from '$lib/data/programs.json';

export const FIELD_SHELL =
'rounded-none border border-zinc-200 bg-[#f5f5f0] px-3 py-2 text-xs text-zinc-900 transition-colors focus-within:border-zinc-500 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100';
'rounded-2xl border border-zinc-200 bg-[#f5f5f0] px-3 py-2 text-xs text-zinc-900 transition-colors focus-within:border-zinc-500 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100';

export const LABEL_CLASS =
'flex items-center gap-1.5 text-[10px] font-bold tracking-[0.2em] uppercase text-zinc-600 dark:text-zinc-400';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@
await tick();
inputEl?.focus();
}

function labelForValue(value: string) {
return suggestions.find((s) => s.value === value)?.label ?? value;
}
</script>

<div class="space-y-2">
Expand All @@ -66,7 +62,7 @@
onclick={() => inputEl?.focus()}
>
{#each values as item, i (item + i)}
<button type="button" class={cn(chipClass, 'shrink-0')} onclick={() => onRemove(i)}>
<button type="button" class={cn(chipClass, 'shrink-0 rounded-2xl')} onclick={() => onRemove(i)}>
{item}
<span class="opacity-60">×</span>
</button>
Expand Down Expand Up @@ -121,7 +117,7 @@
onOpenAutoFocus={(e) => {
e.preventDefault();
}}
class="w-(--bits-popover-anchor-width) border border-zinc-200 bg-[#f5f5f0] p-0 dark:border-zinc-800 dark:bg-zinc-900"
class="w-(--bits-popover-anchor-width) overflow-hidden rounded-2xl border border-zinc-200 bg-[#f5f5f0] p-0 dark:border-zinc-800 dark:bg-zinc-900"
>
<Command.Root shouldFilter={false}>
<Command.List class="max-h-48 overflow-y-auto">
Expand Down
38 changes: 19 additions & 19 deletions client/src/lib/components/onboarding/onboarding-form.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import Navbar from '$lib/components/navbar.svelte';
import { get } from 'svelte/store';
import { type SuperValidated, superForm } from 'sveltekit-superforms';
import { superForm, type SuperValidated } from 'sveltekit-superforms';
import { zod4Client } from 'sveltekit-superforms/adapters';
import Navbar from '$lib/components/navbar.svelte';
import OnboardingProgress from './onboarding-progress.svelte';
import OnboardingStepHeading from './onboarding-step-heading.svelte';
import OnboardingStepGoals from './onboarding-step-learning.svelte';
import OnboardingStepIdentity from './onboarding-step-identity.svelte';
import OnboardingStepGoals from './onboarding-step-learning.svelte';
import OnboardingStepLife from './onboarding-step-life.svelte';
import OnboardingStepNav from './onboarding-step-nav.svelte';
import { onboardingSchema, step1Schema, step2Schema, type OnboardingFormData } from './schema.js';
Expand All @@ -25,18 +26,23 @@

let step = $state(1);

function applyFieldErrors(
currentErrors: Parameters<typeof errors.update>[0] extends (arg: infer T) => unknown ? T : never,
fieldErrors: Record<string, string[] | undefined>
) {
const mutableErrors = currentErrors as Record<string, string[] | undefined>;
for (const [k, v] of Object.entries(fieldErrors)) {
mutableErrors[k] = v;
}
return currentErrors;
}

function validateStep1() {
const d = get(formData);
const r = step1Schema.safeParse(d);
if (!r.success) {
const fe = r.error.flatten().fieldErrors;
errors.update((e) => {
for (const [k, v] of Object.entries(fe)) {
// @ts-ignore
e[k] = v;
}
return e;
});
errors.update((e) => applyFieldErrors(e, fe));
return false;
}
return true;
Expand All @@ -47,13 +53,7 @@
const r = step2Schema.safeParse(d);
if (!r.success) {
const fe = r.error.flatten().fieldErrors;
errors.update((e) => {
for (const [k, v] of Object.entries(fe)) {
// @ts-ignore
e[k] = v;
}
return e;
});
errors.update((e) => applyFieldErrors(e, fe));
return false;
}
return true;
Expand All @@ -75,7 +75,7 @@
function back() {
errors.clear();
if (step === 1) {
goto('/');
goto(resolve('/'));
return;
}
step -= 1;
Expand All @@ -84,7 +84,7 @@

<form method="POST" class="contents" use:enhance>
<main
class="dotmatrix-bg relative flex min-h-screen flex-col text-zinc-900 selection:bg-brand-purple selection:text-white dark:text-zinc-100"
class=" relative flex min-h-screen flex-col text-zinc-900 selection:bg-brand-purple selection:text-white dark:text-zinc-100"
>
<Navbar />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<Popover.Content
align="start"
sideOffset={0}
class="w-(--bits-popover-anchor-width) border border-zinc-200 bg-[#f5f5f0] p-0 dark:border-zinc-800 dark:bg-zinc-900"
class="w-(--bits-popover-anchor-width) overflow-hidden rounded-2xl border border-zinc-200 bg-[#f5f5f0] p-0 dark:border-zinc-800 dark:bg-zinc-900"
>
<Command.Root shouldFilter={false}>
<Command.Input placeholder="Search program..." bind:value={searchQuery} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import type { SuperForm } from 'sveltekit-superforms';
import * as Form from '$lib/components/ui/form/index.js';
import { Slider } from '$lib/components/ui/slider/index.js';
import { cn } from '$lib/utils.js';
import OnboardingChipsField from './onboarding-chips-field.svelte';
import { LABEL_CLASS, professorBand, teachingStyleBand, COURSES } from './constants.js';
import type { OnboardingFormData } from './schema.js';
Expand Down
16 changes: 9 additions & 7 deletions client/src/lib/components/onboarding/onboarding-step-nav.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Form from '$lib/components/ui/form/index.js';
import { ArrowLeft, ArrowRight } from 'phosphor-svelte';

Expand All @@ -18,27 +19,28 @@
<div
class="mt-12 flex items-center justify-between border-t border-zinc-200 pt-8 dark:border-zinc-800"
>
<button
<Button
type="button"
class="inline-flex items-center gap-2 text-xs font-bold tracking-[0.15em] text-zinc-500 uppercase transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
size="sm"
variant="ghost"
onclick={onBack}
>
<ArrowLeft class="h-3.5 w-3.5" weight="bold" />
Back
</button>
</Button>

{#if step < 3}
<button
<Button
type="button"
class="inline-flex h-10 items-center gap-3 border-2 border-zinc-900 bg-zinc-900 px-6 text-xs font-bold tracking-[0.15em] text-white uppercase transition-all hover:bg-zinc-800 dark:border-zinc-100 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
size="sm"
onclick={onNext}
>
Next
<ArrowRight class="h-3.5 w-3.5" weight="bold" />
</button>
</Button>
{:else}
<Form.Button
class="inline-flex h-10 items-center gap-3 border-2 border-zinc-900 bg-zinc-900 px-6 text-xs font-bold tracking-[0.15em] text-white uppercase transition-all hover:bg-zinc-800 dark:border-zinc-100 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
size="sm"
disabled={submitting}
>
{submitting ? 'Saving…' : 'Finish'}
Expand Down
13 changes: 11 additions & 2 deletions client/src/lib/components/plans/add-plan.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
<script>
<script lang="ts">
import { cn } from '$lib/utils';
import { theme } from 'mode-watcher';
import { PlusIcon } from 'phosphor-svelte';
let { index } = $props();

const tints = ['bg-purple', 'bg-blue', 'bg-teal', 'bg-rose'] as const;
const tint = $derived(tints[index % tints.length] + theme.current == 'light' ? ' 200' : ' 800');
</script>

<div
class="flex size-90 flex-col border border-primary bg-[#f5f5f0] p-8 opacity-40 hover:scale-[102%] hover:opacity-75 dark:bg-secondary"
class={cn(
'text-md hover:bg-bg-brand-purple/50 relative flex aspect-square size-full w-full flex-col rounded-2xl border-2 border-dashed border-primary bg-secondary/50 p-5 font-medium text-secondary-foreground drop-shadow-xs drop-shadow-[#c5aaf7] transition ease-out text-shadow-xs hover:scale-105 hover:border-none hover:ring-2 hover:ring-[#AE87F7] active:translate-y-0.75 sm:size-90 md:size-70 lg:size-90 dark:drop-shadow-[#a87ff4]',
tint
)}
>
<PlusIcon size={40} />
</div>
Loading