Skip to content

Commit

Permalink
Merge pull request #211 from litespace-org/mk/calendar
Browse files Browse the repository at this point in the history
feat(ui): added booking lesson functionality
  • Loading branch information
neuodev authored Dec 15, 2024
2 parents 704e290 + 1a2fa59 commit f6dc25b
Show file tree
Hide file tree
Showing 25 changed files with 791 additions and 97 deletions.
86 changes: 86 additions & 0 deletions apps/nova/src/components/Lessons/BookLesson.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useFindUserRulesWithSlots } from "@litespace/headless/rule";
import { BookLessonDialog } from "@litespace/luna/Lessons";
import { ILesson, Void } from "@litespace/types";
import { useCreateLesson } from "@litespace/headless/lessons";
import dayjs from "dayjs";
import { useCallback } from "react";
import { useToast } from "@litespace/luna/Toast";
import { useFormatMessage } from "@litespace/luna/hooks/intl";
import { useQueryClient } from "@tanstack/react-query";
import { QueryKey } from "@litespace/headless/constants";

const BookLesson = ({
open,
close,
user,
}: {
open: boolean;
close: Void;
user: {
tutorId: number;
name: string | null;
imageUrl: string | null;
notice: number;
};
}) => {
const intl = useFormatMessage();
const toast = useToast();
const before = dayjs().toISOString();
const after = dayjs(before).add(user.notice, "minutes").toISOString();
const queryClient = useQueryClient();
const rulesQuery = useFindUserRulesWithSlots({
id: user.tutorId,
before,
after,
});

const onSuccess = useCallback(() => {
close();
toast.success({ title: intl("book-lesson.success", { tutor: user.name }) });
queryClient.invalidateQueries({
queryKey: [
QueryKey.FindRulesWithSlots,
QueryKey.FindLesson,
QueryKey.FindTutors,
],
});
}, [toast, intl, user.name, close, queryClient]);

const onError = useCallback(() => {
toast.error({ title: intl("book-lesson.error") });
}, [toast, intl]);

const bookLessonMutation = useCreateLesson({
tutorId: user.tutorId,
onSuccess,
onError,
});

const onBook = ({
ruleId,
start,
duration,
}: {
ruleId: number;
start: string;
duration: ILesson.Duration;
}) => bookLessonMutation.mutate({ ruleId, start, duration });

return (
<div>
<BookLessonDialog
{...user}
open={open}
close={close}
confirmationLoading={bookLessonMutation.isPending}
loading={rulesQuery.isLoading}
rules={rulesQuery.data?.rules || []}
slots={rulesQuery.data?.slots || []}
notice={user.notice}
onBook={onBook}
/>
</div>
);
};

export default BookLesson;
11 changes: 8 additions & 3 deletions apps/nova/src/components/Tutors/BookLesson.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ const BookLesson: React.FC<{
);

const mutation = useCreateLesson({
selectedEvent,
tutorId,
duration,
onSuccess,
onError,
});
Expand Down Expand Up @@ -142,7 +140,14 @@ const BookLesson: React.FC<{
<Button
disabled={!selectedEvent || mutation.isPending}
loading={mutation.isPending}
onClick={() => mutation.mutate()}
onClick={() => {
if (!selectedEvent) return;
mutation.mutate({
duration,
ruleId: selectedEvent.id,
start: selectedEvent.start,
});
}}
className="mt-4"
size={ButtonSize.Small}
>
Expand Down
33 changes: 27 additions & 6 deletions apps/nova/src/components/Tutors/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Loading } from "@litespace/luna/Loading";
import { ITutor, Void } from "@litespace/types";
import React from "react";
import { Element, ITutor, Void } from "@litespace/types";
import React, { useCallback, useState } from "react";
import { TutorCard } from "@litespace/luna/TutorCard";
import { asFullAssetUrl } from "@litespace/luna/backend";
import { Route } from "@/types/routes";
import { useNavigate } from "react-router-dom";
import { motion } from "framer-motion";
import { InView } from "react-intersection-observer";
import BookLesson from "@/components/Lessons/BookLesson";

type Tutor = Element<ITutor.FindOnboardedTutorsApiResponse["list"]>;

