Bernt Christian Egeland 7 ay önce
ebeveyn
işleme
3dcc2aba40
3 değiştirilmiş dosya ile 1 ekleme ve 336 silme
  1. 1 1
      .gitignore
  2. 0 19
      plans/project_overview.md
  3. 0 316
      plans/subscriptions.md

+ 1 - 1
.gitignore

@@ -12,7 +12,7 @@
 /backups
 # testing
 /coverage
-
+/plans
 # next.js
 /.next/
 /out/

+ 0 - 19
plans/project_overview.md

@@ -1,19 +0,0 @@
-This application will take over for Lubelog and Invoice Ninja, providing a more comprehensive solution for repair shops to manage their work orders, invoices, and customer interactions. The goal is to create a user-friendly platform that streamlines the repair process and enhances communication between repair shops and their customers.
-
-
-/app directory should only contain routes with server components. Pages should allways be loaded with server components to fetch data before rendering.
-
-Client components should allways be placed in the /features directory. Features should be organized by domain, with each feature having its own subdirectory. Each feature directory can contain components, hooks, styles, actions and tests related to that specific feature.
-
-For server actions, use one action pr file, so each file will be named after the action it contains.
-
-Data Fetching
-Data fetching should be done in server components using async/await syntax. This ensures that data is fetched before the component is rendered, improving performance and user experience.
-
-If a client component needs to fetch data, use react-query for efficient data fetching and caching. Allways use zod for schema validation when fetching data.
-
-All server actions should be wrapped with withAuth to ensure that only authenticated users can perform actions that modify data.
-
-For submenus, use same layout as /settings were we have a child sidebar.
-
-Task List:

+ 0 - 316
plans/subscriptions.md

