Materializr
Open-source parametric 3D CAD for makers — constraint sketches, solid modeling, threads, SVG & text engraving, STL/STEP export. Android, iPad, Linux, Mac, Windows. Vibe coded.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗The auth-protected dashboard SPA for Viewora. Built with Nuxt 3, Tailwind CSS, Pinia, and Supabase auth. Deployed to app.viewora.software.
Standalone repo — separate GitHub repository, deployed independently to Vercel. No shared code with
viewora-backendorviewora-marketing.
/p/[slug] and embed spaces via /embed/[slug].| Layer | Tech |
|---|---|
| Framework | Nuxt 3 (CSR/SPA mode for auth-protected routes) |
| Styling | Tailwind CSS v3 (via @nuxtjs/tailwindcss) |
| State | Pinia (stores/auth.ts, stores/plan.ts) |
| Auth | @nuxtjs/supabase — JWT stored in session, forwarded as Bearer token |
| 360° Viewer | @egjs/view360 v3 (via View360Viewer.vue + TourViewer.vue) |
| Fonts | Inter 400–700, Outfit 600–700 via Google Fonts |
| HTTP | useApiFetch composable wraps $fetch with the Supabase JWT |
viewora-app/
├── app.vue # Root layout shell, page transitions
├── nuxt.config.ts # Modules, route rules, runtime config
├── tailwind.config.ts # Design tokens (zinc palette, etc.)
├── .env.example # Required environment variables
│
├── pages/
│ ├── index.vue # Root redirect (→ /app or /login)
│ ├── login.vue # Auth: email/password login
│ ├── register.vue # Auth: email/password registration
│ ├── confirm.vue # Auth: email confirmation callback
│ ├── reset-password.vue # Auth: password reset
│ │
│ ├── app/ # Auth-protected dashboard (all CSR, no-cache)
│ │ ├── index.vue # Dashboard home — summary cards
│ │ ├── analytics.vue # Analytics: views breakdown per space
│ │ ├── billing.vue # Billing: plan selection, Paystack flow, usage
│ │ ├── settings.vue # Profile/account settings
│ │ │
│ │ ├── spaces/
│ │ │ ├── index.vue # Spaces list + create space modal
│ │ │ └── [id]/
│ │ │ └── index.vue # Space detail: upload media, 360 settings, publish
│ │ │
│ │ ├── leads/
│ │ │ └── index.vue # Leads inbox — all inquiries across spaces
│ │ │
│ │ └── capture/
│ │ └── index.vue # 360 capture tool (plan-gated)
│ │
│ ├── p/
│ │ └── [slug].vue # Public space viewer (no auth required)
│ │
│ └── embed/
│ └── [slug].vue # Minimal embed-optimized viewer (iframe safe)
│
├── components/
│ ├── SidebarLink.vue # Dashboard nav link with active state
│ ├── app/
│ │ └── ConfirmationModal.vue # Reusable delete-confirm dialog
│ └── viewer/
│ ├── View360Viewer.vue # @egjs/view360 panorama renderer (client-only)
│ ├── TourViewer.vue # Full tour UI: scene strip, hotspot popups, share sheet
│ └── Hotspot.vue # Individual hotspot overlay marker
│
├── composables/
│ ├── useApiFetch.ts # $fetch wrapper that injects Supabase Bearer token
│ └── useSpaces.ts # CRUD + publish operations for spaces
│
├── stores/
│ ├── auth.ts # User identity, profile, displayName, initials
│ └── plan.ts # Subscription status, plan limits, can() entitlement checks
│
├── middleware/
│ ├── auth.ts # Redirects unauthenticated users to /login
│ ├── guest.ts # Redirects authenticated users away from /login, /register
│ └── redirects.global.ts # Global redirect rules
│
├── layouts/ # Nuxt layouts (dashboard shell with sidebar)
├── plugins/ # Nuxt plugins
├── server/ # Nitro server routes (proxy if NUXT_PUBLIC_API_BASE_URL not set)
├── types/
│ └── database.types.ts # Supabase-generated TypeScript types
└── assets/
└── css/main.css # Global CSS, Tailwind base
composables/useApiFetch.tsThe single HTTP abstraction for the entire app. Reads the Supabase session access token and injects it as Authorization: Bearer <token> on every call. Respects NUXT_PUBLIC_API_BASE_URL — if unset, calls go to Nitro proxy routes at /api/*; if set, calls go directly to the Fastify backend. This is the only way the app should make API calls.
composables/useSpaces.tsEncapsulates all space CRUD operations: fetchSpaces, fetchSpace, createSpace, updateSpace, deleteSpace, publishSpace. All calls go through useApiFetch. Spaces are called "spaces" in the frontend but stored as properties in the database — the backend normalizes this at the API boundary.
stores/auth.tsWraps useSupabaseUser() into a Pinia store. Exposes: user (JWT payload), profile (from /profile API), isLoggedIn, displayName, avatarInitials. fetchProfile() loads the profile row from the backend on login.
stores/plan.tsLoaded once on login via fetchSubscriptionStatus(). Exposes the active plan's feature flags. Use planStore.can('lead_capture_enabled') to gate plan-specific UI. Falls back to a hardcoded Free plan object if the API fails. Every premium UI element must check this store.
pages/p/[slug].vuePublic tour viewer — SSR with Vercel edge caching (s-maxage=60). Fetches via GET /p/:slug which calls the get_tour_data() Supabase RPC (returns space + scenes + hotspots). Renders TourViewer → View360Viewer for 360° panoramas. Includes lead capture form (gated on lead_form_enabled). View analytics are recorded server-side — no client-side ping needed.
pages/app/billing.vueHandles the entire billing lifecycle: plan listing, Paystack checkout initialization, success redirect handling, and subscription status display with usage meters.
nuxt.config.ts)| Route | Mode | Notes |
|---|---|---|
/login, /register, /confirm, /reset-password | CSR | Auth pages |
/p/** | SSR | public, s-maxage=60 — Vercel edge cache, fast first paint |
/embed/** | CSR | Iframe-safe, no auth |
/app/** | CSR, no-store | Auth-protected dashboard |
/api/** | — | private, no-store caching headers |
/login.middleware/auth.ts guards all /app/** routes — redirects to /login if no session.useApiFetch reads useSupabaseSession().access_token and forwards it on every backend call.middleware/auth.ts is bypassed (if (process.dev) return).cp .env.example .env
# Required:
# SUPABASE_URL=https://<ref>.supabase.co
# SUPABASE_KEY=<anon-key>
# Optional (defaults to Nitro proxy if unset):
# NUXT_PUBLIC_API_BASE_URL=http://localhost:3000
npm install
npm run dev # http://localhost:3001
| Variable | Required | Default | Description |
|---|---|---|---|
SUPABASE_URL | ✅ | — | Supabase project URL |
SUPABASE_KEY | ✅ | — | Supabase anon/public key |
NUXT_PUBLIC_APP_URL | — | https://app.viewora.software | App's own public URL |
NUXT_PUBLIC_API_BASE_URL | — | '' (same-origin Nitro) | Set to Fastify URL for external backend |
viewora-backend via useApiFetch. The app does not query Supabase tables directly (except auth session management via @nuxtjs/supabase).viewora-marketing links to app.viewora.software for login/register and for any "Get Started" CTAs. The marketing site's /login and /register pages are thin redirects into this app.viewora.software (marketing) can link to app.viewora.software/p/[slug] for any published space.