const Content: React.FC<{
tutors: ITutor.FindOnboardedTutorsApiResponse["list"] | null;
Expand All @@ -18,6 +21,11 @@ const Content: React.FC<{
}> = ({ tutors, loading, error, more, hasMore, fetching }) => {
const navigate = useNavigate();

const [tutor, setTutor] = useState<Tutor | null>(null);

const openBookingDialog = useCallback((tutor: Tutor) => setTutor(tutor), []);
const closeBookingDialog = useCallback(() => setTutor(null), []);

if (loading) return <Loading className="h-[30vh]" />;
// todo: add error component
if (error) return "ERROR: TODO";
Expand All @@ -41,10 +49,10 @@ const Content: React.FC<{
bio={tutor.bio}
about={tutor.about}
name={tutor.name}
lessonCount={20}
studentCount={20}
rating={4.5}
onBook={() => alert("todo")}
lessonCount={tutor.lessonCount}
studentCount={tutor.studentCount}
rating={tutor.avgRating}
onBook={() => openBookingDialog(tutor)}
onOpenProfile={() => navigate(profileUrl)}
profileUrl={profileUrl}
imageUrl={tutor.image ? asFullAssetUrl(tutor.image) : null}
Expand All @@ -54,6 +62,19 @@ const Content: React.FC<{
})}
</div>

{tutor ? (
<BookLesson
close={closeBookingDialog}
open={!!tutor}
user={{
tutorId: tutor?.id,
imageUrl: tutor.image,
notice: tutor.notice,
name: tutor.name,
}}
/>
) : null}

{fetching ? <Loading className="mt-6 text-natural-950" /> : null}

{!fetching ? (
Expand Down
30 changes: 24 additions & 6 deletions apps/nova/src/pages/TutorProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Loading } from "@litespace/luna/Loading";
import React, { useMemo } from "react";
import { useParams } from "react-router-dom";
import React, { useCallback, useMemo, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { useFindTutorInfo } from "@litespace/headless/tutor";
import RightArrow from "@litespace/assets/ArrowRight";
import { Typography } from "@litespace/luna/Typography";
import { useFormatMessage } from "@litespace/luna/hooks/intl";
import { TutorProfileCard } from "@litespace/luna/TutorProfile";
import { TutorTabs } from "@/components/TutorProfile/TutorTabs";
import BookLesson from "@/components/Lessons/BookLesson";
import { Route } from "@/types/routes";

const TutorProfile: React.FC = () => {
const params = useParams<{ id: string }>();
const intl = useFormatMessage();

const [open, setOpen] = useState<boolean>(false);
const closeDialog = useCallback(() => setOpen(false), []);
const openDialog = useCallback(() => setOpen(true), []);
const id = useMemo(() => {
const id = Number(params.id);
if (Number.isNaN(id)) return null;
Expand All @@ -27,17 +31,31 @@ const TutorProfile: React.FC = () => {
return (
<div className="w-full max-w-screen-3xl p-6 mx-auto mb-12 lg:max-w-screen-3xl">
<div className="flex items-center gap-6">
<button className="w-6 h-6 flex items-center justify-center">
<Link
to={Route.Tutors}
className="w-6 h-6 flex items-center justify-center"
>
<RightArrow className="[&>*]:stroke-brand-700" />
</button>
</Link>
<Typography element="subtitle-2" className="font-bold text-natural-950">
{intl("tutors.title")} /{" "}
<span className="underline text-brand-700">{tutor.data.name}</span>
</Typography>
</div>
<div className="bg-natural-50 border border-natural-100 shadow-tutor-profile rounded-2xl p-10 mt-6">
<TutorProfileCard {...tutor.data} />
<TutorProfileCard {...tutor.data} onBook={openDialog} />
<TutorTabs tutor={tutor.data} />
<BookLesson
user={{
tutorId: tutor.data.id,
imageUrl: tutor.data.image,
name: tutor.data.name,
// TODO: Remove it when we add the changes to the server
notice: tutor.data.notice || 30,
}}
close={closeDialog}
open={open}
/>
</div>
</div>
);
Expand Down
Loading

0 comments on commit f6dc25b

Please sign in to comment.