Synaplan
기업을 위한 오픈소스 AI 플랫폼
댄스 스튜디오 결제 현황을 확인하고 WhatsApp으로 금액을 알려준다 첫 달 무료 카드 필요 없음
See who has paid, who is due and who is overdue at your dance studio. Remind each dancer on WhatsApp with the amount filled in. First month free, no card.
비즈니스TypeScript · PLpgSQLGitHub @anushkanath20032008온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Choreo Studio is a scrapbook-style operations app for independent dance studios in India: fees, attendance, batches and WhatsApp reminders in one place. Live at choreostudio.live.
Studio owner. Finds the landing page, joins the founding-studios waitlist or opens Studio account, signs in (email or Google), saves studio details, and picks a Choreo Studio subscription plan. Day to day, the owner manages students, guardians, batches, attendance, each student's fee plan, ledger payments, and WhatsApp reminders.
Family. Parents and students don't install anything or create an account. They get a WhatsApp message with a link showing upcoming classes, attendance, charges, payments and balance.
There are two separate kinds of money in Choreo Studio:
starter plan id) and Studio+ ₹479/month (studio), after a free first month that needs no card. The older Growth plan is no longer sold but still works for existing subscribers. Checkout is created by the server function createSubscriptionCheckout in src/lib/billing.functions.ts. The Dodo API key and product IDs never reach the browser, and a studio cannot edit its own plan or billing status.Fee plans are separate from batches. A batch says where a student dances; a fee-plan assignment says what that student owes. Two students in the same batch can pay different amounts, and past ledger charges keep the plan and amount that created them.
| Piece | Role |
|---|---|
| TanStack Start, React, TypeScript, Vite | Web app and server functions |
| Supabase | Authentication, application data, and row-level security. Waitlist writes go through validated server functions; owner-only data sits behind authenticated row policies and a separate role table |
| Vercel | Hosts the web app. Pushes to main deploy automatically to choreostudio.live |
| Render | Heavy background jobs, such as scheduled WhatsApp reminders and retries |
| Dodo Payments | Choreo Studio's own studio subscriptions only |
| Lovable | Visual editor, synced with this repository |
npm install
npm run dev
Checks:
npm run lint
npm run test
npm run build
Copy .env.example to .env.local for local work (it's untracked, so secrets stay out of git). In production, set them in Vercel under Project → Settings → Environment Variables.
| Variable | Where it's used |
|---|---|
SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY | Server functions |
VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY | Browser client (publishable values only) |
DODO_PAYMENTS_API_KEY | Server only. Creates checkout sessions |
DODO_PAYMENTS_WEBHOOK_KEY | Server only. Verifies the signature on Dodo webhooks |
SUPABASE_SERVICE_ROLE_KEY | Server only. Webhook, reminder job and the public family view |
RENDER_JOB_SECRET | Server only. Bearer secret the scheduler sends to the reminder job |
WHATSAPP_PROVIDER_URL, WHATSAPP_PROVIDER_TOKEN | Server only. The WhatsApp provider the reminder job posts to. Unset means reminders stay queued |
DODO_PAYMENTS_ENVIRONMENT | test_mode (default) or live_mode |
DODO_PAYMENTS_STARTER_PRODUCT_ID, DODO_PAYMENTS_GROWTH_PRODUCT_ID, DODO_PAYMENTS_STUDIO_PRODUCT_ID | Server only. One Dodo subscription product per plan |
PUBLIC_APP_URL | Base URL for the checkout return link, e.g. https://www.choreostudio.live. Falls back to the origin of the incoming request |
If a plan's product ID or the API key is missing, choosing that plan shows a "not available for checkout yet" message instead of failing silently. A studio with a live subscription can't start a second checkout; one whose subscription has expired (cancelled, back on the waitlist) can subscribe again.
plan and billing_status only change through the Dodo webhook (below), which writes with the service role. Owners can edit their contact details but never their plan or billing state.
Everything is a TanStack Start server function (src/lib/*.functions.ts) that loads its logic from src/lib/*.server.ts inside the handler, so no secret reaches the browser bundle. Rules that must never be bypassed (the ledger, attendance, reminders, billing state) live in SQL (drizzle/migrations/0003 to 0006) behind row-level security, and are tested against a real in-process Postgres.
| Area | What it does |
|---|---|
| Directory | Guardians, students, batches with dated rosters, fee plans assigned per student (separate from batch membership) |
| Ledger | Monthly charges and payments the studio records (cash, UPI, bank). ChoreoStudio never processes student payments. Charges keep the plan and amount that created them; entries are corrected by voiding with a reason, never edited |
| Attendance | One mark per student per class date, only for students on that batch's roster |
| Reminders | queueReminders adds a WhatsApp reminder for each due or overdue charge (once per charge per day). The send job delivers them with retries |
| Family view | Studios create a private link per guardian; the page behind it shows classes, attendance, charges, payments and balance. Only a hash of the link is stored, and it can be revoked |
| Billing | POST /api/webhooks/dodo turns verified Dodo events into the studio's plan and status |
POST /api/jobs/send-reminders sends one batch of due reminders. Schedule it every few minutes (for example a Render cron job) with the header Authorization: Bearer <RENDER_JOB_SECRET>. Failed sends are retried with growing delays and give up after five attempts. If the provider settings are missing the job answers 503 and leaves every reminder queued. Creating the schedule is a deployment step, not part of the code.
In the Dodo dashboard add a webhook pointing to https://<your domain>/api/webhooks/dodo with the subscription events, and copy its signing secret into DODO_PAYMENTS_WEBHOOK_KEY. The signature is checked on the raw body before anything is read. Each event is applied once, only if it is newer than the last one applied, and only for the studio's own subscription.
A dancer can report a payment from their private link (/family/<token>). A claim changes no balance until the owner confirms it under To verify; confirming runs the same amount-versus-owed gate as any payment, and the owner's own bank or UPI statement, matched by reference number, is the source of truth. A screenshot is optional: it uploads straight to the private claim-screenshots storage bucket (PNG, JPEG or WebP, up to 5 MB, created by migration 0013) through a one-time signed URL, is viewed by the owner through a 60-second signed URL, and is deleted when the claim is decided.
POST /api/jobs/purge-screenshots deletes screenshots of decided claims and of claims left pending for over 30 days. Schedule it daily with the same header as the reminder job (Authorization: Bearer <RENDER_JOB_SECRET>). Have a lawyer review the privacy notice on the family page before real dancers' screenshots are collected.
The migrations in drizzle/migrations are additive. Apply 0003 onward (currently to 0013) to the database, then run drizzle/verify/rollout-checks.sql against it: it must return no rows. Apply the migrations before deploying the code that uses them.
.env is managed by Lovable and holds only Supabase publishable values; don't add secrets to it. Put secrets in Vercel's environment variables, or in an untracked .env.local locally.VITE_; Vite exposes those to the browser.Reminders you queue in the console are sent by POST /api/jobs/send-reminders; the owner email nudge is POST /api/jobs/owner-nudge. Call both from your scheduler (the reminder job every few minutes, the nudge once a day) with Authorization: Bearer <RENDER_JOB_SECRET>. With nothing configured the console falls back to tap-to-send chats and the jobs send nothing.
WhatsApp (Meta Cloud API). Set WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_ACCESS_TOKEN and optionally WHATSAPP_TEMPLATE_LANGUAGE (default en). Create two approved utility templates, fee_due_reminder and fee_overdue_reminder, whose body uses these variables in order: {{1}} guardian name, {{2}} dancer, {{3}} studio, {{4}} amount, {{5}} month, {{6}} due date. Example body: "Hi {{1}}, a reminder from {{3}}: {{2}}'s fee for {{5}} ({{4}}) is due on {{6}}. Thank you!"
Owner nudge email (Resend). Set RESEND_API_KEY, NUDGE_FROM_EMAIL (a sender Resend has verified) and PUBLIC_APP_URL. The owner is emailed on a due date and every Monday while any dancer is unpaid this month.
Vobiz instead of your own number. Vobiz (a Meta-approved provider) can supply the WhatsApp number, so reminders do not come from a personal phone. Set VOBIZ_AUTH_ID and VOBIZ_AUTH_TOKEN (console.vobiz.ai), VOBIZ_CHANNEL_ID and VOBIZ_WABA_ID. It is used before the Meta settings when all four are present. Submit the same two templates through Vobiz.