Files
LynkedUpPro_CRM/docs/diagrams/lld_i0_integration.md
T
Satyam 7694788387 feat(ProCanvas): retro game overhaul with log actions, challenges & achievements modals
- Complete ProCanvas redesign with retro sports game aesthetic
- Card-game style photo frame with tilt, shimmer, corner star accents
- Daily Missions card with weekly challenge + individual quest progress bars
- Log Action card (Door Knocked, Lead Gained, Appointment Set, Client Meeting)
- Each log action opens themed modal with relevant input fields
- Challenges modal with 6 active challenges and progress tracking
- Achievements modal with all badges, unlock status, descriptions
- Nav bar tabs (Leaderboard, Challenges, Achievements) wired to modals
- Rewards & Checkpoints with named stages (Daily Grind to Legend Run)
- Smoother Hot Streak pulse animation (2.5s float + 3s pulse rings)
- Hit The Map button with smooth pulsing glow animation
- Grid pattern overlay in Leaderboard card
- Light mode support via dark: Tailwind variants throughout
- Top 3 badges displayed on profile card
- Fixed dropdown option visibility in dark mode
2026-02-26 01:36:36 +05:30

354 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LLD — I0 Integration Overview
**Scope:** Frontend API layer architecture, 401 interceptor, mock-to-real migration, error handling, environment config
**Companion doc:** `docs/integration/00_integration_overview.md`
---
## Diagram 1 — Frontend API Layer Architecture
How the files in `src/api/` relate to each other, to the React components, and to the backend.
```mermaid
graph TD
subgraph Components["React Components / Pages"]
C1["OwnerSnapshot.jsx"]
C2["AdminDashboard.jsx"]
C3["AgentDashboard.jsx"]
C4["Map.jsx"]
C5["VendorDashboard.jsx"]
C6["Chatbot.jsx"]
end
subgraph Hooks["Custom Hooks (src/hooks/)"]
H1["useProperties()"]
H2["useMeetings()"]
H3["useUsers()"]
H4["useVendors()"]
H5["useInvoices()"]
end
subgraph ApiLayer["API Layer (src/api/)"]
CLIENT["client.js\naxios.create({\n baseURL: VITE_API_URL,\n withCredentials: true\n})"]
INTER["interceptors.js\nResponse interceptor:\n401 → refresh → retry\nfailure → logout event"]
AUTH_API["auth.js\nlogin()\nlogout()\ngetMe()"]
PROP_API["properties.js\ngetProperties()\ngetProperty()\nassignAgent()"]
MEET_API["meetings.js\ngetMeetings()\ncreateMeeting()\nupdateMeeting()"]
VEND_API["vendors.js\ngetVendors()\nuploadComplianceDoc()"]
INV_API["invoices.js\ngetInvoices()\napproveInvoice()"]
CHAT_API["chatbot.js\nsendMessage() ← streaming"]
end
subgraph Config["Config (src/config/)"]
ENV["env.js\nAPI_URL\nFEATURE_REAL_API\nGROQ_API_KEY"]
end
subgraph Backend["FastAPI Backend"]
BE["/api/v1/*"]
end
C1 & C2 & C3 --> H1 & H2 & H3
C4 --> H1
C5 --> H4
C6 --> CHAT_API
H1 --> PROP_API
H2 --> MEET_API
H3 --> AUTH_API
H4 --> VEND_API
H5 --> INV_API
AUTH_API & PROP_API & MEET_API & VEND_API & INV_API & CHAT_API --> CLIENT
INTER -->|"wraps"| CLIENT
CLIENT --> ENV
CLIENT -->|"HTTP + cookies"| BE
style CLIENT fill:#1e3a5f,color:#fff
style INTER fill:#1e5f3a,color:#fff
style ENV fill:#5f3a1e,color:#fff
```
---
## Diagram 2 — 401 Interceptor State Machine
The queue-based pattern that handles concurrent requests all expiring at the same time. Only one refresh call is ever made.
```mermaid
stateDiagram-v2
[*] --> Idle : App initialised\ninterceptors.js imported
Idle --> RequestInFlight : Any API call made
RequestInFlight --> Success200 : Response 2xx
Success200 --> Idle : Return response to caller
RequestInFlight --> Error401 : Response 401
Error401 --> AlreadyRetried : originalRequest._retry == true
AlreadyRetried --> PropagateError : Reject — do not loop
Error401 --> RefreshInProgress : isRefreshing == true\n(another request is already refreshing)
RefreshInProgress --> Queued : Push {resolve, reject}\ninto failedQueue
Error401 --> StartRefresh : isRefreshing == false
StartRefresh --> RefreshCall : Set _retry=true\nSet isRefreshing=true\nPOST /auth/refresh
RefreshCall --> RefreshSuccess : 200 — new cookies set\nby backend
RefreshSuccess --> DrainQueue : processQueue(null)\nresume all queued requests
DrainQueue --> RetryOriginal : apiClient(originalRequest)
RetryOriginal --> Success200
RefreshCall --> RefreshFailed : 401 — refresh token\ninvalid/expired/reused
RefreshFailed --> DrainQueueError : processQueue(error)\nreject all queued requests
DrainQueueError --> DispatchLogout : window.dispatchEvent(\n'auth:logout-required'\n)
DispatchLogout --> ForceLogout : AuthContext listener\ncalls logout()\nredirects /login
ForceLogout --> [*]
note right of Queued
Multiple components race
to refresh. First one wins,
others wait here.
end note
note right of RefreshFailed
Refresh token expired
or reuse attack detected.
User must log in again.
end note
```
---
## Diagram 3 — Mock-to-Real Migration: Module Lifecycle
The three phases every integration module (I1I8) goes through. No module skips phases.
```mermaid
flowchart LR
subgraph Phase1["Phase 1 — Feature Flagged (Default OFF)"]
P1_ENV["VITE_FEATURE_REAL_API=false\nin .env.local"]
P1_HOOK["useProperties() hook created\nwith flag branch:\nif (!FEATURE_REAL_API)\n return mockStore data"]
P1_TEST["All existing tests pass\nApp behaves identically\nto before integration started"]
P1_ENV --> P1_HOOK --> P1_TEST
end
subgraph Phase2["Phase 2 — Real API Active"]
P2_ENV["VITE_FEATURE_REAL_API=true\nin .env.local"]
P2_BACKEND["Backend module running\n(e.g. B3 Properties)"]
P2_TEST["QA: real data renders\nloading/error states work\nrole scoping correct"]
P2_ENV --> P2_BACKEND --> P2_TEST
end
subgraph Phase3["Phase 3 — Mock Removed"]
P3_CLEAN["Delete if (!FEATURE_REAL_API)\nbranch from hook"]
P3_FLAG["Remove VITE_FEATURE_REAL_API\nfrom env files"]
P3_MOCK["MockStoreProvider removal\nonly when ALL I1I8 complete"]
P3_CLEAN --> P3_FLAG --> P3_MOCK
end
Phase1 -->|"backend module\nbecomes available"| Phase2
Phase2 -->|"QA signed off"| Phase3
style Phase1 fill:#1e3a5f,color:#fff
style Phase2 fill:#1e5f3a,color:#fff
style Phase3 fill:#5f1e3a,color:#fff
```
---
## Diagram 4 — Feature Flag Decision Tree (Inside Each Hook)
The exact conditional logic inside every `useXxx()` hook created by I1I8.
```mermaid
flowchart TD
HOOK["useProperties(filters)\nor any useXxx() hook called"]
HOOK --> FLAG{FEATURE_REAL_API\n== true?}
FLAG -->|false — mock path| MOCK_STORE["useMockStore()\nRead from MockStoreProvider\nApply filters in JS"]
MOCK_STORE --> MOCK_SHAPE["Return mock-shaped object:\n{ data: [...], loading: false, error: null }"]
FLAG -->|true — real API path| EFFECT["useEffect(() => {\n fetchData()\n return () => { cancelled = true }\n}, [deps])"]
EFFECT --> LOADING["setLoading(true)\nsetError(null)"]
LOADING --> API_CALL["await getProperties(filters)\nsrc/api/properties.js"]
API_CALL --> OK{"Response\nok?"}
OK -->|yes| SET_DATA["setData(result.data)\nsetLoading(false)"]
OK -->|no| SET_ERROR["setError(\n err.response?.data?.detail\n ?? 'Failed to load'\n)\nsetLoading(false)"]
SET_DATA --> REAL_SHAPE["Return real object:\n{ data: [...], loading: false, error: null }"]
SET_ERROR --> ERROR_SHAPE["Return error object:\n{ data: [], loading: false, error: 'message' }"]
subgraph ShapeContract["Shape Contract — Must Match"]
MOCK_SHAPE2["Both paths return:\n{ data: T[], loading: boolean, error: string | null }\n\nComponents never know which path is active"]
end
```
---
## Diagram 5 — Frontend Request Lifecycle
The complete path from a user action in a component all the way to the backend and back, including what each layer is responsible for.
```mermaid
sequenceDiagram
actor User as 👤 User
participant Comp as React Component
participant Hook as useXxx() Hook
participant ApiFile as src/api/properties.js
participant Client as apiClient (client.js)
participant Inter as interceptors.js
participant BE as FastAPI /api/v1
User->>Comp: Interacts (navigate / click / filter)
Comp->>Hook: useProperties({ status: 'Hot Lead' })
Note over Hook: checks FEATURE_REAL_API flag
Hook->>ApiFile: getProperties({ status: 'Hot Lead' })
ApiFile->>Client: apiClient.get('/properties', { params })
Note over Client: Attaches withCredentials=true\nbrowser sends httpOnly cookie automatically
Client->>Inter: Request passes through interceptor
Note over Inter: Response interceptor registered\n(not triggered yet — on response)
Client->>BE: GET /api/v1/properties?status=Hot+Lead\nCookie: access_token=<jwt>
Note over BE: Middleware: CORS → Rate Limit → Logger\nDependencies: get_current_user() → require_role()\nService: property_service.get_properties(role, filters)
BE-->>Client: 200 { data: [...], meta: { total, page, page_size } }
Client-->>Inter: Response passes through interceptor\n(2xx → pass through unchanged)
Inter-->>ApiFile: { data: [...], meta: {...} }
Note over ApiFile: Optional: transform snake_case → camelCase\nassigned_agent_id → agentId
ApiFile-->>Hook: Transformed data object
Hook-->>Comp: { data: [...], loading: false, error: null }
Comp-->>User: Render updated UI
```
---
## Diagram 6 — HTTP Error Handling Decision Tree
How the frontend responds to each HTTP error code the backend can return.
```mermaid
flowchart TD
RES["API response received\nerror.response.status"]
RES --> S400{400\nBad Request}
S400 --> H400["Validation error\n(bad request body)\nShow field-level errors\nfrom error.response.data.detail"]
RES --> S401{401\nUnauthorized}
S401 --> RETRY{originalRequest\n._retry?}
RETRY -->|false — first 401| REFRESH["Trigger token refresh\nPOST /auth/refresh\n(interceptors.js queue)"]
REFRESH --> RFAIL{refresh\nsucceeded?}
RFAIL -->|yes| RETR["Retry original request\nUser sees nothing"]
RFAIL -->|no| LOGOUT["dispatch('auth:logout-required')\nAuthContext.logout()\nRedirect /login"]
RETRY -->|true — already retried| LOGOUT
RES --> S403{403\nForbidden}
S403 --> H403["User authenticated but wrong role\nShow 'Access denied' toast\nor redirect to own portal\n(do not retry)"]
RES --> S404{404\nNot Found}
S404 --> H404["Resource does not exist\nShow empty state or\n'Not found' message"]
RES --> S422{422\nUnprocessable Entity}
S422 --> H422["FastAPI schema validation failed\nShow detail array from\nerror.response.data.detail[]\nHighlight failing fields"]
RES --> S429{429\nRate Limited}
S429 --> H429["Show toast 'Too many requests'\nBackoff — do not auto-retry\nUser must wait"]
RES --> S500{500\nServer Error}
S500 --> H500["Show generic error toast\n'Something went wrong'\nLog error.response.data to console\n(dev only — never to user)"]
subgraph ErrorShape["All error bodies follow this shape"]
ES["{\n detail: 'Human-readable message',\n code: 'MACHINE_CODE'\n}\n\n422 uses FastAPI default:\n{\n detail: [\n { loc: ['body','field'], msg: '...', type: '...' }\n ]\n}"]
end
```
---
## Diagram 7 — Frontend Environment Variable Flow
How `VITE_API_URL` travels from `.env.local` through the build system into every API call.
```mermaid
graph LR
subgraph Files["Source Files"]
ENV_LOCAL[".env.local\n(never committed)\nVITE_API_URL=http://localhost:8000/api/v1"]
ENV_PROD[".env.production\n(Vercel env vars)\nVITE_API_URL=https://api.lynkeduppro.com/api/v1"]
ENV_EXAMPLE[".env.example\n(committed template)\nVITE_API_URL="]
end
subgraph Build["Vite Build Process"]
VITE["Vite reads .env.local\nDuring vite dev / vite build\nOnly VITE_ prefix exposed to bundle\nAll others stripped"]
end
subgraph Runtime["Browser Runtime"]
META["import.meta.env.VITE_API_URL\n(string | undefined)\nbaked into JS bundle at build time"]
end
subgraph Config["src/config/env.js"]
EXPORT["export const API_URL =\n import.meta.env.VITE_API_URL\n ?? 'http://localhost:8000/api/v1'\n\nexport const FEATURE_REAL_API =\n import.meta.env.VITE_FEATURE_REAL_API === 'true'\n\nexport const GROQ_API_KEY =\n import.meta.env.VITE_GROQ_API_KEY"]
end
subgraph ApiClient["src/api/client.js"]
CLIENT["axios.create({\n baseURL: API_URL,\n withCredentials: true\n})"]
end
ENV_LOCAL -->|"vite dev"| VITE
ENV_PROD -->|"vite build (Vercel)"| VITE
ENV_EXAMPLE -.->|"template for"| ENV_LOCAL
VITE --> META
META --> EXPORT
EXPORT -->|"API_URL"| CLIENT
EXPORT -->|"FEATURE_REAL_API"| HOOKS["All useXxx() hooks\nmock vs real branch"]
EXPORT -->|"GROQ_API_KEY"| CHATBOT["Chatbot.jsx\n(pre-integration direct calls)"]
subgraph Warning["⚠️ Security Note"]
W1["VITE_ vars are PUBLIC\nThey appear in the JS bundle\nNever put SECRET_KEY, DB passwords,\nor admin tokens here\nThose belong in backend .env only"]
end
```
---
## Diagram 8 — Axios `withCredentials` + CORS Handshake
Why `withCredentials: true` is required and what happens at the browser + server level.
```mermaid
sequenceDiagram
participant Browser as Browser
participant Axios as Axios (withCredentials: true)
participant CORS as FastAPI CORS Middleware
participant Handler as Route Handler
Note over Browser,Handler: Preflight (first cross-origin request)
Browser->>CORS: OPTIONS /api/v1/properties\nOrigin: https://lynkeduppro.vercel.app\nAccess-Control-Request-Method: GET\nAccess-Control-Request-Headers: Content-Type
Note over CORS: Check ALLOWED_ORIGINS list:\n['https://lynkeduppro.vercel.app',\n 'http://localhost:5173']
CORS-->>Browser: 200\nAccess-Control-Allow-Origin: https://lynkeduppro.vercel.app\nAccess-Control-Allow-Credentials: true ← REQUIRED\nAccess-Control-Allow-Methods: GET, POST, PATCH, DELETE\nAccess-Control-Allow-Headers: Content-Type
Note over Browser,Handler: Actual request (cookies attached automatically)
Axios->>CORS: GET /api/v1/properties\nOrigin: https://lynkeduppro.vercel.app\nCookie: access_token=<jwt> ← browser attaches because withCredentials=true
CORS->>Handler: Pass through (origin allowed)
Handler-->>CORS: 200 { data: [...] }
CORS-->>Browser: 200 { data: [...] }\nAccess-Control-Allow-Origin: https://lynkeduppro.vercel.app\nAccess-Control-Allow-Credentials: true
Note over Browser: Without withCredentials: true\nthe Cookie header would not be sent\neven for same-site API on different port (localhost:8000)
subgraph BackendConfig["Backend: main.py CORS config"]
BC["CORSMiddleware(\n allow_origins=settings.backend_cors_origins,\n allow_credentials=True, ← REQUIRED for cookies\n allow_methods=['*'],\n allow_headers=['*'],\n)"]
end
```