Skip to content
Merged
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
15 changes: 14 additions & 1 deletion app/(main)/worker/booking/[id]/ClientPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BookingStatusBadge } from '@/components/ui/BookingStatusBadge'
import { useAuth } from '@/contexts/AuthContext'
import { createClient } from '@/lib/supabase/client'
import { createNotification } from '@/lib/notifications'
import { getActiveSubscription } from '@/lib/supabase/subscriptions'
import { Geolocation } from '@capacitor/geolocation'
import dynamic from 'next/dynamic'
const LeafletTrackingMap = dynamic(
Expand Down Expand Up @@ -168,12 +169,24 @@ export default function WorkerBookingDetailsPage() {
timestamp: new Date().toISOString()
}

const { data: workerProfile } = await supabase.from('profiles').select('rating').eq('id', profile?.id).single()
const clientSub = await getActiveSubscription(supabase, booking.client_id)

let warrantyDays = 30
if (workerProfile?.rating && workerProfile.rating >= 4.5) warrantyDays += 30
if (clientSub?.plan_type === 'premium' || clientSub?.plan_type === 'master') warrantyDays += 30

const warrantyExpiresAt = new Date()
warrantyExpiresAt.setDate(warrantyExpiresAt.getDate() + warrantyDays)

const { error } = await supabase
.from('bookings')
.update({
status: 'completed',
tracking_status: 'completed',
status_history: [...currentHistory, newHistoryEntry]
status_history: [...currentHistory, newHistoryEntry],
warranty_days: warrantyDays,
warranty_expires_at: warrantyExpiresAt.toISOString()
})
.eq('id', booking.id)
if (error) throw error
Expand Down
10 changes: 5 additions & 5 deletions app/client/addresses/edit/[id]/ClientPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import React, { useEffect, useState, use } from 'react'
import { useRouter } from 'next/navigation'
import { MapPin } from 'lucide-react'
import { MapPin, Home, Briefcase } from 'lucide-react'
import { createClient } from '@/lib/supabase/client'
import { useAuth } from '@/contexts/AuthContext'
import { PageHeader } from '@/components/ui/PageHeader'
Expand All @@ -27,10 +27,10 @@ export default function EditAddressPage({ params }: { params: Promise<{ id: stri
const [saving, setSaving] = useState(false)
const [loading, setLoading] = useState(true)

const types: { key: AddressType; label: string; icon: string }[] = [
{ key: 'home', label: 'المنزل', icon: '🏠' },
{ key: 'work', label: 'العمل', icon: '💼' },
{ key: 'other', label: 'أخرى', icon: '📍' },
const types: { key: AddressType; label: string; icon: React.ReactNode }[] = [
{ key: 'home', label: 'المنزل', icon: <Home size={16} /> },
{ key: 'work', label: 'العمل', icon: <Briefcase size={16} /> },
{ key: 'other', label: 'أخرى', icon: <MapPin size={16} /> },
]

useEffect(() => {
Expand Down
10 changes: 5 additions & 5 deletions app/client/addresses/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import { MapPin } from 'lucide-react'
import { MapPin, Home, Briefcase } from 'lucide-react'
import { createClient } from '@/lib/supabase/client'
import { useAuth } from '@/contexts/AuthContext'
import { PageHeader } from '@/components/ui/PageHeader'
Expand All @@ -25,10 +25,10 @@ export default function AddNewAddressPage() {
const [notes, setNotes] = useState('')
const [saving, setSaving] = useState(false)

const types: { key: AddressType; label: string; icon: string }[] = [
{ key: 'home', label: 'المنزل', icon: '🏠' },
{ key: 'work', label: 'العمل', icon: '💼' },
{ key: 'other', label: 'أخرى', icon: '📍' },
const types: { key: AddressType; label: string; icon: React.ReactNode }[] = [
{ key: 'home', label: 'المنزل', icon: <Home size={16} /> },
{ key: 'work', label: 'العمل', icon: <Briefcase size={16} /> },
{ key: 'other', label: 'أخرى', icon: <MapPin size={16} /> },
]

const handleSave = async (e: React.FormEvent) => {
Expand Down
298 changes: 298 additions & 0 deletions app/client/bundle/confirm/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { ArrowLeft, MapPin, Calendar, Clock, Star, CheckCircle2 } from 'lucide-react'
import { createClient } from '@/lib/supabase/client'

const SERVICES = [
{ id: 'painting', name: 'دهان', basePrice: 150 },
{ id: 'carpentry', name: 'نجارة', basePrice: 150 },
{ id: 'plumbing', name: 'سباكة', basePrice: 150 },
{ id: 'electricity', name: 'كهرباء', basePrice: 150 },
]

interface BundleData {
id: string
services: string[]
date: string
}

interface AssignedWorker {
serviceId: string
serviceName: string
time: string
worker: {
id: string
full_name: string | null
avatar_url: string | null
rating: number | null
} | null
}

export default function BundleConfirmPage() {
const router = useRouter()
const [bundleData, setBundleData] = useState<BundleData | null>(null)
const [assignments, setAssignments] = useState<AssignedWorker[]>([])
const [loading, setLoading] = useState(true)
const [booking, setBooking] = useState(false)

// Addresses
const [addresses, setAddresses] = useState<any[]>([])
const [selectedAddressId, setSelectedAddressId] = useState('')

useEffect(() => {
const data = sessionStorage.getItem('pendingBundle')
if (!data) {
router.push('/client/bundle')
return
}

const parsed = JSON.parse(data)
setBundleData(parsed)
loadData(parsed)
}, [router])

const loadData = async (data: BundleData) => {
setLoading(true)
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
router.push('/login')
return
}

// Load addresses
const { data: addrData } = await supabase.from('addresses').select('*').eq('user_id', user.id)
if (addrData && addrData.length > 0) {
setAddresses(addrData)
setSelectedAddressId(addrData[0].id)
}

// Auto-assign workers
const newAssignments: AssignedWorker[] = []
let currentHour = 10 // Start at 10 AM

for (const serviceId of data.services) {
const serviceInfo = SERVICES.find(s => s.id === serviceId)
if (!serviceInfo) continue

// Find best available worker
const { data: workers } = await supabase
.from('profiles')
.select('id, full_name, avatar_url, rating')
.eq('role', 'worker')
.eq('verified', true)
.eq('is_available', true)
.eq('profession', serviceInfo.name)
.order('rating', { ascending: false })
.limit(1)

const assignedWorker = workers && workers.length > 0 ? workers[0] : null

const timeString = `${currentHour.toString().padStart(2, '0')}:00`

newAssignments.push({
serviceId,
serviceName: serviceInfo.name,
time: timeString,
worker: assignedWorker
})

currentHour += 2 // 2 hours interval
}

setAssignments(newAssignments)
setLoading(false)
}

const handleConfirm = async () => {
if (!bundleData || assignments.length === 0) return
if (!selectedAddressId) {
alert('يرجى إضافة عنوان أولاً')
return
}

setBooking(true)
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return

const address = addresses.find(a => a.id === selectedAddressId)
const addressString = address ? `${address.city}، ${address.street}` : 'عنوان العميل'

// Calculate discounted price per service
const basePrice = 150
const discountedPrice = basePrice * 0.85 // 15% discount

const bundleId = bundleData.id
const promises = assignments.map(assignment => {
// If no worker found for a specific profession, we still create an unassigned booking (or fail?)
// We'll create it as pending if worker is null, or confirmed if assigned
return supabase.from('bookings').insert({
client_id: user.id,
worker_id: assignment.worker?.id || null, // Might be null if no worker is currently available
service_name: `باقة صيانة: ${assignment.serviceName}`,
status: assignment.worker ? 'confirmed' : 'pending',
price: discountedPrice,
payment_method: 'cash',
appointment_date: bundleData.date,
appointment_time: assignment.time,
address: addressString,
is_bundle: true,
bundle_id: bundleId,
notes: `جزء من باقة صيانة مجمعة`
})
})

const results = await Promise.all(promises)
const errors = results.filter(r => r.error)

if (errors.length > 0) {
console.error(errors)
alert('حدث خطأ أثناء حجز الباقة. يرجى المحاولة مرة أخرى.')
setBooking(false)
return
}

sessionStorage.removeItem('pendingBundle')
router.push('/client/orders')
}

if (loading) {
return (
<div dir="rtl" className="min-h-screen bg-[#020617] flex items-center justify-center">
<div className="animate-spin w-8 h-8 border-2 border-purple-500 border-t-transparent rounded-full" />
</div>
)
}

const totalBasePrice = assignments.length * 150
const discount = totalBasePrice * 0.15
const finalPrice = totalBasePrice - discount

return (
<div dir="rtl" className="min-h-screen bg-[#020617]">
<div className="sticky top-0 z-10 bg-[#020617]/80 backdrop-blur-xl border-b border-white/5">
<div className="flex items-center justify-between h-14 max-w-[512px] mx-auto px-4">
<button onClick={() => router.back()}>
<ArrowLeft size={16} className="text-white" />
</button>
<h1 className="text-white text-xl font-semibold">تأكيد الباقة</h1>
<div className="w-6" />
</div>
</div>

<div className="max-w-[512px] mx-auto px-4 py-6 space-y-6 pb-40">
{/* Date Summary */}
<div className="bg-[#0F172A]/60 border border-purple-500/20 rounded-2xl p-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-purple-500/10 flex items-center justify-center text-purple-400">
<Calendar size={20} />
</div>
<div>
<p className="text-white/60 text-xs">تاريخ التنفيذ</p>
<p className="text-white font-bold">{bundleData?.date}</p>
</div>
</div>
</div>

{/* Schedule / Assignments */}
<div>
<h3 className="text-white font-bold mb-4">الجدول الزمني للحرفيين</h3>
<div className="relative border-r-2 border-purple-500/20 pr-4 space-y-6 mr-2">
{assignments.map((assignment, index) => (
<div key={index} className="relative">
<div className="absolute -right-[23px] top-1 w-4 h-4 rounded-full bg-[#020617] border-2 border-purple-500" />
<div className="bg-[#1E2538] rounded-xl p-4">
<div className="flex justify-between items-start mb-3">
<div>
<h4 className="text-white font-bold">{assignment.serviceName}</h4>
<div className="flex items-center gap-1 text-purple-400 text-xs mt-1">
<Clock size={12} />
<span>{assignment.time}</span>
</div>
</div>
{assignment.worker ? (
<div className="flex items-center gap-2 bg-[#0F172A] px-2 py-1 rounded-lg">
<img src={assignment.worker.avatar_url || ''} alt="" className="w-6 h-6 rounded-full bg-slate-700" />
<div className="text-xs">
<p className="text-white font-medium">{assignment.worker.full_name}</p>
<p className="text-[#F97316] flex items-center gap-1">
<Star size={10} className="fill-current" /> {assignment.worker.rating || '4.9'}
</p>
</div>
Comment on lines +218 to +224
</div>
) : (
<span className="text-orange-400 text-xs bg-orange-400/10 px-2 py-1 rounded-md">
جاري البحث عن حرفي
</span>
)}
</div>
</div>
</div>
))}
</div>
</div>

{/* Address */}
<div>
<h3 className="text-white font-bold mb-4">عنوان التنفيذ</h3>
{addresses.length > 0 ? (
<select
value={selectedAddressId}
onChange={e => setSelectedAddressId(e.target.value)}
className="w-full bg-[#1E2538] border border-white/5 rounded-xl p-4 text-white outline-none"
>
{addresses.map(a => (
<option key={a.id} value={a.id}>{a.label ? a.label + ' - ' : ''}{a.city}، {a.street}</option>
))}
</select>
) : (
<div className="bg-orange-500/10 border border-orange-500/20 p-4 rounded-xl text-center">
<p className="text-orange-400 text-sm mb-2">لم تقم بإضافة أي عنوان بعد</p>
<button onClick={() => router.push('/client/addresses/new')} className="text-white text-xs bg-orange-500 px-4 py-2 rounded-lg">
إضافة عنوان
</button>
</div>
)}
</div>

{/* Pricing */}
<div className="bg-[#0F172A]/60 border border-white/5 rounded-2xl p-5">
<h3 className="text-white font-bold mb-4 flex items-center gap-2">
<CheckCircle2 size={18} className="text-purple-400" />
ملخص الدفع
</h3>
<div className="space-y-3 text-sm">
<div className="flex justify-between text-white/70">
<span>التكلفة الأساسية ({assignments.length} تخصصات)</span>
<span>{totalBasePrice} ج.م</span>
</div>
<div className="flex justify-between text-green-400 font-medium">
<span>خصم الباقة المجمعة (١٥٪)</span>
<span>- {discount} ج.م</span>
</div>
<div className="h-px bg-white/10 my-2" />
<div className="flex justify-between text-white font-bold text-lg">
<span>الإجمالي المطلوب</span>
<span>{finalPrice} ج.م</span>
</div>
</div>
</div>
</div>

<div className="fixed bottom-0 left-0 right-0 bg-[#020617] border-t border-white/5 p-4 z-50">
<div className="max-w-[512px] mx-auto">
<button
onClick={handleConfirm}
disabled={booking || !selectedAddressId}
className="w-full py-4 rounded-xl bg-gradient-to-l from-purple-600 to-indigo-600 text-white font-bold disabled:opacity-50 flex items-center justify-center gap-2"
>
{booking ? 'جاري الحجز...' : 'تأكيد وحجز الباقة'}
</button>
</div>
</div>
</div>
)
}
Loading
Loading