Next.js + React frontend for PawGle — a community platform for reuniting lost pets with their owners through AI-powered image recognition, geographic alerts, and in-app chat.
Live site: https://pawgle.neokit.app
Backend API: https://pawglebackend.neokit.app (WhoamiI00/PawGleBackend)
- Register your pets and download a printable QR collar tag.
- Search by photo to identify a pet you've found.
- File lost/found reports with a geo-tagged location and photo.
- Get auto-matched when someone files a found report that looks like your missing pet — chat opens automatically in-app.
- Subscribe to nearby alerts ("ping me if a pet is reported within 5 km of my home").
- Chat with finders / owners inside the app (text + photos) — email is only a "you've got a new message, click to view" pager.
| Layer | Tech |
|---|---|
| Framework | Next.js 16 (App Router) + React 18 |
| Styling | Tailwind CSS 3 + CSS variables for light/dark themes |
| UI primitives | shadcn/ui (Radix) + lucide-react |
| Animation | Framer Motion |
| Maps | Leaflet + React Google Maps API |
| Image tooling | Fabric.js + imgly background removal |
| Auth | JWT access token (in memory) + httpOnly refresh cookie |
| Error tracking | Sentry (Next.js SDK) |
| Host | Cloudflare Pages (edge runtime for dynamic routes) |
┌──────────────────────────────────────────────┐
│ Cloudflare Pages (edge) │
│ │
│ ○ Static pages: /, /login, /chat, /alerts, │
│ /user, /pet/map, ... │
│ ƒ Dynamic edge: /chat/[id], /found/[id] │
└──────────────────┬───────────────────────────┘
│ fetch(withCredentials)
▼
┌─────────────────────────┐
│ pawglebackend.neokit. │
│ app (Django) │
└─────────────────────────┘
Both domains share the eTLD+1 neokit.app, so the refresh cookie is scoped to .neokit.app with SameSite=Lax — no third-party-cookie issues.
git clone https://github.com/WhoamiI00/PawGleFrontend.git
cd PawGleFrontend
npm install# Backend API base. The trailing slash is auto-normalized in code, but
# it's good practice to include it.
NEXT_PUBLIC_BACKEND_API_PORT=http://localhost:8000/
# Google OAuth client (must match the backend's GOOGLE_OAUTH_CLIENT_ID).
NEXT_PUBLIC_GOOGLE_CLIENT_ID=...
# Sentry (optional — leave unset to disable).
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_SENTRY_ENVIRONMENT=development
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0.1
# Build-time only — for source-map upload to Sentry.
SENTRY_AUTH_TOKEN=
SENTRY_ORG=
SENTRY_PROJECT=npm run dev # http://localhost:3000For a production-shaped build / sanity check:
npm run build
npm startapp/
layout.js — root layout (navbar, footer)
page.js — landing page
api/
api.js — axios instance with auto-refresh interceptor
auth.js — in-memory access token store + bootstrap helper
login/page.js — email + password / Google sign-in
auth/magic/page.js — passwordless magic-link sign-in
user/page.js — profile + pets + onboarding checklist
user/edit/, user/update/ — add / edit a pet (multi-photo)
user/search/page.js — image similarity search
pet/map/page.js — Leaflet map of lost/found reports (mobile FAB,
bottom-sheet detail)
pet/report/page.js — file a lost/found report
chat/page.js — conversation list
chat/[id]/page.js — message thread (polling)
alerts/page.js — manage nearby-pet alert subscriptions
found/[animalId]/page.js — public landing for scanned QR tags
dashboard/, lost/, fun/ — extra pages
components/
navbar.js — top nav + notifications + chat unread badge
footer.js
Map.js — Leaflet wrapper
ImageDropzone.js — shared drag-drop + camera + paste upload
...
sentry.client.config.js — Sentry init for browser
sentry.server.config.js — Sentry init for Node runtime
sentry.edge.config.js — Sentry init for Edge runtime
next.config.mjs — image hosts + optional Sentry wrap
- Login mints a short-lived access token (15 min) returned in the response body — kept in memory only, never in localStorage.
- The backend also sets a refresh token as an httpOnly cookie scoped to
.neokit.app, so JavaScript can never read it. - Every API call goes through
app/api/api.js(axios) which attaches the access token to theAuthorizationheader. - On a 401 the response interceptor calls
/api/token/refresh/(the cookie rides along) and retries the original request with the fresh access token. - On page reload the in-memory access token is gone; each page calls
bootstrapAccessToken()on mount which transparently re-mints one from the cookie.
This means an XSS payload can't steal anyone's session — there's no token in storage to grab.
- Mobile-first map — chip strip scrolls horizontally on phones, pet detail rises as a bottom sheet, primary action is a thumb-reach FAB.
- Shared
<ImageDropzone>— every photo input supports drag-drop, native camera capture (capture="environment"on mobile), and paste-from-clipboard. - Skeleton placeholders — shape-matched shimmer (GPU-only animation) on
/chat,/user, and/pet/mapwhile data loads. - Onboarding checklist — a fresh user with zero pets lands on a 3-step guide (add pet → print QR → set alerts).
Hosted on Cloudflare Pages. Two things to remember when adding pages:
-
Dynamic routes (anything with
[param]) need to opt into the edge runtime:"use client"; export const runtime = "edge";
Without this,
@cloudflare/next-on-pagesfails the build. -
NEXT_PUBLIC_*env vars are baked at build time. Changing them in the dashboard does nothing until you trigger a fresh build (push or "Retry deployment").
Production environment variables to set in Cloudflare Pages → Settings → Environment variables → Production:
NEXT_PUBLIC_BACKEND_API_PORT=https://pawglebackend.neokit.app/
NEXT_PUBLIC_GOOGLE_CLIENT_ID=...
NEXT_PUBLIC_SENTRY_DSN=https://...
NEXT_PUBLIC_SENTRY_ENVIRONMENT=production
SENTRY_AUTH_TOKEN=... (optional, for source-map upload)
SENTRY_ORG=...
SENTRY_PROJECT=pawgle-nextjs- Fork + branch from
master. npm installand bring up.env.localper the template above (you'll need a running backend, or point at the deployed one).npm run buildto confirm the route table and edge-runtime requirements stay clean.- Open a PR.
Issues and PRs welcome on WhoamiI00/PawGleFrontend.
This project is part of PawGle. See the umbrella repo for license details.