-
-
Notifications
You must be signed in to change notification settings - Fork 0
Replaces geist and styled-components with Tailwind #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
939083e
035c688
551c440
b15c1da
15441e5
9f44cb5
0a1aaa8
3d93c9a
3e099d6
5205d4f
869f1f4
2ecc2c4
5696b94
9f048bc
b145f2c
062fab7
b2a1849
807da62
acc9023
34f1d3c
956cdf3
647e05e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| # Hemolog - Cursor Rules | ||
|
|
||
| Hemophilia medication tracking app. Next.js 16 + TypeScript + Firebase + TanStack Query. | ||
|
|
||
| ## Tech Stack | ||
| Next.js 16.1.1 (App Router) | TypeScript (strict) | Tailwind 4.0 | TanStack Query v5 | Formik | Firebase Auth + Firestore (lite SDK) | @silk-hq/components | @tabler/icons-react | Recharts | react-hot-toast | Biome | pnpm 10.28 | Node 22.21 | ||
|
|
||
| ## Structure | ||
| ``` | ||
| src/ | ||
| ├── app/ # Pages + API routes (src/app/api/) | ||
| ├── components/ # shared/, home/, emergency/ | ||
| └── lib/ | ||
| ├── db/ # Client Firestore ops (firestore-lite) | ||
| ├── admin-db/ # Server Firestore ops (Firebase Admin) | ||
| ├── hooks/ # React Query hooks | ||
| ├── types/ # TypeScript types | ||
| ├── firebase.ts # Client auth (getAuth()) | ||
| ├── firebase-admin.ts # Server admin (adminFirestore, auth) | ||
| ├── firestore-lite.ts # DB helpers | ||
| └── auth.tsx # useAuth() hook | ||
| ``` | ||
|
|
||
| ## Code Style | ||
| - Imports: `@/` prefix for `src/` | ||
| - Formatting: 2 spaces, single quotes, semicolons asNeeded, ES5 trailing commas | ||
| - Naming: Components=PascalCase, hooks=use*, functions=camelCase, types=PascalCase | ||
|
|
||
| ## Key Patterns | ||
|
|
||
| ### Client Component with SSR Safety | ||
| ```tsx | ||
| 'use client' | ||
| import { useState, useEffect } from 'react' | ||
| import { useAuth } from '@/lib/auth' | ||
|
|
||
| export default function Component({ userId }: { userId: string }) { | ||
| const [mounted, setMounted] = useState(false) | ||
| const { user } = useAuth() | ||
| useEffect(() => { setMounted(true) }, []) | ||
| if (!mounted) return null | ||
| return <div>...</div> | ||
| } | ||
| ``` | ||
|
|
||
| ### Firestore Query (Client) | ||
| ```tsx | ||
| import { getDocuments, where } from '@/lib/firestore-lite' | ||
| const treatments = await getDocuments<TreatmentType>( | ||
| 'infusions', | ||
| where('user.uid', '==', userUid), | ||
| where('deletedAt', '==', null) // Soft delete filter | ||
| ) | ||
| ``` | ||
|
|
||
| ### Database Layer (src/lib/db/*.ts) | ||
| ```tsx | ||
| import { createDocument, getDocuments, softDeleteDocument, where } from '@/lib/firestore-lite' | ||
|
|
||
| export interface TreatmentType { | ||
| uid?: string; date: string; medication: Medication; user: AttachedUserType; deletedAt: string | null | ||
| } | ||
|
|
||
| export const createTreatment = (data: TreatmentType) => createDocument('infusions', data) | ||
| export const fetchTreatments = (userUid: string) => getDocuments<TreatmentType>( | ||
| 'infusions', where('user.uid', '==', userUid), where('deletedAt', '==', null) | ||
| ) | ||
| ``` | ||
|
|
||
| ### Query Hook (src/lib/hooks/use*Query.ts) | ||
| ```tsx | ||
| import { useQuery } from '@tanstack/react-query' | ||
| export const treatmentKeys = { | ||
| all: ['treatments'] as const, | ||
| list: (uid: string) => [...treatmentKeys.all, 'list', uid] as const, | ||
| } | ||
| export function useTreatmentsQuery(userUid: string) { | ||
| return useQuery({ | ||
| queryKey: treatmentKeys.list(userUid), | ||
| queryFn: () => fetchTreatments(userUid), | ||
| enabled: !!userUid, | ||
| staleTime: 10_000, | ||
| }) | ||
| } | ||
| ``` | ||
|
|
||
| ### Mutation Hook with Optimistic Updates (src/lib/hooks/use*Mutations.ts) | ||
| ```tsx | ||
| import { useMutation, useQueryClient } from '@tanstack/react-query' | ||
| import toast from 'react-hot-toast' | ||
|
|
||
| export function useTreatmentMutations() { | ||
| const queryClient = useQueryClient() | ||
| const createMutation = useMutation({ | ||
| mutationFn: createTreatment, | ||
| onMutate: async (newTreatment) => { | ||
| await queryClient.cancelQueries({ queryKey: treatmentKeys.all }) | ||
| const previous = queryClient.getQueryData(treatmentKeys.list(newTreatment.user.uid)) | ||
| queryClient.setQueryData(treatmentKeys.list(newTreatment.user.uid), (old) => | ||
| old ? [{ ...newTreatment, uid: `temp-${Date.now()}` }, ...old] : [newTreatment] | ||
| ) | ||
| return { previous, userUid: newTreatment.user.uid } | ||
| }, | ||
| onError: (err, _, ctx) => { | ||
| if (ctx?.previous) queryClient.setQueryData(treatmentKeys.list(ctx.userUid), ctx.previous) | ||
| toast.error(`Failed: ${err.message}`) | ||
| }, | ||
| onSuccess: () => toast.success('Treatment logged!'), | ||
| onSettled: (_, __, vars) => queryClient.invalidateQueries({ queryKey: treatmentKeys.list(vars.user.uid) }), | ||
| }) | ||
| return { createTreatment: createMutation.mutate, isCreating: createMutation.isPending } | ||
| } | ||
| ``` | ||
|
|
||
| ### API Route (src/app/api/*/route.ts) | ||
| ```tsx | ||
| import type { NextRequest } from 'next/server' | ||
| import { getAllTreatmentsByApiKey } from '@/lib/admin-db/treatments' | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| try { | ||
| const apikey = new URL(request.url).searchParams.get('apikey') | ||
| if (!apikey) throw { message: 'Missing api key' } | ||
| const { treatments, error } = await getAllTreatmentsByApiKey(apikey) | ||
| if (error) throw error | ||
| return Response.json(treatments) | ||
| } catch (error: unknown) { | ||
| const msg = error && typeof error === 'object' && 'message' in error ? String(error.message) : 'Error' | ||
| return Response.json({ error: msg }, { status: 500 }) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Data Models | ||
|
|
||
| **Collections**: `users`, `infusions` (treatments), `feedback` | ||
|
|
||
| **Types** (src/lib/types/): | ||
| - `UserType`: Full user (uid, email, name, alertId, apiKey, medication, etc.) | ||
| - `Person`: Public user data | ||
| - `AttachedUserType`: User reference in documents | ||
| - `TreatmentType`: Treatment record with `deletedAt` for soft delete | ||
| - `TreatmentTypeEnum`: PROPHY | BLEED | PREVENTATIVE | ANTIBODY | ||
|
|
||
| ## Firestore Lite Functions | ||
| `getDocument<T>(coll, id)` | `getDocuments<T>(coll, ...constraints)` | `createDocument<T>(coll, data)` | `setDocument<T>(coll, id, data, merge?)` | `updateDocument<T>(coll, id, data)` | `softDeleteDocument(coll, id)` | `deleteDocument(coll, id)` | ||
|
|
||
| Re-exports: `where`, `limit`, `orderBy` | ||
|
|
||
| ## Tailwind Theme (src/app/globals.css) | ||
| Primary: `primary-500` (#ff062c) | Success: `success-500` (#48bb78) | Warning: `warning-500` (#0070f3) | ||
|
|
||
| ## Scripts | ||
| ```bash | ||
| pnpm dev # Dev server | ||
| pnpm firebase # Emulators (Auth:9099, Firestore:8082, UI:8081) | ||
| pnpm lint:fix # Biome fix | ||
| pnpm build # Production build | ||
| pnpm seed # Seed test data | ||
| ``` | ||
|
|
||
| ## Environment | ||
| ``` | ||
| NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY, NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, NEXT_PUBLIC_FIREBASE_PROJECT_ID | ||
| FIREBASE_PRIVATE_KEY, FIREBASE_CLIENT_EMAIL | ||
| NEXT_PUBLIC_USE_EMULATORS=true # Dev mode | ||
| ``` | ||
|
|
||
| ## Critical Rules | ||
| 1. Use `firebase/firestore/lite` (not full SDK) - smaller bundle | ||
| 2. All mutations need optimistic updates + rollback | ||
| 3. Soft delete via `deletedAt` field, filter with `where('deletedAt', '==', null)` | ||
| 4. Client DB ops → `src/lib/db/`, Server → `src/lib/admin-db/` | ||
| 5. SSR safety: check `typeof window === 'undefined'` or use mounted pattern | ||
| 6. Toast feedback: `react-hot-toast` for all user actions | ||
| 7. Run `pnpm lint:fix` before commits |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "[javascript, typescript]": { | ||
| "editor.defaultFormatter": "biomejs.biome" | ||
| }, | ||
| "[json, jsonc]": { | ||
| "editor.defaultFormatter": "vscode.json-language-features" | ||
| }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. VSCode settings use invalid language-specific override syntaxLow Severity The VSCode settings file uses |
||
| "editor.codeActionsOnSave": { | ||
| "source.organizeImports.biome": "explicit", | ||
| "source.fixAll.biome": "explicit" | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pnpm version mismatch between CI and package.json
Medium Severity
The
package.jsonwas updated to usepnpm@10.28.0but the GitHub Actions workflow still specifiesversion: 10.13.1in both thesetup-environmentandrun-testsjobs. This version mismatch between local development and CI could cause different dependency resolution behavior, lockfile format incompatibilities, or feature differences between environments.Additional Locations (1)
.github/workflows/main.yml#L75-L76