clean
@@ -1,5 +0,0 @@
|
||||
[
|
||||
"1b2e22bdec8f6493bf71ee535b6db6b4b5cd2d373f0ffb25524e229f3b5b7f5f",
|
||||
"e075ff357ef35be2d55b0e383d59c5256980c492ada7ab84c84b2bb5ac26a73f",
|
||||
"994b31c1aef3d59fe59bc3b8e1dec860a6fb3c73cbf41bdf45028e2c1ecbcf7a"
|
||||
]
|
||||
@@ -1,145 +0,0 @@
|
||||
[
|
||||
{
|
||||
"title": "Applications",
|
||||
"iconColorClass": "bg-primary-100",
|
||||
"iconPath": "assets/images/icon-applications.svg",
|
||||
"links": [
|
||||
{
|
||||
"title": "Control Tower",
|
||||
"url": "https://krow-workforce-dev.web.app",
|
||||
"badge": "Dev",
|
||||
"badgeColorClass": "bg-blue-500",
|
||||
"containerClass": "bg-gradient-to-r from-blue-50 to-blue-100 hover:from-blue-100 hover:to-blue-200",
|
||||
"iconClass": "w-2 h-2 bg-blue-500 rounded-full",
|
||||
"textHoverClass": "group-hover:text-blue-700"
|
||||
},
|
||||
{
|
||||
"title": "Control Tower",
|
||||
"url": "https://krow-workforce-staging.web.app",
|
||||
"badge": "Staging",
|
||||
"badgeColorClass": "bg-amber-500",
|
||||
"containerClass": "bg-gradient-to-r from-amber-50 to-amber-100 hover:from-amber-100 hover:to-amber-200",
|
||||
"iconClass": "w-2 h-2 bg-amber-500 rounded-full",
|
||||
"textHoverClass": "group-hover:text-amber-700"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Legacy Mobile Apps",
|
||||
"iconColorClass": "bg-green-100",
|
||||
"iconPath": "assets/images/icon-legacy-mobile-apps.svg",
|
||||
"links": [
|
||||
{
|
||||
"title": "Google Play Store",
|
||||
"url": "https://play.google.com/store/apps/dev?id=9163719228191263405&hl=en",
|
||||
"badge": "Live",
|
||||
"badgeColorClass": "bg-green-500",
|
||||
"containerClass": "bg-gradient-to-r from-green-50 to-emerald-100 hover:from-green-100 hover:to-emerald-200",
|
||||
"iconPath": "assets/images/icon-google-play.svg",
|
||||
"textHoverClass": "group-hover:text-green-700"
|
||||
},
|
||||
{
|
||||
"title": "Apple App Store",
|
||||
"url": "https://apps.apple.com/us/developer/thinkloops-llc/id1719034287",
|
||||
"badge": "Live",
|
||||
"badgeColorClass": "bg-gray-700",
|
||||
"containerClass": "bg-gradient-to-r from-gray-50 to-gray-100 hover:from-gray-100 hover:to-gray-200",
|
||||
"iconPath": "assets/images/icon-apple-app-store.svg",
|
||||
"textHoverClass": "group-hover:text-gray-700"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Cloud Infrastructure",
|
||||
"iconColorClass": "bg-purple-100",
|
||||
"iconPath": "assets/images/icon-cloud-infrastructure.svg",
|
||||
"links": [
|
||||
{
|
||||
"title": "Firebase Console",
|
||||
"url": "https://console.firebase.google.com/project/krow-workforce-dev/overview",
|
||||
"badge": "Dev",
|
||||
"badgeColorClass": "bg-blue-500",
|
||||
"containerClass": "bg-gradient-to-r from-blue-50 to-indigo-100 hover:from-blue-100 hover:to-indigo-200",
|
||||
"iconPath": "assets/images/icon-firebase-console.svg",
|
||||
"textHoverClass": "group-hover:text-indigo-700"
|
||||
},
|
||||
{
|
||||
"title": "Firebase Console",
|
||||
"url": "https://console.firebase.google.com/project/krow-workforce-staging/overview",
|
||||
"badge": "Staging",
|
||||
"badgeColorClass": "bg-amber-500",
|
||||
"containerClass": "bg-gradient-to-r from-amber-50 to-orange-100 hover:from-amber-100 hover:to-orange-200",
|
||||
"iconPath": "assets/images/icon-firebase-console.svg",
|
||||
"textHoverClass": "group-hover:text-orange-700"
|
||||
},
|
||||
{
|
||||
"title": "Google Cloud",
|
||||
"url": "https://console.cloud.google.com/welcome/new?project=krow-workforce-dev",
|
||||
"badge": "Dev",
|
||||
"badgeColorClass": "bg-blue-500",
|
||||
"containerClass": "bg-gradient-to-r from-blue-50 to-cyan-100 hover:from-blue-100 hover:to-cyan-200",
|
||||
"iconPath": "assets/images/icon-google-cloud.svg",
|
||||
"textHoverClass": "group-hover:text-cyan-700"
|
||||
},
|
||||
{
|
||||
"title": "Google Cloud",
|
||||
"url": "https://console.cloud.google.com/welcome/new?project=krow-workforce-staging",
|
||||
"badge": "Staging",
|
||||
"badgeColorClass": "bg-amber-500",
|
||||
"containerClass": "bg-gradient-to-r from-amber-50 to-yellow-100 hover:from-amber-100 hover:to-yellow-200",
|
||||
"iconPath": "assets/images/icon-google-cloud.svg",
|
||||
"textHoverClass": "group-hover:text-yellow-700"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Design",
|
||||
"iconColorClass": "bg-pink-100",
|
||||
"iconPath": "assets/images/icon-design.svg",
|
||||
"links": [
|
||||
{
|
||||
"title": "Staff App Figma File",
|
||||
"url": "https://www.figma.com/design/HfNpoYOpkfUu2lgDMp4xQK/KROW-Staff-App-Revamp?node-id=0-1&t=4TLUeCIWf7I1TTGQ-1",
|
||||
"badge": "Design",
|
||||
"badgeColorClass": "bg-pink-500",
|
||||
"containerClass": "bg-gradient-to-r from-pink-50 to-rose-100 hover:from-pink-100 hover:to-rose-200",
|
||||
"iconPath": "assets/images/icon-figma.svg",
|
||||
"textHoverClass": "group-hover:text-pink-700"
|
||||
},
|
||||
{
|
||||
"title": "Staff App Preview",
|
||||
"url": "https://9000-firebase-studio-1764098159606.cluster-oe5pskshnfducslpwllk6difqk.cloudworkstations.dev/",
|
||||
"badge": "Preview",
|
||||
"badgeColorClass": "bg-purple-500",
|
||||
"containerClass": "bg-gradient-to-r from-purple-50 to-fuchsia-100 hover:from-purple-100 hover:to-fuchsia-200",
|
||||
"iconPath": "assets/images/icon-preview.svg",
|
||||
"textHoverClass": "group-hover:text-purple-700"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Resources",
|
||||
"iconColorClass": "bg-indigo-100",
|
||||
"iconPath": "assets/images/icon-resources.svg",
|
||||
"links": [
|
||||
{
|
||||
"title": "GitHub Repository",
|
||||
"subtitle": "View source code",
|
||||
"url": "https://github.com/Oloodi/krow-workforce",
|
||||
"containerClass": "bg-gradient-to-r from-gray-50 to-slate-100 hover:from-gray-100 hover:to-slate-200",
|
||||
"iconPath": "assets/images/icon-github.svg",
|
||||
"textHoverClass": "group-hover:text-gray-700",
|
||||
"arrowIcon": true
|
||||
},
|
||||
{
|
||||
"title": "Team Communication",
|
||||
"subtitle": "Google Chat",
|
||||
"url": "https://chat.google.com/",
|
||||
"containerClass": "bg-gradient-to-r from-green-50 to-teal-100 hover:from-green-100 hover:to-teal-200",
|
||||
"iconPath": "assets/images/icon-chat.svg",
|
||||
"textHoverClass": "group-hover:text-teal-700",
|
||||
"arrowIcon": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,67 +0,0 @@
|
||||
|
||||
graph TB
|
||||
Title["<b>KROW - Cloud Architecture</b><br/>Production Solution"]
|
||||
|
||||
subgraph Prototype[" "]
|
||||
Proto["🔧 Current KROW Prototype<br/>(Demo Only)"]
|
||||
end
|
||||
|
||||
Title --> Prototype
|
||||
Title --> Production
|
||||
|
||||
subgraph Production["🏢 KROW - PRODUCTION ARCHITECTURE"]
|
||||
subgraph GCP["☁️ Google Cloud Platform - US Regions"]
|
||||
|
||||
subgraph Auth["🔐 Authentication"]
|
||||
Firebase["Firebase Auth<br/>• MFA<br/>• OAuth 2.0"]
|
||||
end
|
||||
|
||||
subgraph Backend["⚙️ Backend"]
|
||||
Functions["Cloud Functions<br/>• Serverless<br/>• Auto-scaling"]
|
||||
API["API Gateway<br/>• Secure APIs<br/>• Rate limiting"]
|
||||
end
|
||||
|
||||
subgraph Data["💾 Data Storage"]
|
||||
Firestore["Firestore/Cloud SQL<br/>• Encrypted at rest<br/>• Auto backup"]
|
||||
Storage["Cloud Storage<br/>• Encrypted<br/>• Access control"]
|
||||
end
|
||||
|
||||
subgraph Security["🛡️ Security"]
|
||||
IAM["Cloud IAM<br/>• Access control<br/>• Least privilege"]
|
||||
Logs["Logging & Monitoring<br/>• Audit trails<br/>• Real-time alerts"]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph Compliance["✅ COMPLIANCE"]
|
||||
GDPR["📋 GDPR Ready<br/>• US data hosting<br/>• User rights<br/>• Data portability"]
|
||||
SOC2["🏆 SOC 2 Ready<br/>• Google certified<br/>• Security controls<br/>• Full audit trails"]
|
||||
end
|
||||
end
|
||||
|
||||
Users["👥 End Users"]
|
||||
|
||||
Proto -.->|Migration vers| Production
|
||||
Users --> Auth
|
||||
Auth --> API
|
||||
API --> Functions
|
||||
Functions --> Firestore
|
||||
Functions --> Storage
|
||||
IAM --> Auth
|
||||
IAM --> Backend
|
||||
IAM --> Data
|
||||
Logs --> Security
|
||||
GCP --> GDPR
|
||||
GCP --> SOC2
|
||||
|
||||
style Prototype fill:#FFF3CD,stroke:#856404,stroke-width:2px,stroke-dasharray: 5 5
|
||||
style Proto fill:#FFF3CD,stroke:#856404
|
||||
style Title fill:#1976D2,stroke:#0D47A1,stroke-width:3px,color:#FFFFFF
|
||||
style Production fill:#D4EDDA,stroke:#155724,stroke-width:3px
|
||||
style GCP fill:#E3F2FD,stroke:#1976D2,stroke-width:2px
|
||||
style Auth fill:#E8F5E9,stroke:#2E7D32
|
||||
style Backend fill:#E8F5E9,stroke:#2E7D32
|
||||
style Data fill:#E8F5E9,stroke:#2E7D32
|
||||
style Security fill:#FFE0B2,stroke:#E65100
|
||||
style Compliance fill:#F3E5F5,stroke:#6A1B9A,stroke-width:2px
|
||||
style GDPR fill:#E1BEE7,stroke:#6A1B9A
|
||||
style SOC2 fill:#E1BEE7,stroke:#6A1B9A
|
||||
@@ -1,70 +0,0 @@
|
||||
sequenceDiagram
|
||||
participant Client as 🏢 Client App
|
||||
participant Backend as 🌐 Backend API
|
||||
participant Admin as ⚙️ KROW Admin
|
||||
participant Staff as 👥 Staff App
|
||||
|
||||
%% Event Creation
|
||||
Note over Client,Backend: 1. Event & Shift Creation
|
||||
Client->>Backend: Create Event with Shifts & Positions
|
||||
Backend-->>Client: Event Created (Draft)
|
||||
Client->>Backend: Publish Event
|
||||
Backend-->>Client: Event Published
|
||||
|
||||
%% Staff Assignment
|
||||
Note over Admin,Backend: 2. Staff Assignment
|
||||
Admin->>Backend: View Available Events
|
||||
Backend-->>Admin: Event List
|
||||
Admin->>Backend: Assign Staff to Shift
|
||||
Backend-->>Admin: Assignment Confirmed
|
||||
Backend->>Staff: Notification: New Shift Assigned
|
||||
|
||||
%% Shift Acceptance
|
||||
Note over Staff,Backend: 3. Shift Acceptance
|
||||
Staff->>Backend: View Shift Details
|
||||
Backend-->>Staff: Shift Information
|
||||
Staff->>Backend: Accept Shift
|
||||
Backend-->>Staff: Shift Confirmed
|
||||
Backend->>Client: Notification: Staff Confirmed
|
||||
|
||||
%% Clock In
|
||||
Note over Client,Staff: 4. Clock In - Day of Event
|
||||
Client->>Client: Generate QR Code for Event
|
||||
Staff->>Staff: Scan QR Code
|
||||
Staff->>Backend: Clock In Request (via QR)
|
||||
Backend-->>Staff: Clock In Confirmed
|
||||
Backend->>Client: Notification: Staff Clocked In
|
||||
Client->>Backend: (Alternative) Manual Clock In
|
||||
Backend-->>Client: Manual Clock In Confirmed
|
||||
|
||||
%% Shift Active
|
||||
Note over Staff: 5. Shift In Progress
|
||||
Staff->>Staff: View Real-time Timer
|
||||
|
||||
%% Clock Out
|
||||
Note over Client,Staff: 6. Clock Out - End of Shift
|
||||
Staff->>Staff: Scan QR Code
|
||||
Staff->>Backend: Clock Out Request (via QR)
|
||||
Backend-->>Staff: Clock Out Confirmed
|
||||
Backend-->>Staff: Shift Status: Completed
|
||||
Backend->>Client: Notification: Staff Clocked Out
|
||||
|
||||
%% Rating & Invoicing
|
||||
Note over Client,Backend: 7. Post-Shift Activities
|
||||
Client->>Backend: Rate Staff Performance
|
||||
Backend-->>Client: Rating Recorded
|
||||
Backend->>Backend: Generate Invoice
|
||||
Backend->>Client: Invoice Created
|
||||
Client->>Backend: Review & Approve Invoice
|
||||
Backend-->>Client: Invoice Approved
|
||||
|
||||
%% Payment
|
||||
Note over Staff,Backend: 8. Staff Payment
|
||||
Backend->>Staff: Payment Processed
|
||||
Staff->>Backend: View Earnings & Payment History
|
||||
Backend-->>Staff: Payment Details
|
||||
|
||||
alt Payment Dispute
|
||||
Staff->>Backend: Contest Payment Amount
|
||||
Backend-->>Staff: Dispute Opened
|
||||
end
|
||||
@@ -1,61 +0,0 @@
|
||||
graph TB
|
||||
subgraph "Actors"
|
||||
Staff[👥 Staff/Employee]
|
||||
Client[🏢 Client/Business]
|
||||
Admin[⚙️ KROW Admin]
|
||||
end
|
||||
|
||||
subgraph "KROW Staff App"
|
||||
SA_Auth[Authentication & Onboarding]
|
||||
SA_Shifts[Shift Management]
|
||||
SA_Earnings[Earnings & Payments]
|
||||
SA_Profile[Profile Management]
|
||||
end
|
||||
|
||||
subgraph "KROW Client App"
|
||||
CA_Events[Event Creation]
|
||||
CA_Staff[Staff Management]
|
||||
CA_Time[Time Tracking - QR Code]
|
||||
CA_Invoice[Invoicing]
|
||||
end
|
||||
|
||||
subgraph "Backend System"
|
||||
API[Backend API]
|
||||
DB[(Database)]
|
||||
end
|
||||
|
||||
%% Staff interactions
|
||||
Staff -->|Registers & Manages Profile| SA_Auth
|
||||
Staff -->|Views & Accepts Shifts| SA_Shifts
|
||||
Staff -->|Scans QR Code - Clock In/Out| SA_Shifts
|
||||
Staff -->|Tracks Earnings| SA_Earnings
|
||||
Staff -->|Updates Skills & Documents| SA_Profile
|
||||
|
||||
%% Client interactions
|
||||
Client -->|Creates Events & Shifts| CA_Events
|
||||
Client -->|Views Assigned Staff| CA_Staff
|
||||
Client -->|Generates QR Code| CA_Time
|
||||
Client -->|Manual Clock In/Out| CA_Time
|
||||
Client -->|Rates Staff| CA_Staff
|
||||
Client -->|Reviews & Approves Invoices| CA_Invoice
|
||||
|
||||
%% Admin interactions
|
||||
Admin -->|Assigns Staff to Shifts| API
|
||||
Admin -->|Validates Staff Profiles| API
|
||||
|
||||
%% App to Backend connections
|
||||
SA_Auth -.->|Profile Data| API
|
||||
SA_Shifts -.->|Shift Status Updates| API
|
||||
SA_Earnings -.->|Payment Data| API
|
||||
SA_Profile -.->|User Data| API
|
||||
|
||||
CA_Events -.->|Event & Shift Data| API
|
||||
CA_Staff -.->|Staff Ratings| API
|
||||
CA_Time -.->|Time Records| API
|
||||
CA_Invoice -.->|Invoice Data| API
|
||||
|
||||
API <-->|Data Storage & Retrieval| DB
|
||||
|
||||
style Staff fill:#e1f5ff
|
||||
style Client fill:#fff4e1
|
||||
style Admin fill:#f0e1ff
|
||||
@@ -1,40 +0,0 @@
|
||||
graph LR
|
||||
START[💼 Shift<br/>Completed<br/>& Rated]
|
||||
|
||||
START --> CALC[📊 INTERNAL<br/><br/>Calculate Invoice<br/>• Hours worked<br/>• Rates × Hours<br/>• Overtime calc<br/>• Platform fees<br/>• Tax amounts]
|
||||
|
||||
CALC --> GENERATE[📄 INTERNAL<br/><br/>Generate Invoice<br/>• Create PDF<br/>• Invoice number<br/>• Line items<br/>• Due date]
|
||||
|
||||
GENERATE --> SAVE[💾 INTERNAL<br/><br/>Save to Database<br/>• Store invoice<br/>• Status: pending<br/>• Track history]
|
||||
|
||||
SAVE --> SEND[📧 API INTEGRATION<br/><br/>SendGrid<br/>• Send email<br/>• Track delivery<br/>• Reliable inbox]
|
||||
|
||||
SEND --> CLIENT[📱 Client<br/>Reviews<br/>Invoice]
|
||||
|
||||
CLIENT --> DECISION{Approve or<br/>Dispute?}
|
||||
|
||||
DECISION -->|⚠️ Dispute| DISPUTE[🔧 INTERNAL<br/><br/>Handle Dispute<br/>• Admin reviews<br/>• Adjustments<br/>• Re-calculate]
|
||||
|
||||
DISPUTE --> SAVE
|
||||
|
||||
DECISION -->|✅ Approve| PAY[💳 API INTEGRATION<br/><br/>Stripe<br/>• Credit card<br/>• ACH transfer<br/>• Payment tracking]
|
||||
|
||||
PAY --> CONFIRM[💾 INTERNAL<br/><br/>Update Status<br/>• Mark as paid<br/>• Record payment<br/>• Update reports]
|
||||
|
||||
CONFIRM --> PAYOUT[💰 API INTEGRATION<br/><br/>Stripe Connect<br/>• Pay staff<br/>• Direct deposit<br/>• Automated]
|
||||
|
||||
PAYOUT --> RECORD[📊 INTERNAL<br/><br/>Track Payments<br/>• Staff earnings<br/>• Payment history<br/>• Reports]
|
||||
|
||||
RECORD --> DONE[✅ Complete]
|
||||
|
||||
style START fill:#E1F5FF
|
||||
style CALC fill:#FFF9E6,stroke:#F9A825,stroke-width:3px
|
||||
style GENERATE fill:#FFF9E6,stroke:#F9A825,stroke-width:3px
|
||||
style SAVE fill:#FFF9E6,stroke:#F9A825,stroke-width:3px
|
||||
style DISPUTE fill:#FFF9E6,stroke:#F9A825,stroke-width:3px
|
||||
style CONFIRM fill:#FFF9E6,stroke:#F9A825,stroke-width:3px
|
||||
style RECORD fill:#FFF9E6,stroke:#F9A825,stroke-width:3px
|
||||
style SEND fill:#E3F2FD,stroke:#1976D2,stroke-width:3px
|
||||
style PAY fill:#E3F2FD,stroke:#1976D2,stroke-width:3px
|
||||
style PAYOUT fill:#E3F2FD,stroke:#1976D2,stroke-width:3px
|
||||
style DONE fill:#90EE90
|
||||
@@ -1,24 +0,0 @@
|
||||
graph TD
|
||||
subgraph KROW Mobile Applications
|
||||
direction LR
|
||||
Mobile_Client[<b>Mobile Client App</b><br>Flutter]
|
||||
Mobile_Staff[<b>Mobile Staff App</b><br>Flutter]
|
||||
end
|
||||
|
||||
subgraph Firebase Backend Services - GCP
|
||||
direction TB
|
||||
Auth[Firebase Authentication]
|
||||
DataConnect[<b>Firebase Data Connect</b><br>GraphQL API &<br>Generated SDKs]
|
||||
SQL_DB[<b>Cloud SQL for PostgreSQL</b><br><i>Managed by Data Connect</i>]
|
||||
end
|
||||
|
||||
Mobile_Client -- "Authenticates with" --> Auth
|
||||
Mobile_Client -- "Calls API via generated SDK" --> DataConnect
|
||||
|
||||
Mobile_Staff -- "Authenticates with" --> Auth
|
||||
Mobile_Staff -- "Calls API via generated SDK" --> DataConnect
|
||||
|
||||
DataConnect -- "Manages & Queries" --> SQL_DB
|
||||
|
||||
style Mobile_Client fill:#eef,stroke:#333,stroke-width:2px
|
||||
style Mobile_Staff fill:#eef,stroke:#333,stroke-width:2px
|
||||
@@ -1,29 +0,0 @@
|
||||
graph LR
|
||||
subgraph Base44 Environment
|
||||
direction TB
|
||||
Client[Client] -- Modifies --> B44_UI[<b>Base44 Visual Builder</b><br><i>Features:</i><br>- Event Management<br>- Staff Directory<br>- Vendor Onboarding]
|
||||
B44_UI --> B44_Backend[<b>Base44 Backend</b><br>Provides Entity Schemas<br>& SDK Operations]
|
||||
B44_Backend --> B44_DB[Base44 Database]
|
||||
end
|
||||
|
||||
subgraph Firebase Ecosystem - GCP
|
||||
direction TB
|
||||
KROW_FE[<b>KROW Frontend</b><br>Vite/React + TanStack Query<br><i>From Export</i>]
|
||||
|
||||
subgraph Firebase Services
|
||||
direction TB
|
||||
Auth[Firebase Authentication]
|
||||
DataConnect[<b>Firebase Data Connect</b><br>GraphQL API &<br>Generated SDKs]
|
||||
SQL_DB[<b>Cloud SQL for PostgreSQL</b><br><i>Managed by Data Connect</i>]
|
||||
end
|
||||
|
||||
KROW_FE -- "Uses" --> Auth
|
||||
KROW_FE -- "Calls Queries/Mutations via SDK" --> DataConnect
|
||||
DataConnect -- "Manages & Queries" --> SQL_DB
|
||||
end
|
||||
|
||||
B44_UI -- "<b>UI Code Export</b><br>(React Components)" --> KROW_FE
|
||||
|
||||
style Client fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style B44_UI fill:#ffe,stroke:#333,stroke-width:2px
|
||||
style KROW_FE fill:#eef,stroke:#333,stroke-width:2px
|
||||
@@ -1,32 +0,0 @@
|
||||
sequenceDiagram
|
||||
participant Staff as Staff Member
|
||||
participant App as KROW Staff App
|
||||
participant Client as Client
|
||||
participant Backend as Backend
|
||||
|
||||
Staff->>App: Opens shift and taps "Clock In"
|
||||
|
||||
activate App
|
||||
App->>App: **1. Geofencing Check (GPS) BEFORE scan**
|
||||
|
||||
alt Proximity Validated
|
||||
App->>Client: Requests QR Code
|
||||
Client-->>Staff: Presents QR Code
|
||||
Staff->>App: Scans the code
|
||||
App->>Backend: Validates Clock In with eventId
|
||||
Backend-->>App: Confirmation
|
||||
App->>Staff: Displays "Clock In Successful"
|
||||
else Proximity Failed (GPS bug, too far)
|
||||
App->>Staff: Displays "Error: You are too far"
|
||||
end
|
||||
deactivate App
|
||||
|
||||
loop Continuous Monitoring DURING the shift
|
||||
App->>App: **2. Checks GPS position (anti-fraud)**
|
||||
opt Staff member leaves the > 500m area
|
||||
App->>Backend: Notifies area exit
|
||||
Backend-->>App: Triggers a "Force Clock-Out"
|
||||
end
|
||||
end
|
||||
|
||||
%% Clock Out process follows a similar logic (Geofencing + QR Code) %%
|
||||
@@ -1,110 +0,0 @@
|
||||
[
|
||||
{
|
||||
"path": "assets/diagrams/architectures/0-infra-compliance.mermaid",
|
||||
"title": "Infrastructure & Compliance",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-shield-check"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/architectures/1-core-workflow.mermaid",
|
||||
"title": "Core Workflow",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-3"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/architectures/2-high-level-architecture.mermaid",
|
||||
"title": "High-Level Architecture",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-3"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/architectures/3-invoice-workflow-simple.mermaid",
|
||||
"title": "Invoice Workflow (Simple)",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-receipt"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/architectures/4-mobile-app-architecture.mermaid",
|
||||
"title": "Mobile App Architecture",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-phone"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/architectures/5-web-app-architecture.mermaid",
|
||||
"title": "Web App Architecture",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-window"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/architectures/6-geofencing-clockin-clockout.mermaid",
|
||||
"title": "Geofencing Clock-In/Out",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-geo-alt"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/roadmap/roadmap.mermaid",
|
||||
"title": "Project Roadmap",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-calendar-check"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/staff-mobile-application/overview.mermaid",
|
||||
"title": "Legacy App Overview",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-phone"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/staff-mobile-application/use-case-flowchart.mermaid",
|
||||
"title": "Legacy App Use Cases",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-2"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/staff-mobile-application/api-map.mermaid",
|
||||
"title": "Legacy App API Map",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-phone"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/staff-mobile-application/use-case-flows.mermaid",
|
||||
"title": "Legacy App Use Cases BE Chart",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-2"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/staff-mobile-application/backend-architecture.mermaid",
|
||||
"title": "Legacy App Backend Architecture",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-2"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/client-mobile-application/overview.mermaid",
|
||||
"title": "Legacy App Overview",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-phone"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/client-mobile-application/use-case-flowchart.mermaid",
|
||||
"title": "Legacy App Use Cases",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-2"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/client-mobile-application/api-map.mermaid",
|
||||
"title": "Legacy App API Map",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-phone"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/client-mobile-application/use-case-flows.mermaid",
|
||||
"title": "Legacy App Use Cases BE Chart",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-2"
|
||||
},
|
||||
{
|
||||
"path": "assets/diagrams/legacy/client-mobile-application/backend-architecture.mermaid",
|
||||
"title": "Legacy App Backend Architecture",
|
||||
"type": "mermaid",
|
||||
"icon": "bi-diagram-2"
|
||||
}
|
||||
]
|
||||
@@ -1,55 +0,0 @@
|
||||
flowchart TD
|
||||
subgraph "GraphQL API"
|
||||
direction LR
|
||||
subgraph "Queries"
|
||||
Q1[getEvents]
|
||||
Q2[getEventDetails]
|
||||
Q3[getInvoices]
|
||||
Q4[getInvoiceDetails]
|
||||
Q5[getNotifications]
|
||||
Q6[getNotificationDetails]
|
||||
Q7[getProfile]
|
||||
Q8[getAssignedStaff]
|
||||
end
|
||||
|
||||
subgraph "Mutations"
|
||||
M1[createEvent]
|
||||
M2[updateProfile]
|
||||
M3[rateStaff]
|
||||
M4[clockIn]
|
||||
M5[clockOut]
|
||||
M6[uploadProfilePicture]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "Firebase"
|
||||
direction LR
|
||||
subgraph "Firestore Collections"
|
||||
FS1[events]
|
||||
FS2[invoices]
|
||||
FS3[notifications]
|
||||
FS4[users]
|
||||
end
|
||||
|
||||
subgraph "Firebase Storage"
|
||||
FB1[Profile Pictures]
|
||||
end
|
||||
end
|
||||
|
||||
Q1 --> FS1
|
||||
Q2 --> FS1
|
||||
Q3 --> FS2
|
||||
Q4 --> FS2
|
||||
Q5 --> FS3
|
||||
Q6 --> FS3
|
||||
Q7 --> FS4
|
||||
Q8 --> FS1
|
||||
Q8 --> FS4
|
||||
|
||||
M1 --> FS1
|
||||
M2 --> FS4
|
||||
M3 --> FS1
|
||||
M3 --> FS4
|
||||
M4 --> FS1
|
||||
M5 --> FS1
|
||||
M6 --> FB1
|
||||
@@ -1,28 +0,0 @@
|
||||
flowchart TD
|
||||
subgraph "Client"
|
||||
A[Flutter App]
|
||||
end
|
||||
|
||||
subgraph "Backend"
|
||||
B[GraphQL Server - Node.js]
|
||||
C[Firebase]
|
||||
end
|
||||
|
||||
subgraph "Firebase Services"
|
||||
C1[Firebase Auth]
|
||||
C2[Firebase Firestore]
|
||||
C3[Firebase Storage]
|
||||
end
|
||||
|
||||
A -- "GraphQL Queries/Mutations" --> B
|
||||
A -- "Authentication" --> C1
|
||||
|
||||
B -- "Data Operations" --> C2
|
||||
B -- "File Operations" --> C3
|
||||
|
||||
C1 -- "User Tokens" --> A
|
||||
C2 -- "Data" --> B
|
||||
C3 -- "Files" --> B
|
||||
|
||||
B -- "Data/Files" --> A
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
flowchart TD
|
||||
subgraph "App Initialization"
|
||||
A[main.dart] --> B(KrowApp);
|
||||
B --> C{MaterialApp.router};
|
||||
C --> D[Initial Route: /];
|
||||
end
|
||||
|
||||
subgraph "Authentication Flow"
|
||||
D --> E(SplashRoute);
|
||||
E --> F{SplashRedirectGuard};
|
||||
F -- Authenticated --> G(HomeRoute);
|
||||
F -- Unauthenticated/Error --> H(SignInFlowRoute);
|
||||
end
|
||||
|
||||
subgraph "Sign-In/Welcome Flow"
|
||||
H --> I[WelcomeRoute];
|
||||
I --> J[SignInRoute];
|
||||
J --> K[ResetPassRoute];
|
||||
L[Deeplink with oobCode] --> M[EnterNewPassRoute];
|
||||
J -- Forgot Password --> K;
|
||||
I -- Sign In --> J;
|
||||
end
|
||||
|
||||
subgraph "Main Application (Home)"
|
||||
G --> G_Tabs((Bottom Navigation));
|
||||
G_Tabs -- Events --> Events;
|
||||
G_Tabs -- Invoices --> Invoices;
|
||||
G_Tabs -- Notifications --> Notifications;
|
||||
G_Tabs -- Profile --> Profile;
|
||||
G_Tabs -- Create Event --> CreateEvent;
|
||||
end
|
||||
|
||||
subgraph "Events Flow"
|
||||
Events[EventsFlow] --> Events_List(EventsListMainRoute);
|
||||
Events_List --> Events_Event_Details(EventDetailsRoute);
|
||||
Events_Event_Details --> Events_Assigned_Staff(AssignedStaffRoute);
|
||||
Events_Assigned_Staff --> Events_Clock_Manual(ClockManualRoute);
|
||||
Events_Event_Details --> Events_Rate_Staff(RateStaffRoute);
|
||||
end
|
||||
|
||||
subgraph "Create Event Flow"
|
||||
CreateEvent[CreateEventFlow] --> Create_Event_Edit(CreateEventRoute);
|
||||
Create_Event_Edit --> Create_Event_Preview(EventDetailsRoute);
|
||||
end
|
||||
|
||||
subgraph "Invoice Flow"
|
||||
Invoices[InvoiceFlow] --> Invoice_List(InvoicesListMainRoute);
|
||||
Invoice_List --> Invoice_Details(InvoiceDetailsRoute);
|
||||
Invoice_Details --> Invoice_Event_Details(EventDetailsRoute);
|
||||
end
|
||||
|
||||
subgraph "Notifications Flow"
|
||||
Notifications[NotificationFlow] --> Notification_List(NotificationsListRoute);
|
||||
Notification_List --> Notification_Details(NotificationDetailsRoute);
|
||||
end
|
||||
|
||||
subgraph "Profile Flow"
|
||||
Profile[ProfileFlow] --> Profile_Preview(ProfilePreviewRoute);
|
||||
Profile_Preview --> Profile_Edit(PersonalInfoRoute);
|
||||
end
|
||||
|
||||
style F fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style G fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style H fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style L fill:#f9f,stroke:#333,stroke-width:2px
|
||||
@@ -1,80 +0,0 @@
|
||||
flowchart TD
|
||||
subgraph "User"
|
||||
U((User))
|
||||
end
|
||||
|
||||
subgraph "Authentication Use Cases"
|
||||
UC1(Sign In)
|
||||
UC2(Sign Out)
|
||||
UC3(Password Reset)
|
||||
end
|
||||
|
||||
subgraph "Event Management Use Cases"
|
||||
UC4(Create Event)
|
||||
UC5(View Event Details)
|
||||
UC6(List Events)
|
||||
end
|
||||
|
||||
subgraph "Invoice Management Use Cases"
|
||||
UC7(List Invoices)
|
||||
UC8(View Invoice Details)
|
||||
end
|
||||
|
||||
subgraph "Staff Management Use Cases"
|
||||
UC9(View Assigned Staff)
|
||||
UC10(Rate Staff)
|
||||
UC11(Manual Clock In/Out)
|
||||
end
|
||||
|
||||
subgraph "Profile Management Use Cases"
|
||||
UC12(View Profile)
|
||||
UC13(Edit Profile)
|
||||
end
|
||||
|
||||
subgraph "Notification Use Cases"
|
||||
UC14(List Notifications)
|
||||
UC15(View Notification Details)
|
||||
end
|
||||
|
||||
U --> UC1
|
||||
UC1 -- Success --> UC6
|
||||
UC1 -- Forgot Password --> UC3
|
||||
|
||||
UC6 --> UC5
|
||||
UC5 --> UC9
|
||||
UC9 --> UC11
|
||||
UC5 --> UC10
|
||||
|
||||
U --> UC4
|
||||
UC4 -- Success --> UC5
|
||||
|
||||
UC6 -- Triggers --> UC7
|
||||
UC7 --> UC8
|
||||
UC8 -- View Event --> UC5
|
||||
|
||||
U --> UC12
|
||||
UC12 --> UC13
|
||||
UC13 -- Success --> UC12
|
||||
|
||||
U --> UC14
|
||||
UC14 --> UC15
|
||||
|
||||
UC12 -- Sign Out --> UC2
|
||||
UC2 -- Success --> UC1
|
||||
|
||||
%% Styling
|
||||
style UC1 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style UC2 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style UC3 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style UC4 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC5 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC6 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC7 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC8 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC9 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC10 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC11 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC12 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC13 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC14 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
style UC15 fill:#bbf,stroke:#333,stroke-width:2px
|
||||
@@ -1,45 +0,0 @@
|
||||
flowchart TD
|
||||
subgraph "Sign-In Flow"
|
||||
A1[User enters credentials] --> B1{SignInBloc};
|
||||
B1 --> C1[Firebase Auth: signInWithEmailAndPassword];
|
||||
C1 -- Success --> D1[Navigate to Home];
|
||||
C1 -- Failure --> E1[Show error message];
|
||||
end
|
||||
|
||||
subgraph "Password Reset Flow"
|
||||
A2[User requests password reset] --> B2{SignInBloc};
|
||||
B2 --> C2[Firebase Auth: sendPasswordResetEmail];
|
||||
C2 -- Email Sent --> D2[User clicks deep link];
|
||||
D2 --> E2[UI with new password fields];
|
||||
E2 --> F2{SignInBloc};
|
||||
F2 --> G2[Firebase Auth: confirmPasswordReset];
|
||||
G2 -- Success --> H2[Show success message];
|
||||
G2 -- Failure --> I2[Show error message];
|
||||
end
|
||||
|
||||
subgraph "Event Listing Flow"
|
||||
A3[User navigates to Events screen] --> B3{EventsBloc};
|
||||
B3 --> C3[GraphQL Query: getEvents];
|
||||
C3 --> D3[Firestore: events collection];
|
||||
D3 -- Returns event data --> C3;
|
||||
C3 -- Returns data --> B3;
|
||||
B3 --> E3[Display list of events];
|
||||
end
|
||||
|
||||
subgraph "Create Event Flow"
|
||||
A4[User submits new event form] --> B4{CreateEventBloc};
|
||||
B4 --> C4[GraphQL Mutation: createEvent];
|
||||
C4 --> D4[Firestore: events collection];
|
||||
D4 -- Success --> C4;
|
||||
C4 -- Returns success --> B4;
|
||||
B4 --> E4[Navigate to event details];
|
||||
end
|
||||
|
||||
subgraph "Profile Viewing Flow"
|
||||
A5[User navigates to Profile screen] --> B5{ProfileBloc};
|
||||
B5 --> C5[GraphQL Query: getProfile];
|
||||
C5 --> D5[Firestore: users collection];
|
||||
D5 -- Returns profile data --> C5;
|
||||
C5 -- Returns data --> B5;
|
||||
B5 --> E5[Display profile information];
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
graph TD
|
||||
subgraph GraphQL API
|
||||
subgraph Queries
|
||||
Q1[getStaffStatus]
|
||||
Q2[getMe]
|
||||
Q3[getStaffPersonalInfo]
|
||||
Q4[getStaffProfileRoles]
|
||||
Q5[getShifts]
|
||||
Q6[staffNoBreakShifts]
|
||||
Q7[getShiftPosition]
|
||||
end
|
||||
|
||||
subgraph Mutations
|
||||
M1[updateStaffPersonalInfo]
|
||||
M2[updateStaffPersonalInfoWithAvatar]
|
||||
M3[uploadStaffAvatar]
|
||||
M4[acceptShift]
|
||||
M5[trackStaffClockin]
|
||||
M6[trackStaffClockout]
|
||||
M7[trackStaffBreak]
|
||||
M8[submitNoBreakStaffShift]
|
||||
M9[cancelStaffShift]
|
||||
M10[declineShift]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph Firebase Services
|
||||
FS[Firebase Storage]
|
||||
FF[Firebase Firestore]
|
||||
FA[Firebase Auth]
|
||||
end
|
||||
|
||||
M2 --> FS;
|
||||
M3 --> FS;
|
||||
|
||||
Q1 --> FF;
|
||||
Q2 --> FF;
|
||||
Q3 --> FF;
|
||||
Q4 --> FF;
|
||||
Q5 --> FF;
|
||||
Q6 --> FF;
|
||||
Q7 --> FF;
|
||||
|
||||
M1 --> FF;
|
||||
M2 --> FF;
|
||||
M4 --> FF;
|
||||
M5 --> FF;
|
||||
M6 --> FF;
|
||||
M7 --> FF;
|
||||
M8 --> FF;
|
||||
M9 --> FF;
|
||||
M10 --> FF;
|
||||
|
||||
Q1 --> FA;
|
||||
Q2 --> FA;
|
||||
Q3 --> FA;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
graph TD
|
||||
subgraph Flutter App
|
||||
A[Flutter UI]
|
||||
B[GraphQL Client]
|
||||
C[Firebase SDK]
|
||||
end
|
||||
|
||||
subgraph Backend
|
||||
D[GraphQL Server]
|
||||
E[Firebase]
|
||||
end
|
||||
|
||||
subgraph Firebase
|
||||
F[Firebase Auth]
|
||||
G[Firebase Firestore]
|
||||
H[Firebase Storage]
|
||||
I[Firebase Cloud Functions]
|
||||
J[Firebase Cloud Messaging]
|
||||
K[Firebase Remote Config]
|
||||
end
|
||||
|
||||
A --> B;
|
||||
A --> C;
|
||||
|
||||
B --> D;
|
||||
C --> F;
|
||||
C --> J;
|
||||
C --> K;
|
||||
|
||||
D --> G;
|
||||
D --> H;
|
||||
D --> I;
|
||||
D --> F;
|
||||
|
||||
I --> G;
|
||||
I --> H;
|
||||
I --> J;
|
||||
@@ -1,117 +0,0 @@
|
||||
flowchart TD
|
||||
A[main.dart] --> B{KrowApp};
|
||||
B --> C{MaterialApp.router};
|
||||
C --> D[SplashRoute];
|
||||
D --> E{SplashRedirectGuard};
|
||||
|
||||
E -- Authenticated --> F[HomeRoute];
|
||||
E -- Admin Validation --> G[WaitingValidationRoute];
|
||||
E -- Prepare Profile --> H[CheckListFlowRoute];
|
||||
E -- Unauthenticated/Error --> I[AuthFlowRoute];
|
||||
|
||||
F --> F1[ShiftsFlowRoute];
|
||||
F --> F2[EarningsFlowRoute];
|
||||
F --> F3[ProfileMainFlowRoute];
|
||||
|
||||
F1 --> F1a[ShiftsListMainRoute];
|
||||
F1a --> F1b[ShiftDetailsRoute];
|
||||
F1a --> F1c[QrScannerRoute];
|
||||
|
||||
F2 --> F2a[EarningsRoute];
|
||||
F2a --> F2b[EarningsHistoryRoute];
|
||||
|
||||
F3 --> F3a[ProfileMainRoute];
|
||||
F3a --> F3b[RoleKitFlowRoute];
|
||||
F3a --> F3c[CertificatesRoute];
|
||||
F3a --> F3d[ScheduleRoute];
|
||||
F3a --> F3e[WorkingAreaRoute];
|
||||
F3a --> F3f[LivePhotoRoute];
|
||||
F3a --> F3g[ProfileSettingsFlowRoute];
|
||||
F3a --> F3h[WagesFormsFlowRoute];
|
||||
F3a --> F3i[BankAccountFlowRoute];
|
||||
F3a --> F3j[BenefitsRoute];
|
||||
F3a --> F3k[SupportRoute];
|
||||
F3a --> F3l[FaqRoute];
|
||||
|
||||
F3g --> F3g1[ProfileSettingsMenuRoute];
|
||||
F3g1 --> F3g2[RoleRoute];
|
||||
F3g1 --> F3g3[PersonalInfoRoute];
|
||||
F3g1 --> F3g4[EmailVerificationRoute];
|
||||
F3g4 --> L1;
|
||||
F3g1 --> F3g5[AddressRoute];
|
||||
F3g1 --> F3g6[EmergencyContactsRoute];
|
||||
F3g1 --> F3g7[MobilityRoute];
|
||||
F3g1 --> F3g8[InclusiveRoute];
|
||||
|
||||
F3h --> F3h1[FormsListRoute];
|
||||
F3h1 --> F3h2[OfferLetterFormRoute];
|
||||
F3h1 --> F3h3[EddFormRoute];
|
||||
F3h1 --> F3h4[INineFormRoute];
|
||||
F3h1 --> F3h5[WFourFormRoute];
|
||||
F3h2 --> F3h6[FormSignRoute];
|
||||
F3h3 --> F3h6;
|
||||
F3h4 --> F3h6;
|
||||
F3h5 --> F3h6;
|
||||
F3h6 --> F3h7[SignedFormRoute];
|
||||
F3h6 --> F3h8[FormPreviewRoute];
|
||||
|
||||
F3i --> F3i1[BankAccountRoute];
|
||||
F3i1 --> F3i2[BankAccountEditRoute];
|
||||
|
||||
F3b --> F3b1[RolesKitListRoute];
|
||||
F3b1 --> F3b2[RoleKitRoute];
|
||||
|
||||
H --> H1[CheckListRoute];
|
||||
H1 --> H2[PersonalInfoRoute];
|
||||
H1 --> H3[EmailVerificationRoute];
|
||||
H3 --> L1;
|
||||
H1 --> H4[EmergencyContactsRoute];
|
||||
H1 --> H5[AddressRoute];
|
||||
H1 --> H6[WorkingAreaRoute];
|
||||
H1 --> H7[RoleRoute];
|
||||
H1 --> H8[CertificatesRoute];
|
||||
H1 --> H9[ScheduleRoute];
|
||||
H1 --> F3i;
|
||||
H1 --> F3h;
|
||||
H1 --> F3b;
|
||||
|
||||
I --> I1[WelcomeRoute];
|
||||
I1 --> I2[PhoneVerificationRoute];
|
||||
I2 --> I3[CodeVerificationRoute];
|
||||
I3 --> J{SingUpRedirectGuard};
|
||||
|
||||
J -- Prepare Profile --> K[SignupFlowRoute];
|
||||
J -- Authenticated --> F;
|
||||
J -- Admin Validation --> G;
|
||||
J -- Unauthenticated/Error --> I;
|
||||
|
||||
K --> K1[PersonalInfoRoute];
|
||||
K1 --> K2[EmailVerificationRoute];
|
||||
K2 --> K3[EmergencyContactsRoute];
|
||||
K2 --> L1;
|
||||
K3 --> K4[MobilityRoute];
|
||||
K4 --> K5[InclusiveRoute];
|
||||
K5 --> K6[AddressRoute];
|
||||
K6 --> K7[WorkingAreaRoute];
|
||||
K7 --> K8[RoleRoute];
|
||||
|
||||
subgraph PhoneReLoginFlow
|
||||
L1[PhoneReLoginFlowRoute]
|
||||
L1 --> L2[CodeVerificationRoute]
|
||||
end
|
||||
|
||||
subgraph CheckListFlow
|
||||
H
|
||||
end
|
||||
|
||||
subgraph AuthFlow
|
||||
I
|
||||
end
|
||||
|
||||
subgraph SignUpFlow
|
||||
K
|
||||
end
|
||||
|
||||
subgraph HomeFlow
|
||||
F
|
||||
end
|
||||
@@ -1,85 +0,0 @@
|
||||
flowchart TD
|
||||
subgraph AppInitialization
|
||||
A[Start App] --> B{Check Auth Status};
|
||||
B -- Authenticated --> C[Go to Home];
|
||||
B -- Unauthenticated --> D[Go to Auth];
|
||||
B -- Admin Validation --> E[Go to Waiting Validation];
|
||||
B -- Prepare Profile --> F[Go to Checklist];
|
||||
end
|
||||
|
||||
subgraph UserAuthentication
|
||||
D --> G[Welcome Screen];
|
||||
G --> H{Select Sign In/Up};
|
||||
H -- Sign In --> I[Sign In With Phone];
|
||||
I --> J[Verify Phone Code];
|
||||
J -- Success --> C;
|
||||
H -- Sign Up --> K[Go to User Onboarding];
|
||||
end
|
||||
|
||||
subgraph UserOnboarding
|
||||
K --> L[Collect Personal Info];
|
||||
L --> M[Verify Email];
|
||||
M --> N[Add Emergency Contacts];
|
||||
N --> O[Specify Mobility];
|
||||
O --> P[Provide Inclusivity Info];
|
||||
P --> Q[Enter Address];
|
||||
Q --> R[Define Working Area];
|
||||
R --> S[Select Role];
|
||||
S -- Success --> F;
|
||||
end
|
||||
|
||||
subgraph ProfileCompletion
|
||||
F --> T[View Checklist];
|
||||
T --> U[Complete Personal Info];
|
||||
T --> V[Complete Email Verification];
|
||||
T --> W[Complete Emergency Contacts];
|
||||
T --> X[Manage Certificates];
|
||||
T --> Y[Set Schedule];
|
||||
T --> Z[Manage Bank Account];
|
||||
T --> AA[Fill Out Wage Forms];
|
||||
T --> AB[Manage Role Kit];
|
||||
AB -- All Complete --> C;
|
||||
end
|
||||
|
||||
subgraph MainApp
|
||||
C --> AC[Home Screen];
|
||||
AC --> AD{Navigate};
|
||||
AD -- Shifts --> AE[Shift Management];
|
||||
AD -- Earnings --> AF[Earnings Management];
|
||||
AD -- Profile --> AG[Profile Management];
|
||||
end
|
||||
|
||||
subgraph ShiftManagement
|
||||
AE --> AH[View Shifts];
|
||||
AH --> AI[View Shift Details];
|
||||
AH --> AJ[Clock In/Out via QR];
|
||||
end
|
||||
|
||||
subgraph EarningsManagement
|
||||
AF --> AK[View Current Earnings];
|
||||
AK --> AL[View Earnings History];
|
||||
end
|
||||
|
||||
subgraph ProfileManagement
|
||||
AG --> AM[View Profile];
|
||||
AM --> AN[Manage Role Kit];
|
||||
AM --> AO[Manage Certificates];
|
||||
AM --> AP[Manage Schedule];
|
||||
AM --> AQ[Manage Working Area];
|
||||
AM --> AR[Update Live Photo];
|
||||
AM --> AS[Manage Profile Settings];
|
||||
AS --> AT[Update Personal Info];
|
||||
AS --> AU[Update Email];
|
||||
AU --> AV[Re-login with Phone];
|
||||
AS --> AW[Update Address];
|
||||
AM --> AX[Manage Wage Forms];
|
||||
AM --> AY[Manage Bank Account];
|
||||
AM --> AZ[View Benefits];
|
||||
AM --> BA[Access Support];
|
||||
BA --> BB[View FAQs];
|
||||
BA --> BC[Contact Support];
|
||||
end
|
||||
|
||||
subgraph QRScanning
|
||||
AJ --> BD[Scan QR Code];
|
||||
end
|
||||
@@ -1,39 +0,0 @@
|
||||
graph TD
|
||||
subgraph User Authentication
|
||||
direction LR
|
||||
UA1[Flutter App] -->|Phone Number| UA2[Firebase Auth];
|
||||
UA2 -->|Verification Code| UA1;
|
||||
UA1 -->|Verification Code| UA2;
|
||||
UA2 -->|Auth Token| UA1;
|
||||
UA1 -->|Auth Token| UA3[GraphQL Server];
|
||||
UA3 -->|User Data| UA1;
|
||||
end
|
||||
|
||||
subgraph User Onboarding
|
||||
direction LR
|
||||
UO1[Flutter App] -->|Personal Info| UO2[GraphQL Server];
|
||||
UO2 -->|update_staff_personal_info| UO3[Firebase Firestore];
|
||||
UO2 -->|User Data| UO1;
|
||||
end
|
||||
|
||||
subgraph Shift Management
|
||||
direction LR
|
||||
SM1[Flutter App] -->|Get Shifts| SM2[GraphQL Server];
|
||||
SM2 -->|getShifts| SM3[Firebase Firestore];
|
||||
SM3 -->|Shift Data| SM2;
|
||||
SM2 -->|Shift Data| SM1;
|
||||
|
||||
SM1 -->|Accept Shift| SM2;
|
||||
SM2 -->|accept_shift| SM3;
|
||||
SM3 -->|Updated Shift| SM2;
|
||||
SM2 -->|Updated Shift| SM1;
|
||||
end
|
||||
|
||||
subgraph Profile Update with Avatar
|
||||
direction LR
|
||||
PU1[Flutter App] -->|Image| PU2[Firebase Storage];
|
||||
PU2 -->|Image URL| PU1;
|
||||
PU1 -->|Image URL & Personal Info| PU3[GraphQL Server];
|
||||
PU3 -->|update_staff_personal_info & upload_staff_avatar| PU4[Firebase Firestore];
|
||||
PU3 -->|User Data| PU1;
|
||||
end
|
||||
@@ -1,17 +0,0 @@
|
||||
timeline
|
||||
title KROW Platform - Accelerated Migration & Enhancement Roadmap (2 Months to Production)
|
||||
|
||||
section Phase 1: Foundation & New Workflow Adoption
|
||||
Week 1-2 : Initialize Firebase projects (Dev, Staging) and configure hosting<br>Set up CI/CD pipelines for automated deployments<br>Adopt new development and deployment workflows
|
||||
Week 3-4 : Configure internal launchpad for easy access to resources<br>Team training on new tools and workflows<br>Documentation setup
|
||||
Key Milestones : <b>Dev & Staging environments fully operational</b><br><b>Team ready to work with new infrastructure</b>
|
||||
|
||||
section Phase 2: Functional Parity & New Experience
|
||||
Week 5-6 : Migrate core backend logic (Cloud Functions, Firestore/Cloud SQL)<br>Reconnect Web and Mobile apps to new APIs<br>Database migration and validation
|
||||
Week 7-8 : Integrate new UI/UX design across all applications<br>Implement new core business workflows<br>Feature parity testing
|
||||
Key Milestones : <b>All existing features are ISO-functional on the new platform</b><br><b>New design and core workflows fully integrated and tested</b>
|
||||
|
||||
section Phase 3: Preparation & Production Launch
|
||||
Week 9-10 : Complete end-to-end testing and performance optimization<br>Security audits and penetration testing<br>Load testing and optimization
|
||||
Week 11-12 : Set up robust monitoring and alerting system for production<br>Final production deployment<br>Legacy infrastructure decommissioning plan
|
||||
Key Milestones : <b>KROW is live in production with new design and workflows</b><br><b>Legacy infrastructure ready for decommissioning</b>
|
||||
@@ -1,10 +0,0 @@
|
||||
[
|
||||
{
|
||||
"title": "Architecture Document & Migration Plan",
|
||||
"path": "./assets/documents/legacy/staff-mobile-application/architecture.md"
|
||||
},
|
||||
{
|
||||
"title": "Architecture Document & Migration Plan",
|
||||
"path": "./assets/documents/legacy/client-mobile-application/architecture.md"
|
||||
}
|
||||
]
|
||||
@@ -1,252 +0,0 @@
|
||||
# Krow Mobile Client App Architecture Document
|
||||
|
||||
## A. Introduction
|
||||
|
||||
This document provides a comprehensive overview of the Krow mobile client application's architecture. The Krow app is a Flutter-based mobile application designed to connect staff with work opportunities. It includes features for event management, invoicing, staff rating, and profile management.
|
||||
|
||||
The core purpose of the app is to provide a seamless experience for staff to find, manage, and get paid for work, while allowing clients to manage their events and staff effectively.
|
||||
|
||||
## B. Full Architecture Overview
|
||||
|
||||
The Krow app is built using a layered architecture that separates concerns and promotes modularity. The main layers are the **Presentation Layer**, the **Domain Layer**, and the **Data Layer**, organized into feature-based modules.
|
||||
|
||||
### Key Modules and Layers
|
||||
|
||||
* **Features:** The `lib/features` directory contains the main features of the app, such as `sign_in`, `events`, `profile`, etc. Each feature directory is further divided into `presentation` and `domain` layers.
|
||||
* **Presentation Layer:** This layer is responsible for the UI and user interaction. It contains the screens (widgets) and the BLoCs (Business Logic Components) that manage the state of the UI.
|
||||
* **Domain Layer:** This layer contains the core business logic of the application. It includes the BLoCs, which are responsible for orchestrating the flow of data between the UI and the data layer, and the business objects (entities).
|
||||
* **Data Layer:** This layer is responsible for all data-related operations. It includes the repositories that fetch data from the backend and the data sources themselves (e.g., GraphQL API, local cache).
|
||||
* **Core:** The `lib/core` directory contains shared code that is used across multiple features, such as the API client, dependency injection setup, routing, and common widgets.
|
||||
|
||||
### Integration Points
|
||||
|
||||
* **UI to Domain:** The UI (widgets) dispatches events to the BLoCs in the domain layer based on user interactions.
|
||||
* **Domain to Data:** The BLoCs in the domain layer call methods on the repositories in the data layer to fetch or update data.
|
||||
* **Data to Backend:** The repositories in the data layer use the `ApiClient` to make GraphQL calls to the backend.
|
||||
|
||||
## C. Backend Architecture
|
||||
|
||||
The backend of the Krow app is a hybrid system that leverages both a **GraphQL server** and **Firebase services**.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Client"
|
||||
A[Flutter App]
|
||||
end
|
||||
|
||||
subgraph "Backend"
|
||||
B[GraphQL Server (e.g., Node.js)]
|
||||
C[Firebase]
|
||||
end
|
||||
|
||||
subgraph "Firebase Services"
|
||||
C1[Firebase Auth]
|
||||
C2[Firebase Firestore]
|
||||
C3[Firebase Storage]
|
||||
end
|
||||
|
||||
A -- "GraphQL Queries/Mutations" --> B
|
||||
A -- "Authentication" --> C1
|
||||
|
||||
B -- "Data Operations" --> C2
|
||||
B -- "File Operations" --> C3
|
||||
|
||||
C1 -- "User Tokens" --> A
|
||||
C2 -- "Data" --> B
|
||||
C3 -- "Files" --> B
|
||||
|
||||
B -- "Data/Files" --> A
|
||||
```
|
||||
|
||||
### GraphQL
|
||||
|
||||
The GraphQL server acts as an intermediary between the Flutter app and the Firebase services. It exposes a set of queries and mutations that the app can use to interact with the backend. This provides a single, unified API for the app to consume, simplifying data fetching and manipulation.
|
||||
|
||||
### Firebase Integration
|
||||
|
||||
* **Firebase Auth:** Firebase Auth is used for user authentication. The Flutter app interacts directly with Firebase Auth to handle user sign-in, sign-up, and password reset flows. Once authenticated, the app retrieves a Firebase ID token, which is then used to authenticate with the GraphQL server.
|
||||
* **Firebase Firestore:** Firestore is the primary database for the application. The GraphQL server is responsible for all interactions with Firestore, including fetching, creating, updating, and deleting data.
|
||||
* **Firebase Storage:** Firebase Storage is used for storing user-generated content, such as profile pictures. The GraphQL server handles file uploads and retrieves file URLs that are then sent to the app.
|
||||
|
||||
### End-to-End Communication Flow
|
||||
|
||||
1. The Flutter app authenticates the user with Firebase Auth.
|
||||
2. The app receives a Firebase ID token.
|
||||
3. For all subsequent API requests, the app sends the Firebase ID token in the authorization header of the GraphQL request.
|
||||
4. The GraphQL server verifies the token and then executes the requested query or mutation.
|
||||
5. The GraphQL server interacts with Firestore or Firebase Storage to fulfill the request.
|
||||
6. The GraphQL server returns the requested data to the app.
|
||||
|
||||
## D. API Layer
|
||||
|
||||
The API layer is responsible for all communication with the backend. It is built around the `graphql_flutter` package and a custom `ApiClient`.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "GraphQL API"
|
||||
direction LR
|
||||
subgraph "Queries"
|
||||
Q1[getEvents]
|
||||
Q2[getEventDetails]
|
||||
Q3[getInvoices]
|
||||
Q4[getInvoiceDetails]
|
||||
Q5[getNotifications]
|
||||
Q6[getNotificationDetails]
|
||||
Q7[getProfile]
|
||||
Q8[getAssignedStaff]
|
||||
end
|
||||
|
||||
subgraph "Mutations"
|
||||
M1[createEvent]
|
||||
M2[updateProfile]
|
||||
M3[rateStaff]
|
||||
M4[clockIn]
|
||||
M5[clockOut]
|
||||
M6[uploadProfilePicture]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "Firebase"
|
||||
direction LR
|
||||
subgraph "Firestore Collections"
|
||||
FS1[events]
|
||||
FS2[invoices]
|
||||
FS3[notifications]
|
||||
FS4[users]
|
||||
end
|
||||
|
||||
subgraph "Firebase Storage"
|
||||
FB1[Profile Pictures]
|
||||
end
|
||||
end
|
||||
|
||||
Q1 --> FS1
|
||||
Q2 --> FS1
|
||||
Q3 --> FS2
|
||||
Q4 --> FS2
|
||||
Q5 --> FS3
|
||||
Q6 --> FS3
|
||||
Q7 --> FS4
|
||||
Q8 --> FS1
|
||||
Q8 --> FS4
|
||||
|
||||
M1 --> FS1
|
||||
M2 --> FS4
|
||||
M3 --> FS1
|
||||
M3 --> FS4
|
||||
M4 --> FS1
|
||||
M5 --> FS1
|
||||
M6 --> FB1
|
||||
```
|
||||
|
||||
### API Handling
|
||||
|
||||
* **Error Handling:** The `ApiClient` uses the `ErrorPolicy.all` policy to catch all GraphQL errors. The BLoCs are responsible for catching these errors and updating the UI state accordingly.
|
||||
* **Caching:** The `GraphQLCache` with `HiveStore` is used to cache GraphQL query results. The `fetchPolicy` is set to `cacheAndNetwork` to provide a fast user experience while keeping the data up-to-date.
|
||||
* **Parsing:** The app uses the `json_serializable` package to parse the JSON responses from the GraphQL server into Dart objects.
|
||||
|
||||
## E. State Management
|
||||
|
||||
The Krow app uses the **BLoC (Business Logic Component)** pattern for state management, powered by the `flutter_bloc` package.
|
||||
|
||||
### Why BLoC?
|
||||
|
||||
* **Separation of Concerns:** BLoC separates the business logic from the UI, making the code more organized, testable, and maintainable.
|
||||
* **Testability:** BLoCs are easy to test in isolation from the UI.
|
||||
* **Reactivity:** BLoC uses streams to manage state, which makes it easy to update the UI in response to state changes.
|
||||
|
||||
### State Flow
|
||||
|
||||
1. The UI dispatches an event to the BLoC.
|
||||
2. The BLoC receives the event and interacts with the data layer (repositories) to fetch or update data.
|
||||
3. The data layer returns data or a success/failure status to the BLoC.
|
||||
4. The BLoC updates its state based on the result from the data layer.
|
||||
5. The UI rebuilds itself in response to the new state.
|
||||
|
||||
### Integration with the API Layer
|
||||
|
||||
The BLoCs do not interact directly with the `ApiClient`. Instead, they go through a repository layer, which abstracts the data source. This makes it possible to switch out the backend without having to change the BLoCs.
|
||||
|
||||
## F. Use-Case Flows
|
||||
|
||||
The following diagrams illustrate the flow for some of the major use cases in the app.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Sign-In Flow"
|
||||
A1[User enters credentials] --> B1{SignInBloc};
|
||||
B1 --> C1[Firebase Auth: signInWithEmailAndPassword];
|
||||
C1 -- Success --> D1[Navigate to Home];
|
||||
C1 -- Failure --> E1[Show error message];
|
||||
end
|
||||
|
||||
subgraph "Password Reset Flow"
|
||||
A2[User requests password reset] --> B2{SignInBloc};
|
||||
B2 --> C2[Firebase Auth: sendPasswordResetEmail];
|
||||
C2 -- Email Sent --> D2[User clicks deep link];
|
||||
D2 --> E2[UI with new password fields];
|
||||
E2 --> F2{SignInBloc};
|
||||
F2 --> G2[Firebase Auth: confirmPasswordReset];
|
||||
G2 -- Success --> H2[Show success message];
|
||||
G2 -- Failure --> I2[Show error message];
|
||||
end
|
||||
|
||||
subgraph "Event Listing Flow"
|
||||
A3[User navigates to Events screen] --> B3{EventsBloc};
|
||||
B3 --> C3[GraphQL Query: getEvents];
|
||||
C3 --> D3[Firestore: events collection];
|
||||
D3 -- Returns event data --> C3;
|
||||
C3 -- Returns data --> B3;
|
||||
B3 --> E3[Display list of events];
|
||||
end
|
||||
|
||||
subgraph "Create Event Flow"
|
||||
A4[User submits new event form] --> B4{CreateEventBloc};
|
||||
B4 --> C4[GraphQL Mutation: createEvent];
|
||||
C4 --> D4[Firestore: events collection];
|
||||
D4 -- Success --> C4;
|
||||
C4 -- Returns success --> B4;
|
||||
B4 --> E4[Navigate to event details];
|
||||
end
|
||||
|
||||
subgraph "Profile Viewing Flow"
|
||||
A5[User navigates to Profile screen] --> B5{ProfileBloc};
|
||||
B5 --> C5[GraphQL Query: getProfile];
|
||||
C5 --> D5[Firestore: users collection];
|
||||
D5 -- Returns profile data --> C5;
|
||||
C5 -- Returns data --> B5;
|
||||
B5 --> E5[Display profile information];
|
||||
end
|
||||
```
|
||||
|
||||
## G. Replacing or Plugging in a New Backend: Considerations & Recommendations
|
||||
|
||||
This section provides guidance on how to replace the current GraphQL + Firebase backend with a different backend solution.
|
||||
|
||||
### Tightly Coupled Components
|
||||
|
||||
* **`ApiClient`:** This class is tightly coupled to `graphql_flutter`.
|
||||
* **Firebase Auth:** The authentication logic is directly tied to the `firebase_auth` package.
|
||||
* **BLoCs:** Some BLoCs might have direct dependencies on Firebase or GraphQL-specific models.
|
||||
|
||||
### Abstraction Recommendations
|
||||
|
||||
To make the architecture more backend-agnostic, the following abstractions should be implemented:
|
||||
|
||||
* **Repositories:** Create an abstract `Repository` class for each feature in the `domain` layer. The implementation of this repository will be in the `data` layer. The BLoCs should only depend on the abstract repository.
|
||||
* **Authentication Service:** Create an abstract `AuthService` class that defines the methods for authentication (e.g., `signIn`, `signOut`, `getToken`). The implementation of this service will be in the `data` layer and will use the specific authentication provider (e.g., Firebase Auth, OAuth).
|
||||
* **Data Transfer Objects (DTOs):** Use DTOs to transfer data between the data layer and the domain layer. This will prevent the domain layer from having dependencies on backend-specific models.
|
||||
|
||||
### Suggested Design Improvements
|
||||
|
||||
* **Formalize Clean Architecture:** While the current architecture has elements of Clean Architecture, it could be more formally implemented by creating a clear separation between the `domain`, `data`, and `presentation` layers for all features.
|
||||
* **Introduce Use Cases:** Introduce `UseCase` classes in the `domain` layer to encapsulate specific business operations. This will make the BLoCs simpler and more focused on state management.
|
||||
|
||||
### Migration Strategies
|
||||
|
||||
To replace the current backend with a new one (e.g., REST API, Supabase), follow these steps:
|
||||
|
||||
1. **Implement New Repositories:** Create new implementations of the repository interfaces for the new backend.
|
||||
2. **Implement New Auth Service:** Create a new implementation of the `AuthService` interface for the new authentication provider.
|
||||
3. **Update Dependency Injection:** Use dependency injection (e.g., `get_it` and `injectable`) to provide the new repository and auth service implementations to the BLoCs.
|
||||
4. **Gradual Migration:** If possible, migrate one feature at a time to the new backend. This will reduce the risk of breaking the entire application at once.
|
||||
@@ -1,120 +0,0 @@
|
||||
# Krow Mobile Staff App - Architecture Document
|
||||
|
||||
## A. Introduction
|
||||
|
||||
This document outlines the architecture of the Krow Mobile Staff App, a Flutter application designed to connect staff with job opportunities. The app provides features for staff to manage their profiles, view and apply for shifts, track earnings, and complete necessary paperwork.
|
||||
|
||||
The core purpose of the app is to streamline the process of finding and managing temporary work, providing a seamless experience for staff from onboarding to payment.
|
||||
|
||||
## B. Full Architecture Overview
|
||||
|
||||
The application follows a **Clean Architecture** pattern, separating concerns into three main layers: **Presentation**, **Domain**, and **Data**. This layered approach promotes a separation of concerns, making the codebase more maintainable, scalable, and testable.
|
||||
|
||||
- **Presentation Layer:** This layer is responsible for the UI and user interaction. It consists of widgets, screens, and Blocs that manage the UI state. The Presentation Layer depends on the Domain Layer to execute business logic.
|
||||
|
||||
- **Domain Layer:** This layer contains the core business logic of the application. It consists of use cases (interactors), entities (business objects), and repository interfaces. The Domain Layer is independent of the other layers.
|
||||
|
||||
- **Data Layer:** This layer is responsible for data retrieval and storage. It consists of repository implementations, data sources (API clients, local database), and data transfer objects (DTOs). The Data Layer depends on the Domain Layer and implements the repository interfaces defined in it.
|
||||
|
||||
### Integration Points
|
||||
|
||||
- **UI → Domain:** The UI (e.g., a button press) triggers a method in a Bloc. The Bloc then calls a use case in the Domain Layer to execute the business logic.
|
||||
- **Domain → Data:** The use case calls a method on a repository interface.
|
||||
- **Data → External:** The repository implementation, located in the Data Layer, communicates with external data sources (GraphQL API, Firebase, local storage) to retrieve or store data.
|
||||
|
||||
## C. Backend Architecture
|
||||
|
||||
The backend is built on a combination of a **GraphQL server** and **Firebase services**.
|
||||
|
||||
- **GraphQL Server:** The primary endpoint for the Flutter app. It handles most of the business logic and data aggregation. The server is responsible for communicating with Firebase services to fulfill requests.
|
||||
|
||||
- **Firebase Services:**
|
||||
- **Firebase Auth:** Used for user authentication, primarily with phone number verification.
|
||||
- **Firebase Firestore:** The main database for storing application data, such as user profiles, shifts, and earnings.
|
||||
- **Firebase Storage:** Used for storing user-generated content, such as profile avatars.
|
||||
- **Firebase Cloud Messaging:** Used for sending push notifications to users.
|
||||
- **Firebase Remote Config:** Used for remotely configuring app parameters.
|
||||
|
||||
### API Flow
|
||||
|
||||
1. **Flutter App to GraphQL:** The Flutter app sends GraphQL queries and mutations to the GraphQL server.
|
||||
2. **GraphQL to Firebase:** The GraphQL server resolves these operations by interacting with Firebase services. For example, a `getShifts` query will fetch data from Firestore, and an `updateStaffPersonalInfoWithAvatar` mutation will update a document in Firestore and upload a file to Firebase Storage.
|
||||
3. **Response Flow:** The data flows back from Firebase to the GraphQL server, which then sends it back to the Flutter app.
|
||||
|
||||
## D. API Layer
|
||||
|
||||
The API layer is responsible for all communication with the backend.
|
||||
|
||||
- **GraphQL Operations:** The app uses the `graphql_flutter` package to interact with the GraphQL server. Queries, mutations, and subscriptions are defined in `.dart` files within each feature's `data` directory.
|
||||
|
||||
- **API Error Handling:** The `ApiClient` class is responsible for handling API errors. It catches exceptions and returns a `Failure` object, which is then handled by the Bloc in the Presentation Layer to show an appropriate error message to the user.
|
||||
|
||||
- **Caching:** The `graphql_flutter` client provides caching capabilities. The app uses a `HiveStore` to cache GraphQL responses, reducing the number of network requests and improving performance.
|
||||
|
||||
- **Parsing:** JSON responses from the API are parsed into Dart objects using the `json_serializable` package.
|
||||
|
||||
## E. State Management
|
||||
|
||||
The application uses the **Bloc** library for state management.
|
||||
|
||||
- **Why Bloc?** Bloc is a predictable state management library that helps to separate business logic from the UI. It enforces a unidirectional data flow, making the app's state changes predictable and easier to debug.
|
||||
|
||||
- **State Flow:**
|
||||
1. **UI Event:** The UI dispatches an event to the Bloc.
|
||||
2. **Bloc Logic:** The Bloc receives the event, executes the necessary business logic (often by calling a use case), and emits a new state.
|
||||
3. **UI Update:** The UI listens to the Bloc's state changes and rebuilds itself to reflect the new state.
|
||||
|
||||
- **Integration with API Layer:** Blocs interact with the API layer through use cases. When a Bloc needs to fetch data from the backend, it calls a use case, which in turn calls a repository that communicates with the API.
|
||||
|
||||
## F. Use-Case Flows
|
||||
|
||||
### User Authentication
|
||||
|
||||
1. **UI:** The user enters their phone number.
|
||||
2. **Logic:** The `AuthBloc` sends the phone number to Firebase Auth for verification.
|
||||
3. **Backend:** Firebase Auth sends a verification code to the user's phone.
|
||||
4. **UI:** The user enters the verification code.
|
||||
5. **Logic:** The `AuthBloc` verifies the code with Firebase Auth.
|
||||
6. **Backend:** Firebase Auth returns an auth token.
|
||||
7. **Logic:** The app sends the auth token to the GraphQL server to get the user's profile.
|
||||
8. **Response:** The GraphQL server returns the user's data, and the app navigates to the home screen.
|
||||
|
||||
### Shift Management
|
||||
|
||||
1. **UI:** The user navigates to the shifts screen.
|
||||
2. **Logic:** The `ShiftsBloc` requests a list of shifts.
|
||||
3. **Backend:** The use case calls the `ShiftsRepository`, which sends a `getShifts` query to the GraphQL server. The server fetches the shifts from Firestore.
|
||||
4. **Response:** The GraphQL server returns the list of shifts, which is then displayed on the UI.
|
||||
|
||||
## G. Replacing or Plugging in a New Backend: Considerations & Recommendations
|
||||
|
||||
This section provides guidance on how to replace the current GraphQL + Firebase backend with a different solution (e.g., REST, Supabase, Hasura).
|
||||
|
||||
### Tightly Coupled Components
|
||||
|
||||
- **Data Layer:** The current `ApiProvider` implementations are tightly coupled to the GraphQL API.
|
||||
- **Authentication:** The authentication flow is tightly coupled to Firebase Auth.
|
||||
- **DTOs:** The data transfer objects are generated based on the GraphQL schema.
|
||||
|
||||
### Abstraction Recommendations
|
||||
|
||||
To make the architecture more backend-agnostic, the following components should be abstracted:
|
||||
|
||||
- **Repositories:** The repository interfaces in the Domain Layer should remain unchanged. The implementations in the Data Layer will need to be rewritten for the new backend.
|
||||
- **Services:** Services like authentication should be abstracted behind an interface. For example, an `AuthService` interface can be defined in the Domain Layer, with a `FirebaseAuthService` implementation in the Data Layer.
|
||||
- **DTOs:** The DTOs should be mapped to domain entities in the Data Layer. This ensures that the Domain Layer is not affected by changes in the backend's data model.
|
||||
- **Error Handling:** A generic error handling mechanism should be implemented to handle different types of backend errors.
|
||||
|
||||
### Suggested Design Improvements
|
||||
|
||||
- **Introduce a Service Locator:** Use a service locator like `get_it` to decouple the layers and make it easier to swap out implementations.
|
||||
- **Define Abstract Data Sources:** Instead of directly calling the API client in the repository implementations, introduce abstract data source interfaces (e.g., `UserRemoteDataSource`). This adds another layer of abstraction and makes the repositories more testable.
|
||||
|
||||
### Migration Strategies
|
||||
|
||||
1. **Define Interfaces:** Start by defining abstract interfaces for all backend interactions (repositories, services).
|
||||
2. **Implement New Data Layer:** Create a new implementation of the Data Layer for the new backend. This will involve writing new repository implementations, API clients, and DTOs.
|
||||
3. **Swap Implementations:** Use the service locator to swap the old Data Layer implementation with the new one.
|
||||
4. **Test:** Thoroughly test the application to ensure that everything works as expected with the new backend.
|
||||
|
||||
By following these recommendations, the Krow Mobile Staff App can be migrated to a new backend with minimal impact on the overall architecture and business logic.
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg class="w-5 h-5 text-gray-700" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 439 B |
@@ -1 +0,0 @@
|
||||
<svg class="w-6 h-6 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path></svg>
|
||||
|
Before Width: | Height: | Size: 330 B |
@@ -1,5 +0,0 @@
|
||||
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 354 B |
@@ -1,4 +0,0 @@
|
||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 280 B |
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" class="w-6 h-6 text-pink-600">
|
||||
<path
|
||||
d="M22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C12.8417 22 14 22.1163 14 21C14 20.391 13.6832 19.9212 13.3686 19.4544C12.9082 18.7715 12.4523 18.0953 13 17C13.6667 15.6667 14.7778 15.6667 16.4815 15.6667C17.3334 15.6667 18.3334 15.6667 19.5 15.5C21.601 15.1999 22 13.9084 22 12Z"
|
||||
stroke="currentColor" stroke-width="2"></path>
|
||||
<path d="M7 15.002L7.00868 14.9996" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round"></path>
|
||||
<circle cx="9.5" cy="8.5" r="1.5" stroke="currentColor" stroke-width="2"></circle>
|
||||
<circle cx="16.5" cy="9.5" r="1.5" stroke="currentColor" stroke-width="2"></circle>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 819 B |
@@ -1,30 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 200 300" class="w-5 h-5">
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #0acf83
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #a259ff
|
||||
}
|
||||
|
||||
.st2 {
|
||||
fill: #f24e1e
|
||||
}
|
||||
|
||||
.st3 {
|
||||
fill: #ff7262
|
||||
}
|
||||
|
||||
.st4 {
|
||||
fill: #1abcfe
|
||||
}
|
||||
</style>
|
||||
<title>Figma.logo</title>
|
||||
<desc>Created using Figma</desc>
|
||||
<path id="path0_fill" class="st0" d="M50 300c27.6 0 50-22.4 50-50v-50H50c-27.6 0-50 22.4-50 50s22.4 50 50 50z" />
|
||||
<path id="path1_fill" class="st1" d="M0 150c0-27.6 22.4-50 50-50h50v100H50c-27.6 0-50-22.4-50-50z" />
|
||||
<path id="path1_fill_1_" class="st2" d="M0 50C0 22.4 22.4 0 50 0h50v100H50C22.4 100 0 77.6 0 50z" />
|
||||
<path id="path2_fill" class="st3" d="M100 0h50c27.6 0 50 22.4 50 50s-22.4 50-50 50h-50V0z" />
|
||||
<path id="path3_fill" class="st4" d="M200 150c0 27.6-22.4 50-50 50s-50-22.4-50-50 22.4-50 50-50 50 22.4 50 50z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 995 B |
@@ -1,5 +0,0 @@
|
||||
<svg class="w-5 h-5 text-orange-500" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M3.89 15.672L6.255.461A.542.542 0 017.27.288l2.543 4.771zm16.794 3.692l-2.25-14.03a.542.542 0 00-.919-.295L3.316 19.365l7.856 4.427a1.621 1.621 0 001.588 0zM14.3 7.147l-1.82-3.482a.542.542 0 00-.96 0L3.53 17.984z">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 332 B |
@@ -1,5 +0,0 @@
|
||||
<svg class="w-5 h-5 text-gray-900" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 831 B |
@@ -1,5 +0,0 @@
|
||||
<svg class="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12.19 2.38a8.85 8.85 0 00-10.4 9.28l5.18-5.18a4.89 4.89 0 012.5-.72 4.89 4.89 0 011.72-.07zm7.6 2.24a8.85 8.85 0 01-7.6 14.91l4.5-4.5a4.89 4.89 0 002.45-2.55 4.89 4.89 0 00.65-7.86z">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 300 B |
@@ -1,5 +0,0 @@
|
||||
<svg class="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M3,20.5V3.5C3,2.91 3.34,2.39 3.84,2.15L13.69,12L3.84,21.85C3.34,21.6 3,21.09 3,20.5M16.81,15.12L6.05,21.34L14.54,12.85L16.81,15.12M20.16,10.81C20.5,11.08 20.75,11.5 20.75,12C20.75,12.5 20.53,12.9 20.18,13.18L17.89,14.5L15.39,12L17.89,9.5L20.16,10.81M6.05,2.66L16.81,8.88L14.54,11.15L6.05,2.66Z">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 412 B |
@@ -1,4 +0,0 @@
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 267 B |
@@ -1,6 +0,0 @@
|
||||
<svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 436 B |
@@ -1,5 +0,0 @@
|
||||
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 440 B |
@@ -1,88 +0,0 @@
|
||||
async function loadLinks() {
|
||||
const container = document.getElementById('links-container');
|
||||
if (!container) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('./assets/data/links.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load links: ${response.status}`);
|
||||
}
|
||||
|
||||
const groups = await response.json();
|
||||
|
||||
// Helper function to fetch SVG content
|
||||
const fetchSvg = async (path) => {
|
||||
if (!path) return '';
|
||||
try {
|
||||
const res = await fetch(path);
|
||||
if (!res.ok) return '';
|
||||
return await res.text();
|
||||
} catch (e) {
|
||||
console.error(`Failed to load SVG: ${path}`, e);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// Process groups and fetch SVGs in parallel
|
||||
const groupsWithSvgs = await Promise.all(groups.map(async (group) => {
|
||||
const groupIconSvg = await fetchSvg(group.iconPath);
|
||||
|
||||
const linksWithSvgs = await Promise.all(group.links.map(async (link) => {
|
||||
const linkIconSvg = link.iconPath ? await fetchSvg(link.iconPath) : '';
|
||||
return { ...link, iconSvg: linkIconSvg };
|
||||
}));
|
||||
|
||||
return { ...group, iconSvg: groupIconSvg, links: linksWithSvgs };
|
||||
}));
|
||||
|
||||
container.innerHTML = groupsWithSvgs.map(group => `
|
||||
<!-- ${group.title} Card -->
|
||||
<div class="card-hover bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
|
||||
<div class="flex items-center space-x-3 mb-6">
|
||||
<div class="w-10 h-10 ${group.iconColorClass} rounded-lg flex items-center justify-center">
|
||||
${group.iconSvg}
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900">${group.title}</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
${group.links.map(link => `
|
||||
<a href="${link.url}" target="_blank"
|
||||
class="flex items-center justify-between p-4 ${link.containerClass} rounded-xl transition-all group">
|
||||
<div class="flex items-center space-x-3">
|
||||
${link.iconClass ? `<div class="${link.iconClass}"></div>` : link.iconSvg}
|
||||
${link.subtitle ? `
|
||||
<div>
|
||||
<div class="font-medium text-gray-900 ${link.textHoverClass}">${link.title}</div>
|
||||
<div class="text-xs text-gray-500">${link.subtitle}</div>
|
||||
</div>
|
||||
` : `
|
||||
<span class="font-medium text-gray-900 ${link.textHoverClass}">${link.title}</span>
|
||||
`}
|
||||
</div>
|
||||
${link.badge ? `
|
||||
<span class="px-3 py-1 ${link.badgeColorClass} text-white text-xs font-semibold rounded-full">${link.badge}</span>
|
||||
` : ''}
|
||||
${link.arrowIcon ? `
|
||||
<svg class="w-5 h-5 text-gray-400 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
` : ''}
|
||||
</a>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading links:', error);
|
||||
container.innerHTML = `
|
||||
<div class="col-span-full p-4 bg-red-50 text-red-600 rounded-xl border border-red-200">
|
||||
Failed to load application links. Please try refreshing the page.
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Load links when the DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', loadLinks);
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="200 180 280 140" style="enable-background:new 0 0 500 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#24303B;}
|
||||
.st1{fill:#002FE3;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st1" d="M459.81,202.55c-5.03,0.59-9.08,4.49-10.36,9.38l-15.99,59.71l-16.24-56.3
|
||||
c-1.68-5.92-6.22-10.86-12.19-12.34c-1.58-0.39-3.11-0.54-4.64-0.49h-0.15c-1.53-0.05-3.11,0.1-4.64,0.49
|
||||
c-5.97,1.48-10.51,6.42-12.24,12.34l-3.6,12.53l-11.35,39.38l-7.9-27.54c-10.76-37.5-48.56-62.23-88.38-55.32
|
||||
c-33.26,5.82-57.05,35.68-56.99,69.48v0.79c0,4.34,0.39,8.73,1.13,13.18c0.18,1.02,0.37,2.03,0.6,3.03
|
||||
c1.84,8.31,10.93,12.73,18.49,8.8v0c5.36-2.79,7.84-8.89,6.42-14.77c-0.85-3.54-1.28-7.23-1.23-11.03
|
||||
c0-25.02,20.48-45.5,45.55-45.2c7.6,0.1,15.59,2.07,23.59,6.37c13.52,7.3,23.15,20.18,27.34,34.94l13.32,46.34
|
||||
c1.73,5.97,6.22,11,12.24,12.58c9.62,2.62,19-3.06,21.51-12.04l16.09-56.7l0.2-0.1l16.09,56.85c1.63,5.68,5.87,10.41,11.55,11.99
|
||||
c9.13,2.57,18.11-2.66,20.67-11.2l24.13-79.6C475.35,209.85,468.64,201.56,459.81,202.55z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,14 +0,0 @@
|
||||
# List of authorized users for the Krow DevOps Launchpad
|
||||
# Format: one email per line, lines starting with # are comments
|
||||
#
|
||||
# Users must be listed here to access the Launchpad via Firebase Auth.
|
||||
# Both internal (@krowwithus.com) and external emails are supported.
|
||||
|
||||
user:admin@krowwithus.com
|
||||
|
||||
# External users - Oloodi employees
|
||||
user:boris@oloodi.com
|
||||
user:achintha.isuru@oloodi.com
|
||||
|
||||
# External users - Legendary employees
|
||||
|
||||
@@ -1,832 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Krow DevOps Launchpad</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.svg">
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Mermaid -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||
|
||||
<!-- Marked.js for Markdown parsing -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
|
||||
<!-- Custom Tailwind Config -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
|
||||
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.gradient-bg {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.badge-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: .7; }
|
||||
}
|
||||
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
#diagram-container:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* Accordion styles */
|
||||
.accordion-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
text-align: left;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.accordion-button:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.accordion-button .chevron {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.accordion-button[aria-expanded="true"] .chevron {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.accordion-panel {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
/* Markdown styling */
|
||||
.markdown-content { line-height: 1.7; color: #374151; }
|
||||
.markdown-content h1 { font-size: 2em; font-weight: 700; margin-top: 1.5em; margin-bottom: 0.5em; padding-bottom: 0.3em; border-bottom: 2px solid #e5e7eb; color: #111827; }
|
||||
.markdown-content h1:first-child { margin-top: 0; }
|
||||
.markdown-content h2 { font-size: 1.5em; font-weight: 600; margin-top: 1.5em; margin-bottom: 0.5em; padding-bottom: 0.2em; border-bottom: 1px solid #e5e7eb; color: #111827; }
|
||||
.markdown-content h3 { font-size: 1.25em; font-weight: 600; margin-top: 1.2em; margin-bottom: 0.5em; color: #111827; }
|
||||
.markdown-content h4 { font-size: 1.1em; font-weight: 600; margin-top: 1em; margin-bottom: 0.5em; color: #111827; }
|
||||
.markdown-content p { margin-bottom: 1em; }
|
||||
.markdown-content ul, .markdown-content ol { margin-bottom: 1em; padding-left: 2em; }
|
||||
.markdown-content ul { list-style-type: disc; }
|
||||
.markdown-content ol { list-style-type: decimal; }
|
||||
.markdown-content li { margin-bottom: 0.5em; }
|
||||
.markdown-content code { background-color: #f3f4f6; padding: 0.2em 0.4em; border-radius: 0.25em; font-size: 0.9em; font-family: 'Courier New', monospace; color: #dc2626; }
|
||||
.markdown-content pre { background-color: #1f2937; color: #f9fafb; padding: 1em; border-radius: 0.5em; overflow-x: auto; margin-bottom: 1em; }
|
||||
.markdown-content pre code { background-color: transparent; padding: 0; color: inherit; }
|
||||
.markdown-content blockquote { border-left: 4px solid #3b82f6; padding-left: 1em; margin: 1em 0; color: #6b7280; font-style: italic; }
|
||||
.markdown-content a { color: #3b82f6; text-decoration: underline; }
|
||||
.markdown-content a:hover { color: #2563eb; }
|
||||
.markdown-content table { width: 100%; border-collapse: collapse; margin-bottom: 1em; }
|
||||
.markdown-content th, .markdown-content td { border: 1px solid #e5e7eb; padding: 0.5em; text-align: left; }
|
||||
.markdown-content th { background-color: #f9fafb; font-weight: 600; }
|
||||
.markdown-content img { max-width: 100%; height: auto; border-radius: 0.5em; margin: 1em 0; }
|
||||
.markdown-content hr { border: none; border-top: 2px solid #e5e7eb; margin: 2em 0; }
|
||||
|
||||
/* Loading Overlay */
|
||||
#auth-loading {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: white; z-index: 50;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
}
|
||||
#login-screen {
|
||||
display: none;
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
|
||||
z-index: 40;
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
|
||||
|
||||
<!-- Auth Loading State -->
|
||||
<div id="auth-loading">
|
||||
<div class="w-16 h-16 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin mb-4"></div>
|
||||
<p class="text-gray-500 font-medium">Verifying access...</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Screen -->
|
||||
<div id="login-screen" class="flex">
|
||||
<div class="bg-white p-8 rounded-2xl shadow-xl max-w-md w-full mx-4 text-center">
|
||||
<div class="flex justify-center mb-6">
|
||||
<img src="logo.svg" alt="KROW Logo" class="h-16 w-16" onerror="this.src='https://via.placeholder.com/64?text=KROW'">
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Internal DevOps Launchpad</h1>
|
||||
<p class="text-gray-500 mb-8">Please sign in to access internal development resources.</p>
|
||||
|
||||
<button id="google-signin-btn" class="w-full flex items-center justify-center space-x-3 bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium py-3 px-4 rounded-xl transition-all shadow-sm">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24">
|
||||
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
|
||||
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
||||
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
||||
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
||||
</svg>
|
||||
<span>Sign in with Google</span>
|
||||
</button>
|
||||
<p id="login-error" class="mt-4 text-red-500 text-sm hidden"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content (Protected) -->
|
||||
<div id="app-content" class="hidden flex h-screen overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="w-72 bg-white shadow-xl flex flex-col border-r border-gray-200">
|
||||
<!-- Logo Section -->
|
||||
<div class="p-6 border-b border-gray-200">
|
||||
<div class="flex items-center justify-center space-x-3">
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center">
|
||||
<img src="logo.svg" alt="Krow Logo" style="height: 60px;" onerror="this.style.display='none'">
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-gray-900">KROW DevOps</h1>
|
||||
<p class="text-xs text-gray-500">Launchpad Hub</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex-1 overflow-y-auto scrollbar-hide p-4" id="sidebar-nav">
|
||||
<a href="#" id="nav-home" onclick="showView('home', this)"
|
||||
class="nav-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 bg-primary-50 border border-primary-200 font-medium mb-2">
|
||||
<svg class="w-5 h-5 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
|
||||
</path>
|
||||
</svg>
|
||||
<span>Home</span>
|
||||
</a>
|
||||
|
||||
<!-- Dynamic diagrams section - ALL diagrams loaded here -->
|
||||
<div id="dynamic-diagrams-section"></div>
|
||||
|
||||
<!-- Documentation section -->
|
||||
<div id="documentation-section"></div>
|
||||
</nav>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="p-4 border-t border-gray-200 bg-gray-50">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="flex items-center space-x-2 text-xs text-gray-500">
|
||||
<div class="w-2 h-2 bg-green-500 rounded-full badge-pulse"></div>
|
||||
<span>System Online</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between pt-2 border-t border-gray-100">
|
||||
<span id="user-email" class="text-xs text-gray-400 truncate max-w-[150px]"></span>
|
||||
<button id="logout-btn" class="text-xs text-red-500 hover:text-red-700 font-medium">Sign Out</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
|
||||
<!-- Home View -->
|
||||
<div id="home-view" class="p-8">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-3xl font-bold text-gray-900 mb-2">Krow DevOps Launchpad</h2>
|
||||
<p class="text-gray-600">Central hub for KROW development and operations infrastructure</p>
|
||||
</div>
|
||||
|
||||
<div id="links-container" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Links will be loaded dynamically -->
|
||||
<div class="col-span-full flex justify-center p-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diagram Viewer -->
|
||||
<div id="diagram-viewer" class="hidden h-full flex flex-col p-8">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 id="diagram-title" class="text-2xl font-bold text-gray-900"></h3>
|
||||
<div class="flex items-center space-x-2 bg-white rounded-xl shadow-lg p-2 border border-gray-200">
|
||||
<button id="zoomInBtn" class="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
title="Zoom In">
|
||||
<svg class="w-5 h-5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="zoomOutBtn" class="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
title="Zoom Out">
|
||||
<svg class="w-5 h-5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="resetBtn" class="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
title="Reset View">
|
||||
<svg class="w-5 h-5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="diagram-container" tabindex="-1"
|
||||
class="flex-1 bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden cursor-grab flex items-center justify-center p-8">
|
||||
<!-- SVG will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Document Viewer -->
|
||||
<div id="document-viewer" class="hidden h-full flex flex-col p-8">
|
||||
<div class="mb-6">
|
||||
<h3 id="document-title" class="text-2xl font-bold text-gray-900"></h3>
|
||||
</div>
|
||||
<div id="document-container"
|
||||
class="flex-1 bg-white rounded-2xl shadow-xl border border-gray-200 overflow-y-auto p-8 markdown-content">
|
||||
<!-- Document content will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Links Loader -->
|
||||
<script src="assets/js/links-loader.js"></script>
|
||||
|
||||
<!-- Panzoom -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@panzoom/panzoom@4.5.1/dist/panzoom.min.js"></script>
|
||||
|
||||
<!-- Firebase Auth Logic -->
|
||||
<script type="module">
|
||||
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js';
|
||||
import { getAuth, GoogleAuthProvider, signInWithPopup, onAuthStateChanged, signOut } from 'https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js';
|
||||
|
||||
// Elements
|
||||
const authLoading = document.getElementById('auth-loading');
|
||||
const loginScreen = document.getElementById('login-screen');
|
||||
const appContent = document.getElementById('app-content');
|
||||
const googleBtn = document.getElementById('google-signin-btn');
|
||||
const logoutBtn = document.getElementById('logout-btn');
|
||||
const loginError = document.getElementById('login-error');
|
||||
const userEmailSpan = document.getElementById('user-email');
|
||||
|
||||
let pendingError = null;
|
||||
|
||||
// Initialize Firebase
|
||||
// Note: fetch('/__/firebase/init.json') works only on deployed Firebase Hosting or firebase serve
|
||||
async function initAuth() {
|
||||
try {
|
||||
let config;
|
||||
// Fetch config from hosting environment
|
||||
const res = await fetch('/__/firebase/init.json');
|
||||
if (!res.ok) throw new Error('Failed to load Firebase config. Ensure you are running via "make launchpad-dev" or deployed on Hosting.');
|
||||
config = await res.json();
|
||||
console.log("Firebase Config Loaded:", config.projectId);
|
||||
|
||||
const app = initializeApp(config);
|
||||
const auth = getAuth(app);
|
||||
|
||||
// Auth State Observer
|
||||
onAuthStateChanged(auth, async (user) => {
|
||||
if (user) {
|
||||
// User is signed in, check if allowed
|
||||
const allowed = await checkAccess(user.email);
|
||||
if (allowed) {
|
||||
showApp(user);
|
||||
} else {
|
||||
pendingError = `Access Denied: <b>${user.email}</b> is not authorized.<br>Please contact the Krow Internal Developers or an Administrator to request access.`;
|
||||
signOut(auth);
|
||||
}
|
||||
} else {
|
||||
// User is signed out
|
||||
if (pendingError) {
|
||||
showLogin(pendingError);
|
||||
pendingError = null;
|
||||
} else {
|
||||
showLogin();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Login Handler
|
||||
googleBtn.addEventListener('click', () => {
|
||||
// Clear previous errors
|
||||
loginError.classList.add('hidden');
|
||||
const provider = new GoogleAuthProvider();
|
||||
signInWithPopup(auth, provider).catch((error) => {
|
||||
console.error("Login failed:", error);
|
||||
showLogin("Login failed: " + error.message);
|
||||
});
|
||||
});
|
||||
|
||||
// Logout Handler
|
||||
logoutBtn.addEventListener('click', () => {
|
||||
signOut(auth);
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error("Auth init error:", e);
|
||||
// Fallback for local dev without firebase serving
|
||||
if (window.location.hostname === 'localhost') {
|
||||
alert("Firebase Auth failed to load. Ensure you are using 'firebase serve' or have configured local auth.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAccess(email) {
|
||||
try {
|
||||
// 1. Fetch the secure list of hashes
|
||||
const res = await fetch('allowed-hashes.json');
|
||||
if (!res.ok) return false;
|
||||
const allowedHashes = await res.json();
|
||||
|
||||
// 2. Hash the user's email (SHA-256)
|
||||
const normalizedEmail = email.trim().toLowerCase();
|
||||
const msgBuffer = new TextEncoder().encode(normalizedEmail);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
// 3. Compare
|
||||
return allowedHashes.includes(hashHex);
|
||||
} catch (e) {
|
||||
console.error("Failed to check access list:", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showApp(user) {
|
||||
authLoading.style.display = 'none';
|
||||
loginScreen.style.display = 'none';
|
||||
appContent.classList.remove('hidden');
|
||||
userEmailSpan.textContent = user.email;
|
||||
|
||||
// Trigger link loading if not already done
|
||||
if (typeof loadLinks === 'function') loadLinks();
|
||||
}
|
||||
|
||||
function showLogin(errorMsg) {
|
||||
authLoading.style.display = 'none';
|
||||
appContent.classList.add('hidden');
|
||||
loginScreen.style.display = 'flex';
|
||||
if (errorMsg) {
|
||||
loginError.innerHTML = errorMsg;
|
||||
loginError.classList.remove('hidden');
|
||||
} else {
|
||||
loginError.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Start
|
||||
initAuth();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
let allDiagrams = [];
|
||||
let allDocuments = [];
|
||||
const homeView = document.getElementById('home-view');
|
||||
const diagramViewer = document.getElementById('diagram-viewer');
|
||||
const diagramContainer = document.getElementById('diagram-container');
|
||||
const diagramTitle = document.getElementById('diagram-title');
|
||||
const documentViewer = document.getElementById('document-viewer');
|
||||
const documentContainer = document.getElementById('document-container');
|
||||
const documentTitle = document.getElementById('document-title');
|
||||
const zoomInBtn = document.getElementById('zoomInBtn');
|
||||
const zoomOutBtn = document.getElementById('zoomOutBtn');
|
||||
const resetBtn = document.getElementById('resetBtn');
|
||||
|
||||
let panzoomInstance = null;
|
||||
let currentScale = 1;
|
||||
|
||||
// Initialize Mermaid
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: 'default',
|
||||
flowchart: {
|
||||
useMaxWidth: false,
|
||||
htmlLabels: true,
|
||||
curve: 'basis'
|
||||
}
|
||||
});
|
||||
|
||||
// Build hierarchical structure from paths
|
||||
function buildHierarchy(items, pathPrefix) {
|
||||
const hierarchy = { _root: { _items: [], _children: {} } };
|
||||
|
||||
items.forEach(item => {
|
||||
let relativePath = item.path;
|
||||
if (relativePath.startsWith('./')) {
|
||||
relativePath = relativePath.substring(2);
|
||||
}
|
||||
if (relativePath.startsWith(pathPrefix)) {
|
||||
relativePath = relativePath.substring(pathPrefix.length);
|
||||
}
|
||||
|
||||
const parts = relativePath.split('/');
|
||||
const relevantParts = parts.slice(0, -1); // remove filename
|
||||
|
||||
let current = hierarchy._root;
|
||||
relevantParts.forEach(part => {
|
||||
if (!current._children[part]) {
|
||||
current._children[part] = { _items: [], _children: {} };
|
||||
}
|
||||
current = current._children[part];
|
||||
});
|
||||
current._items.push(item);
|
||||
});
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
// Generic function to create accordion navigation
|
||||
function createAccordionNavigation(hierarchy, parentElement, createLinkFunction, sectionTitle) {
|
||||
const createAccordion = (title, items, children) => {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'mb-1';
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.className = 'accordion-button';
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
button.innerHTML = `
|
||||
<span class="font-medium">${title.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}</span>
|
||||
<svg class="chevron w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
|
||||
`;
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'accordion-panel pl-4 pt-1';
|
||||
|
||||
if (items) {
|
||||
items.forEach(item => createLinkFunction(item, panel, 1));
|
||||
}
|
||||
|
||||
if (children) {
|
||||
Object.keys(children).forEach(childKey => {
|
||||
const childSection = children[childKey];
|
||||
const childHeading = document.createElement('div');
|
||||
childHeading.className = 'px-4 pt-2 pb-1 text-xs font-semibold text-gray-400 uppercase tracking-wider';
|
||||
childHeading.textContent = childKey.replace(/-/g, ' ');
|
||||
panel.appendChild(childHeading);
|
||||
childSection._items.forEach(item => createLinkFunction(item, panel, 2));
|
||||
});
|
||||
}
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
const isExpanded = button.getAttribute('aria-expanded') === 'true';
|
||||
button.setAttribute('aria-expanded', !isExpanded);
|
||||
if (!isExpanded) {
|
||||
panel.style.maxHeight = panel.scrollHeight + 'px';
|
||||
} else {
|
||||
panel.style.maxHeight = '0px';
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(button);
|
||||
container.appendChild(panel);
|
||||
return container;
|
||||
};
|
||||
|
||||
const heading = document.createElement('div');
|
||||
heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
|
||||
heading.textContent = sectionTitle;
|
||||
parentElement.appendChild(heading);
|
||||
|
||||
// Process root items first
|
||||
if (hierarchy._root && hierarchy._root._items.length > 0) {
|
||||
hierarchy._root._items.forEach(item => createLinkFunction(item, parentElement, 0));
|
||||
}
|
||||
|
||||
// Process categories as accordions
|
||||
Object.keys(hierarchy._root._children).forEach(key => {
|
||||
if (key.startsWith('_')) return;
|
||||
const section = hierarchy._root._children[key];
|
||||
const accordion = createAccordion(key, section._items, section._children);
|
||||
parentElement.appendChild(accordion);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to create a document link
|
||||
function createDocumentLink(doc, parentElement, level) {
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.className = 'nav-item flex items-center space-x-3 px-4 py-2.5 rounded-lg text-gray-600 hover:bg-gray-100 text-sm mb-1' +
|
||||
(level > 0 ? ' ' : '');
|
||||
link.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
showView('document', link, doc.path, doc.title);
|
||||
};
|
||||
|
||||
const iconSvg = `<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>`;
|
||||
|
||||
link.innerHTML = `${iconSvg}<span class="truncate">${doc.title}</span>`;
|
||||
parentElement.appendChild(link);
|
||||
}
|
||||
|
||||
// Helper function to create a diagram link
|
||||
function createDiagramLink(diagram, parentElement, level) {
|
||||
const link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.className = 'nav-item flex items-center space-x-3 px-4 py-2.5 rounded-lg text-gray-600 hover:bg-gray-100 text-sm mb-1' +
|
||||
(level > 0 ? ' ' : '');
|
||||
link.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
showView('diagram', link, diagram.path, diagram.title, diagram.type);
|
||||
};
|
||||
|
||||
const iconSvg = `<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4l2 2h4a2 2 0 012 2v12a2 2 0 01-2 2h-4l-2-2H7z"></path>
|
||||
</svg>`;
|
||||
|
||||
link.innerHTML = `${iconSvg}<span class="truncate">${diagram.title}</span>`;
|
||||
parentElement.appendChild(link);
|
||||
}
|
||||
|
||||
// Load all diagrams from config
|
||||
async function loadAllDiagrams() {
|
||||
const dynamicSection = document.getElementById('dynamic-diagrams-section');
|
||||
|
||||
try {
|
||||
const response = await fetch('./assets/diagrams/diagrams-config.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load diagrams config: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
allDiagrams = JSON.parse(text);
|
||||
|
||||
if (allDiagrams && allDiagrams.length > 0) {
|
||||
const hierarchy = buildHierarchy(allDiagrams, 'assets/diagrams/');
|
||||
createAccordionNavigation(hierarchy, dynamicSection, createDiagramLink, 'Diagrams');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading diagrams configuration:', error);
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'px-4 py-3 mx-2 mt-4 text-xs text-amber-600 bg-amber-50 rounded-lg border border-amber-200';
|
||||
errorDiv.innerHTML = `
|
||||
<div class="font-semibold mb-1">⚠️ Diagrams</div>
|
||||
<div>Unable to load diagrams-config.json</div>
|
||||
<div class="mt-1 text-amber-500">${error.message}</div>
|
||||
`;
|
||||
dynamicSection.appendChild(errorDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// Load all documentation from config
|
||||
async function loadAllDocuments() {
|
||||
const documentationSection = document.getElementById('documentation-section');
|
||||
|
||||
try {
|
||||
const response = await fetch('./assets/documents/documents-config.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load documents config: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
allDocuments = JSON.parse(text);
|
||||
|
||||
if (allDocuments && allDocuments.length > 0) {
|
||||
const hierarchy = buildHierarchy(allDocuments, 'assets/documents/');
|
||||
createAccordionNavigation(hierarchy, documentationSection, createDocumentLink, 'Documentation');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading documents configuration:', error);
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'px-4 py-3 mx-2 mt-4 text-xs text-amber-600 bg-amber-50 rounded-lg border border-amber-200';
|
||||
errorDiv.innerHTML = `
|
||||
<div class="font-semibold mb-1">⚠️ Documentation</div>
|
||||
<div>Unable to load documents-config.json</div>
|
||||
<div class="mt-1 text-amber-500">${error.message}</div>
|
||||
`;
|
||||
documentationSection.appendChild(errorDiv);
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveNav(activeLink) {
|
||||
document.querySelectorAll('#sidebar-nav a').forEach(link => {
|
||||
link.classList.remove('bg-primary-50', 'border', 'border-primary-200', 'text-primary-700');
|
||||
link.classList.add('text-gray-700');
|
||||
});
|
||||
activeLink.classList.remove('text-gray-700');
|
||||
activeLink.classList.add('bg-primary-50', 'border', 'border-primary-200', 'text-primary-700');
|
||||
}
|
||||
|
||||
async function showView(viewName, navLink, filePath, title, type = 'svg') {
|
||||
setActiveNav(navLink);
|
||||
if (panzoomInstance) {
|
||||
panzoomInstance.destroy();
|
||||
panzoomInstance = null;
|
||||
}
|
||||
diagramContainer.innerHTML = '';
|
||||
documentContainer.innerHTML = '';
|
||||
currentScale = 1;
|
||||
|
||||
if (viewName === 'home') {
|
||||
homeView.classList.remove('hidden');
|
||||
diagramViewer.classList.add('hidden');
|
||||
documentViewer.classList.add('hidden');
|
||||
} else if (viewName === 'diagram') {
|
||||
homeView.classList.add('hidden');
|
||||
diagramViewer.classList.remove('hidden');
|
||||
documentViewer.classList.add('hidden');
|
||||
diagramTitle.textContent = title;
|
||||
diagramContainer.innerHTML = `
|
||||
<div class="flex flex-col items-center space-y-3">
|
||||
<div class="w-12 h-12 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin"></div>
|
||||
<p class="text-gray-600 font-medium">Loading diagram...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
if (type === 'svg') {
|
||||
const response = await fetch(filePath);
|
||||
if (!response.ok) throw new Error(`Network error: ${response.status}`);
|
||||
const svgContent = await response.text();
|
||||
|
||||
const parser = new DOMParser();
|
||||
const svgDoc = parser.parseFromString(svgContent, "image/svg+xml");
|
||||
const svgElement = svgDoc.querySelector('svg');
|
||||
|
||||
if (svgElement) {
|
||||
diagramContainer.innerHTML = '';
|
||||
diagramContainer.appendChild(svgElement);
|
||||
panzoomInstance = Panzoom(svgElement, {
|
||||
canvas: true,
|
||||
maxScale: 10,
|
||||
minScale: 0.3,
|
||||
startScale: 1
|
||||
});
|
||||
diagramContainer.addEventListener('wheel', panzoomInstance.zoomWithWheel);
|
||||
diagramContainer.focus();
|
||||
} else {
|
||||
throw new Error('No SVG element found.');
|
||||
}
|
||||
} else if (type === 'mermaid') {
|
||||
const response = await fetch(filePath);
|
||||
if (!response.ok) throw new Error(`Network error: ${response.status}`);
|
||||
const mermaidCode = await response.text();
|
||||
|
||||
const { svg } = await mermaid.render('mermaidDiagram_' + Date.now(), mermaidCode);
|
||||
|
||||
diagramContainer.innerHTML = svg;
|
||||
const svgElement = diagramContainer.querySelector('svg');
|
||||
|
||||
if (svgElement) {
|
||||
svgElement.style.maxWidth = 'none';
|
||||
svgElement.style.height = 'auto';
|
||||
|
||||
panzoomInstance = Panzoom(svgElement, {
|
||||
canvas: true,
|
||||
maxScale: 10,
|
||||
minScale: 0.3,
|
||||
startScale: 1
|
||||
});
|
||||
diagramContainer.addEventListener('wheel', panzoomInstance.zoomWithWheel);
|
||||
diagramContainer.focus();
|
||||
} else {
|
||||
throw new Error('Failed to render Mermaid diagram.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
diagramContainer.innerHTML = `
|
||||
<div class="bg-red-50 border border-red-200 rounded-xl p-6 max-w-md">
|
||||
<div class="flex items-center space-x-3 mb-2">
|
||||
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<h4 class="font-bold text-red-900">Failed to load diagram</h4>
|
||||
</div>
|
||||
<p class="text-red-700 text-sm">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} else if (viewName === 'document') {
|
||||
homeView.classList.add('hidden');
|
||||
diagramViewer.classList.add('hidden');
|
||||
documentViewer.classList.remove('hidden');
|
||||
documentTitle.textContent = title;
|
||||
documentContainer.innerHTML = `
|
||||
<div class="flex flex-col items-center justify-center space-y-3 py-12">
|
||||
<div class="w-12 h-12 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin"></div>
|
||||
<p class="text-gray-600 font-medium">Loading document...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await fetch(filePath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load document: ${response.status}`);
|
||||
}
|
||||
|
||||
const markdownText = await response.text();
|
||||
const htmlContent = marked.parse(markdownText);
|
||||
documentContainer.innerHTML = htmlContent;
|
||||
} catch (error) {
|
||||
console.error('Error loading document:', error);
|
||||
documentContainer.innerHTML = `
|
||||
<div class="bg-red-50 border border-red-200 rounded-xl p-6">
|
||||
<div class="flex items-center space-x-3 mb-2">
|
||||
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<h4 class="font-bold text-red-900">Failed to load document</h4>
|
||||
</div>
|
||||
<p class="text-red-700 text-sm">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoomInBtn.addEventListener('click', () => {
|
||||
if (panzoomInstance) {
|
||||
panzoomInstance.zoomIn();
|
||||
diagramContainer.focus();
|
||||
}
|
||||
});
|
||||
|
||||
zoomOutBtn.addEventListener('click', () => {
|
||||
if (panzoomInstance) {
|
||||
panzoomInstance.zoomOut();
|
||||
diagramContainer.focus();
|
||||
}
|
||||
});
|
||||
|
||||
resetBtn.addEventListener('click', () => {
|
||||
if (panzoomInstance) {
|
||||
panzoomInstance.reset();
|
||||
diagramContainer.focus();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadAllDiagrams();
|
||||
loadAllDocuments();
|
||||
showView('home', document.getElementById('nav-home'));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="200 180 280 140" style="enable-background:new 0 0 500 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#24303B;}
|
||||
.st1{fill:#002FE3;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st1" d="M459.81,202.55c-5.03,0.59-9.08,4.49-10.36,9.38l-15.99,59.71l-16.24-56.3
|
||||
c-1.68-5.92-6.22-10.86-12.19-12.34c-1.58-0.39-3.11-0.54-4.64-0.49h-0.15c-1.53-0.05-3.11,0.1-4.64,0.49
|
||||
c-5.97,1.48-10.51,6.42-12.24,12.34l-3.6,12.53l-11.35,39.38l-7.9-27.54c-10.76-37.5-48.56-62.23-88.38-55.32
|
||||
c-33.26,5.82-57.05,35.68-56.99,69.48v0.79c0,4.34,0.39,8.73,1.13,13.18c0.18,1.02,0.37,2.03,0.6,3.03
|
||||
c1.84,8.31,10.93,12.73,18.49,8.8v0c5.36-2.79,7.84-8.89,6.42-14.77c-0.85-3.54-1.28-7.23-1.23-11.03
|
||||
c0-25.02,20.48-45.5,45.55-45.2c7.6,0.1,15.59,2.07,23.59,6.37c13.52,7.3,23.15,20.18,27.34,34.94l13.32,46.34
|
||||
c1.73,5.97,6.22,11,12.24,12.58c9.62,2.62,19-3.06,21.51-12.04l16.09-56.7l0.2-0.1l16.09,56.85c1.63,5.68,5.87,10.41,11.55,11.99
|
||||
c9.13,2.57,18.11-2.66,20.67-11.2l24.13-79.6C475.35,209.85,468.64,201.56,459.81,202.55z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |