- )
+ );
}
-export default Signup
+export default Signup;
diff --git a/src/components/landing/hero.tsx b/src/components/landing/hero.tsx
index 316ce71..69dc8ee 100644
--- a/src/components/landing/hero.tsx
+++ b/src/components/landing/hero.tsx
@@ -1,33 +1,25 @@
-import localFont from "next/font/local";
+'use client';
import Image from "next/image";
import badge from '../../../public/badge-ticket.png'
import arrow from '../../../public/arrow.png'
-const figtree = localFont({
- src: [
- {
- path: '../../../public/Fonts/figtree/figtree.ttf',
- },
- ],
-})
-
function Hero() {
return (
-
-
Find Your PakStay Home in
-
-
-
-
Travel Karo, Tension Free!
+
+
Find Your PakStay Home in
+
+
+
+
Travel Karo, Tension Free!
+
)
diff --git a/src/components/list-property/LivePreview.tsx b/src/components/list-property/LivePreview.tsx
new file mode 100644
index 0000000..b41ff6e
--- /dev/null
+++ b/src/components/list-property/LivePreview.tsx
@@ -0,0 +1,86 @@
+import Image from "next/image";
+import { Camera, Star } from "@phosphor-icons/react/dist/ssr";
+import { FormData } from "./types";
+import { figtree } from "./styles";
+
+export const LivePreview = ({ data }: { data: FormData }) => {
+ // Helpers to derive display values
+ const getCity = () => {
+ if (!data.location) return "YOUR CITY";
+ const parts = data.location.split(',');
+ return parts[0].trim().toUpperCase();
+ };
+
+ const getTitle = () => {
+ if (data.title) return data.title;
+ const type = data.propertyType ? data.propertyType.charAt(0).toUpperCase() + data.propertyType.slice(1) : "Property";
+ const place = data.placeType === 'entire' ? 'Entire place' : data.placeType === 'room' ? 'Private room' : 'Shared room';
+ return `${type} · ${place}`;
+ };
+
+ const getPrice = () => {
+ return data.price ? `₨${parseInt(data.price).toLocaleString()}` : "₨0";
+ };
+
+ return (
+
+
+
+
+ {/* Card Replica */}
+
+ {/* Image Placeholder */}
+
+ {data.photos && data.photos.length > 0 ? (
+
+ ) : (
+
+
+ Cover Photo
+
+ )}
+
+
+ {/* Details */}
+
+
+
+ {getCity()}
+
+
+
+ New
+
+
+
+
+ {getTitle()}
+
+
+
+ {getPrice()}
+ night
+
+
+
+
+ {/* Live Updates Summary */}
+
+
+ Guests
+ {data.guests || 0}
+
+
+ Amenities
+ {data.amenities?.length || 0} selected
+
+
+
+
+ );
+};
diff --git a/src/components/list-property/PropertyAmenities.tsx b/src/components/list-property/PropertyAmenities.tsx
new file mode 100644
index 0000000..59f0949
--- /dev/null
+++ b/src/components/list-property/PropertyAmenities.tsx
@@ -0,0 +1,42 @@
+import { WifiHigh, CookingPot, Car, SwimmingPool, Wind, TelevisionSimple } from "@phosphor-icons/react/dist/ssr";
+import { StepProps } from "./types";
+import { titleStyle, subtitleStyle, optionCardStyle, figtree } from "./styles";
+
+export const PropertyAmenities = ({ data, updateData }: StepProps) => {
+ const amenities = [
+ { id: 'wifi', label: 'Wi-Fi', icon: WifiHigh },
+ { id: 'kitchen', label: 'Kitchen', icon: CookingPot },
+ { id: 'parking', label: 'Free parking', icon: Car },
+ { id: 'pool', label: 'Pool', icon: SwimmingPool },
+ { id: 'ac', label: 'Air conditioning', icon: Wind }, // Using House as placeholder for AC if needed
+ { id: 'tv', label: 'TV', icon: TelevisionSimple },
+ ];
+
+ const toggleAmenity = (id: string) => {
+ const current = data.amenities || [];
+ const updated = current.includes(id)
+ ? current.filter((item: string) => item !== id)
+ : [...current, id];
+ updateData('amenities', updated);
+ };
+
+ return (
+
+
What does your place offer?
+
You can add more amenities after you publish.
+
+ {amenities.map((amenity) => (
+
toggleAmenity(amenity.id)}
+ className={`${optionCardStyle((data.amenities || []).includes(amenity.id))} items-start w-full text-left`}
+ >
+
+ {amenity.label}
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyCapacity.tsx b/src/components/list-property/PropertyCapacity.tsx
new file mode 100644
index 0000000..510a841
--- /dev/null
+++ b/src/components/list-property/PropertyCapacity.tsx
@@ -0,0 +1,48 @@
+import { StepProps, FormData } from "./types";
+import { titleStyle, subtitleStyle, figtree } from "./styles";
+
+export const PropertyCapacity = ({ data, updateData }: StepProps) => {
+ const counters: { key: keyof FormData; label: string }[] = [
+ { key: 'guests', label: 'Guests' },
+ { key: 'bedrooms', label: 'Bedrooms' },
+ { key: 'beds', label: 'Beds' },
+ { key: 'bathrooms', label: 'Bathrooms' },
+ ];
+
+ const updateCount = (key: keyof FormData, delta: number) => {
+ const current = (data[key] as number) || 0;
+ updateData(key, Math.max(0, current + delta));
+ };
+
+ return (
+
+
Share some basics about your place
+
You'll add more details later, like bed types.
+
+ {counters.map((item) => (
+
+
{item.label}
+
+ updateCount(item.key, -1)}
+ disabled={!data[item.key]}
+ className="w-12 h-12 rounded-full border-2 border-black flex items-center justify-center hover:bg-gray-100 disabled:opacity-30 disabled:hover:bg-transparent transition-colors"
+ >
+ -
+
+ {data[item.key] || 0}
+ updateCount(item.key, 1)}
+ className="w-12 h-12 rounded-full border-2 border-black flex items-center justify-center hover:bg-gray-100 transition-colors"
+ >
+ +
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyDescription.tsx b/src/components/list-property/PropertyDescription.tsx
new file mode 100644
index 0000000..9344ce5
--- /dev/null
+++ b/src/components/list-property/PropertyDescription.tsx
@@ -0,0 +1,32 @@
+import { StepProps } from "./types";
+import { titleStyle, subtitleStyle, inputStyle } from "./styles";
+
+export const PropertyDescription = ({ data, updateData }: StepProps) => {
+ return (
+
+
How would you describe your place?
+
Short and sweet works best.
+
+
+ Title
+ updateData('title', e.target.value)}
+ />
+
+
+ Description
+
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyInstantApproval.tsx b/src/components/list-property/PropertyInstantApproval.tsx
new file mode 100644
index 0000000..67aa4c6
--- /dev/null
+++ b/src/components/list-property/PropertyInstantApproval.tsx
@@ -0,0 +1,35 @@
+import { CheckCircle, Article } from "@phosphor-icons/react/dist/ssr";
+import { StepProps } from "./types";
+import { titleStyle, optionCardStyle } from "./styles";
+
+export const PropertyInstantApproval = ({ data, updateData }: StepProps) => {
+ return (
+
+
Decide how you'll confirm reservations
+
+
updateData('instantBook', true)}
+ className={`${optionCardStyle(data.instantBook === true)} w-full flex-row items-start text-left`}
+ >
+
+
+ Use Instant Book
+ Guests can book automatically. No need to approve reservations. You will be notified of each booking request.
+
+
+
updateData('instantBook', false)}
+ className={`${optionCardStyle(data.instantBook === false)} w-full flex-row items-start text-left`}
+ >
+
+
+ Approve manually
+ You approve or decline booking requests for each reservation. You will be notified of each booking request.
+
+
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyLocation.tsx b/src/components/list-property/PropertyLocation.tsx
new file mode 100644
index 0000000..a2c3759
--- /dev/null
+++ b/src/components/list-property/PropertyLocation.tsx
@@ -0,0 +1,29 @@
+import { MapPin } from "@phosphor-icons/react/dist/ssr";
+import { StepProps } from "./types";
+import { titleStyle, subtitleStyle, inputStyle, figtree } from "./styles";
+
+export const PropertyLocation = ({ data, updateData }: StepProps) => {
+ return (
+
+
Where's your place located?
+
Your address is only shared with guests after they've made a reservation.
+
+
+
+ updateData('location', e.target.value)}
+ />
+
+
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyPhotos.tsx b/src/components/list-property/PropertyPhotos.tsx
new file mode 100644
index 0000000..613a620
--- /dev/null
+++ b/src/components/list-property/PropertyPhotos.tsx
@@ -0,0 +1,94 @@
+import { useRef, ChangeEvent } from "react";
+import Image from "next/image";
+import { Camera, X, WarningCircle } from "@phosphor-icons/react/dist/ssr";
+import { StepProps } from "./types";
+import { titleStyle, subtitleStyle, figtree } from "./styles";
+
+export const PropertyPhotos = ({ data, updateData }: StepProps) => {
+ const fileInputRef = useRef
(null);
+
+ const handleFileChange = (e: ChangeEvent) => {
+ if (e.target.files) {
+ const newPhotos = Array.from(e.target.files).map(file => URL.createObjectURL(file));
+ const currentPhotos = data.photos || [];
+ updateData('photos', [...currentPhotos, ...newPhotos]);
+ }
+ };
+
+ const removePhoto = (index: number) => {
+ const currentPhotos = data.photos || [];
+ const updated = currentPhotos.filter((_, i) => i !== index);
+ updateData('photos', updated);
+ };
+
+ const makeCover = (index: number) => {
+ const currentPhotos = data.photos || [];
+ if (index === 0 || index >= currentPhotos.length) return;
+
+ const newPhotos = [...currentPhotos];
+ const [selectedPhoto] = newPhotos.splice(index, 1);
+ newPhotos.unshift(selectedPhoto);
+
+ updateData('photos', newPhotos);
+ };
+
+ return (
+
+
Add some photos of your house
+
You'll need 5 photos to get started. You can add more or make changes later.
+
+
+
fileInputRef.current?.click()}
+ className="border-2 border-dashed border-gray-300 rounded-lg p-12 flex flex-col items-center justify-center gap-4 cursor-pointer hover:bg-gray-50 transition-colors"
+ >
+
+ Upload photos
+ JPG, PNG up to 10MB
+
+
+
+ {data.photos && data.photos.length > 0 && (
+
+ {data.photos.map((photo, index) => (
+
+
+
removePhoto(index)}
+ className="absolute top-2 right-2 bg-white rounded-full p-1 border-2 border-black hover:bg-red-50 transition-colors z-10"
+ >
+
+
+ {index === 0 && (
+
Cover Photo
+ )}
+ {index !== 0 && (
+
makeCover(index)}
+ className="absolute bottom-2 left-2 bg-white/90 text-black text-xs font-bold px-2 py-1 rounded border-2 border-black opacity-0 group-hover:opacity-100 transition-opacity z-10 hover:bg-[#E7FE78]"
+ >
+ Make Cover
+
+ )}
+
+ ))}
+
+ )}
+
+ {(!data.photos || data.photos.length < 5) && (
+
+
+ Please upload at least {5 - (data.photos?.length || 0)} more photos
+
+ )}
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyPlaceType.tsx b/src/components/list-property/PropertyPlaceType.tsx
new file mode 100644
index 0000000..e178670
--- /dev/null
+++ b/src/components/list-property/PropertyPlaceType.tsx
@@ -0,0 +1,34 @@
+import { CheckCircle } from "@phosphor-icons/react/dist/ssr";
+import { StepProps } from "./types";
+import { titleStyle, subtitleStyle, optionCardStyle, figtree } from "./styles";
+
+export const PropertyPlaceType = ({ data, updateData }: StepProps) => {
+ const places = [
+ { id: 'entire', label: 'An entire place', description: 'Guests have the entire property to themselves, with full privacy and unrestricted access.' },
+ { id: 'room', label: 'A room', description: 'Guests have their own private room within the home, along with comfortable access to shared spaces.' },
+ { id: 'shared', label: 'A shared room', description: 'Guests sleep in a room or common area that may be shared with others.' },
+ ];
+
+ return (
+
+
What type of place will guests have?
+
Choose the level of privacy your guests will enjoy.
+
+ {places.map((place) => (
+
updateData('placeType', place.id)}
+ className={`${optionCardStyle(data.placeType === place.id)} flex-row justify-between text-left items-center w-full p-6`}
+ >
+
+ {place.label}
+ {place.description}
+
+ {data.placeType === place.id && }
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyPrice.tsx b/src/components/list-property/PropertyPrice.tsx
new file mode 100644
index 0000000..6482b76
--- /dev/null
+++ b/src/components/list-property/PropertyPrice.tsx
@@ -0,0 +1,24 @@
+import { StepProps } from "./types";
+import { titleStyle, subtitleStyle, figtree } from "./styles";
+
+export const PropertyPrice = ({ data, updateData }: StepProps) => {
+ return (
+
+
Now, set your price
+
You can change it anytime.
+
+
+ ₨
+ updateData('price', e.target.value)}
+ />
+
+
per night
+
+
+ )
+}
diff --git a/src/components/list-property/PropertyType.tsx b/src/components/list-property/PropertyType.tsx
new file mode 100644
index 0000000..4b632e1
--- /dev/null
+++ b/src/components/list-property/PropertyType.tsx
@@ -0,0 +1,32 @@
+import { House, Buildings, Farm, Bed } from "@phosphor-icons/react/dist/ssr";
+import { StepProps } from "./types";
+import { titleStyle, subtitleStyle, optionCardStyle, figtree } from "./styles";
+
+export const PropertyType = ({ data, updateData }: StepProps) => {
+ const types = [
+ { id: 'house', label: 'House', icon: House },
+ { id: 'flat', label: 'Flat', icon: Buildings },
+ { id: 'farmhouse', label: 'Farmhouse', icon: Farm },
+ { id: 'guesthouse', label: 'Guesthouse', icon: Bed },
+ ];
+
+ return (
+
+
What kind of place will you host?
+
Select the category that best describes your property.
+
+ {types.map((type) => (
+ updateData('propertyType', type.id)}
+ className={optionCardStyle(data.propertyType === type.id)}
+ >
+
+ {type.label}
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/list-property/SafetyDetails.tsx b/src/components/list-property/SafetyDetails.tsx
new file mode 100644
index 0000000..18d9ceb
--- /dev/null
+++ b/src/components/list-property/SafetyDetails.tsx
@@ -0,0 +1,43 @@
+import { Camera, ShieldCheck, Farm } from "@phosphor-icons/react/dist/ssr";
+import { StepProps } from "./types";
+import { titleStyle, optionCardStyle, figtree } from "./styles";
+
+export const SafetyDetails = ({ data, updateData }: StepProps) => {
+ const safetyItems = [
+ { id: 'camera', label: 'Security camera(s)', icon: Camera },
+ { id: 'weapons', label: 'Dangerous weapons', icon: ShieldCheck },
+ { id: 'animals', label: 'Dangerous animals', icon: Farm },
+ ];
+
+ const toggleSafety = (id: string) => {
+ const current = data.safety || [];
+ const updated = current.includes(id)
+ ? current.filter((item: string) => item !== id)
+ : [...current, id];
+ updateData('safety', updated);
+ };
+
+ return (
+
+
Does your place have any of these?
+
+ {safetyItems.map((item) => (
+
toggleSafety(item.id)}
+ className={`${optionCardStyle((data.safety || []).includes(item.id))} flex-row justify-between items-center w-full`}
+ >
+
+
+ {item.label}
+
+
+ {(data.safety || []).includes(item.id) &&
}
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/list-property/list.tsx b/src/components/list-property/list.tsx
index 5b5210b..98c2f5f 100644
--- a/src/components/list-property/list.tsx
+++ b/src/components/list-property/list.tsx
@@ -1,528 +1,24 @@
"use client";
-import { useState, useRef, useEffect } from "react";
-import localFont from "next/font/local";
+import { useState, useRef } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
-import Image from "next/image";
-import {
- House,
- Buildings,
- Farm,
- Bed,
- MapPin,
- Camera,
- WifiHigh,
- Article,
- CurrencyDollar,
- CheckCircle,
- CalendarBlank,
- ShieldCheck,
- CaretLeft,
- CaretRight,
- Star,
- SwimmingPool,
- Car,
- CookingPot,
- TelevisionSimple,
- Wind,
- X,
- WarningCircle,
-} from "@phosphor-icons/react/dist/ssr";
+import { CaretLeft, CaretRight } from "@phosphor-icons/react/dist/ssr";
-const figtree = localFont({
- src: [
- {
- path: '../../../public/Fonts/figtree/figtree.ttf',
- },
- ],
-})
+import { FormData, FormValue } from "./types";
+import { containerStyle, buttonStyle, secondaryButtonStyle, figtree } from "./styles";
-// Shared styles matching search.tsx and results.tsx
-const containerStyle = `flex flex-col gap-8 w-full max-w-xl mx-auto pb-24`; // Added padding bottom for fixed footer
-const titleStyle = `${figtree.className} text-4xl font-bold mb-2`;
-const subtitleStyle = `${figtree.className} text-lg text-gray-500 mb-8`;
-const inputStyle = `${figtree.className} w-full p-4 border-2 border-black text-lg outline-none focus:bg-[#F7F7F7] transition-colors placeholder:text-gray-400`;
-const buttonStyle = `${figtree.className} flex items-center justify-center gap-2 px-8 py-3 border-2 border-black bg-[#E7FE78] text-lg font-medium hover:bg-[#dcfc4e] transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[4px] active:shadow-none transition-all`;
-const secondaryButtonStyle = `${figtree.className} flex items-center justify-center gap-2 px-8 py-3 border-2 border-black bg-white text-lg font-medium hover:bg-gray-50 transition-colors shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[4px] active:shadow-none transition-all`;
-const optionCardStyle = (selected: boolean) =>
- `flex flex-col items-center justify-center gap-4 p-6 border-2 border-black cursor-pointer transition-all ${selected ? 'bg-[#E7FE78]' : 'bg-white hover:bg-gray-50'}`;
-
-interface FormData {
- propertyType?: string;
- placeType?: string;
- location?: string;
- guests?: number;
- bedrooms?: number;
- beds?: number;
- bathrooms?: number;
- amenities?: string[];
- description?: string;
- price?: string;
- instantBook?: boolean;
- safety?: string[];
- title?: string; // Added title for preview
- photos?: string[];
-}
-
-type FormValue = string | number | boolean | string[] | undefined;
-
-interface StepProps {
- data: FormData;
- updateData: (key: keyof FormData, value: FormValue) => void;
-}
-
-/* --- Live Preview Component --- */
-const LivePreview = ({ data }: { data: FormData }) => {
- // Helpers to derive display values
- const getCity = () => {
- if (!data.location) return "YOUR CITY";
- const parts = data.location.split(',');
- return parts[0].trim().toUpperCase();
- };
-
- const getTitle = () => {
- if (data.title) return data.title;
- const type = data.propertyType ? data.propertyType.charAt(0).toUpperCase() + data.propertyType.slice(1) : "Property";
- const place = data.placeType === 'entire' ? 'Entire place' : data.placeType === 'room' ? 'Private room' : 'Shared room';
- return `${type} · ${place}`;
- };
-
- const getPrice = () => {
- return data.price ? `₨${parseInt(data.price).toLocaleString()}` : "₨0";
- };
-
- return (
-
-
-
-
- {/* Card Replica */}
-
- {/* Image Placeholder */}
-
- {data.photos && data.photos.length > 0 ? (
-
- ) : (
-
-
- Cover Photo
-
- )}
-
-
- {/* Details */}
-
-
-
- {getCity()}
-
-
-
- New
-
-
-
-
- {getTitle()}
-
-
-
- {getPrice()}
- night
-
-
-
-
- {/* Live Updates Summary */}
-
-
- Guests
- {data.guests || 0}
-
-
- Amenities
- {data.amenities?.length || 0} selected
-
-
-
-
- );
-};
-
-/* --- Step Components --- */
-
-const PropertyType = ({ data, updateData }: StepProps) => {
- const types = [
- { id: 'house', label: 'House', icon: House },
- { id: 'flat', label: 'Flat', icon: Buildings },
- { id: 'farmhouse', label: 'Farmhouse', icon: Farm },
- { id: 'guesthouse', label: 'Guesthouse', icon: Bed },
- ];
-
- return (
-
-
What kind of place will you host?
-
Select the category that best describes your property.
-
- {types.map((type) => (
- updateData('propertyType', type.id)}
- className={optionCardStyle(data.propertyType === type.id)}
- >
-
- {type.label}
-
- ))}
-
-
- )
-}
-
-const PropertyPlaceType = ({ data, updateData }: StepProps) => {
- const places = [
- { id: 'entire', label: 'An entire place', description: 'Guests have the entire property to themselves, with full privacy and unrestricted access.' },
- { id: 'room', label: 'A room', description: 'Guests have their own private room within the home, along with comfortable access to shared spaces.' },
- { id: 'shared', label: 'A shared room', description: 'Guests sleep in a room or common area that may be shared with others.' },
- ];
-
- return (
-
-
What type of place will guests have?
-
Choose the level of privacy your guests will enjoy.
-
- {places.map((place) => (
-
updateData('placeType', place.id)}
- className={`${optionCardStyle(data.placeType === place.id)} flex-row justify-between text-left items-center w-full p-6`}
- >
-
- {place.label}
- {place.description}
-
- {data.placeType === place.id && }
-
- ))}
-
-
- )
-}
-
-const PropertyLocation = ({ data, updateData }: StepProps) => {
- return (
-
-
Where's your place located?
-
Your address is only shared with guests after they've made a reservation.
-
-
-
- updateData('location', e.target.value)}
- />
-
-
-
-
- )
-}
-
-const PropertyCapacity = ({ data, updateData }: StepProps) => {
- const counters: { key: keyof FormData; label: string }[] = [
- { key: 'guests', label: 'Guests' },
- { key: 'bedrooms', label: 'Bedrooms' },
- { key: 'beds', label: 'Beds' },
- { key: 'bathrooms', label: 'Bathrooms' },
- ];
-
- const updateCount = (key: keyof FormData, delta: number) => {
- const current = (data[key] as number) || 0;
- updateData(key, Math.max(0, current + delta));
- };
-
- return (
-
-
Share some basics about your place
-
You'll add more details later, like bed types.
-
- {counters.map((item) => (
-
-
{item.label}
-
- updateCount(item.key, -1)}
- disabled={!data[item.key]}
- className="w-12 h-12 rounded-full border-2 border-black flex items-center justify-center hover:bg-gray-100 disabled:opacity-30 disabled:hover:bg-transparent transition-colors"
- >
- -
-
- {data[item.key] || 0}
- updateCount(item.key, 1)}
- className="w-12 h-12 rounded-full border-2 border-black flex items-center justify-center hover:bg-gray-100 transition-colors"
- >
- +
-
-
-
- ))}
-
-
- )
-}
-
-const PropertyAmenities = ({ data, updateData }: StepProps) => {
- const amenities = [
- { id: 'wifi', label: 'Wi-Fi', icon: WifiHigh },
- { id: 'kitchen', label: 'Kitchen', icon: CookingPot },
- { id: 'parking', label: 'Free parking', icon: Car },
- { id: 'pool', label: 'Pool', icon: SwimmingPool },
- { id: 'ac', label: 'Air conditioning', icon: Wind }, // Using House as placeholder for AC if needed
- { id: 'tv', label: 'TV', icon: TelevisionSimple },
- ];
-
- const toggleAmenity = (id: string) => {
- const current = data.amenities || [];
- const updated = current.includes(id)
- ? current.filter((item: string) => item !== id)
- : [...current, id];
- updateData('amenities', updated);
- };
-
- return (
-
-
What does your place offer?
-
You can add more amenities after you publish.
-
- {amenities.map((amenity) => (
-
toggleAmenity(amenity.id)}
- className={`${optionCardStyle((data.amenities || []).includes(amenity.id))} items-start w-full text-left`}
- >
-
- {amenity.label}
-
- ))}
-
-
- )
-}
-
-const PropertyDescription = ({ data, updateData }: StepProps) => {
- return (
-
-
How would you describe your place?
-
Short and sweet works best.
-
-
- Title
- updateData('title', e.target.value)}
- />
-
-
- Description
-
-
-
- )
-}
-
-const PropertyPhotos = ({ data, updateData }: StepProps) => {
- const fileInputRef = useRef(null);
-
- const handleFileChange = (e: React.ChangeEvent) => {
- if (e.target.files) {
- const newPhotos = Array.from(e.target.files).map(file => URL.createObjectURL(file));
- const currentPhotos = data.photos || [];
- updateData('photos', [...currentPhotos, ...newPhotos]);
- }
- };
-
- const removePhoto = (index: number) => {
- const currentPhotos = data.photos || [];
- const updated = currentPhotos.filter((_, i) => i !== index);
- updateData('photos', updated);
- };
-
- return (
-
-
Add some photos of your house
-
You'll need 5 photos to get started. You can add more or make changes later.
-
-
-
fileInputRef.current?.click()}
- className="border-2 border-dashed border-gray-300 rounded-lg p-12 flex flex-col items-center justify-center gap-4 cursor-pointer hover:bg-gray-50 transition-colors"
- >
-
- Upload photos
- JPG, PNG up to 10MB
-
-
-
- {data.photos && data.photos.length > 0 && (
-
- {data.photos.map((photo, index) => (
-
-
-
removePhoto(index)}
- className="absolute top-2 right-2 bg-white rounded-full p-1 border-2 border-black hover:bg-red-50 transition-colors z-10"
- >
-
-
- {index === 0 && (
-
Cover Photo
- )}
-
- ))}
-
- )}
-
- {(!data.photos || data.photos.length < 5) && (
-
-
- Please upload at least {5 - (data.photos?.length || 0)} more photos
-
- )}
-
-
- )
-}
-
-const PropertyPrice = ({ data, updateData }: StepProps) => {
- return (
-
-
Now, set your price
-
You can change it anytime.
-
-
- ₨
- updateData('price', e.target.value)}
- />
-
-
per night
-
-
- )
-}
-
-const PropertyInstantApproval = ({ data, updateData }: StepProps) => {
- return (
-
-
Decide how you'll confirm reservations
-
-
updateData('instantBook', true)}
- className={`${optionCardStyle(data.instantBook === true)} w-full flex-row items-start text-left`}
- >
-
-
- Use Instant Book
- Guests can book automatically. No need to approve reservations.
-
-
-
updateData('instantBook', false)}
- className={`${optionCardStyle(data.instantBook === false)} w-full flex-row items-start text-left`}
- >
-
-
- Approve manually
- You approve or decline booking requests. You will be notified of each booking request.
-
-
-
-
- )
-}
-
-const SafetyDetails = ({ data, updateData }: StepProps) => {
- const safetyItems = [
- { id: 'camera', label: 'Security camera(s)', icon: Camera },
- { id: 'weapons', label: 'Dangerous weapons', icon: ShieldCheck },
- { id: 'animals', label: 'Dangerous animals', icon: Farm },
- ];
-
- const toggleSafety = (id: string) => {
- const current = data.safety || [];
- const updated = current.includes(id)
- ? current.filter((item: string) => item !== id)
- : [...current, id];
- updateData('safety', updated);
- };
-
- return (
-
-
Does your place have any of these?
-
- {safetyItems.map((item) => (
-
toggleSafety(item.id)}
- className={`${optionCardStyle((data.safety || []).includes(item.id))} flex-row justify-between items-center w-full`}
- >
-
-
- {item.label}
-
-
- {(data.safety || []).includes(item.id) &&
}
-
-
- ))}
-
-
- )
-}
-
-/* --- Main List Component --- */
+import { PropertyType } from "./PropertyType";
+import { PropertyPlaceType } from "./PropertyPlaceType";
+import { PropertyLocation } from "./PropertyLocation";
+import { PropertyCapacity } from "./PropertyCapacity";
+import { PropertyAmenities } from "./PropertyAmenities";
+import { PropertyDescription } from "./PropertyDescription";
+import { PropertyPhotos } from "./PropertyPhotos";
+import { PropertyPrice } from "./PropertyPrice";
+import { PropertyInstantApproval } from "./PropertyInstantApproval";
+import { SafetyDetails } from "./SafetyDetails";
+import { LivePreview } from "./LivePreview";
function List() {
const [step, setStep] = useState(0);
@@ -548,9 +44,17 @@ function List() {
SafetyDetails
];
+ const phases = [
+ { name: "Basics", start: 0, end: 4 },
+ { name: "Description", start: 5, end: 6 },
+ { name: "Publish", start: 7, end: 9 }
+ ];
+
+ const currentPhaseIndex = phases.findIndex(p => step >= p.start && step <= p.end);
+ const currentPhase = phases[currentPhaseIndex];
+
const CurrentStepComponent = steps[step];
const totalSteps = steps.length;
- const progress = ((step + 1) / totalSteps) * 100;
const updateData = (key: keyof FormData, value: FormValue) => {
setFormData(prev => ({ ...prev, [key]: value }));
@@ -588,30 +92,42 @@ function List() {
{/* Left Panel - Form */}
- {/* Header */}
-
-
-
- {step + 1}
-
-
-
Step {step + 1} of {totalSteps}
-
{
- step === 0 ? "Basics" :
- step < 4 ? "Details" :
- step < 7 ? "Description" : "Finish"
- }
+ {/* Header Section */}
+
+
+
+
+ {step + 1}
+
+
+ Step {step + 1} of {totalSteps}
+ {currentPhase.name}
+
-
Save & Exit
-
- {/* Progress Bar */}
-
-
+ {/* Segmented Progress Bar */}
+
+ {phases.map((phase, index) => {
+ let fill = 0;
+ if (index < currentPhaseIndex) {
+ fill = 100;
+ } else if (index === currentPhaseIndex) {
+ const phaseTotal = phase.end - phase.start + 1;
+ const phaseStep = step - phase.start + 1;
+ fill = (phaseStep / phaseTotal) * 100;
+ }
+
+ return (
+
+ );
+ })}
+
{/* Scrollable Content */}
diff --git a/src/components/list-property/styles.ts b/src/components/list-property/styles.ts
new file mode 100644
index 0000000..423f47d
--- /dev/null
+++ b/src/components/list-property/styles.ts
@@ -0,0 +1,18 @@
+import localFont from "next/font/local";
+
+export const figtree = localFont({
+ src: [
+ {
+ path: '../../../public/Fonts/figtree/figtree.ttf',
+ },
+ ],
+})
+
+export const containerStyle = `flex flex-col gap-8 w-full max-w-xl mx-auto pb-24`; // Added padding bottom for fixed footer
+export const titleStyle = `${figtree.className} text-4xl font-bold mb-2`;
+export const subtitleStyle = `${figtree.className} text-lg text-gray-500 mb-8`;
+export const inputStyle = `${figtree.className} w-full p-4 border-2 border-black text-lg outline-none focus:bg-[#F7F7F7] transition-colors placeholder:text-gray-400`;
+export const buttonStyle = `${figtree.className} flex items-center justify-center gap-2 px-8 py-3 border-2 border-black bg-[#E7FE78] text-lg font-medium hover:bg-[#dcfc4e] transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[4px] active:shadow-none transition-all`;
+export const secondaryButtonStyle = `${figtree.className} flex items-center justify-center gap-2 px-8 py-3 border-2 border-black bg-white text-lg font-medium hover:bg-gray-50 transition-colors shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[4px] active:shadow-none transition-all`;
+export const optionCardStyle = (selected: boolean) =>
+ `flex flex-col items-center justify-center gap-4 p-6 border-2 border-black cursor-pointer transition-all ${selected ? 'bg-[#E7FE78]' : 'bg-white hover:bg-gray-50'}`;
diff --git a/src/components/list-property/types.ts b/src/components/list-property/types.ts
new file mode 100644
index 0000000..a952c3f
--- /dev/null
+++ b/src/components/list-property/types.ts
@@ -0,0 +1,23 @@
+export interface FormData {
+ propertyType?: string;
+ placeType?: string;
+ location?: string;
+ guests?: number;
+ bedrooms?: number;
+ beds?: number;
+ bathrooms?: number;
+ amenities?: string[];
+ description?: string;
+ price?: string;
+ instantBook?: boolean;
+ safety?: string[];
+ title?: string;
+ photos?: string[];
+}
+
+export type FormValue = string | number | boolean | string[] | undefined;
+
+export interface StepProps {
+ data: FormData;
+ updateData: (key: keyof FormData, value: FormValue) => void;
+}
diff --git a/src/components/manage-property/manage.tsx b/src/components/manage-property/manage.tsx
new file mode 100644
index 0000000..db570a2
--- /dev/null
+++ b/src/components/manage-property/manage.tsx
@@ -0,0 +1,188 @@
+"use client";
+
+import { useState } from "react";
+import localFont from "next/font/local";
+import {
+ House,
+ MapPin,
+ List,
+ Image as ImageIcon,
+ CurrencyDollar,
+ ShieldCheck,
+ CaretLeft,
+ Check,
+ Info
+} from "@phosphor-icons/react/dist/ssr";
+import { FormData, FormValue } from "../list-property/types";
+import { PropertyType } from "../list-property/PropertyType";
+import { PropertyPlaceType } from "../list-property/PropertyPlaceType";
+import { PropertyLocation } from "../list-property/PropertyLocation";
+import { PropertyCapacity } from "../list-property/PropertyCapacity";
+import { PropertyAmenities } from "../list-property/PropertyAmenities";
+import { PropertyDescription } from "../list-property/PropertyDescription";
+import { PropertyPhotos } from "../list-property/PropertyPhotos";
+import { PropertyPrice } from "../list-property/PropertyPrice";
+import { PropertyInstantApproval } from "../list-property/PropertyInstantApproval";
+import { SafetyDetails } from "../list-property/SafetyDetails";
+
+const figtree = localFont({
+ src: [
+ {
+ path: '../../../public/Fonts/figtree/figtree.ttf',
+ },
+ ],
+})
+
+const sidebarItemStyle = (active: boolean) =>
+ `flex items-center gap-3 p-4 rounded-lg cursor-pointer transition-all ${active ? 'bg-black text-white' : 'hover:bg-gray-100 text-gray-700'}`;
+
+const sectionTitleStyle = `${figtree.className} text-3xl font-bold mb-6`;
+
+export default function ManageProperty() {
+ const [activeSection, setActiveSection] = useState("details");
+ const [formData, setFormData] = useState
({
+ // Mock existing data
+ title: "Cozy Cottage in the Hills",
+ description: "This unique place has a style all its own. Located in the heart of the mountains, this cottage offers a perfect getaway.",
+ propertyType: "house",
+ placeType: "entire",
+ location: "123 Mountain View Rd, Hilltop, CA",
+ guests: 4,
+ bedrooms: 2,
+ beds: 3,
+ bathrooms: 1,
+ amenities: ["wifi", "kitchen", "parking"],
+ price: "15000",
+ instantBook: true,
+ safety: ["camera"],
+ photos: [
+ "https://images.unsplash.com/photo-1499793983690-e29da59ef1c2?ixlib=rb-4.0.3&auto=format&fit=crop&w=2070&q=80",
+ "https://images.unsplash.com/photo-1502005229762-cf1b2da7c5d6?ixlib=rb-4.0.3&auto=format&fit=crop&w=1974&q=80",
+ "https://images.unsplash.com/photo-1484154218962-a1c002085d2f?ixlib=rb-4.0.3&auto=format&fit=crop&w=2070&q=80",
+ "https://images.unsplash.com/photo-1513694203232-719a280e022f?ixlib=rb-4.0.3&auto=format&fit=crop&w=2069&q=80",
+ "https://images.unsplash.com/photo-1505691938895-1758d7feb511?ixlib=rb-4.0.3&auto=format&fit=crop&w=2070&q=80"
+ ]
+ });
+
+ const updateData = (key: keyof FormData, value: FormValue) => {
+ setFormData(prev => ({ ...prev, [key]: value }));
+ };
+
+ const sections = [
+ { id: "details", label: "Property Details", icon: Info },
+ { id: "location", label: "Location", icon: MapPin },
+ { id: "amenities", label: "Amenities", icon: List },
+ { id: "photos", label: "Photos", icon: ImageIcon },
+ { id: "pricing", label: "Pricing & Booking", icon: CurrencyDollar },
+ { id: "safety", label: "Safety", icon: ShieldCheck },
+ ];
+
+ const renderContent = () => {
+ switch (activeSection) {
+ case "details":
+ return (
+
+ );
+ case "location":
+ return (
+
+ );
+ case "amenities":
+ return (
+
+ );
+ case "photos":
+ return (
+
+ );
+ case "pricing":
+ return (
+
+ );
+ case "safety":
+ return (
+
+
Safety
+
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+ {/* Sidebar */}
+
+
+ {/* Main Content */}
+
+ {renderContent()}
+
+
+
+ );
+}
diff --git a/src/pages/landing/page.tsx b/src/pages/landing/page.tsx
new file mode 100644
index 0000000..dc508f0
--- /dev/null
+++ b/src/pages/landing/page.tsx
@@ -0,0 +1,24 @@
+
+'use client';
+import Hero from "@/components/landing/hero";
+import BestPrices from "@/components/landing/best-prices";
+import BrowseByCity from "@/components/landing/browse-by-city";
+import Review from "@/components/landing/review";
+import FAQs from "@/components/landing/faqs";
+import Search from "@/components/landing/search";
+
+
+function Landing() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Landing;
diff --git a/src/pages/profile/page.tsx b/src/pages/profile/page.tsx
new file mode 100644
index 0000000..daa1429
--- /dev/null
+++ b/src/pages/profile/page.tsx
@@ -0,0 +1,12 @@
+'use client';
+import Profile from '@/components/Profile/profile';
+
+function ProfilePage() {
+ return (
+
+ );
+}
+
+export default ProfilePage;