diff --git a/app/layout.tsx b/app/layout.tsx index 52fcaa1c..016ed920 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -20,6 +20,4 @@ export default function RootLayout({ {children} ); -} - - +} \ No newline at end of file diff --git a/components/AvailabilityCalendar.tsx b/components/AvailabilityCalendar.tsx index cc860359..229e58a9 100644 --- a/components/AvailabilityCalendar.tsx +++ b/components/AvailabilityCalendar.tsx @@ -1,21 +1,30 @@ -// ./components/AvailabilityCalendar.tsx - -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Calendar, { CalendarProps } from 'react-calendar'; import 'react-calendar/dist/Calendar.css'; -const AvailabilityCalendar = ({ availableDates }: { availableDates: Date[] }) => { +interface AvailabilityCalendarProps { + availableDates: Date[]; + onDateChange?: (date: Date) => void; + isProvider?: boolean; +} + +const AvailabilityCalendar: React.FC = ({ availableDates, onDateChange, isProvider }) => { const [selectedDate, setSelectedDate] = useState(null); + useEffect(() => { + if (availableDates.length > 0) { + setSelectedDate(availableDates[0]); + } + }, [availableDates]); + const isAvailable = (date: Date) => { return availableDates.some(availableDate => availableDate.toDateString() === date.toDateString()); }; - const handleDateChange: CalendarProps['onChange'] = (date, event) => { - if (Array.isArray(date)) { - setSelectedDate(date[0]); - } else { + const handleDateChange: CalendarProps['onChange'] = (date) => { + if (date instanceof Date) { setSelectedDate(date); + if (onDateChange) onDateChange(date); } }; diff --git a/components/BookingForm.tsx b/components/BookingForm.tsx index c04b0380..20723a1a 100644 --- a/components/BookingForm.tsx +++ b/components/BookingForm.tsx @@ -1,60 +1,158 @@ -// components/BookingForm.tsx +// ./components/BookingForm.tsx -'use client'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; +import React, { useState, useEffect } from 'react'; +import { databases } from '../lib/appwrite.config'; +import { BookingFormProps } from '../types/appwrite.type'; import { useRouter } from 'next/router'; -import { z } from 'zod'; -import { getBookingSchema } from '../lib/validation'; -import CustomFormField, { FormFieldType } from './CustomFormField'; -import SubmitButton from './SubmitButton'; -import { createBooking } from '../lib/booking.actions'; -const BookingForm = () => { +const BookingForm: React.FC = ({ providerId, serviceId, selectedDate, onSubmit }) => { + const [address, setAddress] = useState(''); + const [city, setCity] = useState(''); + const [state, setState] = useState(''); + const [zipcode, setZipcode] = useState(''); + const [coupon, setCoupon] = useState(''); + const [discount, setDiscount] = useState(0); const router = useRouter(); - const form = useForm>>({ - resolver: zodResolver(getBookingSchema('create')), - defaultValues: { - service: '', - date: new Date(), - time: '', - }, - }); - - const onSubmit = async (values: z.infer>) => { + const consumerId = JSON.parse(localStorage.getItem('appwriteSession') || '{}').userId; + + useEffect(() => { + const fetchServiceDetails = async () => { + try { + const service = await databases.getDocument( + process.env.DATABASE_ID!, + process.env.SERVICE_COLLECTION_ID!, + serviceId + ); + setDiscount(service.price); + } catch (error) { + console.error('Error fetching service details:', error); + } + }; + + fetchServiceDetails(); + }, [serviceId]); + + const handleCouponApply = async () => { + if (coupon === '100OFF') { + setDiscount(100); + } else { + setDiscount(0); + } + }; + + const handleFormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!selectedDate) { + alert("Please select a date for booking."); + return; + } + + const formData = { + date: selectedDate.toISOString(), + consumerId, + providerId, + serviceId, + status: 'pending', + address, + city, + state, + zipcode, + discount + }; + try { - await createBooking(values); - router.push('/customerProfile'); // Redirect after booking + const response = await fetch('/api/bookings/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + const responseData = await response.json(); + router.push(`/payment-confirmation?bookingId=${responseData.bookingId}`); + } else { + console.error('Failed to create booking.'); + } } catch (error) { console.error('Error creating booking:', error); } }; return ( -
- - - - Create Booking + +
+ + +
+
+ + setAddress(e.target.value)} + className="mt-1 block w-1/2 sm:w-1/3 lg:w-1/4 border border-gray-300 rounded-md shadow-sm p-2" + required + /> +
+
+ + setCity(e.target.value)} + className="mt-1 block w-1/2 sm:w-1/3 lg:w-1/4 border border-gray-300 rounded-md shadow-sm p-2" + required + /> +
+
+ + setState(e.target.value)} + className="mt-1 block w-1/2 sm:w-1/3 lg:w-1/4 border border-gray-300 rounded-md shadow-sm p-2" + required + /> +
+
+ + setZipcode(e.target.value)} + className="mt-1 block w-1/2 sm:w-1/3 lg:w-1/4 border border-gray-300 rounded-md shadow-sm p-2" + required + /> +
+
+ + setCoupon(e.target.value)} + className="mt-1 block w-1/2 sm:w-1/3 lg:w-1/4 border border-gray-300 rounded-md shadow-sm p-2" + /> + +
+ ); }; diff --git a/components/CustomerAccountDetails.tsx b/components/CustomerAccountDetails.tsx index 141f851e..2ba89340 100644 --- a/components/CustomerAccountDetails.tsx +++ b/components/CustomerAccountDetails.tsx @@ -125,4 +125,4 @@ const CustomerAccountDetails: React.FC = () => { ); }; -export default CustomerAccountDetails; +export default CustomerAccountDetails; \ No newline at end of file diff --git a/components/CustomerContent.tsx b/components/CustomerContent.tsx new file mode 100644 index 00000000..f633e221 --- /dev/null +++ b/components/CustomerContent.tsx @@ -0,0 +1,19 @@ +// components/CustomerContent.tsx +import Link from 'next/link'; + +const CustomerContent = () => { + return ( +
+

Looking for inspirations?!

+

Check out our latest services and offers tailored just for you.

+ + + View Popular Services in Your Area + + + {/* Add more customer-specific content or components here */} +
+ ); +}; + +export default CustomerContent; diff --git a/components/CustomerSearchServices.tsx b/components/CustomerSearchServices.tsx index bb397501..d7c08867 100644 --- a/components/CustomerSearchServices.tsx +++ b/components/CustomerSearchServices.tsx @@ -6,7 +6,12 @@ import { Service } from '../types/appwrite.type'; import { fetchAllServices } from './DataServiceConsumer'; import { FaSearch } from 'react-icons/fa'; -const CustomerSearchServices: React.FC = () => { +// Define the prop type for CustomerSearchServices +interface CustomerSearchServicesProps { + onServiceClick: React.Dispatch>; +} + +const CustomerSearchServices: React.FC = ({ onServiceClick }) => { const [services, setServices] = useState([]); const [filteredServices, setFilteredServices] = useState([]); const [selectedService, setSelectedService] = useState(null); @@ -225,7 +230,10 @@ const CustomerSearchServices: React.FC = () => { providerIcon={'/assets/DefaultProviderProfile.jpeg'} rating={parseFloat(calculateAverageRating(service.ratings).toFixed(1))} imageUrl={service.imageUrl} - onClick={() => setSelectedService(service)} // Set the selected service on click + onClick={() => { + setSelectedService(service); // Set the selected service on click + onServiceClick(service); // Notify the parent component of the selected service + }} onProviderClick={() => setSelectedProvider(service.providerId)} // Set the selected provider on click /> ))} diff --git a/components/CustomerViewBookings.tsx b/components/CustomerViewBookings.tsx index e3fc74e6..163bb0b3 100644 --- a/components/CustomerViewBookings.tsx +++ b/components/CustomerViewBookings.tsx @@ -1,11 +1,113 @@ +// components/CustomerViewBookings.tsx +import React, { useEffect, useState } from 'react'; +import { Booking } from '../types/appwrite.type'; +import { databases } from '../lib/appwrite.config'; +import * as sdk from 'node-appwrite'; +import EditBookingForm from './EditBookingForm'; + const CustomerViewBookings = () => { - return ( -
-

View Bookings

-

This is a placeholder for the customer bookings section.

-
- ); - }; - - export default CustomerViewBookings; - \ No newline at end of file + const [bookings, setBookings] = useState([]); + const [selectedBooking, setSelectedBooking] = useState(null); + const [isEditing, setIsEditing] = useState(false); + const userId = JSON.parse(localStorage.getItem('appwriteSession') || '{}').userId; + + const fetchBookings = async () => { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.BOOKING_COLLECTION_ID!, + [sdk.Query.equal('consumerId', userId)] + ); + + const bookingsData: Booking[] = response.documents.map((doc: sdk.Models.Document) => ({ + $id: doc.$id, + $permissions: doc.$permissions, + bookingId: doc.$id, + $collectionId: doc.$collectionId, + $databaseId: doc.$databaseId, + $createdAt: doc.$createdAt, + $updatedAt: doc.$updatedAt, + consumerId: doc.consumerId, + providerId: doc.providerId, + serviceId: doc.serviceId, + date: new Date(doc.date), + status: doc.status, + address: doc.address, + city: doc.city, + state: doc.state, + zipcode: doc.zipcode, + })); + + setBookings(bookingsData); + } catch (error) { + console.error('Error fetching bookings:', error); + } + }; + + useEffect(() => { + fetchBookings(); + }, [userId]); + + const handleEditClick = (booking: Booking) => { + setSelectedBooking(booking); + setIsEditing(true); + }; + + const handleDelete = async (bookingId: string) => { + if (confirm('Are you sure you want to delete this booking?')) { + try { + await databases.deleteDocument( + process.env.DATABASE_ID!, + process.env.BOOKING_COLLECTION_ID!, + bookingId + ); + setBookings((prevBookings) => prevBookings.filter((booking) => booking.bookingId !== bookingId)); + } catch (error) { + console.error('Error deleting booking:', error); + } + } + }; + + const handleSave = async () => { + setIsEditing(false); + setSelectedBooking(null); + await fetchBookings(); // Fetch the bookings again to get the updated list + }; + + return ( +
+

View Bookings

+ {bookings.length > 0 ? ( + bookings.map((booking) => ( +
+

Service: {booking.serviceId}

+

Date: {new Date(booking.date).toLocaleDateString()}

+

Status: {booking.status}

+

Address: {booking.address}, {booking.city}, {booking.state}, {booking.zipcode}

+
+ + +
+
+ )) + ) : ( +

No bookings found.

+ )} + {isEditing && selectedBooking && ( + setIsEditing(false)} onSave={handleSave} /> + )} +
+ ); +}; + +export default CustomerViewBookings; diff --git a/components/EditBookingForm.tsx b/components/EditBookingForm.tsx new file mode 100644 index 00000000..fcc1cc97 --- /dev/null +++ b/components/EditBookingForm.tsx @@ -0,0 +1,125 @@ +// components/EditBookingForm.tsx +import React, { useState, useEffect } from 'react'; +import { Booking } from '../types/appwrite.type'; +import { databases } from '../lib/appwrite.config'; + +interface EditBookingFormProps { + booking: Booking; + onClose: () => void; + onSave: () => void; +} + +const EditBookingForm: React.FC = ({ booking, onClose, onSave }) => { + const [formData, setFormData] = useState({ + ...booking, + date: new Date(booking.date), + }); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prevData) => ({ + ...prevData, + [name]: value, + })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await databases.updateDocument( + process.env.DATABASE_ID!, + process.env.BOOKINGS_COLLECTION_ID!, + booking.$id, + { + ...formData, + date: formData.date.toISOString(), + } + ); + onSave(); + } catch (error) { + console.error('Error updating booking:', error); + } + }; + + return ( +
+
+

