-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor consultant profile components
- Loading branch information
Showing
8 changed files
with
420 additions
and
280 deletions.
There are no files selected for viewing
47 changes: 47 additions & 0 deletions
47
app/explore/experts/[consultantId]/components/AboutSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { User } from "@prisma/client"; | ||
import { TConsultantProfile } from "@/types/consultant"; | ||
|
||
interface AboutSectionProps { | ||
userDetails: User; | ||
consultantDetails: TConsultantProfile; | ||
} | ||
|
||
export function AboutSection({ userDetails, consultantDetails }: AboutSectionProps) { | ||
return ( | ||
<div className="space-y-4 sm:space-y-6"> | ||
<div> | ||
<h3 className="text-lg sm:text-xl font-semibold mb-2">About</h3> | ||
<p className="text-sm sm:text-base text-gray-600"> | ||
{userDetails.name} is a seasoned{" "} | ||
{consultantDetails.specialization} with{" "} | ||
{consultantDetails.experience} of experience in the{" "} | ||
{consultantDetails.domain.name} sector. | ||
</p> | ||
</div> | ||
|
||
<div> | ||
<h3 className="text-lg sm:text-xl font-semibold mb-2"> | ||
Education & Background | ||
</h3> | ||
<p className="text-sm sm:text-base text-gray-600"> | ||
{userDetails.name} has experience across multiple industries, | ||
with a particular focus on{" "} | ||
{consultantDetails?.subDomains | ||
?.map((domain: { name: string }) => domain.name) | ||
.join(", ")} | ||
. | ||
</p> | ||
</div> | ||
|
||
<div> | ||
<h3 className="text-lg sm:text-xl font-semibold mb-2"> | ||
Skills & Specialties | ||
</h3> | ||
<p className="text-sm sm:text-base text-gray-600"> | ||
{userDetails.name} focuses on{" "} | ||
{consultantDetails.tags?.map((tag: { name: string }) => tag.name).join(", ")}. | ||
</p> | ||
</div> | ||
</div> | ||
); | ||
} |
91 changes: 91 additions & 0 deletions
91
app/explore/experts/[consultantId]/components/AvailabilitySection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { TConsultantProfile } from "@/types/consultant"; | ||
import { TSlotTiming, TWeeklySlot, TCustomSlot } from "@/types/slots"; | ||
import { WeeklyAvailability } from "./WeeklyAvailability"; | ||
import { CustomAvailability } from "./CustomAvailability"; | ||
import { normalizeWeeklySlot, normalizeCustomSlot, formatTime } from "../utils"; | ||
import { dayMap } from "../utils"; | ||
|
||
interface AvailabilitySectionProps { | ||
consultantDetails: TConsultantProfile; | ||
timezone: string | null | undefined; | ||
selectedSlot: TSlotTiming | null; | ||
setSelectedSlot: (slot: TSlotTiming | null) => void; | ||
} | ||
|
||
export function AvailabilitySection({ | ||
consultantDetails, | ||
timezone, | ||
selectedSlot, | ||
setSelectedSlot | ||
}: AvailabilitySectionProps) { | ||
const renderAvailability = () => { | ||
if (!consultantDetails || !timezone) return null; | ||
|
||
if (consultantDetails.scheduleType === "WEEKLY") { | ||
// Convert Date objects to strings for weekly slots | ||
const weeklySlots = consultantDetails.slotsOfAvailabilityWeekly.map(normalizeWeeklySlot); | ||
|
||
return ( | ||
<WeeklyAvailability | ||
slots={weeklySlots} | ||
onSlotSelect={slot => { | ||
const normalizedSlot = normalizeWeeklySlot(slot); | ||
setSelectedSlot({ | ||
slotId: normalizedSlot.id, | ||
dateInISO: new Date().toISOString(), | ||
dayOfWeek: normalizedSlot.dayOfWeekforStartTimeInUTC, | ||
slotStartTimeInUTC: normalizedSlot.slotStartTimeInUTC, | ||
slotEndTimeInUTC: normalizedSlot.slotEndTimeInUTC, | ||
slotOfAvailabilityId: normalizedSlot.id, | ||
slotOfAppointmentId: "", | ||
localStartTime: formatTime(normalizedSlot.slotStartTimeInUTC, timezone), | ||
localEndTime: formatTime(normalizedSlot.slotEndTimeInUTC, timezone), | ||
}); | ||
}} | ||
selectedSlotId={selectedSlot?.slotId} | ||
/> | ||
); | ||
} else if (consultantDetails.scheduleType === "CUSTOM") { | ||
// Convert Date objects to strings for custom slots | ||
const customSlots = consultantDetails.slotsOfAvailabilityCustom.map(normalizeCustomSlot); | ||
|
||
return ( | ||
<CustomAvailability | ||
slots={customSlots} | ||
onSlotSelect={slot => { | ||
const normalizedSlot = normalizeCustomSlot(slot); | ||
setSelectedSlot({ | ||
slotId: normalizedSlot.id, | ||
dateInISO: new Date(normalizedSlot.slotStartTimeInUTC).toISOString(), | ||
dayOfWeek: dayMap[new Date(normalizedSlot.slotStartTimeInUTC).getDay()], | ||
slotStartTimeInUTC: normalizedSlot.slotStartTimeInUTC, | ||
slotEndTimeInUTC: normalizedSlot.slotEndTimeInUTC, | ||
slotOfAvailabilityId: normalizedSlot.id, | ||
slotOfAppointmentId: "", | ||
localStartTime: formatTime(normalizedSlot.slotStartTimeInUTC, timezone), | ||
localEndTime: formatTime(normalizedSlot.slotEndTimeInUTC, timezone), | ||
}); | ||
}} | ||
selectedSlotId={selectedSlot?.slotId} | ||
/> | ||
); | ||
} | ||
return null; | ||
}; | ||
|
||
return ( | ||
<div className="overflow-x-auto"> | ||
<h3 className="text-lg sm:text-xl font-semibold mb-2 sm:mb-4"> | ||
Consultant Availability | ||
</h3> | ||
<p className="text-xs sm:text-sm text-gray-600 mb-4"> | ||
{consultantDetails.scheduleType === "WEEKLY" | ||
? "Weekly schedule. Select a time slot to schedule a meeting." | ||
: "Custom schedule for the next 7 days. Select a time slot to schedule a meeting."} | ||
</p> | ||
<div className="min-w-full"> | ||
{renderAvailability()} | ||
</div> | ||
</div> | ||
); | ||
} |
37 changes: 37 additions & 0 deletions
37
app/explore/experts/[consultantId]/components/ClassPlansSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Button } from "@/components/ui/button"; | ||
import { ClassPlan } from "@prisma/client"; | ||
|
||
interface ClassPlansSectionProps { | ||
classPlans: ClassPlan[]; | ||
} | ||
|
||
export function ClassPlansSection({ classPlans }: ClassPlansSectionProps) { | ||
if (!classPlans.length) return null; | ||
|
||
return ( | ||
<div className="space-y-6"> | ||
<h3 className="text-lg sm:text-xl font-semibold">Class Plans</h3> | ||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> | ||
{classPlans.map((plan, index) => ( | ||
<div key={index} className="bg-white p-4 sm:p-6 rounded-lg shadow-md"> | ||
<h4 className="text-lg font-semibold mb-2">{plan.title}</h4> | ||
<div className="space-y-2 text-sm sm:text-base"> | ||
<p>Duration: {plan.durationInMonths} months</p> | ||
<p>Calls per week: {plan.callsPerWeek}</p> | ||
<p>Video meetings: {plan.videoMeetings}</p> | ||
<p>Email support: {plan.emailSupport}</p> | ||
{plan.certificateProvided && ( | ||
<p>✓ Certificate provided upon completion</p> | ||
)} | ||
<p className="text-gray-600 line-clamp-3">{plan.description}</p> | ||
<p className="text-lg font-semibold mt-2">${plan.price}</p> | ||
</div> | ||
<Button variant="outline" className="w-full mt-4"> | ||
Register Now | ||
</Button> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
} |
75 changes: 75 additions & 0 deletions
75
app/explore/experts/[consultantId]/components/ConsultationSidebar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import Image from "next/image"; | ||
import { User } from "@prisma/client"; | ||
import { TConsultantProfile } from "@/types/consultant"; | ||
import { TSlotTiming } from "@/types/slots"; | ||
import PricingToggle from "./PricingToggle"; | ||
|
||
interface ConsultationSidebarProps { | ||
userDetails: User; | ||
consultantDetails: TConsultantProfile; | ||
handleBooking: () => void; | ||
selectedDate: Date | null; | ||
setSelectedDate: (date: Date | null) => void; | ||
currentDate: Date; | ||
setCurrentDate: (date: Date) => void; | ||
renderCalendar: () => JSX.Element[]; | ||
slotTimings: TSlotTiming[]; | ||
selectedSlot: TSlotTiming | null; | ||
setSelectedSlot: (slot: TSlotTiming | null) => void; | ||
consultationOptions: any[]; | ||
subscriptionOptions: any[]; | ||
} | ||
|
||
export function ConsultationSidebar({ | ||
userDetails, | ||
consultantDetails, | ||
handleBooking, | ||
selectedDate, | ||
setSelectedDate, | ||
currentDate, | ||
setCurrentDate, | ||
renderCalendar, | ||
slotTimings, | ||
selectedSlot, | ||
setSelectedSlot, | ||
consultationOptions, | ||
subscriptionOptions, | ||
}: ConsultationSidebarProps) { | ||
return ( | ||
<div className="w-full lg:w-1/3 xl:w-1/4 mt-6 lg:mt-0"> | ||
<div className="lg:sticky lg:top-24 space-y-6"> | ||
{/* Profile image for desktop */} | ||
<div className="hidden lg:block"> | ||
<div className="relative w-48 h-48 xl:w-64 xl:h-64 mx-auto"> | ||
<Image | ||
alt="Profile" | ||
src={userDetails.image || "/placeholder.svg"} | ||
fill | ||
className="rounded-full object-cover" | ||
/> | ||
</div> | ||
</div> | ||
<div className="bg-white shadow-lg rounded-lg"> | ||
<div className="p-4 sm:p-6"> | ||
<h3 className="text-lg sm:text-xl font-semibold mb-4">Consultation Pricing</h3> | ||
<PricingToggle | ||
consultationOptions={consultationOptions} | ||
subscriptionOptions={subscriptionOptions} | ||
consultantDetails={consultantDetails} | ||
userDetails={userDetails} | ||
handleBooking={handleBooking} | ||
selectedDate={selectedDate} | ||
setSelectedDate={setSelectedDate} | ||
currentDate={currentDate} | ||
setCurrentDate={setCurrentDate} | ||
renderCalendar={renderCalendar} | ||
slotTimings={slotTimings} | ||
selectedSlot={selectedSlot} | ||
setSelectedSlot={setSelectedSlot} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
43 changes: 43 additions & 0 deletions
43
app/explore/experts/[consultantId]/components/ProfileHeader.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { StarIcon } from "lucide-react"; | ||
import Image from "next/image"; | ||
import { User } from "@prisma/client"; | ||
import { TConsultantProfile } from "@/types/consultant"; | ||
import { Badge } from "@/components/ui/badge"; | ||
|
||
interface ProfileHeaderProps { | ||
userDetails: User; | ||
consultantDetails: TConsultantProfile; | ||
} | ||
|
||
export function ProfileHeader({ userDetails, consultantDetails }: ProfileHeaderProps) { | ||
return ( | ||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6"> | ||
{/* Profile image for mobile */} | ||
<div className="block lg:hidden relative w-32 h-32 sm:w-40 sm:h-40"> | ||
<Image | ||
alt="Profile" | ||
src={userDetails.image || "/placeholder.svg"} | ||
fill | ||
className="rounded-full object-cover" | ||
/> | ||
</div> | ||
<div className="flex flex-col"> | ||
<h2 className="text-2xl sm:text-3xl font-semibold">{userDetails.name}</h2> | ||
<div className="flex items-center mt-2"> | ||
{[...Array(5)].map((_, i) => ( | ||
<StarIcon | ||
key={`${i}-${consultantDetails.rating}`} | ||
className={`w-4 h-4 sm:w-5 sm:h-5 ${i < consultantDetails.rating ? "text-blue-500" : "text-gray-300"}`} | ||
/> | ||
))} | ||
<span className="ml-2 text-sm text-gray-600"> | ||
({consultantDetails.rating}) | ||
</span> | ||
</div> | ||
<div className="mt-3"> | ||
<Badge variant="outline">{consultantDetails.specialization}</Badge> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
25 changes: 25 additions & 0 deletions
25
app/explore/experts/[consultantId]/components/ReviewsSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { ConsultantReview } from "@prisma/client"; | ||
import Review from "./Review"; | ||
|
||
interface ReviewsSectionProps { | ||
reviews: ConsultantReview[]; | ||
} | ||
|
||
export function ReviewsSection({ reviews }: ReviewsSectionProps) { | ||
return ( | ||
<div> | ||
<h3 className="text-lg sm:text-xl font-semibold mb-4"> | ||
All Reviews ({reviews?.length || 0}) | ||
</h3> | ||
<div className="space-y-4"> | ||
{reviews && reviews.length > 0 ? ( | ||
reviews.map((review) => ( | ||
<Review key={review.id} {...review} /> | ||
)) | ||
) : ( | ||
<p className="text-sm sm:text-base text-gray-600">No reviews available.</p> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} |
37 changes: 37 additions & 0 deletions
37
app/explore/experts/[consultantId]/components/WebinarPlansSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Button } from "@/components/ui/button"; | ||
import { WebinarPlan } from "@prisma/client"; | ||
|
||
interface WebinarPlansSectionProps { | ||
webinarPlans: WebinarPlan[]; | ||
} | ||
|
||
export function WebinarPlansSection({ webinarPlans }: WebinarPlansSectionProps) { | ||
if (!webinarPlans.length) return null; | ||
|
||
return ( | ||
<div className="space-y-6"> | ||
<h3 className="text-lg sm:text-xl font-semibold">Webinar Plans</h3> | ||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> | ||
{webinarPlans.map((plan, index) => ( | ||
<div key={index} className="bg-white p-4 sm:p-6 rounded-lg shadow-md"> | ||
<h4 className="text-lg font-semibold mb-2">{plan.title}</h4> | ||
<div className="space-y-2 text-sm sm:text-base"> | ||
<p>Duration: {plan.durationInHours} hours</p> | ||
<p>Max participants: {plan.maxParticipants}</p> | ||
{plan.language && <p>Language: {plan.language}</p>} | ||
{plan.level && <p>Level: {plan.level}</p>} | ||
{plan.prerequisites && ( | ||
<p>Prerequisites: {plan.prerequisites}</p> | ||
)} | ||
<p className="text-gray-600 line-clamp-3">{plan.description}</p> | ||
<p className="text-lg font-semibold mt-2">${plan.price}</p> | ||
</div> | ||
<Button variant="outline" className="w-full mt-4"> | ||
Register Now | ||
</Button> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.