Compare commits

..

No commits in common. "main" and "feat/about-version" have entirely different histories.

11 changed files with 181 additions and 356 deletions

View file

@ -1,70 +0,0 @@
# Inspannings Monitor Energie Management Tool
## Portfolio samenvatting
De Inspannings Monitor is een webapplicatie gericht op het inzichtelijk maken van energieverbruik en belastbaarheid.
De applicatie is ontwikkeld als praktijkgericht project om moderne webdevelopment en gebruikersgerichte oplossingen te combineren.
## Doel
Gebruikers hebben vaak onvoldoende inzicht in hun energieverdeling gedurende de dag.
Deze applicatie helpt bij:
- plannen van activiteiten
- monitoren van energie
- voorkomen van overbelasting
## Belangrijk
Deze applicatie is een **wellness tool** en geen medisch hulpmiddel.
## Mijn rol
- Concept en ontwerp
- Fullstack development
- UX/UI ontwerp
- Database en authenticatie
- Deployment
## Functionaliteiten
- Gebruikerslogin (Supabase Auth)
- Dagplanning en activiteitenbeheer
- Energie-check-ins
- Overzicht van belasting vs herstel
- Dashboard weergave
- Server-side sessiebeheer
## Technologie stack
- Next.js
- React
- TypeScript
- Supabase (auth + database)
- Vercel (hosting)
## Architectuur (kort)
- Next.js frontend + server routes
- Supabase voor auth en data
- Server-side validatie van sessies
- Cloud deployment via Vercel
## Live demo
👉 Voeg hier je Vercel link toe
## Screenshots
👉 Voeg hier screenshots toe (dashboard, check-in, etc.)
## Privacy & security
- Authenticatie via Supabase
- Server-side sessiebeheer
- Geen medische claims
- (Toekomst) row-level security (RLS)
## Wat ik geleerd heb
- Integratie van Supabase in moderne apps
- Werken met gebruikersauthenticatie
- Opzetten van een data-driven applicatie
- UX voor laagdrempelige interactie
## Toekomstige verbeteringen
- Uitbreiding dashboard
- Betere data-analyse
- Notificaties
- Mobile optimalisatie
## Repository
https://github.com/madhura68/inspannings-monitor

214
README.md
View file