Edit Booking

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ ); +}; + +export default EditBookingForm; diff --git a/components/Footer.tsx b/components/Footer.tsx index 245ee79c..a689638f 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -122,4 +122,4 @@ const Footer: React.FC = () => { ); }; -export default Footer; +export default Footer; \ No newline at end of file diff --git a/components/Header.tsx b/components/Header.tsx index 1ac85231..2f4ab908 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -11,21 +11,26 @@ import { logout } from '../lib/authUtils'; const Header: React.FC = () => { const router = useRouter(); const [isLoggedIn, setIsLoggedIn] = useState(false); + const [userType, setUserType] = useState(null); const checkAuth = async () => { try { // Retrieve session info from local storage const session = JSON.parse(localStorage.getItem('appwriteSession') || '{}'); - if (session && session.userId) { + const type = localStorage.getItem('userType'); + if (session && session.userId && type) { setIsLoggedIn(true); + setUserType(type); console.log('Logged in.'); } else { setIsLoggedIn(false); + setUserType(null); console.log('Not logged in.'); } } catch (error) { console.error('Error checking login status', error); setIsLoggedIn(false); + setUserType(null); } }; @@ -43,11 +48,18 @@ const Header: React.FC = () => { }, []); const handleLogout = async () => { - await logout(); - setIsLoggedIn(false); - router.push('/'); + try { + await logout(); + setIsLoggedIn(false); + setUserType(null); + router.push('/'); + } catch (error) { + console.error('Error logging out', error); + } }; + const dashboardLink = userType === 'Customer' ? '/customerProfile' : '/serviceProfile'; + return (

ProBooker

@@ -69,6 +81,9 @@ const Header: React.FC = () => { ) : ( <> + + Dashboard + + +
+ {selectedDates.length > 0 && ( +
+

Selected Dates and Times:

+
    + {selectedDates.map((d, idx) => ( +
  • + {new Date(d.date).toDateString()}: {d.startTime} - {d.endTime} + +
  • + ))} +
+
+ )} +
+ + + ); +}; + +export default ProviderSetAvailability; \ No newline at end of file diff --git a/components/ProviderViewAvailability.tsx b/components/ProviderViewAvailability.tsx new file mode 100644 index 00000000..d4974d4d --- /dev/null +++ b/components/ProviderViewAvailability.tsx @@ -0,0 +1,72 @@ +// components/ProviderViewAvailability.tsx +// This component displays the availability slots of a provider. +// The component fetches the availability slots from the Appwrite database and displays them in a table. +// The component is used in the ProviderProfile page. +// It also uses the date-fns library for date formatting. + +'use client'; + +import React, { useEffect, useState } from 'react'; +import { databases } from '../lib/appwrite.config'; +import { Availability } from '../types/appwrite.type'; +import { format } from 'date-fns'; // Import date-fns for date formatting + +const ProviderViewAvailability: React.FC = () => { + const [availability, setAvailability] = useState([]); + const session = JSON.parse(localStorage.getItem('appwriteSession') || '{}'); + const providerId = session.userId; + + useEffect(() => { + const fetchAvailability = async () => { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.AVAILABILITY_COLLECTION_ID! + ); + const allAvailability = response.documents as Availability[]; + const filteredAvailability = allAvailability.filter(doc => doc.providerId === providerId); + setAvailability(filteredAvailability); + } catch (error) { + console.error('Error fetching availability:', error); + } + }; + + fetchAvailability(); + }, [providerId]); + + return ( +
+

View Your Availability

+
+ + + + + + + + + + + {availability.length > 0 ? ( + availability.map((slot, idx) => ( + + + + + + + )) + ) : ( + + + + )} + +
DateStart TimeEnd TimeRecurring
{format(new Date(slot.date), 'EEE MMM dd yyyy')}{format(new Date(slot.startTime), 'hh:mm a')}{format(new Date(slot.endTime), 'hh:mm a')}{slot.recurring ? 'Yes' : 'No'}
No availability set.
+
+
+ ); +}; + +export default ProviderViewAvailability; diff --git a/components/ServiceAccountDetails.tsx b/components/ServiceAccountDetails.tsx index fdd228ca..1497258a 100644 --- a/components/ServiceAccountDetails.tsx +++ b/components/ServiceAccountDetails.tsx @@ -154,4 +154,4 @@ const ServiceAccountDetails: React.FC = () => { ); }; -export default ServiceAccountDetails; +export default ServiceAccountDetails; \ No newline at end of file diff --git a/components/ServiceCard.tsx b/components/ServiceCard.tsx index e21405ab..fb62bcd8 100644 --- a/components/ServiceCard.tsx +++ b/components/ServiceCard.tsx @@ -1,3 +1,9 @@ +// inspired by AdrianHajdin's CardCard component +// https://github.com/adrianhajdin/project_next13_car_showcase/blob/main/components/CarCard.tsx +// a card component to display each service, +// which will be used in the services page to display services in a grid format + +// components/ServiceCard.tsx import React from 'react'; import { ServiceCardProps } from '../types/appwrite.type'; @@ -42,3 +48,49 @@ const ServiceCard: React.FC = ({ title, summary, description, }; export default ServiceCard; + +/* +import React from 'react'; + +interface ServiceCardProps { + title: string; + description: string; + price: number; + providerName: string; + providerId: string; + category: string; + imageUrl?: string; + onClick?: () => void; +} + +const ServiceCard: React.FC = ({ + title, + description, + price, + providerName, + providerId, + category, + imageUrl, + onClick, +}) => { + return ( +
+
+

{title}

+

Provider: {providerName}

+

${price}

+

{description}

