A full-stack marketplace connecting brands with creators: profiles, a Discover feed, campaigns, applications, direct collaboration requests, an auto-created collaboration workspace, real-time chat, notifications, dashboards with charts, and an admin panel.
connectify/
├── backend/ Express + TypeScript + Prisma (PostgreSQL) + Socket.IO API
├── frontend/ React + Vite + TypeScript + Tailwind CSS
├── docker-compose.yml Postgres + backend + frontend, one command, local only
├── render.yaml Render Blueprint (must live at repo root — see Section 6)
└── .github/workflows/ CI (typecheck/build/test) + optional CD (deploy hooks)
Fully wired, end to end (frontend ↔ backend ↔ database):
- Auth: register (brand/influencer), login, logout, JWT access tokens + rotating refresh tokens in an httpOnly cookie, forgot/reset password, email verification (backend + frontend confirmation page)
- Influencer profile + portfolio (up to 20 images, featured toggle, Cloudinary storage)
- Brand profile + logo upload (also Cloudinary)
- Discover feed — filters: category, tier, availability, location, search, sort — with save/unsave
- Campaigns: full CRUD for brands (with search + status-tab filtering), browse/filter for influencers, deliverables, moodboard images
- Applications: apply → brand accepts/rejects (status-tab filtering on both sides) → auto-creates a Collaboration workspace
- Collaboration Requests: brand invites a creator directly → accept/reject (status-tab filtering) → auto-creates a workspace
- Collaboration workspace: notes, deadline, shared files
- Real-time chat via Socket.IO: typing indicators, online presence, read receipts, image messages
- Notifications (in-app, created on every key event, pushed live over the socket)
- Saved creators (with search) / saved campaigns
- Admin: platform stats with charts (user breakdown, platform activity), user list with suspend/activate/delete (with debounced search), reports list
- Dashboards now chart real data: influencer dashboard (application status donut), brand
dashboard (campaign status bar chart), admin overview (user breakdown donut + activity bar
chart) — built with Recharts, styled to the brand palette, and reused via
components/Charts.tsx - Consistent loading/error handling: a shared
useAsynchook (hooks/useAsync.ts) backs every major data-fetching page with skeleton loaders (card/row/stat variants incomponents/ui.tsx) while loading and a retry-capableErrorStatewhen a request fails, instead of failures silently rendering an empty list. An app-wideErrorBoundarycatches render-time crashes with a friendly fallback instead of a blank screen.
Backend endpoint exists but has no dedicated frontend screen yet (easy to wire up — the API is ready): admin portfolio-image moderation, admin campaign moderation.
Deliberately simplified — needs a real provider to go further:
- Mobile OTP — the schema and registration form collect a mobile number and the architecture is OTP-ready, but no SMS provider is wired in. Email verification is the implemented path today.
- Email sending — uses Nodemailer. Without
SMTP_*env vars set, emails are logged to the server console instead of sent, so auth flows keep working in local dev. Add real SMTP credentials (or swap in Resend/SendGrid) for production. - Emoji picker — a quick-reaction strip rather than a full picker library, to avoid an extra dependency for something cosmetic.
Both apps have real, running test suites (Vitest), wired into npm test and into CI.
Backend (backend/src/**/__tests__, backend/src/__tests__) — 33 tests:
ApiError.test.ts,validators.test.ts— pure unit tests, no database needed.app.test.ts— supertest against the real Express app: health check, 404 handling, validation-layer rejections, and auth-guard rejections (401s) on protected routes.auth.integration.test.ts— full register → login →/auth/meflow against a real database.
Frontend (frontend/src/**/__tests__) — 18 tests:
ui.test.tsx— Button, Input, Badge, EmptyState, ErrorState behavior (render, click, disabled/ loading states, retry callback).useAsync.test.ts— the shared data-fetching hook: loading → data, loading → error,reload(), optimisticsetData().api.test.ts—extractErrorMessageagainst real Axios error shapes.
Run them:
cd backend && npm test # or npm run test:watch
cd frontend && npm test # or npm run test:watchSandbox note: this project was built inside a sandbox whose network allowlist doesn't include
binaries.prisma.sh(Prisma's engine-binary host), soprisma generatecould never fully complete there. I validated everything that doesn't need a live query engine — the full schema, every model/relation/field name (grepped and cross-checked by hand againstschema.prisma), a filteredtsc --noEmitpass, and 24/24 backend unit tests that don't touch the database, plus all 18 frontend tests and both production builds, all genuinely green. The two backend suites that need a real Prisma client (app.test.ts,auth.integration.test.ts) fail in that sandbox specifically with@prisma/client did not initialize yet— that's the same blocked-domain issue, not a code defect, and it will not happen on your machine or in the GitHub Actions workflow below, both of which have normal internet access.
Local full stack, one command (Postgres + backend + frontend):
docker compose up --buildFrontend on http://localhost:5173, backend on http://localhost:5000. Cloudinary env vars
default to empty, so uploads will fail until you export CLOUDINARY_CLOUD_NAME,
CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET before running (docker compose picks up a
.env file in the project root automatically). Seed the admin account once the stack is up:
docker compose exec backend npm run seedEach app also has a standalone Dockerfile (backend/Dockerfile, frontend/Dockerfile — the
frontend one is a multi-stage build served by nginx, config in frontend/nginx.conf) for
platforms that build from a single image rather than a compose file.
.github/workflows/ci.yml runs on every push/PR to main: installs, typechecks, and builds both
apps, and runs both test suites — the backend job spins up a real postgres:16-alpine service
container, so the full integration test suite (the one this sandbox couldn't run) executes for
real on every push.
.github/workflows/deploy.yml is optional — Render and Vercel both auto-deploy from GitHub once
connected, so you likely don't need it. It's there if you'd rather gate deploys behind CI passing
first, firing each platform's deploy-hook URL from secrets.
cd backend
cp .env.example .env # fill in DATABASE_URL, JWT secrets, Cloudinary, etc.
npm install
npx prisma migrate dev --name init # creates tables from prisma/schema.prisma
npm run seed # creates the admin account from ADMIN_EMAIL/ADMIN_PASSWORD
npm run dev # http://localhost:5000cd frontend
cp .env.example .env # point at your local backend
npm install
npm run dev # http://localhost:5173- Create a project at supabase.com.
- Click Connect on the project dashboard to see your connection strings.
- Render runs your backend as one persistent process (not serverless), so the direct
connection (port
5432) is the simplest choice forDATABASE_URL— no pooler quirks to worry about. If you later move the backend to a serverless/edge platform, switch to the Supavisor transaction pooler (port6543) and append?pgbouncer=trueto the URL, since Prisma's default prepared-statement behavior doesn't always play well with transaction-mode pooling.
- Create a free account at cloudinary.com.
- Copy your Cloud Name, API Key, and API Secret from the dashboard.
- Push this repo to GitHub as-is —
render.yamlis already at the repo root, which is where Render's Blueprint flow looks for it by default. It hasrootDir: backendset internally, so Render still builds from thebackend/folder. - On render.com: New → Blueprint → connect the repo → Render reads
render.yamland shows you the service it'll create → fill in the secret env vars it prompts for (anything markedsync: false):DATABASE_URL,CORS_ORIGIN(your Vercel URL, added after step 4 — usehttp://localhost:5173as a placeholder for now and update it later),CLIENT_URL,CLOUDINARY_*, optionallySMTP_*, andADMIN_EMAIL/ADMIN_PASSWORD.JWT_ACCESS_SECRET/JWT_REFRESH_SECRETare auto-generated by the blueprint — leave those. - Click Deploy Blueprint.
- After the first deploy, open the service's Shell tab (or a one-off job) and run:
npx prisma migrate deploy npm run seed
Don't have a repo yet, or want to deploy the backend without Blueprints? New → Web Service →
connect the repo → set Root Directory to backend → Build Command
npm install && npm run build → Start Command npm run start → add the same env vars manually.
- New Project on vercel.com → import the same repo → set Root Directory
to
frontend. - Env vars:
VITE_API_URL=https://<your-render-service>.onrender.com/api,VITE_SOCKET_URL=https://<your-render-service>.onrender.com. - Deploy, then copy the resulting
https://<project>.vercel.appURL back into Render'sCORS_ORIGINandCLIENT_URL, and redeploy the backend.
The refresh token is an httpOnly cookie set with SameSite=None; Secure in production (needed
since Vercel and Render are different domains). This works in all major browsers, but some
privacy-hardened browser configurations block third-party cookies by default — if that ever
matters to your users, putting both apps on subdomains of one root domain removes the issue
entirely.
- Auth: short-lived JWT access token (kept in memory on the frontend, never localStorage) + a rotating opaque refresh token stored (hashed) server-side and sent only as an httpOnly cookie — matches the pattern already used elsewhere in this project.
- Uploads: Multer (memory storage) → streamed directly to Cloudinary, no temp files on disk.
- Real-time: a single Socket.IO namespace, JWT-authenticated at handshake, rooms per user
(
user:<id>) and per collaboration (collab:<id>). - Validation: Zod schemas for every mutating endpoint.
- Data fetching (frontend):
useAsyncstandardizes loading/error/retry across pages;api/index.tscentralizes every endpoint call;lib/api.tshandles the access-token refresh dance transparently via an Axios interceptor. - Data model: see
backend/prisma/schema.prisma— 17 models covering every entity in the spec (Users, both profile types, portfolio images, campaigns + moodboards + deliverables, applications, requests, the collaboration workspace + files, messages, notifications, saves, reports, refresh tokens).
- Run
prisma migrate devlocally and click through the whole flow once end to end. - Wire up the two remaining admin-moderation buttons (endpoints already exist).
- Add real SMTP credentials so verification/reset emails actually send.
- If you want it, I can also add an OTP provider integration or the two moderation UI panels — just ask.