sedifex

Sedifex — PWA + Firebase Starter.

This repo is a drop-in starter for Sedifex (inventory & POS). It ships as a website that is also installable as a PWA, with Firebase (Auth + Firestore + Functions).

What’s inside

Product image fields + downstream contract

Sedifex product documents now support first-class image metadata:

Production image uploads (same-origin /api/uploads on Vercel)

Sedifex now uses a real serverless API route at web/api/uploads.ts (deployed by Vercel) so the browser uploads to the same origin as the app (for example https://www.sedifex.com/api/uploads).

Storage backend

Uploads are stored in your configured Firebase Storage bucket under the product-images/ prefix using the existing Firebase Admin credentials already used by Vercel API routes. The API returns a public Google Cloud Storage URL, which can be stored in Firestore and rendered by the Products page.

Required environment variables

Set these in your Vercel project:

Note: Firebase Functions .env* files reserve the FIREBASE_* prefix. Use non-reserved names like IMAGE_UPLOAD_BUCKET and ADMIN_SERVICE_ACCOUNT_JSON for deploy-safe config.

Deploy notes

  1. Deploy from the repo (or web/) to Vercel with web/ as the build root for the frontend.
  2. Ensure web/vercel.json (and root vercel.json, if used) keeps SPA rewrites from catching /api/*.
  3. In production, verify:
    • POST https://www.sedifex.com/api/uploads returns 201 + { "url": "..." }
    • Uploaded URL is publicly accessible and renders in the product list.
  4. Ensure bucket/object access allows public reads for product images (for example, grant Storage Object Viewer on the bucket to allUsers, or apply an equivalent public-read policy for the product-images/ path).

The previous local-only npm run upload:server helper has been removed to avoid confusion with production deployment.

CSV import/export (items)

Firestore behavior

Downstream consumer read contract (Glittering)

Integration quickstart (website sync)

One-time backfill utility

Quick start (local dev)

1) Install Node 20+. 2) Go to web/ and install deps:

   cd web
   npm i
   npm run dev

3) Create a Firebase project (e.g., sedifex-dev) and fill these env vars in web/.env.local:

   VITE_FB_API_KEY=REPLACE_ME
   VITE_FB_AUTH_DOMAIN=sedifex-dev.firebaseapp.com
   VITE_FB_PROJECT_ID=sedifex-dev
   VITE_FB_STORAGE_BUCKET=sedifex-dev.appspot.com
   VITE_FB_APP_ID=REPLACE_ME
   # If you manually created your default Firestore database and it uses the ID "default",
   # surface it so the client targets the correct instance (falls back to "default" automatically).
   VITE_FB_DATABASE_ID=default

4) (Optional) Deploy Functions:

   cd functions
   npm i
   # Login to Firebase
   npx firebase login
   # Set your project
   npx firebase use sedifex-dev
   # Deploy
   npm run deploy

Activity feed notifications (frontend + backend)

Deploy the PWA (Vercel/Netlify/Firebase Hosting)

Use this checklist when deploying the Google Ads integration so OAuth, callbacks, and backend syncs work correctly.

1) Set required environment variables (Functions runtime config/env)

Minimum backend vars:

These are required by OAuth/token exchange and callback redirect logic.

Firebase Functions uses its runtime service account by default, so no extra Admin SDK JSON/base64 secret is required for deployed functions.

2) Configure Google OAuth in Google Cloud Console

In your OAuth client:

The backend builds the OAuth URL with that redirect URI and exchanges auth code for tokens. The URI in Google Cloud Console must be an exact string match with GOOGLE_REDIRECT_URI (or GOOGLE_ADS_REDIRECT_URI when using fallback mode).

3) Deploy Firebase Hosting + Cloud Functions

This implementation depends on Hosting/server rewrites that forward these paths:

Cron is provided by a scheduled Cloud Function:

4) Verify app flow in the UI

In the Ads page:

  1. Enter Google account email + customer ID.
  2. Click Connect Google Ads (starts OAuth via backend).
  3. Complete Google consent.
  4. Callback should return to /ads and show success/failure notice.
  5. Confirm billing.
  6. Save brief, create campaign, and pause/resume.

Those actions now call backend endpoints instead of client-only writes.

5) Ensure Firestore writes succeed

Server writes target:

These are Admin SDK writes, so Firestore client security rules do not block them. Project health, billing status, and index/state health still matter.

6) Metrics sync expectations

Metrics sync runs every 30 minutes through:

Optional manual sync endpoint:

7) Quick sanity notes

Firebase setup notes

Workspace access records (Firestore)

Seeding / maintenance steps

  1. Ensure you have the Firebase CLI installed and are logged in: npx firebase login.
  2. Create a JSON seed file with workspace documents (see seed/workspaces.seed.json for a ready-to-use example you can tweak per environment).
  3. Import the seed data into Firestore: npx firebase firestore:delete workspaces --project <project-id> --force && npx firebase firestore:import seed/workspaces.seed.json --project <project-id>.
  4. For ongoing updates, edit the documents directly in the Firebase console or via your preferred admin tooling.

One-command Firestore bootstrap

Team member access (teamMembers collection)

Quick seed for local/testing environments

  1. Update seed/team-members.seed.json with the UID, email, and store ID that you want to allow through login.
  2. Import the roster seed into the default database:
    npx firebase firestore:delete teamMembers --project <project-id> --force
    npx firebase firestore:import seed/team-members.seed.json --project <project-id>
    
  3. If you prefer to seed manually, create a document at teamMembers/<uid> (and optionally teamMembers/<email>) in the default database containing the same fields as the JSON example. The login callable will reject accounts that lack both documents or that do not specify a storeId.

Troubleshooting: new signups do not create team/store records

If you create a Firebase Auth user and do not see corresponding documents in Firestore, walk through the checklist below:

  1. Confirm the Cloud Function is deployed. In the Firebase console open Functions and ensure onAuthCreate appears with a green check. If it is missing, redeploy from the repo root:
    cd functions
    npm install
    npm run deploy
    
  2. Inspect execution logs. In the Firebase console → FunctionsonAuthCreateLogs, look for errors such as permission issues (PERMISSION_DENIED) or missing indices. Fix any issues surfaced there so the default service account can write to Firestore.
  3. Retry with a fresh user. After resolving any deployment or permission issues, create a brand-new Auth user. The function only runs the first time the user is created, so deleting and re-creating the user ensures the trigger fires again.

Following these steps should result in new documents at teamMembers/<uid> and stores/<uid> in the default database immediately after signup.

Branding


Happy shipping! — 2025-09-23

Integrating Paystack payments

Follow the flow below to connect Paystack as the card/mobile processor for Sedifex. The checklist assumes you already followed the Firebase setup steps above and that your stores are created by the onAuthCreate trigger.

  1. Create Paystack credentials
    • Sign in to your Paystack dashboard and create a Live and Test secret key pair.
    • Store the keys in your deployment environment (e.g., Vercel, Firebase Functions config) rather than hard-coding them in the repo. The frontend only needs the public key; keep the secret key scoped to Cloud Functions.
    • For Cloud Functions, set PAYSTACK_SECRET_KEY, PAYSTACK_PUBLIC_KEY, and APP_BASE_URL (used for webhook verification and default redirects). For example:
      cd functions
      firebase functions:config:set PAYSTACK_SECRET_KEY="sk_live_xxx" PAYSTACK_PUBLIC_KEY="pk_live_xxx" APP_BASE_URL="https://app.sedifex.com"
      
  2. Publish provider metadata to Firestore
    • Open the stores/<storeId> document (or update your seeding script) and set paymentProvider to paystack.
    • Keep billing status fields (paymentStatus, amountPaid, contract dates) up to date so the resolveStoreAccess callable can block suspended workspaces while still returning provider info for paid or trial accounts.
  3. Expose the Paystack public key to the PWA
    • Add VITE_PAYSTACK_PUBLIC_KEY=<pk_test_or_live_value> to web/.env.local for local development and to your hosting provider for production.
    • Update any environment loader (for example web/src/config/env.ts) to read the new variable and export it alongside the Firebase config.
  4. Invoke Paystack during checkout
    • Inside the Sell screen, intercept non-cash tenders before calling commitSale. Load Paystack’s inline widget or SDK with the amount, customer email/phone, and receive the transaction reference.
    • On success, enrich the existing payment payload with the Paystack response: e.g. { method: 'card', amountPaid, changeDue, provider: 'paystack', providerRef: response.reference, status: response.status }.
    • Persist the payload as-is—commitSale already stores the payment object verbatim, so downstream reporting can access the Paystack reference without schema changes.
  5. Handle offline and retries
    • Reuse the existing offline queue: if a sale is queued because the network is down, add the Paystack reference and mark the local payment status so the cashier can reconcile it when connectivity returns.
    • Create a reconciliation job (CLI script or scheduled Cloud Function) that pulls unsettled Paystack transactions and compares them to Firestore sales records, updating statuses or flagging discrepancies for review.
  6. Secure credentials and webhooks
    • Store the Paystack secret key via firebase functions:config:set paystack.secret="sk_live_..." (or your preferred secret manager) and read it in the Cloud Function that confirms transactions.
    • If you enable Paystack webhooks, deploy a HTTPS Cloud Function that validates the signature with the secret key and updates the matching sales/<id> document.
    • Update firestore.rules and callable permissions so only privileged roles can change payment-related fields.
  7. Test the full flow
    • Run end-to-end tests against Paystack’s Test mode to validate successful, declined, and timed-out transactions.
    • Confirm that resolveStoreAccess still returns billing metadata for new signups and that the UI gracefully handles both paid and trial workspaces with Paystack enabled.

Documenting these steps keeps the integration consistent across environments and makes it easy to onboard additional stores with Paystack support.