+ +
+
+ ); +}; + +export default ServiceCard; + +*/ \ No newline at end of file diff --git a/components/ServiceDetails.tsx b/components/ServiceDetails.tsx index b628ffd7..db64f23d 100644 --- a/components/ServiceDetails.tsx +++ b/components/ServiceDetails.tsx @@ -1,10 +1,13 @@ -import React, { useState, useEffect } from 'react'; +// ./components/ServiceDetails.tsx + +import React, { useState, useEffect, useCallback } from 'react'; import BookingForm from './BookingForm'; import AvailabilityCalendar from './AvailabilityCalendar'; import { Service, ReviewCardProps } from '../types/appwrite.type'; import ReviewCard from './ReviewCard'; import ReviewForm from './ReviewForm'; import { fetchReviewsForService } from './DataReviewConsumerView'; +import { useRouter } from 'next/router'; interface ServiceDetailsProps { service: Service; @@ -38,15 +41,12 @@ const calculateAverageRating = (ratings: number[]): number => { }; const ServiceDetails: React.FC = ({ service, onBack }) => { - const [availableDates, setAvailableDates] = useState([ - // Mocked available dates - new Date('2024-07-25'), - new Date('2024-07-26'), - new Date('2024-07-27'), - ]); + const [availableDates, setAvailableDates] = useState([]); + const [selectedDate, setSelectedDate] = useState(null); const [isBookingSectionVisible, setIsBookingSectionVisible] = useState(false); const [isReviewFormVisible, setIsReviewFormVisible] = useState(false); const [reviews, setReviews] = useState([]); + const router = useRouter(); const toggleBookingSection = () => { setIsBookingSectionVisible(!isBookingSectionVisible); @@ -56,18 +56,42 @@ const ServiceDetails: React.FC = ({ service, onBack }) => { setIsReviewFormVisible(!isReviewFormVisible); }; - const fetchReviews = async () => { + const fetchReviews = useCallback(async () => { try { const fetchedReviews = await fetchReviewsForService(service.$id); setReviews(fetchedReviews); } catch (error) { console.error('Error fetching reviews:', error); } - }; + }, [service.$id]); useEffect(() => { fetchReviews(); - }, [service.$id]); + }, [service.$id, fetchReviews]); + + const handleDateChange = (date: Date) => { + setSelectedDate(date); + }; + + const handleFormSubmit = async (formData: any) => { + try { + const response = await fetch('/api/bookings/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + router.push('/customerProfile#bookings'); + } else { + console.error('Failed to create booking.'); + } + } catch (error) { + console.error('Error creating booking:', error); + } + }; return (
@@ -103,8 +127,15 @@ const ServiceDetails: React.FC = ({ service, onBack }) => { {isBookingSectionVisible && (

Booking: {service.name}

- - + + {selectedDate && ( + + )}
)} diff --git a/components/ServiceProviderDashboard.tsx b/components/ServiceProviderDashboard.tsx new file mode 100644 index 00000000..d966858a --- /dev/null +++ b/components/ServiceProviderDashboard.tsx @@ -0,0 +1,69 @@ +// components/ServiceProviderDashboard.tsx + +import React from 'react'; + +interface ServiceProviderDashboardProps { + activeTab: string; + setActiveTab: (tab: string) => void; + handleLogout: () => void; + children: React.ReactNode; // Accept children props +} + +const ServiceProviderDashboard: React.FC = ({ activeTab, setActiveTab, handleLogout, children }) => { + return ( +
+ +
+ {children} {/* Render children */} +
+
+ ); +}; + +export default ServiceProviderDashboard; diff --git a/components/ServiceServices.tsx b/components/ServiceServices.tsx index af307b65..55550cbe 100644 --- a/components/ServiceServices.tsx +++ b/components/ServiceServices.tsx @@ -1,9 +1,12 @@ +// components/ServiceServices.tsx + import React, { useEffect, useState } from 'react'; import CreateServiceForm from './ServiceCreateForm'; import ServiceDetailsProvider from './ServiceDetailsProvider'; import ServiceCard from './ServiceCard'; import { Service } from '../types/appwrite.type'; import { fetchAndFilterServices } from './DataServiceProvider'; +// import ServiceDetails from './ServiceDetails'; const ServiceServices: React.FC = () => { const [services, setServices] = useState([]); @@ -95,3 +98,4 @@ const ServiceServices: React.FC = () => { }; export default ServiceServices; + diff --git a/components/ServiceViewBookings.tsx b/components/ServiceViewBookings.tsx index 25b54780..6b1ae30e 100644 --- a/components/ServiceViewBookings.tsx +++ b/components/ServiceViewBookings.tsx @@ -1,11 +1,109 @@ +// components/ServiceViewBookings.tsx +import React, { useEffect, useState } from 'react'; +import { Booking } from '../types/appwrite.type'; +import { databases } from '../lib/appwrite.config'; +import * as sdk from 'node-appwrite'; + const ServiceViewBookings = () => { - return ( -
-

View Bookings

-

This is a placeholder for the service provider to view their bookings.