@ -1,70 +1,172 @@
# Inspannings Monitor Energie Management Tool
# Inspannings Monitor
## Portfolio samenvatting
De Inspannings Monitor is een webapplicatie gericht op het inzichtelijk maken van energieverbruik en belastbaarheid.
De applicatie is ontwikkeld als praktijkgericht project om moderne webdevelopment en gebruikersgerichte oplossingen te combineren.
Wellness-first webapp voor volwassen individuele gebruikers die hun energie
willen plannen, uitvoeren en evalueren.
## Doel
Gebruikers hebben vaak onvoldoende inzicht in hun energieverdeling gedurende de dag.
Deze applicatie helpt bij:
- plannen van activiteiten
- monitoren van energie
- voorkomen van overbelasting
## Productrichting
## Belangrijk
Deze applicatie is een **wellness tool** en geen medisch hulpmiddel.
`Inspannings Monitor` wordt bewust gebouwd als `wellness/self-management`
product, niet als medisch hulpmiddel. Release 1 blijft smal:
## Mijn rol
- Concept en ontwerp
- Fullstack development
- UX/UI ontwerp
- Database en authenticatie
- Deployment
- alleen individuele gebruikers
- alleen Nederlands
- geen delen met zorgverleners of naasten
- geen AI of medische workflows in de MVP
## Functionaliteiten
- Gebruikerslogin (Supabase Auth)
- Dagplanning en activiteitenbeheer
- Energie-check-ins
- Overzicht van belasting vs herstel
- Dashboard weergave
- Server-side sessiebeheer
## Huidige scope
## Technologie stack
- Next.js
- React
- e-mail/wachtwoord-auth via Supabase
- protected dashboard met server-side sessiecontrole
- ochtendcheck-in voor energiescore en slaapkwaliteit van vandaag
- eenvoudig dagbudget en energieniveau op basis van de ochtendscore
- dashboardweergave van check-instatus, energieniveau en dagbudget
- planningsfundering met activiteitenmodel, categorieën en skip-redenen in Supabase
- planningpagina voor vandaag met activiteit toevoegen en directe lijstweergave
- statusflows voor activiteiten van vandaag (`gepland`, `uitgevoerd`, `overgeslagen`, `aangepast`)
- contextuele evaluatievelden voor overgeslagen en aangepaste activiteiten
- dagoverzicht op planning met gepland versus werkelijk en statusverdeling
- autocomplete op basis van eerdere eigen activiteiten voor sneller hergebruik in planning
- energiemeter met lopend totaal ten opzichte van het dagbudget
- niet-blokkerende waarschuwing bij budgetoverschrijding in planning en dashboard
- eerste unit tests voor budget- en meterlogica via `Vitest`
- korte onboardingflow voor eerste voorkeuren
- instellingen voor profieltekst, avatar, taal, timezone, reminders en zichtbaarheid van energiepunten
- `shadcn/ui` foundation voor knoppen, formulieren, kaarten en meldingen
- `Dusk`-theme met dark-mode prioriteit, semantische oppervlakken en verbeterde focus-/toegankelijkheidsstijlen
## Stack
- Next.js 16 App Router
- React 19
- TypeScript
- Supabase (auth + database)
- Vercel (hosting)
- Tailwind CSS
- shadcn/ui component foundation
- Vercel als hostingdoel
- Supabase voor database en authenticatie
## Architectuur (kort)
- Next.js frontend + server routes
- Supabase voor auth en data
- Server-side validatie van sessies
- Cloud deployment via Vercel
## Snel lokaal starten
## Live demo
👉 Voeg hier je Vercel link toe
1. Kopieer `.env.example` naar `.env.local`
2. Vul in:
- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`
- optioneel: `NEXT_PUBLIC_ENABLE_TEST_WIZARD=true` voor de interne wizard-testpagina
3. Installeer dependencies met `npm install`
4. Start lokaal met `npm run dev`
## Screenshots
👉 Voeg hier screenshots toe (dashboard, check-in, etc.)
## Scripts
## Privacy & security
- Authenticatie via Supabase
- Server-side sessiebeheer
- Geen medische claims
- (Toekomst) row-level security (RLS)
- `npm run dev`
- `npm run build`
- `npm run start`
- `npm run lint`
- `npm run test`
- `npm run seed:demo-users`
## Wat ik geleerd heb
- Integratie van Supabase in moderne apps
- Werken met gebruikersauthenticatie
- Opzetten van een data-driven applicatie
- UX voor laagdrempelige interactie
## Supabase Auth configuratie
## Toekomstige verbeteringen
- Uitbreiding dashboard
- Betere data-analyse
- Notificaties
- Mobile optimalisatie
1. Zet in Supabase Dashboard aan:
- Email/password auth
- Self-signup
- Email confirmation verplicht
2. Voeg redirect URLs toe voor:
- `http://localhost:3000/auth/confirm`
- je Vercel productie-URL
- eventuele preview-URL's die je wilt testen
## Repository
https://github.com/madhura68/inspannings-monitor
## Omgevingsbestanden
Gebruik alleen `.env.example` als template. Lokale bestanden zoals `.env` en
`.env.local` horen niet in git thuis.
## Supabase database migraties
De huidige app gebruikt onder meer deze migraties:
- `supabase/migrations/20260418_create_profiles_and_user_settings.sql`
- `supabase/migrations/20260418_add_onboarding_seen_to_profiles.sql`
- `supabase/migrations/20260418_create_morning_check_ins.sql`
- `supabase/migrations/20260418_add_budget_fields_to_morning_check_ins.sql`
- `supabase/migrations/20260419_create_activities_and_reference_data.sql`
- `supabase/migrations/20260419_add_profile_details_and_avatar_storage.sql`
Voer deze SQL uit in de Supabase SQL Editor of via de Supabase CLI voordat je
de profile-, check-in- en budgetlagen lokaal test.
## Demo-gebruikers seeden
Er staat nu een seedscript klaar voor fictieve demo-gebruikers op basis van de
persona-set in [inspannings-monitor-08-gebruikerspersonas-v01.docx](/Users/janpetervisser/Development/third/docs/inspannings-monitor-08-gebruikerspersonas-v01.docx).
Benodigd:
- `NEXT_PUBLIC_SUPABASE_URL`
- `SUPABASE_SECRET_KEY` voorkeur
- `SUPABASE_SERVICE_ROLE_KEY` mag nog als legacy alias
- `DEMO_USER_PASSWORD`
Uitvoeren:
1. zorg dat de profiel- en storage-migraties al in Supabase zijn uitgevoerd
2. zet de drie env-vars lokaal
3. run `npm run seed:demo-users`
Voor bestaande lokale setups accepteert het script tijdelijk ook:
- `NEXT_PUBLIC_SUPABASE_SERVICE_KEY`
Maar mijn advies is om voor seedscripts alleen deze nette niet-public adminnaam te gebruiken:
- `SUPABASE_SECRET_KEY`
De seeddata zelf staat in:
- [demo-personas.mjs](/Users/janpetervisser/Development/third/scripts/seed/demo-personas.mjs)
Een dry run kan ook:
- `npm run seed:demo-users -- --dry-run`
## UI foundation
De app gebruikt `shadcn/ui` bovenop `Tailwind CSS` als herbruikbare basis voor
knoppen, formulieren, kaarten en meldingen. De theme tokens staan centraal in
`app/globals.css`, zodat kleur, focus-states en componentgedrag consistenter blijven.
Voor feedback na redirects of server actions krijgt de app nu standaard de voorkeur
voor `sonner`-toasts boven losse inline statusmeldingen.
De actuele visuele richting is `Dusk`: warme paper-achtergronden, gedempte indigo
als primaire kleur, dark mode als standaard en semantische `success`/`warning`
tokens voor rustige, niet-medische feedback.
## Navigatie
De app gebruikt nu een gedeelde topnavigatie:
- links: `About`, `Dashboard`, `Planning`, `Check-in`
- rechts: `Account` en `Theme`
`/` is de publieke `About`-pagina met informatie over de maker en de scope van
de app. In het `Account`-menu komen ingelogde gebruikers bij `Instellingen` en
`Uitloggen`; uitgelogde gebruikers zien daar `Inloggen` en `Account aanmaken`.
## Interne wizard-test
Er is een interne testwizard beschikbaar op `/wizard-test` om een toekomstige
generieke wizard-core te valideren. Deze route en de dashboardknop worden alleen
zichtbaar als `NEXT_PUBLIC_ENABLE_TEST_WIZARD=true` staat.
## CI/CD
- `CI`: GitHub Actions draait automatisch `lint`, `test` en `build` op pull requests en op `main`
- `CD`: Vercel deployt automatisch previews voor branches/PR's en productie vanaf `main`
- Uitwerking: [docs/inspannings-monitor-cicd-en-deploy.md](/Users/janpetervisser/Development/third/docs/inspannings-monitor-cicd-en-deploy.md)
## Documentatie
- Hoofdset specificaties en plannen: [docs/README.md](/Users/janpetervisser/Development/third/docs/README.md)
- Dusk theme-specificatie: [inspannings-monitor-09-dusk-theme-specificatie-v01.md](/Users/janpetervisser/Development/third/docs/inspannings-monitor-09-dusk-theme-specificatie-v01.md)
- Technische architectuur: [inspannings-monitor-05-technische-architectuur-en-implementatie-v01.docx](/Users/janpetervisser/Development/third/docs/inspannings-monitor-05-technische-architectuur-en-implementatie-v01.docx)
- Implementatieplan en backlog: [inspannings-monitor-06-implementatieplan-en-backlog-v01.docx](/Users/janpetervisser/Development/third/docs/inspannings-monitor-06-implementatieplan-en-backlog-v01.docx)
## Eerstvolgende bouwstappen
1. `ST-105` RLS-policy tests en hardening afronden
2. logging en monitoring toevoegen
3. rate limiting op kritieke mutaties

