ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기ios
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Multi-church PWA for managing church youth services. The full product and technical spec is in CLAUDE.md; deployment steps are in DEPLOY.md.
More: service admin · servant, church admin, super admin
Next.js 16 (App Router, TypeScript strict) · Tailwind CSS v4 + shadcn/ui (RTL) · Supabase (Auth, Postgres + RLS) · Serwist PWA.
npm install
npx supabase start # local Supabase (needs Docker)
cp .env.example .env.local # paste the URL + publishable key printed by `supabase start`
npm run dev
Useful scripts:
| Script | What it does |
|---|---|
npm run db:reset | Recreate the local DB from supabase/migrations (+ seed) |
npm run db:types | Regenerate lib/database.types.ts from the local DB |
npm run test:db | RLS test suite (pgTAP, supabase/tests/database) |
npm run test:e2e | Playwright: join-by-QR, quiz → gift → approval, servant visit (needs a fresh db reset + build; starts npm start if nothing is on :3000) |
npm run typecheck / npm run lint | TypeScript / ESLint |
npm run icons | Regenerate PWA icons in public/icons |
Kids sign up with name + Egyptian mobile + password. To avoid needing an SMS provider, the phone is normalized to E.164 (+201012345678, accepts 01…, +20…, 0020…, Arabic-Indic digits) and stored in Supabase Auth under a synthetic email [email protected]. The real phone goes into profiles.phone.
Login accepts either a phone (mapped the same way) or a real email (for admin accounts created with one).
Hosted Supabase project: turn off Auth → Providers → Email → Confirm email, otherwise phone signups wait for a confirmation mail that can never arrive.
supabase db reset loads supabase/seed.sql. Password for all: password123.
| Role | Phone |
|---|---|
| Super admin | 01000000001 |
| Church admin | 01000000002 |
| Service admin (خدمة إعدادي) | 01000000003 |
| Servants | 01100000001, 01100000002 |
| Served ×6 | 01200000001 … 01200000006 |
Join codes (one per class): EADY1A (أولى إعدادي), EADY2B (تانية إعدادي). Try /join?code=EADY1A.
Every table has RLS with default deny; see supabase/migrations/*_rls_*.sql. Things to know when writing queries:
profiles.phone and competition_questions.correct_index are not selectable (column grants), so select("*") on those tables fails. List the columns. Read phones with rpc("visible_phone", { p_user }) (self, admins over that user, and their assigned servant only) and answer keys with rpc("competition_answer_key", { p_competition }) (service admins only).points_ledger, visits, prep_attendance, competition_entries, gift_redemptions, user_achievements. They are written by SECURITY DEFINER RPCs that also add the matching ledger entry: award_points(_many), reverse_points, log_visit, check_in_prep, mark_prep_attendance, submit_competition, redeem_gift, decide_redemption. All ledger rows go through the internal ledger_add.{"type": "points_total" | "competitions_count" | "visits_count", "gte": N}, evaluated by triggers after points, quiz entries and visits, and for everyone when an achievement is created. Earned achievements are permanent.v_service_leaderboard / v_class_leaderboard, filtered to services the caller belongs to.avatars/<user_id>/…, service-media/<service_id>/…. Both buckets are private, so use signed URLs.| Route | Who | What |
|---|---|---|
/ | everyone | "اختار الخدمة" picker; skips straight to the only/last-picked place (?pick=1 to choose again) |
/profile | everyone | name and photo |
/join?code=…, /scan | anyone | join a class by QR or typed code (logged out → signup → back to join) |
/admin | super admin | churches CRUD; appoint/remove church admins (search users by name or phone) |
/church/[churchId] | church admin | services CRUD, service admins, servants pool |
/s/[serviceId]/manage | service admin | dashboard · classes · members · join codes/QR · points (ledger, awards, corrections, leaderboards, rules) · visitation (assignments, overdue, log) · prep sessions/attendance · competitions · posts · gifts/requests · achievements |
/s/[serviceId]/servant | servant | home (points, rank, next prep + check-in) · مخدوميني (log visits, call) · التحضير · الترتيب |
/s/[serviceId]/home | served | home (points, timeline, gifts) · competitions · achievements + leaderboard |
Church admins find people by exact phone number only (find_user_by_phone), so they can't browse the platform's users. Only super admins get a name/phone search (search_users).
app/(auth)/ login, signup + server actions
app/serwist/[path]/ service worker route (Serwist + Turbopack)
app/sw.ts service worker source
app/manifest.ts PWA manifest (ar, rtl)
components/ui/ shadcn/ui components
lib/supabase/ browser, server and proxy clients
lib/strings.ts all Arabic UI strings
proxy.ts session refresh + optimistic auth redirect (Next 16's name for middleware.ts)
supabase/migrations/ SQL migrations (schema, RLS, RPCs per phase)
supabase/tests/ pgTAP RLS/RPC tests
e2e/ Playwright tests
CI (.github/workflows/ci.yml) runs lint, typecheck, build, the pgTAP suite and Playwright against a local Supabase on every push/PR.
One code per class (owner decision, phase 4): scanning puts the kid straight into that class as an active member. Codes are 6 characters without look-alikes (no 0/O, 1/I/L), can expire at the end of a chosen day (Cairo time), can be paused, and can be regenerated (the old QR stops working). Scanning a code for a service you're already in does nothing; only the service admin moves kids between classes. A kid the admin removed can't rejoin on their own.
The QR encodes NEXT_PUBLIC_SITE_URL/join?code=… (falls back to the request host), so set NEXT_PUBLIC_SITE_URL in production. The scanner's fallback decoder for iOS (zxing_reader.wasm) is copied into public/zxing on postinstall and served from our own origin.
components/image-input.tsx), stored privately and shown via short-lived signed URLs.cacheComponents is off: nearly every page is per-user, so the classic dynamic rendering model is simpler here.components.json is set up, so npx shadcn add <component> works normally from now on.