-
- ); + const [bookings, setBookings] = useState([]); + const userId = JSON.parse(localStorage.getItem('appwriteSession') || '{}').userId; + + useEffect(() => { + const fetchBookings = async () => { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.BOOKING_COLLECTION_ID!, + [sdk.Query.equal('providerId', userId)] + ); + + const bookingsData: Booking[] = response.documents.map((doc: sdk.Models.Document) => ({ + $id: doc.$id, + $permissions: doc.$permissions, + bookingId: doc.$id, + $collectionId: doc.$collectionId, + $databaseId: doc.$databaseId, + $createdAt: doc.$createdAt, + $updatedAt: doc.$updatedAt, + consumerId: doc.consumerId, + providerId: doc.providerId, + serviceId: doc.serviceId, + date: new Date(doc.date), + status: doc.status, + address: doc.address, + city: doc.city, + state: doc.state, + zipcode: doc.zipcode, + })); + + setBookings(bookingsData); + } catch (error) { + console.error('Error fetching bookings:', error); + } + }; + + fetchBookings(); + }, [userId]); + + const handleApproval = async (bookingId: string, status: string) => { + const reason = prompt('Please provide a reason for this decision:'); + if (!reason) { + alert('A reason is required.'); + return; + } + try { + const response = await fetch(`/api/bookings/update`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ bookingId, status, reason }), + }); + + if (response.ok) { + alert('Booking status updated.'); + setBookings(bookings.map(b => b.bookingId === bookingId ? { ...b, status } : b)); + } else { + console.error('Failed to update booking.'); + } + } catch (error) { + console.error('Error updating booking:', error); + } }; - - export default ServiceViewBookings; - \ No newline at end of file + + return ( +
+

View Bookings

+ {bookings.length > 0 ? ( + bookings.map((booking) => ( +
+

Service: {booking.serviceId}

+

Date: {new Date(booking.date).toLocaleDateString()}

+

Status: {booking.status}

+

Address: {booking.address}, {booking.city}, {booking.state}, {booking.zipcode}

+ {booking.status === 'pending' && ( +
+ + +
+ )} +
+ )) + ) : ( +

No bookings found.

+ )} +
+ ); +}; + +export default ServiceViewBookings; diff --git a/lib/DataBookings.ts b/lib/DataBookings.ts new file mode 100644 index 00000000..db97e035 --- /dev/null +++ b/lib/DataBookings.ts @@ -0,0 +1,32 @@ +// lib/DataBookings.ts +import { databases } from '../lib/appwrite.config'; +import { Booking } from '../types/appwrite.type'; +import * as sdk from 'node-appwrite'; + +export const fetchCustomerBookings = async (consumerId: string): Promise => { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.BOOKINGS_COLLECTION_ID!, + [sdk.Query.equal('consumerId', consumerId)] + ); + return response.documents as Booking[]; + } catch (error) { + console.error('Error fetching customer bookings:', error); + return []; + } +}; + +export const fetchProviderBookings = async (providerId: string): Promise => { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.BOOKINGS_COLLECTION_ID!, + [sdk.Query.equal('providerId', providerId)] + ); + return response.documents as Booking[]; + } catch (error) { + console.error('Error fetching provider bookings:', error); + return []; + } +}; diff --git a/lib/appwrite.config.ts b/lib/appwrite.config.ts index 794d4e02..a44de325 100644 --- a/lib/appwrite.config.ts +++ b/lib/appwrite.config.ts @@ -10,8 +10,9 @@ export const { SERVICE_COLLECTION_ID, BOOKING_COLLECTION_ID, REVIEW_COLLECTION_ID, - NEXT_PUBLIC_BUCKET_ID:BUCKET_ID, - NEXT_PUBLIC_ENDPOINT:ENDPOINT + AVAILABILITY_COLLECTION_ID, + BUCKET_ID:BUCKET_ID, + ENDPOINT:ENDPOINT } = process.env; const client = new sdk.Client(); diff --git a/lib/authContext.tsx b/lib/authContext.tsx deleted file mode 100644 index eef145e6..00000000 --- a/lib/authContext.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// lib/authContext.tsx -'use client' - -import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; -import { account } from './appwrite.config'; -import { useRouter } from 'next/router'; -import { users, databases } from '../lib/appwrite.config'; - -interface AuthContextType { - isLoggedIn: boolean; - setIsLoggedIn: (value: boolean) => void; -} - -const AuthContext = createContext({ isLoggedIn: false, setIsLoggedIn: () => {} }); - -interface AuthProviderProps { - children: ReactNode; -} - -export const AuthProvider = ({ children }: AuthProviderProps) => { - const [isLoggedIn, setIsLoggedIn] = useState(false); - - useEffect(() => { - const checkLoginStatus = async () => { - try { - await account.get(); - setIsLoggedIn(true); - } catch (error) { - setIsLoggedIn(false); - } - }; - checkLoginStatus(); - }, []); - - return ( - - {children} - - ); -}; - -export const useAuth = () => useContext(AuthContext); diff --git a/lib/availability.actions.ts b/lib/availability.actions.ts new file mode 100644 index 00000000..e905ce76 --- /dev/null +++ b/lib/availability.actions.ts @@ -0,0 +1,40 @@ +// ./lib/availability.actions.ts +// This file contains functions to fetch and update provider availability in the database. + +import { databases } from './appwrite.config'; +import { Query } from 'node-appwrite'; + +export const fetchProviderAvailability = async (providerId: string) => { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.AVAILABILITY_COLLECTION_ID!, + [Query.equal('providerId', providerId)] + ); + if (response.documents.length > 0) { + return response.documents[0].availableDates; + } + return []; +}; + +export const updateProviderAvailability = async (providerId: string, availableDates: string[]) => { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.AVAILABILITY_COLLECTION_ID!, + [Query.equal('providerId', providerId)] + ); + if (response.documents.length > 0) { + await databases.updateDocument( + process.env.DATABASE_ID!, + process.env.AVAILABILITY_COLLECTION_ID!, + response.documents[0].$id, + { availableDates } + ); + } else { + await databases.createDocument( + process.env.DATABASE_ID!, + process.env.AVAILABILITY_COLLECTION_ID!, + 'unique()', + { providerId, availableDates } + ); + } +}; diff --git a/next.config.mjs b/next.config.mjs index 2bab2a2f..191abb86 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -25,6 +25,7 @@ const nextConfig = { SERVICE_COLLECTION_ID: process.env.SERVICE_COLLECTION_ID, BOOKING_COLLECTION_ID: process.env.BOOKING_COLLECTION_ID, REVIEW_COLLECTION_ID: process.env.REVIEW_COLLECTION_ID, + AVAILABILITY_COLLECTION_ID: process.env.AVAILABILITY_COLLECTION_ID, NEXT_PUBLIC_BUCKET_ID: process.env.BUCKET_ID, }, }; diff --git a/package-lock.json b/package-lock.json index 3af8034d..fe018f73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@hookform/resolvers": "^3.9.0", "appwrite": "^15.0.0", "clsx": "^2.1.1", + "date-fns": "^3.6.0", "dotenv": "^16.4.5", "module-alias": "^2.2.3", "next": "^14.2.4", @@ -12454,7 +12455,8 @@ "node_modules/papaparse": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", - "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==", + "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", diff --git a/package.json b/package.json index 59dd10fd..f28ee468 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@hookform/resolvers": "^3.9.0", "appwrite": "^15.0.0", "clsx": "^2.1.1", + "date-fns": "^3.6.0", "dotenv": "^16.4.5", "module-alias": "^2.2.3", "next": "^14.2.4", diff --git a/pages/_app.tsx b/pages/_app.tsx index 6d00642f..8a2b0c70 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,5 +1,4 @@ // moving from old repo - // The pages/_app.tsx file in a Next.js project is used to customize the default App component, // which is the top-level component that wraps all the pages in the application. // This file is useful for persisting layout between page changes, @@ -7,7 +6,6 @@ // pages/_app.tsx import '../styles/globals.css'; -import { AuthProvider } from '../lib/authContext'; import { ReactNode, ComponentType } from 'react'; import Layout from '../components/Layout' @@ -18,11 +16,9 @@ interface MyAppProps { function MyApp({ Component, pageProps }: MyAppProps) { return ( - - ); } diff --git a/pages/api/availability/[userId].ts b/pages/api/availability/[userId].ts new file mode 100644 index 00000000..ca706a43 --- /dev/null +++ b/pages/api/availability/[userId].ts @@ -0,0 +1,27 @@ +// pages/api/availability/[userId].ts +// This API endpoint is used to fetch availability of a provider by userId +// It uses the appwrite sdk to fetch availability from the database +// It returns the availability of the provider as a response +// It returns an error if the availability is not found + +import { NextApiRequest, NextApiResponse } from 'next'; +import { databases } from '../../../lib/appwrite.config'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { userId } = req.query; + if (req.method === 'GET') { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.AVAILABILITY_COLLECTION_ID!, + [`userId=${userId}`] + ); + res.status(200).json(response.documents); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch availability' }); + } + } else { + res.setHeader('Allow', ['GET']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} \ No newline at end of file diff --git a/pages/api/availability/update.ts b/pages/api/availability/update.ts new file mode 100644 index 00000000..39969f35 --- /dev/null +++ b/pages/api/availability/update.ts @@ -0,0 +1,30 @@ +// pages/api/availability/update.ts +// This API endpoint is used to update the availability of a provider +// It receives the userId and availability as a request body +// It uses the appwrite sdk to update the availability in the database + +import { NextApiRequest, NextApiResponse } from 'next'; +import { databases } from '../../../lib/appwrite.config'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === 'POST') { + const { userId, availability } = req.body; // Availability is an array of objects with date, startTime, and endTime + try { + const promises = availability.map(async (slot: any) => { + await databases.createDocument( + process.env.DATABASE_ID!, + process.env.AVAILABILITY_COLLECTION_ID!, + 'unique()', + { userId, ...slot } + ); + }); + await Promise.all(promises); + res.status(200).json({ message: 'Availability updated successfully' }); + } catch (error) { + res.status(500).json({ error: 'Failed to update availability' }); + } + } else { + res.setHeader('Allow', ['POST']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/bookings/create.ts b/pages/api/bookings/create.ts new file mode 100644 index 00000000..5fca595e --- /dev/null +++ b/pages/api/bookings/create.ts @@ -0,0 +1,49 @@ +// ./pages/api/bookings/create.ts +// This API endpoint is used to create a new booking in the database +// It receives the booking details as a request body +// It uses the appwrite sdk to create a new booking document in the database +// It returns the new booking id as a response +// It returns an error if the booking creation fails +// Availability is an array of objects with date, startTime, and endTime +// It uses the appwrite sdk to update the availability in the database + +// ./pages/api/bookings/create.ts +// ./pages/api/bookings/create.ts + +import { NextApiRequest, NextApiResponse } from 'next'; +import { databases } from '../../../lib/appwrite.config'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === 'POST') { + try { + const { date, consumerId, providerId, serviceId, address, city, state, zipcode } = req.body; + + // Create a new booking document in Appwrite + const newBooking = await databases.createDocument( + process.env.DATABASE_ID!, + process.env.BOOKING_COLLECTION_ID!, + 'unique()', + { + consumerId, + providerId, + serviceId, + date, + status: 'confirmed', // Set status to 'confirmed' + address, + city, + state, + zipcode, + } + ); + + // Return the new booking ID + res.status(200).json({ bookingId: newBooking.$id }); + } catch (error) { + console.error('Error creating booking:', error); + res.status(500).json({ message: 'Error creating booking', error: (error as Error).message }); + } + } else { + res.setHeader('Allow', ['POST']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/bookings/customer/[id].ts b/pages/api/bookings/customer/[id].ts new file mode 100644 index 00000000..44213444 --- /dev/null +++ b/pages/api/bookings/customer/[id].ts @@ -0,0 +1,24 @@ +// This API endpoint is used to fetch all bookings of a customer by customer id +// pages/api/bookings/customer/[id].ts +import { NextApiRequest, NextApiResponse } from 'next'; +import { databases } from '../../../../lib/appwrite.config'; +import * as sdk from 'node-appwrite'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { id } = req.query; + if (req.method === 'GET') { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.BOOKINGS_COLLECTION_ID!, + [sdk.Query.equal('consumerId', id as string)] + ); + res.status(200).json(response); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch bookings' }); + } + } else { + res.setHeader('Allow', ['GET']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/api/bookings/provider/[id].ts b/pages/api/bookings/provider/[id].ts new file mode 100644 index 00000000..198efa81 --- /dev/null +++ b/pages/api/bookings/provider/[id].ts @@ -0,0 +1,25 @@ +// This API endpoint is used to fetch all bookings of a provider by providerId +// pages/api/bookings/provider/[id].ts +import { NextApiRequest, NextApiResponse } from 'next'; +import { databases } from '../../../../lib/appwrite.config'; +import * as sdk from 'node-appwrite'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { id } = req.query; + if (req.method === 'GET') { + try { + const response = await databases.listDocuments( + process.env.DATABASE_ID!, + process.env.BOOKINGS_COLLECTION_ID!, + [sdk.Query.equal('providerId', id as string)] + ); + res.status(200).json(response); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch bookings' }); + } + } else { + res.setHeader('Allow', ['GET']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} + diff --git a/pages/api/bookings/update.ts b/pages/api/bookings/update.ts new file mode 100644 index 00000000..4d2816b2 --- /dev/null +++ b/pages/api/bookings/update.ts @@ -0,0 +1,24 @@ +// pages/api/bookings/update.ts +import { NextApiRequest, NextApiResponse } from 'next'; +import { databases } from '../../../lib/appwrite.config'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === 'POST') { + const { bookingId, ...updatedBooking } = req.body; + try { + await databases.updateDocument( + process.env.DATABASE_ID!, + process.env.BOOKING_COLLECTION_ID!, + bookingId, + updatedBooking + ); + res.status(200).json({ message: 'Booking updated successfully' }); + } catch (error) { + console.error('Error updating booking:', error); + res.status(500).json({ message: 'Error updating booking', error: (error as Error).message }); + } + } else { + res.setHeader('Allow', ['POST']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } +} diff --git a/pages/customerBooking.tsx b/pages/customerBooking.tsx index b91e3fd2..c49f0f8f 100644 --- a/pages/customerBooking.tsx +++ b/pages/customerBooking.tsx @@ -1,26 +1,78 @@ -// pages/customerBooking.tsx +// ./pages/customerBooking.tsx + +// Purpose: Provide the user with a page to book a service with a provider. +// The page will display the availability of the provider and allow the user to select a date to book a service. +// The page will also display a form for the user to input their information and book the service. +// The page will also have a button to return to the customer profile page. +// ./pages/customerBooking.tsx 'use client' -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import AvailabilityCalendar from '../components/AvailabilityCalendar'; import BookingForm from '../components/BookingForm'; +import { fetchProviderAvailability } from '../lib/availability.actions'; const CustomerBooking = () => { const router = useRouter(); - - const availableDates = [ - new Date(), // Add available dates here - // new Date('2024-08-01'), - ]; + const { providerId, serviceId } = router.query; + const [availableDates, setAvailableDates] = useState([]); + const [selectedDate, setSelectedDate] = useState(null); + + useEffect(() => { + const fetchAvailability = async () => { + if (providerId) { + const dates = await fetchProviderAvailability(providerId as string); + setAvailableDates(dates.map((dateStr: string) => new Date(dateStr))); + } + }; + + fetchAvailability(); + }, [providerId]); + + const handleDateChange = (date: Date) => { + setSelectedDate(date); + }; + + const handleBookingSubmit = async (formData: any) => { + try { + const response = await fetch('/api/bookings/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + const responseData = await response.json(); + router.push('/customerProfile?view=bookings'); + } else { + console.error('Failed to create booking.'); + } + } catch (error) { + console.error('Error creating booking:', error); + } + }; + + if (!providerId || !serviceId) { + return
Loading...
; // Or handle the missing IDs appropriately + } return (

