Compare commits
No commits in common. "main" and "feat/about-version" have entirely different histories.
main
...
feat/about
11 changed files with 181 additions and 356 deletions
70
READDME-2.md
70
READDME-2.md
|
|
@ -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
214
README.md
|
|
@ -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
|
||||
|
|
|
|||
22
app/page.tsx
22
app/page.tsx
|
|
@ -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>
|
||||
}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (1–10) 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 ? (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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
2
next-env.d.ts
vendored
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "inspannings-monitor",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue