Skip to content
Merged

Sami #11

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 7 additions & 21 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ name: Build Android APK

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch: # Allows manual triggering of the workflow

jobs:
Expand All @@ -21,7 +17,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
java-version: '21'

- name: Setup Node.js
uses: actions/setup-node@v4
Expand All @@ -46,24 +42,14 @@ jobs:
env:
NODE_ENV: production

- name: Prepare Capacitor files
- name: Configure Capacitor for Production
run: |
# Install capacitor CLI and core
pnpm add -D @capacitor/cli @capacitor/core @capacitor/android
# Remove the server block from capacitor.config.ts to bundle the web assets locally
sed -i '/server:/,/}/d' capacitor.config.ts

# Create a root capacitor config to bundle the static files
cat <<EOF > capacitor.config.json
{
"appId": "com.hirfa.app",
"appName": "Hirfa",
"webDir": "out",
"bundledWebRuntime": false
}
EOF

# Add and sync the android platform
rm -rf android # Remove the broken or incomplete android folder
npx cap add android
- name: Sync Capacitor Android
run: |
npx cap sync android

- name: Build APK
working-directory: ./android
Expand Down
58 changes: 30 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,42 @@

# Craftsmen Maintenance Marketplace - حِرفة (Hirfa)

**A production-ready, fully functional Arabic-first home maintenance marketplace.** The all-in-one home services platform for the Arabic world. Browse verified craftsmen, compare prices, view before/after portfolios, book instantly, and track your orders, all from your phone.
**A full-stack, mobile-first app, The all-in-one home services platform for the Arabic world. Browse verified craftsmen, compare prices, view before/after portfolios, book instantly, and track your orders , all from your phone.**

---

## Overview
## Overview

Hirfa is a complete home maintenance platform built with Next.js 16 (App Router) and Supabase. It serves two user types:
Hirfa is a complete home maintenance platform built with Next.js 16 and Supabase. It serves two user types:

- **Clients** — Browse craftsmen, book services, request emergency help, manage orders and wallet
- **Craftsmen (Workers)** — Manage gallery with before/after photos, accept/reject orders, set services, manage schedule and payments

---

## 🚀 Features
## Features

### 🔐 Authentication & Onboarding
- Mail-based login with 6-digit OTP verification and auto-focus

- Phone-based login with OTP verification
- Role selection (Client / Craftsman)
- Multi-step onboarding flow with splash, welcome, intro carousel, and success animation with particle effects
- Multi-step onboarding flow with splash, welcome, intro carousel, and success animation

### 📱 Client App
### Client App

| Feature | Description |
| --------------------- | ---------------------------------------------------------------------- |
| **Home** | Category grid, featured craftsmen, nearby listings, availability indicators |
| **Craftsman Profile** | Full profile with before/after gallery, services, reviews, tier badges |
| **Home** | Category grid, featured craftsmen, nearby listings |
| **Craftsman Profile** | Full profile with before/after gallery, services, reviews, booking CTA |
| **Gallery Modal** | Card-based photo viewer with navigation (before/after comparison) |
| **Booking** | 4-step wizard with date picker, time slots, address, payment method |
| **Emergency SOS** | Quick emergency booking with live tracking animation and ETA |
| **Emergency SOS** | Quick emergency booking with live tracking simulation |
| **Orders** | Track order status (pending → confirmed → completed) |
| **Wallet** | Deposit, cards management, transaction history |
| **Addresses** | Saved addresses with CRUD |
| **Search** | Search craftsmen by name or profession |
| **Notifications** | Real-time notifications |
| **Profile** | Edit personal info, settings menu, quick action buttons |
| **Profile** | Edit personal info, settings menu |

### 🔧 Craftsman (Worker) App

Expand All @@ -53,12 +54,13 @@ Hirfa is a complete home maintenance platform built with Next.js 16 (App Router)
| **Wallet** | Earnings overview, withdrawals |
| **Calendar** | View booked appointments |

### 👑 Admin Dashboard
### Admin Dashboard

- User management, system rules, content moderation

---

## 🏗️ Tech Stack & Architecture
## Tech Stack

| Layer | Technology |
| ------------------------ | ----------------------------------- |
Expand All @@ -74,12 +76,6 @@ Hirfa is a complete home maintenance platform built with Next.js 16 (App Router)
| **State** | React Hooks + TanStack Query |
| **UI Primitives** | shadcn/ui + Base UI |

**Architecture Highlights:**
- **Mobile-first approach:** Max-width 390px centered, flexbox layout, touch-optimized.
- **Type Safety:** Full TypeScript implementation with robust interfaces.
- **Performance:** Turbopack for fast compilation (~9 seconds), optimized images via Next.js Image, code splitting with Suspense.
- **Internationalization Ready:** RTL layout support (`dir="rtl"`), Arabic typography optimized (Cairo font).

---

## 📁 Project Structure
Expand All @@ -88,7 +84,7 @@ Hirfa is a complete home maintenance platform built with Next.js 16 (App Router)
Hirfa/
├── app/
│ ├── (auth)/ # Login, register, OTP, verification
│ ├── (main)/ # Worker/Client pages (home, orders, etc.)
│ ├── (main)/ # Worker pages (home, orders, gallery, etc.)
│ ├── (onboarding)/ # Splash, welcome, intro, success
│ ├── admin/ # Admin dashboard
│ ├── api/ # Supabase API routes
Expand All @@ -100,17 +96,23 @@ Hirfa/
│ ├── auth/ # ProtectedRoute, Route guards
│ ├── shared/ # CraftsmanCard, CategoryCard, OTPInput
│ └── ui/ # BeforeAfterCard, ImageUploader, modals, etc.
├── contexts/ # Context providers (e.g. AuthContext)
├── hooks/ # Custom React hooks
├── lib/ # Types, mock-data, utils, Supabase clients
├── services/ # Auth and profile API services
├── contexts/ # AuthContext
├── hooks/ # useGallery, useCraftsmanProfile, etc.
├── lib/ # Types, utils, Supabase client/server
├── services/ # Auth and profile services
├── supabase/ # Migrations, schema
└── public/ # Static assets
```

---

## 💻 Getting Started
### Emergency SOS

One-tap emergency booking that auto-fills user address and creates an urgent order with real-time tracking simulation.

---

## 🚀 Getting Started

### Prerequisites

Expand Down Expand Up @@ -138,11 +140,11 @@ SMTP_FROM=noreply@example.com
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_api_key
```

### 🛠️ Installation & Local Development
### 💻 Installation & Local Development

1. Clone the repository and install dependencies:
```bash
git clone https://github.com/codestcode/Hirfa.git
git clone https://github.com/your-username/Hirfa.git
cd Hirfa
pnpm install
```
Expand All @@ -152,7 +154,7 @@ pnpm install
pnpm run dev
```

3. Open [http://localhost:3000](http://localhost:3000) in your browser. The app starts at `/splash` and flows through onboarding.
3. Open [http://localhost:3000](http://localhost:3000) in your browser.

---

Expand Down
6 changes: 3 additions & 3 deletions app/(main)/worker/booking/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ id: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/(main)/worker/messages/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ id: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
4 changes: 2 additions & 2 deletions app/(onboarding)/intro/[step]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export function generateStaticParams() {
];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
2 changes: 0 additions & 2 deletions app/api/auth/callback/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'

export const dynamic = 'force-dynamic'

export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
Expand Down
6 changes: 3 additions & 3 deletions app/client/addresses/edit/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ id: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/client/booking/[workerId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ workerId: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/client/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ id: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/client/craftsman/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ id: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/client/order/cancel/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ id: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/client/rate-review/[bookingId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ bookingId: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/client/services/[categoryId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ categoryId: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
6 changes: 3 additions & 3 deletions app/client/wallet/edit-card/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientPage from './ClientPage';

export function generateStaticParams() {
return [];
return [{ id: 'dummy' }];
}

export default function Page() {
return <ClientPage />;
export default function Page({ params }: { params: any }) {
return <ClientPage params={params} />;
}
2 changes: 1 addition & 1 deletion capacitor.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const config: CapacitorConfig = {
appName: 'Hirfa',
webDir: 'out',
server: {
url: 'https://hirfa-five.vercel.app/',
url: 'https://hirfa-amber.vercel.app',
cleartext: true
}
};
Expand Down
44 changes: 44 additions & 0 deletions fix_dynamic_routes_v4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os
import glob
import re

routes = [
"app/client/rate-review/[bookingId]",
"app/client/addresses/edit/[id]",
"app/client/order/cancel/[id]",
"app/client/craftsman/[id]",
"app/client/services/[categoryId]",
"app/client/booking/[workerId]",
"app/client/chat/[id]",
"app/client/wallet/edit-card/[id]",
"app/(main)/worker/booking/[id]",
"app/(main)/worker/messages/[id]",
"app/(onboarding)/intro/[step]"
]

for route in routes:
page_path = os.path.join(route, "page.tsx")

# extract parameter name
match = re.search(r'\[([^\]]+)\]$', route)
if not match:
continue
param_name = match.group(1)

if route == "app/(onboarding)/intro/[step]":
continue # intro is already fine with 1, 2, 3, 4

new_page_content = f"""import ClientPage from './ClientPage';

export function generateStaticParams() {{
return [{{ {param_name}: 'dummy' }}];
}}

export default function Page() {{
return <ClientPage />;
}}
"""
with open(page_path, 'w') as f:
f.write(new_page_content)
print(f"Updated {page_path} to return [{{ {param_name}: 'dummy' }}]")

Loading
Loading