View file

@ -64,22 +64,12 @@ export default async function Home({ searchParams }: HomePageProps) {
title="Over de maker en de app"
description="Inspannings Monitor is een rustige wellness-first webapp voor volwassenen die hun energie willen plannen, uitvoeren en evalueren zonder medische claims of overmatige frictie."
aside={
<div className="flex flex-wrap gap-2">
<Link
href="https://jp-visser.nl"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-full border border-border/80 bg-card/84 px-4 py-2 text-sm font-medium text-foreground shadow-[var(--shadow-1)] transition-colors hover:bg-secondary"
>
Curriculum vitae
</Link>
<Link
href="/specificatie"
className="inline-flex items-center rounded-full border border-border/80 bg-card/84 px-4 py-2 text-sm font-medium text-foreground shadow-[var(--shadow-1)] transition-colors hover:bg-secondary"
>
Specificatie
</Link>
</div>
<Link
href="/planning"
className="inline-flex items-center rounded-full border border-border/80 bg-card/84 px-4 py-2 text-sm font-medium text-foreground shadow-[var(--shadow-1)] transition-colors hover:bg-secondary"
>
Bekijk planning
</Link>
}
/>

View file

@ -1,138 +0,0 @@
import { redirect } from "next/navigation";
import { getAuthState } from "@/lib/auth/session";
import { AppShell } from "@/components/navigation/app-shell";
import { PageIntro } from "@/components/navigation/page-intro";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
export const dynamic = "force-dynamic";
const chapters = [
{
number: "01",
title: "Productkader en positionering",
version: "v06",
summary:
"Legt vast wat Inspannings Monitor wel en niet is. De app positioneert zich expliciet als wellness/self-management tool, zonder medische claims of zorgverlenerrollen. De intended use is energieplanning en dagstructuur voor volwassenen. Non-intended use — zoals diagnostiek, behandeling of klinische besluitvorming — is bewust uitgesloten en wordt bewaakt via claim-guardrails in tekst, UI en onboarding.",
points: [
"Productnaam: Inspannings Monitor",
"Positionering: wellness / self-management",
"Doelgroep: volwassen individuele gebruikers",
"Voertaal release 1: Nederlands (nl-NL)",
"Geen medische claims, geen zorgverlenerrollen",
"Expliciete non-intended use vastgelegd als guardrail",
],
},
{
number: "02",
title: "Functionele specificatie MVP",
version: "v06",
summary:
"Beschrijft de MVP in toetsbare functionele requirements. De kern is een plan-doe-evalueer-dagflow: ochtendcheck-in met energiescore en slaapkwaliteit, activiteitenplanning met een licht energiebudget, en een dagafsluiting. Elke stap is bewust klein gehouden om de cognitieve belasting laag te houden. Release 1 bevat geen AI, geen deelfunctionaliteit en geen medische workflows.",
points: [
"Ochtendcheck-in: energiescore (110) en slaapkwaliteit",
"Dagbudget afgeleid van energiescore",
"Activiteitenplanning met duur, impact en prioriteit",
"Statussen: gepland → uitgevoerd / aangepast / overgeslagen",
"Dashboard met dag-overzicht en energiemeter",
"Geen sharing, AI of medische workflows in de MVP",
],
},
{
number: "03",
title: "Privacy, security en safety baseline",
version: "v02",
summary:
"Bundelt de minimale randvoorwaarden voor privacy, informatiebeveiliging en productveiligheid. Gebruikersdata blijft strikt gescheiden via Row Level Security in Supabase. Authenticatie vereist e-mailverificatie. Gevoelige sleutels worden nooit in de frontend geplaatst. De baseline is ontworpen als minimumset die uitbreidbaar is als het product richting een medisch spoor beweegt.",
points: [
"Row Level Security: gebruikers zien alleen eigen data",
"Supabase Auth met verplichte e-mailverificatie",
"Service-role key alleen server-side, nooit in de frontend",
"HTTPS-only via Vercel, geen gevoelige data in URL-params",
"Geen opslag van medische of klinische gegevens",
"Baseline uitbreidbaar richting NEN 7510 / MDR bij medisch spoor",
],
},
{
number: "04",
title: "Roadmap wellness naar medisch",
version: "v02",
summary:
"Laat zien hoe Inspannings Monitor gecontroleerd kan doorgroeien naar een medisch product, zonder dat dit ongemerkt in de wellness-release binnensluipt. De roadmap onderscheidt drie fasen: verankerde wellness-MVP, optionele zorgverlenerlaag en een apart medisch productspoor. Elke fase kent eigen vereisten voor regelgeving, documentatie en technische architectuur.",
points: [
"Fase 1: wellness MVP — volledig buiten MDR-scope",
"Fase 2: optionele zorgverlenerlaag met expliciete scope-bewaking",
"Fase 3: apart medisch productspoor met eigen regulatoir traject",
"Faseovergangen vereisen bewuste go/no-go beslissing",
"Geen sluipende scope-uitbreiding zonder documentatieverandering",
"Pad opengehouden zonder verplichting om het te bewandelen",
],
},
{
number: "05",
title: "Technische architectuur en implementatie",
version: "v01",
summary:
"Beschrijft de technische implementatielaag van de wellness-first MVP als zelfstandig architectuurdocument. De stack is bewust compact gehouden: Next.js App Router met Server Actions, Supabase voor auth en data, en Vercel voor hosting en CI/CD. De architectuur is opgezet met duidelijke laagscheiding zodat uitbreiding naar een medisch spoor later geïsoleerd kan plaatsvinden.",
points: [
"Stack: Next.js 16 (App Router) + React 19 + TypeScript",
"Database en auth: Supabase (PostgreSQL + Row Level Security)",
"UI: shadcn/ui + Tailwind CSS, Dusk dark-mode thema",
"Hosting en deploys: Vercel met GitHub Actions CI",
"Server Actions voor alle formmutaties, geen aparte API-laag",
"Migraties via supabase/migrations, seeding via npm-script",
],
},
];
export default async function SpecificatiePage() {
const authState = await getAuthState();
if (!authState.isAuthenticated) {
redirect("/login");
}
return (
<AppShell contentClassName="space-y-8">
<PageIntro
eyebrow="Documentatie"
title="Productspecificatie"
description="Samenvatting van de vijf kerndocumenten die de scope, vereisten en architectuur van Inspannings Monitor vastleggen."
/>
<div className="space-y-6">
{chapters.map((chapter) => (
<Card key={chapter.number} elevation="raised" className="pb-0">
<CardHeader className="pb-0">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted-foreground">
Hoofdstuk {chapter.number} · {chapter.version}
</p>
<CardTitle className="text-2xl text-foreground">
{chapter.title}
</CardTitle>
<CardDescription className="max-w-3xl text-sm leading-7 text-muted-foreground">
{chapter.summary}
</CardDescription>
</CardHeader>
<CardContent className="pb-6">
<div className="mt-4 grid gap-2 sm:grid-cols-2">
{chapter.points.map((point) => (
<Card key={point} tone="subtle" className="pb-0 shadow-none">
<CardContent className="px-4 py-3 text-sm leading-6 text-muted-foreground">
{point}
</CardContent>
</Card>
))}
</div>
</CardContent>
</Card>
))}
</div>
</AppShell>
);
}

View file

@ -10,15 +10,6 @@ import {
} from "lucide-react";
import { signOutAction } from "@/app/auth-actions";
import type { AuthState } from "@/lib/auth/session";
import { ProfileAvatar } from "@/components/profile/profile-avatar";
import type { NavProfile } from "@/lib/profile/service";
function formatNavDisplayName(displayName: string | null): string | null {
if (!displayName) return null;
const parts = displayName.trim().split(/\s+/);
if (parts.length < 2) return displayName;
return `${parts[0][0]}. ${parts[parts.length - 1]}`;
}
import {
DropdownMenu,
DropdownMenuContent,
@ -30,27 +21,14 @@ import {
type AccountMenuProps = {
authState: AuthState;
navProfile: NavProfile | null;
};
export function AccountMenu({ authState, navProfile }: AccountMenuProps) {
const showAvatar = authState.isAuthenticated && navProfile?.avatarUrl;
const navLabel = formatNavDisplayName(navProfile?.displayName ?? null) ?? "Account";
export function AccountMenu({ authState }: AccountMenuProps) {
return (
<DropdownMenu>
<DropdownMenuTrigger aria-label="Account menu">
{showAvatar ? (
<ProfileAvatar
avatarUrl={navProfile!.avatarUrl}
displayName={navProfile!.displayName}
email={authState.email}
size="xs"
/>
) : (
<CircleUserRoundIcon className="size-4" />
)}
<span className="hidden sm:inline">{navLabel}</span>
<CircleUserRoundIcon className="size-4" />
<span className="hidden sm:inline">Account</span>
</DropdownMenuTrigger>
<DropdownMenuContent>
{authState.isConfigured ? (

View file

@ -1,6 +1,5 @@
import type { ReactNode } from "react";
import { getAuthState } from "@/lib/auth/session";
import { getNavProfileForCurrentUser } from "@/lib/profile/service";
import { BottomNav } from "@/components/navigation/bottom-nav";
import { TopNav } from "@/components/navigation/top-nav";
import { cn } from "@/lib/utils";
@ -15,14 +14,11 @@ export async function AppShell({
contentClassName,
}: AppShellProps) {
const authState = await getAuthState();
const navProfile = authState.userId
? await getNavProfileForCurrentUser(authState.userId)
: null;
return (
<main className="app-page">
<div className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-8">
<TopNav authState={authState} navProfile={navProfile} />
<TopNav authState={authState} />
<div className={cn("flex-1", contentClassName)}>{children}</div>
<BottomNav />
</div>

View file

@ -1,10 +1,8 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { AuthState } from "@/lib/auth/session";
import type { NavProfile } from "@/lib/profile/service";
import { AccountMenu } from "@/components/navigation/account-menu";
import {
isActivePath,
@ -16,32 +14,22 @@ import { cn } from "@/lib/utils";
type TopNavProps = {
authState: AuthState;
navProfile: NavProfile | null;
};
export function TopNav({ authState, navProfile }: TopNavProps) {
export function TopNav({ authState }: TopNavProps) {
const pathname = usePathname();
const useCompactBottomNav = shouldUseBottomNav(pathname);
return (
<header className="sticky top-4 z-40">
<div className="flex flex-wrap items-center gap-4 rounded-[var(--radius-4xl)] border border-border/70 bg-card/86 px-5 py-4 shadow-[var(--shadow-2)] backdrop-blur">
<Link href="/" className="flex shrink-0 items-center gap-3">
<Image
src="/icon.svg"
alt="Inspannings Monitor icoon"
width={36}
height={36}
className="shrink-0 rounded-xl"
/>
<div>
<span className="block text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
Inspannings Monitor
</span>
<span className="mt-1 block text-base font-semibold tracking-[-0.02em] text-foreground">
Wellness-first dagflow
</span>
</div>
<Link href="/" className="shrink-0">
<span className="block text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
Inspannings Monitor
</span>
<span className="mt-1 block text-base font-semibold tracking-[-0.02em] text-foreground">
Wellness-first dagflow
</span>
</Link>
<nav
@ -76,7 +64,7 @@ export function TopNav({ authState, navProfile }: TopNavProps) {
<div className="ml-auto flex flex-wrap items-center gap-2">
<ThemeMenu />
<AccountMenu authState={authState} navProfile={navProfile} />
<AccountMenu authState={authState} />
</div>
</div>
</header>

View file

@ -4,12 +4,11 @@ type ProfileAvatarProps = {
avatarUrl: string | null;
displayName: string | null;
email?: string | null;
size?: "xs" | "sm" | "md" | "lg";
size?: "sm" | "md" | "lg";
className?: string;
};
const avatarSizeClasses = {
xs: "size-6 text-[10px]",
sm: "size-12 text-sm",
md: "size-16 text-base",
lg: "size-20 text-xl",
@ -52,7 +51,7 @@ export function ProfileAvatar({
<div
aria-label={label}
className={cn(
"relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-primary/50 bg-muted/70 font-semibold tracking-[0.08em] text-foreground shadow-[var(--shadow-1)]",
"relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border/70 bg-muted/70 font-semibold tracking-[0.08em] text-foreground shadow-[var(--shadow-1)]",
avatarSizeClasses[size],
className,
)}

View file

@ -500,23 +500,3 @@ export async function ensureProfileBundleForCurrentUser(): Promise<ProfileBundle
export async function getProfileBundleForCurrentUser(): Promise<ProfileBundle | null> {
return ensureProfileBundleForCurrentUser();
}
export type NavProfile = {
avatarUrl: string | null;
displayName: string | null;
};
export async function getNavProfileForCurrentUser(userId: string): Promise<NavProfile> {
const supabase = await createClient();
const { data } = await supabase
.from("profiles")
.select("avatar_path, display_name")
.eq("id", userId)
.maybeSingle();
return {
avatarUrl: await getProfileAvatarUrl(data?.avatar_path ?? null),
displayName: data?.display_name ?? null,
};
}

2
next-env.d.ts vendored
View file

@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View file

@ -1,6 +1,6 @@
{
"name": "inspannings-monitor",
"version": "0.1.1",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",