ViewTouch Point of Sale
GNU/Linux Restaurant Point of Sale software - Free and open source POS solution
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Admin Dashboard for Puntos club platform built with Next.js, Shadcn/ui and Supabase.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Multi-tenant loyalty rewards SaaS for Argentine retailers. Businesses run their own points programs: customers earn points on purchases and redeem them for rewards, all coordinated through this admin portal.
This repository hosts the web admin portal consumed by business owners, store managers, and cashiers. End customers (beneficiaries) interact with the platform via separate mobile apps that talk to this backend through public API routes.查看完整 README ↗Multi-tenant loyalty rewards SaaS for Argentine retailers. Businesses run their own points programs: customers earn points on purchases and redeem them for rewards, all coordinated through this admin portal.
This repository hosts the web admin portal consumed by business owners, store managers, and cashiers. End customers (beneficiaries) interact with the platform via separate mobile apps that talk to this backend through public API routes.
Puntos Club is a SaaS platform where each retailer (an organization) runs their own customer loyalty program. The admin portal is the operational cockpit for that program.
Who uses it
| Role | Where | What they do |
|---|---|---|
admin | This portal | Cross-org platform administration |
owner | This portal | Manages a single organization end-to-end |
collaborator | This portal | Limited operational access (products, purchases) |
cashier | This portal / POS | Records sales, processes redemptions |
final_user (beneficiary) | Mobile app | Earns points, redeems rewards |
Core capabilities
product.stock) with realtime updates| Concern | Tool |
|---|---|
| Framework | Next.js 16 (App Router, React Server Components) |
| Language | TypeScript (strict) |
| UI | React 19, Tailwind CSS 4, shadcn/ui (Radix), Lucide icons, next-themes |
| Database / Auth | Supabase (Postgres 17, Auth, Realtime, Storage) |
| Server-side data | Server Components + Server Actions; service-role admin client for privileged ops |
| i18n | next-intl (es / en) |
| Validation | Zod schemas in /schemas |
| Payments | Mercado Pago (subscriptions / preapproval) |
| Resend (transactional) | |
| AI | Anthropic Claude SDK — content moderation for push notifications |
| Maps | Google Maps JS API + reCAPTCHA v3 |
| Charts | Recharts |
| 3D / Animation | Three.js, @react-three/fiber, @react-three/drei, GSAP (landing) |
| QR codes | qrcode.react |
| Tests | Jest (unit/integration, jsdom), Playwright (E2E) |
| Quality | ESLint, Knip (dead code), react-doctor |
| Monitoring | Sentry + Vercel Analytics + Speed Insights |
| Hosting | Vercel |
flowchart LR
subgraph Clients
WEB[Web Admin Portal<br/>Next.js App Router]
MOB[Mobile Apps<br/>PuntosClub / Caja]
end
subgraph NextJS[Next.js 16 on Vercel]
MW[Middleware<br/>auth + session refresh]
RSC[Server Components<br/>Pages]
SA[Server Actions<br/>/actions/*]
API[Route Handlers<br/>/app/api/*]
end
subgraph External
SB[(Supabase<br/>Postgres + Auth + Realtime + Storage)]
MP[Mercado Pago]
RES[Resend]
ANT[Anthropic Claude]
GM[Google Maps]
SEN[Sentry]
end
WEB --> MW --> RSC
RSC --> SA
MOB --> API
SA --> SB
API --> SB
API --> MP
API --> RES
SA --> ANT
SA --> RES
RSC --> GM
NextJS --> SEN
MP -. webhooks .-> API
Request flow on the web portal
/dashboard/* route.lib/supabase/middleware.ts) refreshes the Supabase session cookie and redirects to /auth/login if unauthenticated.lib/supabase/server.ts), automatically scoped to the user's active organization (see multi-tenancy)./actions/*, which validate with Zod, persist via Supabase, and revalidatePath() the relevant routes.erDiagram
ORGANIZATION ||--o{ BRANCH : has
ORGANIZATION ||--o{ PRODUCT : owns
ORGANIZATION ||--o{ CATEGORY : owns
ORGANIZATION ||--o{ POINTS_RULE : configures
ORGANIZATION ||--o{ SUBSCRIPTION : "billed via"
ORGANIZATION ||--o{ ORGANIZATION_PLAN_LIMITS : "constrained by"
ORGANIZATION ||--o{ BENEFICIARY_ORGANIZATION : enrolls
BENEFICIARY ||--o{ BENEFICIARY_ORGANIZATION : "member of"
BENEFICIARY ||--o{ PURCHASE : makes
BENEFICIARY ||--o{ REDEMPTION : claims
BENEFICIARY ||--o{ PUSH_TOKEN : registers
BRANCH ||--o{ PURCHASE : "happens at"
CATEGORY ||--o{ PRODUCT : groups
APP_USER ||--o{ APP_USER_ORGANIZATION : "belongs to"
APP_USER ||--|| USER_ROLE : "has"
ORGANIZATION ||--o{ APP_USER_ORGANIZATION : staffed_by
ORGANIZATION ||--o{ PUSH_NOTIFICATION : sends
Key entities
organization_id and is RLS-scoped to it.beneficiary_organization junction.title, body, target audience; goes through AI moderation before send..
├── app/ # Next.js App Router
│ ├── (auth)/ # Login, password reset, callbacks
│ ├── owner/onboarding/ # New-org wizard (public)
│ ├── mobile-apps/ # Mobile download landing
│ ├── dashboard/ # Protected admin portal
│ │ ├── organization/
│ │ ├── beneficiary/
│ │ ├── branch/
│ │ ├── product/
│ │ ├── category/
│ │ ├── purchase/
│ │ ├── redemption/
│ │ ├── push_notifications/
│ │ ├── points-rules/
│ │ ├── subscription/
│ │ ├── settings/
│ │ └── … # ~20 feature areas, all CRUD
│ └── api/ # Route handlers (mobile + webhooks)
│ ├── purchase/
│ ├── beneficiary/
│ ├── notifications/
│ ├── mercadopago/
│ └── monitoring/ # Sentry tunnel
├── actions/ # Server Actions (web mutations)
├── components/ # UI: dashboard/, onboarding/, landing/, ui/ (shadcn)
├── lib/
│ ├── auth/ # getCurrentUser, permissions, getActiveOrgIdFilter
│ ├── supabase/ # client / server / admin / middleware
│ ├── ai/ # Claude content moderator
│ └── …
├── schemas/ # Zod schemas per entity
├── types/ # Shared TS types + supabase.ts (generated)
├── hooks/ # Custom React hooks
├── i18n/ # next-intl request config
├── messages/ # es.json / en.json
├── supabase/
│ ├── migrations/ # 22+ SQL migrations
│ └── config.toml # Local Supabase config
├── __tests__/ # Jest tests
├── e2e/ # Playwright tests
├── scripts/ # tsx-runnable scripts (seed, MP setup)
└── public/
Auth is handled by Supabase Auth (email + password). The cookie session is refreshed by middleware on every request.
Multi-tenancy is enforced at two layers:
Postgres RLS policies — every tenant-scoped table has an organization_id and policies that match the calling user's org membership.
Active-org cookie scoping in app code — a logged-in user can belong to multiple orgs (or be a platform admin). To pick which org they're acting in, the portal stores active_org_id in a cookie. Server-side queries are filtered through the central helper getActiveOrgIdFilter():
flowchart TD
Q[Server Component / Action]
Q --> H[getActiveOrgIdFilter]
H --> R{Role?}
R -->|admin| ALL[returns null<br/>no filter — sees all orgs]
R -->|owner / collaborator / cashier| C[reads active_org_id cookie<br/>fallback: user.organization_id]
C --> F[returns org id<br/>query is .eq org_id]
This closes a class of cross-org leaks where the initial server render could show another org's data while the client was still rehydrating the active-org context. Always route org-scoped queries through this helper rather than reading the cookie directly.
Role / permission model
admin, owner, collaborator, cashier, final_user.collaborator/cashier, per-entity, per-action permissions (view, create, edit, delete) are configurable via the user-role admin UI.lib/auth/permissions.ts exposes the canonical permission checks; pages and actions both consult it.flowchart LR
subgraph Web[Web Admin Portal]
FORM[Form / button] -->|server action| SA["/actions/*"]
end
subgraph Mobile[Mobile Apps + 3rd parties]
APP[App / webhook] -->|HTTP| API["/app/api/*"]
end
SA --> DB[(Supabase)]
API --> DB
| Server Actions | API Routes | |
|---|---|---|
| Caller | Web portal (cookies, same-origin) | Mobile apps, Mercado Pago webhooks, external |
| Auth | Supabase session cookie | Bearer tokens / shared secrets / signed webhooks |
| Validation | Zod schemas in /schemas | Same Zod schemas |
| Cache | revalidatePath() after mutation | N/A |
| Examples | Create product, edit beneficiary, moderate notification | POST /api/purchase/create, POST /api/mercadopago/webhook, POST /api/notifications/send |
Action error shape is standardized via ActionState and the helpers in lib/error-handler.ts.
Push and email campaigns flow through Claude before they reach customers:
sequenceDiagram
autonumber
actor Owner
participant Portal as Admin Portal
participant Mod as lib/ai/content-moderator (Claude)
participant DB as Supabase
participant Push as Push provider
participant Mail as Resend
Owner->>Portal: Draft notification (title + body)
Portal->>Mod: Moderate (spelling, profanity, relevance)
Mod-->>Portal: severity + reasons (low/med/high)
alt severity = high
Portal-->>Owner: block + show reasons
else severity = low/medium
Portal->>DB: Check plan limits
DB-->>Portal: ok
Portal->>DB: Save notification
Portal->>Push: Send to device tokens
Portal->>Mail: Send transactional email
Portal->>DB: Persist send status
end
/api/notifications/send and the purchase-notify flow added recently.flowchart LR
O[Owner] -->|select plan| P[Settings / Plan page]
P -->|create preapproval| MP[Mercado Pago]
MP -->|redirect| O
MP -.webhook.-> WH["/api/mercadopago/webhook"]
WH --> DB[(subscriptions)]
DB --> LIM[organization_plan_limits<br/>+ notification limits]
LIM --> APP[gate features in app]
MP_PLAN_ID_* env vars and seeded with npm run setup:mp-plans.MP_WEBHOOK_SECRET.organization_plan_limits / organization_notification_limits and are checked at the action/API boundary before mutations.Prerequisites
.nvmrc (use nvm use)Steps
# 1. Install deps
npm install
# 2. Configure env (see Environment variables section)
cp .env .env.local # fill in values
# 3. Generate Supabase types (optional, requires Supabase CLI + local stack)
npm run types:supabase
# 4. (Optional) Seed dev data
npm run seed
# 5. Run the dev server (port 3001)
npm run dev
The portal listens on http://localhost:3001.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase project URL |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Supabase anon/publishable key |
SUPABASE_SERVICE_ROLE_KEY | Service-role key (server-only, privileged ops) |
NEXT_PUBLIC_SITE_URL | Canonical site URL (used in emails, callbacks) |
REGISTRATION_SECRET | Token check for the onboarding flow |
RESEND_API_KEY | Transactional email |
ANTHROPIC_API_KEY | Claude — push-notification moderation |
MERCADOPAGO_ACCESS_TOKEN | MP API key |
MP_PLAN_ID_ADVANCE, MP_PLAN_ID_PRO | MP subscription plan IDs |
MP_WEBHOOK_SECRET | MP webhook signature verification |
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY | Maps (client) |
NEXT_PUBLIC_RECAPTCHA_SITE_KEY | reCAPTCHA v3 (client) |
RECAPTCHA_SECRET_KEY | reCAPTCHA verification (server) |
NEXT_PUBLIC_SENTRY_DSN, SENTRY_DSN, SENTRY_AUTH_TOKEN | Sentry |
NEXT_PUBLIC_* variables are exposed to the browser; the rest are server-only.
| Command | What it does |
|---|---|
npm run dev | Start Next.js dev server on port 3001 |
npm run build / start | Production build / serve |
npm run lint | ESLint |
npm run type-check | tsc --noEmit against tsconfig.typecheck.json |
npm run checks | lint + type-check + build |
npm test | Jest |
npm run test:watch / test:coverage / test:ci | Jest variants |
npm run e2e / e2e:headed / e2e:ui / e2e:test | Playwright variants |
npm run seed | Populate dev DB with example data |
npm run setup:mp-plans | Provision Mercado Pago plans |
npm run types:supabase | Regenerate types/supabase.ts from the local DB |
flowchart LR
UNIT[Jest unit tests<br/>__tests__/]:::a
E2E[Playwright E2E<br/>e2e/]:::b
COV[Coverage thresholds<br/>80% lines / 65% branches]:::c
CI[CI gate]
UNIT --> COV --> CI
E2E --> CI
classDef a fill:#dbeafe,stroke:#1e40af
classDef b fill:#fef3c7,stroke:#92400e
classDef c fill:#dcfce7,stroke:#166534
.env.test via dotenv-cli for e2e:test.jest.config.js); test:ci is what CI runs.withSentryConfig() in next.config.ts and instrumentation*.ts. Client requests tunnel through /monitoring to dodge ad blockers.lib/error-handler path; uncaught exceptions surface in Sentry.getActiveOrgIdFilter() — see lib/auth/get-active-org-id.ts. Don't read active_org_id from cookies directly inside server code; the helper handles the admin-bypass and fallback semantics.lib/supabase/admin.ts from a client component or expose its results to the browser./schemas. Re-use those schemas — don't duplicate shapes.organization_plan_limits before mutating.revalidatePath after mutations in server actions, otherwise the affected list/detail pages will keep showing stale data.messages/{es,en}.json. The Spanish bundle is the source of truth (rioplatense / Argentine voseo); keep keys in sync.MP_WEBHOOK_SECRET before trusting payload state./, /auth/*, /owner/onboarding, /mobile-apps, /api/*. Everything under /dashboard/* is auth-gated by middleware.See LICENSE.