Materializr
오픈소스 파라메트릭 3D CAD로 제약 스케치·솔리드 모델·나사·SVG·텍스트 조각·STL/STEP 내보내기를 지원합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 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.