Skip to content

Commit

Permalink
Refactor consultant profile components
Browse files Browse the repository at this point in the history
  • Loading branch information
teetangh committed Dec 12, 2024
1 parent 2e5bd85 commit 99e2826
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 280 deletions.
47 changes: 47 additions & 0 deletions app/explore/experts/[consultantId]/components/AboutSection.tsx
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>
);
}
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>
);
}
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>
);
}
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 app/explore/experts/[consultantId]/components/ProfileHeader.tsx
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 app/explore/experts/[consultantId]/components/ReviewsSection.tsx
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>
);
}
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>
);
}
Loading

0 comments on commit 99e2826

Please sign in to comment.