# Sanad Dashboard

A Next.js admin dashboard for managing influencer marketing campaigns, clients, AI tools, and marketplace operations.

## Run & Operate

- **Run Dev Server**: `npm run dev` (runs on port 3002 with Turbopack)
- **Build**: `npm run build`
- **Required Environment Variables**:
    - `NEXT_PUBLIC_FRONTEND_PROXY_URL`: Backend API URL (e.g., `https://sanad.work/production/api`)
    - `NEXT_PUBLIC_API_URL`: Public API URL
    - `NEXTAUTH_URL`: Auth callback URL (Replit dev domain)
    - `NEXTAUTH_SECRET`: Session signing secret
    - `DATABASE_URL`: PostgreSQL connection string

## Stack

- **Framework**: Next.js 15.3.1 (App Router, Turbopack)
- **Styling**: Tailwind CSS v4, ReUI theme
- **Runtime**: Node.js (via Next.js)
- **ORM**: Prisma (PostgreSQL)
- **Validation**: _Populate as you build_
- **Build Tool**: Next.js (Turbopack)

## Recently shipped

- **Same-origin API proxy to bypass CORS (2026-06-08)**: The browser at `dashboard.sanad.website` was blocked by CORS calling `sanad.work/production/api` directly — the backend's CORS allowlist includes `https://sanad.website` but NOT the dashboard subdomain, and the backend cannot be modified. Fix: client-side API calls now go to a same-origin path `/be-api/*` which `next.config.mjs` `rewrites()` forwards server-side to `${NEXT_PUBLIC_FRONTEND_PROXY_URL}/*`. `network/axios.ts` exports `apiBaseUrl` (= `/be-api` in the browser, = absolute backend URL on the server, since server→server has no CORS). `modules/blogs/hooks/index.ts` reuses `apiBaseUrl` for its parallel en/ar `axiosDefault` calls. Server contexts (login `app/(auth)/actions.ts`, media-origin `collab-requests/utils.ts`) keep using the absolute `NEXT_PUBLIC_FRONTEND_PROXY_URL` and are unaffected (no CORS server-side / `<img>` tags). Auth Bearer token + XSRF still attach in the axios request interceptor as before.


- **Assign Subscription to existing client (2026-05-14)**: Added an "Assign Subscription" button + dialog to the `UserSubscriptions` block on the client detail page (`/client-management/[id]`) so admins can attach a package to a client they forgot to assign one to at creation time. Reuses the existing `usePackagesList` hook (`GET /select/packages-list`) for a searchable package picker. On confirm, posts `package_id` as form-data to `POST /users/update/{userId}` — same mechanism the create-client form uses; BE creates an active subscription with auto-derived start/end dates from the package duration. Invalidates `['user_subscriptions',userId]`, `['user_details',userId]`, `['balance-summary',userId]` so the subscription list and balance-summary table refresh in place. Surfaces BE error message in-dialog (e.g. "You already have an active subscription"), shows an amber warning if an active sub already exists, and toasts on success. Files: `modules/client-management/components/UserSubscriptions/{UserSubscriptions,AssignSubscriptionDialog}.tsx`. Existing edit-subscription dialog (start_date/end_date only via `POST /subscription/update/{id}`) is untouched.

- **Customers Registrations — DataGrid refactor with advanced filters (2026-05-13)**: Rebuilt `/customers-registrations` on the same TanStack `DataGrid` pattern. `page.tsx` became a thin `Suspense` wrapper; all logic moved into the client component `modules/customers-registrations/components/customersRegistrations/CustomersRegistrations.tsx`. Features: server-side pagination (sends `page`/`per_page`/`search`/`country_name` to `GET /customer-registration`), URL-persisted state with a `didMountFilters` ref guard so deep-links aren't wiped on first render, Filters popover (conversion-status: all/converted/not-converted · country dropdown faceted from current page · has-website toggle), search w/ Enter-to-apply + clear, active-filter chips, sortable columns (visible-page only — BE has no sort param), column visibility toggle, Refresh, Export CSV (existing endpoint preserved), per-row dropdown (View details / Convert / Delete) + inline Convert button when not yet converted, status badge, avatar + company subtitle on Full Name. Existing `ConvertToClient` + `CustomerDeleteDialog` dialogs reused unchanged.

- **Bio Contact Extractor — DataGrid refactor with advanced filters (2026-05-13)**: Rebuilt `/snap-phones-bio` on top of the project's TanStack-based `DataGrid` system (`components/ui/data-grid*.tsx`) following the `account/members/permissions-toggle` pattern. New: sortable column headers (sort applies to the visible page only — BE has no sort param), column visibility toggle, server-side pagination via `meta.last_page` with sizes [25/50/100/200], "Filters" popover (search + has_phone + has_email), active-filter chips with one-click clear, four stats cards (total / page-with-email / page-with-phone / page-with-both), platform switcher in the card header, per-row eye + re-extract icon buttons, full row detail dialog (bio, all emails/phones with copy, profile_id/social_id, created/updated). All filter+pagination state URL-persisted (`?platform=&search=&has_email=&has_phone=&per_page=&page=`); two `didMount` refs guard the platform/filter reset effects so deep-links aren't wiped on first render. BE path corrected: `/bio-phones` (NOT `/admin/bio-phones`) — spec was wrong.