@@ -1,316 +0,0 @@
-# Torqvoice Subscription Plans
-
-Same model as Invoice Ninja. One codebase, two modes controlled by env var.
-
----
-
-## Architecture
-
-Same Torqvoice app runs everywhere. One env var determines the gating logic:
-
-```
-TORQVOICE_MODE=cloud        # set on app.torqvoice.com
-TORQVOICE_MODE=self-hosted  # default if not set
-```
-
-### Cloud mode (`app.torqvoice.com`)
-- Plans stored per-organization in the database (free/pro/enterprise)
-- Managed via Stripe subscriptions
-- Feature gating checks the org's plan
-
-### Self-hosted mode (default)
-- Single license key check (valid or not)
-- No license = free tier limits
-- Valid license = everything unlocked + branding removed
-- License validated against `torqvoice.com/api/license/validate`
-
----
-
-## Cloud-Hosted Plans (app.torqvoice.com)
-
-### Free — $0/year
-- Up to 5 customers
-- Unlimited vehicles
-- Basic invoicing & quotes (2 templates)
-- Work order management
-- Inventory tracking
-- 1 user
-- Community support
-
-### Torq Pro — $99/year
-- Unlimited customers
-- All invoice templates + custom designs
-- Reports & analytics
-- SMTP / custom email
-- API access
-- Up to 5 users
-- Online payments
-- Custom fields
-- Priority support
-
-### Enterprise — $140/year
-- Everything in Torq Pro
-- Up to 50 users
-- Priority support
-
----
-
-## Self-Hosted Plans (GitHub / Docker)
-
-### Free — $0
-Core features, same limits as cloud free:
-- Up to 5 customers
-- Unlimited vehicles
-- Basic invoicing & quotes (2 templates)
-- Work order management
-- Inventory tracking
-- 1 user
-- "Powered by Torqvoice" branding on PDFs, sidebar, login pages
-
-### White-Label License — $30/year
-Unlocks all features + removes branding:
-- Unlimited customers
-- All invoice templates + custom designs
-- Reports & analytics
-- SMTP / custom email
-- API access
-- Unlimited users
-- Online payments
-- Custom fields
-- Remove "Torqvoice" branding from PDFs, sidebar, login pages
-- Custom platform name
-
-Purchase page: `torqvoice.com/white-label`
-
----
-
-## License Key System (Self-Hosted)
-
-Same approach as Invoice Ninja. No hardware ID, no domain binding. Simple server-side validation.
-
-### How Invoice Ninja does it (reference)
-1. User purchases white-label license on invoiceninja.com
-2. Receives a license key string via email
-3. Enters key in self-hosted app: Settings > Account Management
-4. App calls `invoicing.co/claim_license?license_key={key}&product_id={id}`
-5. Server responds with expiration date (or failure)
-6. App stores plan type (`white_label`) and expiry date in local database
-7. Key is not tied to hardware or domain — just a random string linked to a purchase record
-8. If shared/abused, Invoice Ninja can revoke it server-side
-
-### How Torqvoice does it (our implementation)
-1. User purchases white-label license at `torqvoice.com/white-label`
-2. Receives a license key string via email
-3. Enters key in self-hosted Torqvoice app: Admin > Settings
-4. App calls `torqvoice.com/api/license/validate` with the key
-5. Server responds with `{ valid: true, plan: "white-label", expiresAt: "..." }`
-6. Torqvoice stores in local database (SystemSetting table):
-   - `license.key` — the key itself
-   - `license.valid` — "true" or "false"
-   - `license.plan` — "white-label"
-   - `license.checkedAt` — last validation timestamp
-7. On subsequent app loads, Torqvoice checks cached validity — no API call needed
-8. Re-validates periodically (e.g. on startup or once per day) to catch expirations/revocations
-
-### Key properties
-- **Not tied to hardware** — no machine fingerprinting
-- **Not tied to domain** — works on any server
-- **One key = one license** — random string (cuid) linked to a purchase record on torqvoice.com
-- **Server-side revocation** — if a key is shared/abused, we revoke it in the torqvoice.com database, next validation returns `{ valid: false }`
-- **Graceful offline** — if torqvoice.com is unreachable, Torqvoice falls back to cached `license.valid` value so the app keeps working
-- **Annual renewal** — key expires after 1 year, user gets a new key on renewal (or same key with extended expiry)
-
-### License validation flow
-
-```
-Self-hosted Torqvoice                         torqvoice.com
-─────────────────────                         ─────────────
-
-User enters key
-       │
-       ▼
-POST /api/license/validate ──────────────────► Check key in License table
-  { key: "clx..." }                            - exists?
-                                               - status = "active"?
-                                               - not expired?
-       ◄──────────────────────────────────────
-  { valid: true,
-    plan: "white-label",
-    expiresAt: "2027-02-15" }
-       │
-       ▼
-Store in SystemSetting:
-  license.key = "clx..."
-  license.valid = "true"
-  license.plan = "white-label"
-  license.checkedAt = now()
-       │
-       ▼
-Unlock all features
-Remove branding
-```
-
-### Fallback (API unreachable)
-
-```
-Self-hosted Torqvoice                         torqvoice.com
-─────────────────────                         ─────────────
-
-POST /api/license/validate ──────── X ────────► (unreachable)
-
-       │
-       ▼
-Read cached SystemSetting:
-  license.valid = "true" (from last check)
-       │
-       ▼
-Keep features unlocked
-(will re-validate on next startup)
-```
-
----
-
-## Feature Gating Implementation
-
-One shared gating function, two strategies:
-
-```typescript
-// src/lib/features.ts
-
-type Plan = "free" | "pro" | "enterprise" | "white-label";
-
-function getEffectivePlan(): Plan {
-  if (process.env.TORQVOICE_MODE === "cloud") {
-    // Read from organization's subscription in database
-    return orgPlan; // "free" | "pro" | "enterprise"
-  } else {
-    // Self-hosted: license valid = white-label, otherwise free
-    return licenseValid ? "white-label" : "free";
-  }
-}
-```
-
-Feature checks map plan → permissions:
-
-```typescript
-const PLAN_FEATURES = {
-  free: {
-    maxCustomers: 5,
-    maxUsers: 1,
-    templates: 2,
-    reports: false,
-    smtp: false,
-    api: false,
-    payments: false,
-    customFields: false,
-    brandingRemoved: false,
-    customPlatformName: false,
-  },
-  pro: {
-    maxCustomers: Infinity,
-    maxUsers: 5,
-    templates: Infinity,
-    reports: true,
-    smtp: true,
-    api: true,
-    payments: true,
-    customFields: true,
-    brandingRemoved: false,
-    customPlatformName: false,
-  },
-  enterprise: {
-    maxCustomers: Infinity,
-    maxUsers: 50,
-    templates: Infinity,
-    reports: true,
-    smtp: true,
-    api: true,
-    payments: true,
-    customFields: true,
-    brandingRemoved: false,
-    customPlatformName: false,
-  },
-  "white-label": {
-    maxCustomers: Infinity,
-    maxUsers: Infinity,
-    templates: Infinity,
-    reports: true,
-    smtp: true,
-    api: true,
-    payments: true,
-    customFields: true,
-    brandingRemoved: true,
-    customPlatformName: true,
-  },
-};
-```
-
-Usage in the app:
-
-```typescript
-// In a server component or action
-const plan = await getEffectivePlan();
-const features = PLAN_FEATURES[plan];
-
-if (!features.reports) {
-  // Hide reports nav item / return 403
-}
-
-if (customerCount >= features.maxCustomers) {
-  // Block creating new customers, show upgrade prompt
-}
-```
-
----
-
-## What Users See
-
-### Self-hosted admin settings
-- White-Label License section (key input + validate)
-- Without license: "Upgrade to unlock all features" prompt
-- With license: "All features unlocked" badge
-
-### Cloud admin / billing page
-- Current plan badge (Free / Pro / Enterprise)
-- Upgrade/downgrade buttons → Stripe checkout
-- Usage stats (customers, users vs plan limits)
-
-### torqvoice.com marketing site
-- Landing page (`/#pricing`): 3-tier cloud plans
-- White-label page (`/white-label`): Purchase page for self-hosted users
-
----
-
-## License Model (torqvoice.com database)
-
-```
-License {
-  key       — unique license key (cuid, e.g. "clx8f7g2k0001...")
-  email     — customer email
-  status    — active | expired | revoked
-  plan      — pro | enterprise | white-label
-  expiresAt — expiration date (1 year from purchase)
-}
-```
-
-- `plan: "pro"` / `"enterprise"` — cloud subscription licenses (managed by Stripe webhooks)
-- `plan: "white-label"` — self-hosted white-label licenses
-
----
-
-## Summary
-
-|                        | Self-Hosted Free | Self-Hosted Licensed | Cloud Free | Cloud Pro | Cloud Enterprise |
-|------------------------|:----------------:|:--------------------:|:----------:|:---------:|:----------------:|
-| Price                  | $0               | $30/yr               | $0         | $99/yr    | $140/yr          |
-| Customers              | 5                | Unlimited            | 5          | Unlimited | Unlimited        |
-| Vehicles               | Unlimited        | Unlimited            | Unlimited  | Unlimited | Unlimited        |
-| Users                  | 1                | Unlimited            | 1          | 5         | 50               |
-| Invoice Templates      | 2                | All                  | 2          | All       | All              |
-| Reports & Analytics    | No               | Yes                  | No         | Yes       | Yes              |
-| SMTP / Custom Email    | No               | Yes                  | No         | Yes       | Yes              |
-| API Access             | No               | Yes                  | No         | Yes       | Yes              |
-| Online Payments        | No               | Yes                  | No         | Yes       | Yes              |
-| Custom Fields          | No               | Yes                  | No         | Yes       | Yes              |
-| Branding Removed       | No               | Yes                  | No         | No        | No               |
-| Custom Platform Name   | No               | Yes                  | No         | No        | No               |