Skip to content

Commit 845a92f

Browse files
committed
feat: cards
1 parent 3161961 commit 845a92f

37 files changed

+719
-2153
lines changed

.github/copilot-instructions.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
## Purpose
2+
3+
This file contains targeted, repository-specific guidance for AI coding agents working on the `bible-time` Nuxt 4 application. Use it to discover the architecture, developer workflows, conventions, and integration points that matter for code changes.
4+
5+
## Quick overview
6+
7+
- Tech: Nuxt 4 (Vue 3), TypeScript, Pinia, Nuxt UI (component library), Supabase (server & client), @nuxtjs/i18n, Zod.
8+
- Frontend lives in `app/` (pages, components, composables, stores). Server endpoints live in `server/api/`.
9+
- Shared TypeScript types live under `shared/` and generated Supabase types are placed at `shared/types/database-generated.types.ts` (see `package.json` script `supabase:types`).
10+
11+
## Important files to open first
12+
13+
- `nuxt.config.ts` — modules, aliases (`#server` -> `./server`), i18n config, sitemap sources, supabase types link.
14+
- `package.json` — dev scripts: `pnpm dev`, `pnpm build`, `pnpm preview`, `pnpm lint`, `pnpm typecheck`, and `pnpm supabase:types`.
15+
- `app/utils/supabase.ts` and `server/utils/supabase.ts` — central supabase helpers (error handling). Mirror patterns when adding new server/api code.
16+
- `shared/types/` — shared type contracts (database types and general types).
17+
- `server/api/` — server endpoints. Example files: `customers.ts`, `members.ts`, `mails.ts`, `notifications.ts`, `__sitemap__/urls.ts`.
18+
19+
## Architecture notes & common patterns
20+
21+
- Data flow: client pages (in `app/pages/`) and composables call server endpoints (`server/api/*`) which use Supabase utilities in `server/utils/supabase.ts`. Shared types in `shared/` are the contract between client and server.
22+
- Supabase types are generated with the `supabase` CLI and kept under `shared/types/`. Keep `nuxt.config.ts` setting `supabase.types` in sync if moving files.
23+
- Server routes follow Nuxt server API conventions. Add `server/api/yourname.get.ts` or `.post.ts` and use `defineEventHandler` / `createError` patterns (see `supabaseService.handleError`).
24+
- UI: components use Nuxt UI and composables live under `app/composables/`. Pinia stores are in `app/stores/`.
25+
26+
### UI library (Nuxt UI)
27+
28+
- This project uses Nuxt UI as its primary component library (installed as `@nuxt/ui` in `nuxt.config.ts`). Nuxt UI components are auto-registered and commonly use a `U` prefix (for example `UButton`, `UDropdownMenu`).
29+
- Local, app-specific components live under `app/components/` (see `app/components/home/*` and `app/components/people/*`). Prefer reusing Nuxt UI primitives and compose them into local components when you need project-specific behavior.
30+
- Use `.client` / `.server` component suffixes when you need to split rendering between server and client (this repo already contains examples: `app/components/home/HomeChart.client.vue` and `HomeChart.server.vue`).
31+
- When typing or interacting with Nuxt UI components in TypeScript, prefer using lightweight Vue `Component` types or the library's own types if available; avoid `any`.
32+
33+
## Developer workflows & commands
34+
35+
- Install: `pnpm install` (pnpm v10+ required per `package.json`).
36+
- Dev server: `pnpm dev` (runs `nuxt dev` and serves on http://localhost:3000).
37+
- Build: `pnpm build` then `pnpm preview` to locally preview the production build.
38+
- Linting: `pnpm lint` and auto-fix with `pnpm lint:fix`.
39+
- Typecheck: `pnpm typecheck` (uses `nuxt typecheck` / `vue-tsc`).
40+
- Generate Supabase types: `pnpm supabase:types` (invokes `supabase gen types` and writes to `shared/types/database-generated.types.ts`).
41+
42+
## Conventions to follow (observed in repo)
43+
44+
- TypeScript everywhere; avoid `any` where possible. When a temporary `any` is needed, prefer creating a narrow typed interface and add a TODO to refine.
45+
- Linter conventions: unused variables should be prefixed with `_` to satisfy the project's ESLint rules (e.g. `_unusedVar`). Avoid leaving declared-but-unused symbols.
46+
- Components use `<script setup lang="ts">` and Vue composition API patterns (see `eslint.config.mjs` rules).
47+
- Centralize Supabase error handling via `supabaseService.handleError` to ensure consistent HTTP errors.
48+
49+
## Integration points & external dependencies
50+
51+
- Supabase: configured via `@nuxtjs/supabase` (see `nuxt.config.ts`) — runtime auth redirect options and types configured there.
52+
- Sitemap: generated from server APIs: `/api/__sitemap__/urls?type=events` and `?type=people`.
53+
- i18n: uses `@nuxtjs/i18n` with `langDir: 'locales'` and `restructureDir: 'app'`. Adding pages/components must consider route prefixing for locales.
54+
55+
## Troubleshooting & debugging tips
56+
57+
- If runtime types are inconsistent, re-run `pnpm supabase:types` and `pnpm prepare` / `pnpm install` to refresh generated types and `.nuxt` artifacts.
58+
- To debug server endpoints, inspect `server/api/*` and `server/utils/supabase.ts`. Server logs appear in the terminal running `pnpm dev`.
59+
- For UI issues, check `app/components/`, `app/composables/`, and `app/stores/` in that order — composables often encapsulate data logic used across pages.
60+
61+
## Where to add new functionality
62+
63+
- New pages: `app/pages/your-page.vue` or nested route folders. Use `pages/<name>/index.vue` or dynamic `[slug].vue` as shown in `app/pages/people/[slug].vue`.
64+
- New API routes: create `server/api/<name>.get.ts` or `.post.ts`. Reuse `supabaseService.handleError`.
65+
- New shared types: add to `shared/types/` and, if needed, update generated supabase types.
66+
67+
## Quick examples
68+
69+
- Add a GET API route: `server/api/hello.get.ts` -> export default defineEventHandler(async () => { return { hello: 'world' } })
70+
- Use supabase error handler in server code:
71+
- import { supabaseService } from '#server/utils/supabase'
72+
- on PostgrestError: `supabaseService.handleError(error, 500, 'Supabase error')`
73+
74+
---
75+
76+
If anything above is unclear or you want more details about a particular area (build, auth flow, data model, or test setup), tell me which area to expand and I will update this file accordingly.

app/components/DataTable.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<UInput
44
v-model="globalFilter"
55
class="max-w-sm"
6-
icon="i-lucide-search"
6+
icon="i-lucide:search"
77
:placeholder="searchLabel ? searchLabel : $t('general.search')"
88
/>
99

@@ -34,7 +34,7 @@
3434
color="neutral"
3535
variant="outline"
3636
:label="$t('general.display')"
37-
trailing-icon="i-lucide-settings-2"
37+
trailing-icon="i-lucide:settings-2"
3838
/>
3939
</UDropdownMenu>
4040
<UButton

app/components/NotificationsSlideover.vue

Lines changed: 0 additions & 41 deletions
This file was deleted.

app/components/UserMenu.vue

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class="data-[state=open]:bg-elevated"
1414
:ui="{ trailingIcon: 'text-dimmed' }"
1515
:label="collapsed ? undefined : user.display_name"
16-
:trailing-icon="collapsed ? undefined : 'i-lucide-chevrons-up-down'"
16+
:trailing-icon="collapsed ? undefined : 'i-lucide:chevrons-up-down'"
1717
/>
1818

1919
<template #chip-leading="{ item }">
@@ -68,13 +68,13 @@ const items = computed<DropdownMenuItem[][]>(() => [
6868
],
6969
[
7070
{
71-
icon: 'i-lucide-user',
71+
icon: 'i-lucide:user',
7272
label: t('settings.profile'),
7373
to: localePath('/settings/profile')
7474
},
7575
{
7676
exact: true,
77-
icon: 'i-lucide-settings',
77+
icon: 'i-lucide:settings',
7878
label: t('settings.title'),
7979
to: localePath('/settings')
8080
}
@@ -92,15 +92,15 @@ const items = computed<DropdownMenuItem[][]>(() => [
9292
slot: 'flag',
9393
type: 'checkbox'
9494
})),
95-
icon: 'i-lucide-globe',
95+
icon: 'i-lucide:globe',
9696
label: t('settings.language')
9797
},
9898
appearanceDropdownItem.value,
9999
{
100100
children: [
101-
{ icon: 'i-lucide-monitor', key: 'system', label: t('settings.system') },
102-
{ icon: 'i-lucide-sun', key: 'light', label: t('settings.light') },
103-
{ icon: 'i-lucide-moon', key: 'dark', label: t('settings.dark') }
101+
{ icon: 'i-lucide:monitor', key: 'system', label: t('settings.system') },
102+
{ icon: 'i-lucide:sun', key: 'light', label: t('settings.light') },
103+
{ icon: 'i-lucide:moon', key: 'dark', label: t('settings.dark') }
104104
].map((item) => ({
105105
checked: colorMode.preference === item.key,
106106
icon: item.icon,
@@ -111,13 +111,13 @@ const items = computed<DropdownMenuItem[][]>(() => [
111111
},
112112
type: 'checkbox'
113113
})),
114-
icon: 'i-lucide-sun-moon',
114+
icon: 'i-lucide:sun-moon',
115115
label: t('settings.theme')
116116
}
117117
],
118118
[
119119
{
120-
icon: 'i-lucide-log-out',
120+
icon: 'i-lucide:log-out',
121121
label: t('settings.log-out'),
122122
onSelect: () => {
123123
userStore.signOut()

app/components/customers/AddModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<UModal v-model:open="open" title="New customer" description="Add a new customer to the database">
3-
<UButton icon="i-lucide-plus" label="New customer" />
3+
<UButton icon="i-lucide:plus" label="New customer" />
44

55
<template #body>
66
<UForm :state="state" :schema="schema" class="space-y-4" @submit="onSubmit">
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<template>
2+
<UCard>
3+
<h3 class="text-sm font-semibold mb-2">{{ $t('people.related-events') }}</h3>
4+
<div class="space-y-2">
5+
<div v-if="events.length === 0" class="text-sm text-zinc-500">
6+
{{ $t('people.no-related-events') }}
7+
</div>
8+
<ul v-else class="space-y-2">
9+
<li v-for="(e, idx) in events" :key="e?.slug || idx">
10+
<ULink :to="localePath(`/events/${e.slug}`)">
11+
<div class="flex items-center justify-between gap-3">
12+
<div class="flex items-center gap-3">
13+
<img
14+
v-if="e?.cover_url"
15+
alt=""
16+
:src="e.cover_url"
17+
class="w-12 h-8 rounded object-cover"
18+
/>
19+
<div>
20+
<div class="text-sm font-medium">
21+
{{ i18nStore.translate(String(e?.title)) }}
22+
</div>
23+
</div>
24+
</div>
25+
<div>
26+
<span class="inline-block text-xs px-2 py-0.5 rounded bg-zinc-100 text-zinc-700">
27+
{{ e.relation_kind }}
28+
</span>
29+
</div>
30+
</div>
31+
</ULink>
32+
</li>
33+
</ul>
34+
</div>
35+
</UCard>
36+
</template>
37+
<script setup lang="ts">
38+
defineProps<{
39+
events: (Pick<Tables<'events'>, 'cover_url' | 'slug' | 'title'> & {
40+
relation_kind: Enums<'event_relation'>
41+
})[]
42+
}>()
43+
44+
const i18nStore = useI18nStore()
45+
const localePath = useLocalePath()
46+
</script>

app/components/home/HomeChart.client.vue

Lines changed: 0 additions & 111 deletions
This file was deleted.

app/components/home/HomeChart.server.vue

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)