- **Bio Contact Extractor — unified across Snapchat / TikTok / Instagram (2026-05-09)**: Refactored the old single-platform `/snap-phones-bio` page into one unified admin tool that talks to the new BE endpoint group `GET|POST /admin/bio-phones[?platform=snapchat|tiktok|instagram]`. Single page with a platform dropdown (URL-persisted via `?platform=`), platform-aware row shapes (Snapchat: `snap_user_name`/`display_name`/`profile_id` ; TikTok+IG: `username`/`title`/`social_id`) normalized through `normalizeRow()`. Filters: search + has_email/has_phone + per_page (25/50/100/200). Three actions: **Refresh**, **Extract Single** (top-level dialog with platform-aware placeholder — Profile ID for Snapchat, Username/Social ID for TT+IG; gracefully handles "No contact info found" as not-found rather than success), **Re-scan all** (confirm dialog warns about TikTok/IG taking minutes). Multiple emails/phones (comma-separated by BE) split into individual rows with per-value copy buttons. Sidebar entry renamed "Bio Contact Extractor" (kept `/snap-phones-bio` route for backward compat). File: `app/(protected)/(sanad)/snap-phones-bio/page.tsx`.

- **Collaboration Requests Oversight (2026-05-06)**: Owner-gated pages at `/marketplace-admin/collab-requests` (list with status/brand/creator/payment filters) and `/marketplace-admin/collab-requests/[crId]` (detail with brand/creator/money breakdown + content-submission gallery). For non-final submissions (pending review, rejected by brand), an amber **"Admin-Approve & Release Funds" / "Override Brand Refusal & Release Funds"** button calls `POST /admin/marketplace/collaboration-requests/:crId/submissions/:submissionId/admin-approve` with optional `{override_reason, admin_notes}` — releases escrowed funds to the creator's wallet. Files: `modules/marketplace-admin/collab-requests/{CollabRequestsList,CollabRequestDetail,utils}.ts(x)`, `network/apis/dashboard/marketplace/collab-requests.{types,apis}.ts(x)`. BE wraps list in `data.collaborations + data.meta`, detail in `data.collaboration`, submissions in `data.submissions[]`. Sidebar: "Collaboration Requests" (Megaphone icon).

## Where things live

- `app/`: Next.js App Router structure
    - `(auth)/signin/page.tsx`: Login page
    - `(protected)/`: Authenticated routes
        - `(sanad)/client-management/page.tsx`: Client management module
        - `(sanad)/ai-hub/*`: AI Hub module
        - `(sanad)/marketplace/*`: Marketplace module
        - `(sanad)/balance-logs/*`: Balance Logs module
        - `(sanad)/blogs/*`: Blog Management module
        - `(sanad)/users/*`: User Management module (users list)
        - `(sanad)/roles/*`: User Management module (roles list)
        - `(sanad)/marketplace-admin/*`: Marketplace Admin module (owner-only)
- `modules/`: Feature-specific modules
- `network/axios.ts`: Axios client configuration
- `next.config.mjs`: Next.js configuration
- `middleware.ts`: Replit proxy fix for `x-forwarded-host`
- `config/menu.config.tsx`: Sidebar navigation menu configuration
- `css/styles.css`: Main Tailwind CSS stylesheet

## Architecture decisions

- **Turbopack for Tailwind v4**: Explicitly using `--turbopack` for dev server due to Tailwind CSS v4 compatibility.
- **Pure `contentEditable` Rich Text Editor**: Instead of `@tiptap/*` or other dependencies, a custom `contentEditable` editor is implemented due to Turbopack resolution issues.
- **Bilingual API Fetching**: For modules like Blog Management, API data for both English and Arabic is fetched in parallel using the `Accept-Language` header.
- **Server-side Auth with Cookies**: Authentication uses server-side cookie management for `XSRF-TOKEN` and `SANAD_IS_OWNER`, integrating with Next.js Server Actions.
- **Marketplace Admin Auth Gating**: Marketplace Admin access is gated both client-side (hiding sidebar links) and server-side (redirecting non-owners) using a `SANAD_IS_OWNER` cookie.

## Product

- **Client Management**: Full client lifecycle management with user balance tracking.
- **AI Hub**: Suite of 7 AI-powered tools for influencer marketing campaign creation and optimization.
- **Marketplace**: Influencer campaign offers, merchant orders, escrow, and payout management.
- **Balance Logs**: Comprehensive history of all client balance transactions.
- **Blog Management**: Internal CMS with a rich text editor, media uploads, and multilingual support.
- **User Management**: Unified module for managing users, roles, and permissions.
- **Packages**: Define subscription packages that auto-credit various balance types to clients.
- **Marketplace Admin**: Owner-only section for moderating marketplace activities, including withdrawals, money center (wallets, ledger), and reports.

## User preferences

_Populate as you build_

## Gotchas

- **Tailwind CSS v4**: Requires `source(none)` with explicit `@source` directives.
- **Rich Text Editor**: Do NOT use `@tiptap/*` packages due to Turbopack compatibility issues; use the custom `contentEditable` implementation.
- **Marketplace Admin Offers**: The backend `/admin/marketplace/offers` endpoint requires a `status` query parameter (`Published`, `Draft`, or `Archived`) to return data; omitting it will result in an empty response.
- **Balance Display**: `initial_balance` and `total_used_amount` are backend-accumulated values and should not be displayed as current balances.

## Pointers

- **Next.js Documentation**: [https://nextjs.org/docs](https://nextjs.org/docs)
- **Tailwind CSS v4 Documentation**: [https://tailwindcss.com/docs/upgrade-guide](https://tailwindcss.com/docs/upgrade-guide)
- **Prisma Documentation**: [https://www.prisma.io/docs](https://www.prisma.io/docs)
- **Replit Docs**: [https://docs.replit.com/](https://docs.replit.com/)
- **Metronic Theme (if applicable)**: _Link to Metronic documentation_