Book a Service

- - + + {selectedDate && ( + + )} - -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • - - - -
    - {renderContent()} -
    -
    - ); -}; - -export default CustomerProfile; - -/* -'use client' - -import React, { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; -import CustomerProfileOverview from '../components/CustomerProfileOverview'; -import CustomerViewBookings from '../components/CustomerViewBookings'; -import CustomerAccountDetails from '../components/CustomerAccountDetails'; -import CustomerSearchServices from '../components/CustomerSearchServices'; -import { logout } from '../lib/authUtils'; - -const CustomerProfile = () => { - const router = useRouter(); - const [activeTab, setActiveTab] = useState('overview'); - const [isAuthenticated, setIsAuthenticated] = useState(false); - - useEffect(() => { - const session = localStorage.getItem('appwriteSession'); - const userType = localStorage.getItem('userType'); - if (session && userType === 'Customer') { - setIsAuthenticated(true); - } else { - router.push('/customer-login'); - } - }, [router]); - - const handleLogout = async () => { - await logout(); - router.push('/customer-login'); - }; - - if (!isAuthenticated) { - return null; // Render nothing until authentication status is determined - } - - const renderContent = () => { - switch (activeTab) { - case 'overview': - return ; - case 'bookings': - return ; - case 'account': - return ; - case 'search': - return ; - default: - return ; - } + const navigateToTab = (tab: string) => { + setActiveTab(tab); + router.push(`/customerProfile#${tab}`); }; return ( -
    +
    @@ -231,6 +132,4 @@ const CustomerProfile = () => { ); }; -export default CustomerProfile; - -*/ \ No newline at end of file +export default CustomerProfile; \ No newline at end of file diff --git a/pages/index.tsx b/pages/index.tsx index 277a6429..5927044e 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,9 +1,46 @@ // pages/index.tsx import Head from 'next/head'; import Link from 'next/link'; -import Header from '../components/Header'; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/router'; +import { account } from '../lib/appwrite.config'; +import CustomerContent from '../components/CustomerContent'; +import ProviderContent from '../components/ProviderContent'; export default function Home() { + const router = useRouter(); + const [userType, setUserType] = useState(null); + + useEffect(() => { + const fetchSession = async () => { + try { + const session = JSON.parse(localStorage.getItem('appwriteSession') || '{}'); + const type = localStorage.getItem('userType'); + + if (session && type) { + setUserType(type); + } else { + setUserType(null); + } + } catch (error) { + console.error('Error fetching session:', error); + } + }; + + fetchSession(); + }, []); + + const handleLogout = async () => { + try { + await account.deleteSession('current'); + localStorage.removeItem('appwriteSession'); + localStorage.removeItem('userType'); + router.push('/customer-login'); + } catch (error) { + console.error('Error logging out:', error); + } + }; + return (
    @@ -13,20 +50,24 @@ export default function Home() {
    -
    -

    ProBooker

    -

    Connect with the pros, book with confidence

    - - - Get Started as a Customer - - - - - Join as a Pro - - -
    + {userType ? ( + userType === 'Customer' ? : + ) : ( +
    +

    ProBooker

    +

    Connect with the pros, book with confidence

    + + + Get Started as a Customer + + + + + Join as a Pro + + +
    + )}
    ); diff --git a/pages/payment-confirmation.tsx b/pages/payment-confirmation.tsx new file mode 100644 index 00000000..6046d5cf --- /dev/null +++ b/pages/payment-confirmation.tsx @@ -0,0 +1,54 @@ +// pages/payment-confirmation.tsx + +import { useRouter } from 'next/router'; +import React, { useEffect, useState } from 'react'; +import { databases } from '../lib/appwrite.config'; +import { Models } from 'node-appwrite'; + +const PaymentConfirmation = () => { + const router = useRouter(); + const { bookingId } = router.query; + const [bookingDetails, setBookingDetails] = useState(null); + + useEffect(() => { + const fetchBookingDetails = async () => { + if (bookingId) { + try { + const booking = await databases.getDocument( + process.env.DATABASE_ID!, + process.env.BOOKING_COLLECTION_ID!, + bookingId as string + ); + setBookingDetails(booking); + } catch (error) { + console.error('Error fetching booking details:', error); + } + } + }; + + fetchBookingDetails(); + }, [bookingId]); + + if (!bookingDetails) { + return
    Loading...
    ; + } + + return ( +
    +
    +

    Payment Confirmation

    +

    Booking ID: {bookingDetails.$id}

    + {/* Remove the amount display */} + {/*

    Amount: ${bookingDetails.servicePrice - bookingDetails.discount}

    */} + +
    +
    + ); +}; + +export default PaymentConfirmation; diff --git a/pages/provider-login.tsx b/pages/provider-login.tsx index 1aaff391..a4d653f4 100644 --- a/pages/provider-login.tsx +++ b/pages/provider-login.tsx @@ -1,5 +1,5 @@ // pages/provider-login.tsx - +// Uses the LoginFormProvider component to render the login form for Providers import LoginFormProvider from '../components/LoginFormProvider'; export default function ProviderLogin() { diff --git a/pages/provider-register.tsx b/pages/provider-register.tsx index ce5b5063..888c9382 100644 --- a/pages/provider-register.tsx +++ b/pages/provider-register.tsx @@ -1,4 +1,6 @@ // ../pages/provider-register.tsx +// Uses the ProviderRegisterForm component to render the registration form for Providers + import ProviderRegisterForm from '../components/ProviderRegisterForm'; export default function ProviderRegisterPage() { diff --git a/pages/provider-start.tsx b/pages/provider-start.tsx index 37a10954..9a28fc74 100644 --- a/pages/provider-start.tsx +++ b/pages/provider-start.tsx @@ -1,6 +1,5 @@ // provider-start.tsx pages to guide users to either register or log in. - import Head from 'next/head'; import Link from 'next/link'; diff --git a/pages/providerBooking.tsx b/pages/providerBooking.tsx index 6a1fce56..413eda9e 100644 --- a/pages/providerBooking.tsx +++ b/pages/providerBooking.tsx @@ -1,28 +1,71 @@ -// pages/providerBooking.tsx +// ./pages/providerBooking.tsx -'use client' - -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; import AvailabilityCalendar from '../components/AvailabilityCalendar'; import BookingForm from '../components/BookingForm'; +import { fetchProviderAvailability } from '../lib/availability.actions'; const ProviderBooking = () => { const router = useRouter(); - - const availableDates = [ - new Date(), // need to add available dates here somehow, from DB ? - // new Date('2024-08-01'), - ]; + const { providerId, serviceId } = router.query; + const [availableDates, setAvailableDates] = useState([]); + const [selectedDate, setSelectedDate] = useState(null); + + useEffect(() => { + const fetchAvailability = async () => { + if (providerId) { + const dates = await fetchProviderAvailability(providerId as string); + setAvailableDates(dates.map((dateStr: string) => new Date(dateStr))); + } + }; + + fetchAvailability(); + }, [providerId]); + + const handleDateChange = (date: Date) => { + setSelectedDate(date); + }; + + const handleFormSubmit = async (formData: any) => { + try { + const response = await fetch('/api/bookings/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + if (response.ok) { + router.push('/providerProfile/viewBookings'); + } else { + console.error('Failed to create booking.'); + } + } catch (error) { + console.error('Error creating booking:', error); + } + }; + + if (!providerId || !serviceId) { + return
    Loading...
    ; + } return (

    Manage Bookings

    - - + + {selectedDate && ( + + )} - - - - -
    - {renderContent()} -
    -
    - ); -}; - -export default ProviderProfile; - - -/* -'use client' - -import React, { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; -import ServiceProfileOverview from '../components/ServiceProfileOverview'; -import ServiceViewBookings from '../components/ServiceViewBookings'; -import ServiceAccountDetails from '../components/ServiceAccountDetails'; -import ServiceServices from '../components/ServiceServices'; -import { logout } from '../lib/authUtils'; - -const ProviderProfile = () => { - const router = useRouter(); - const [activeTab, setActiveTab] = useState('overview'); - const [isAuthenticated, setIsAuthenticated] = useState(false); - - useEffect(() => { - const session = localStorage.getItem('appwriteSession'); - const userType = localStorage.getItem('userType'); - if (session && userType === 'Provider') { - setIsAuthenticated(true); - } else { - router.push('/provider-login'); - } - }, [router]); - - const handleLogout = async () => { - await logout(); - router.push('/provider-login'); - }; - - if (!isAuthenticated) { - return null; // Render nothing until authentication status is determined - } - - const renderContent = () => { - switch (activeTab) { - case 'overview': - return ; - case 'bookings': - return ; - case 'account': - return ; - case 'service': - return ; - default: - return ; - } - }; - - return ( -
    -