From d7585bdd34af27495dcbea0fe4f32175c4de15b5 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 21:31:25 +0700 Subject: [PATCH 01/33] all mentors view --- app/mentorship/page.tsx | 12 +++++++----- components/MentorList.tsx | 38 ++++++++++++++++++++++++++++++++++++++ lib/firebaseUtils.ts | 24 ++++++++++++++++++++++++ lib/types.ts | 8 ++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 components/MentorList.tsx diff --git a/app/mentorship/page.tsx b/app/mentorship/page.tsx index 68b17f6..9758bf0 100644 --- a/app/mentorship/page.tsx +++ b/app/mentorship/page.tsx @@ -1,5 +1,8 @@ +"use client"; + +import MentorListComponent from "@/components/MentorList"; import PageHeader from "@/components/PageHeader"; -import UnderConstruction from "@/components/UnderConstruction"; +import { useAuth } from "@/contexts/AuthContext"; export default function Mentorship() { return ( @@ -8,10 +11,9 @@ export default function Mentorship() { title="Mentorship" subtitle="Connect mentors with participants and manage mentorship programs." /> - +
+ +
); } diff --git a/components/MentorList.tsx b/components/MentorList.tsx new file mode 100644 index 0000000..4582ce9 --- /dev/null +++ b/components/MentorList.tsx @@ -0,0 +1,38 @@ +import { fetchMentors } from "@/lib/firebaseUtils" +import { FirestoreMentor, FirestoreUser } from "@/lib/types" +import { useEffect, useState } from "react" + +export default function MentorListComponent() { + + const [mentors, setMentors] = useState() + + useEffect(() => { + try { + fetchMentors().then((result) => { + setMentors(result) + }); + } catch (error) { + console.log(error) + } + }, []) + + return ( +
+

All Mentors

+
+ {mentors?.map((m) => ( +
+
+

{m.name}

+

{m.email}

+

Specialization: {m.specialization}

+
+ +
+ ))} +
+
+ ) +} \ No newline at end of file diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index c7516d8..acc9418 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -16,6 +16,7 @@ import { CombinedApplicationData, PortalConfig, Question, + FirestoreMentor, } from "./types"; export { APPLICATION_STATUS } from "./types"; @@ -429,4 +430,27 @@ export async function updateApplicationAcceptanceEmail(userId: string): Promise< console.error(`Error updating application acceptance email for ${userId}:`, error); return false; } +} + +/** + * Fetch list of mentors from database. + */ +export async function fetchMentors(): Promise { + try { + const usersRef = collection(db, 'users'); + const firebaseQuery = query(usersRef, where('mentor', '==', true)); + const querySnapshot = await getDocs(firebaseQuery); + + const users: FirestoreMentor[] = []; + querySnapshot.forEach((doc) => { + users.push({ + id: doc.id, + ...doc.data() + } as FirestoreMentor); + }); + + return users; + } catch { + throw new Error('Failed to fetch mentors'); + } } \ No newline at end of file diff --git a/lib/types.ts b/lib/types.ts index 8d0771a..084fbaa 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -41,6 +41,14 @@ export interface FirestoreUser { year: number; } +export interface FirestoreMentor { + id: string; + email: string; + name: string; + mentor: boolean; + specialization: string; +} + /** * Graded applications are applications that have been scored. * This category is exclusive to the admin portal, and is not recorded in the DB. From defd87f125bbc346cced1e4db02845928d4f3fc7 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 21:35:24 +0700 Subject: [PATCH 02/33] minor layouting --- components/MentorList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/MentorList.tsx b/components/MentorList.tsx index 4582ce9..d6ce834 100644 --- a/components/MentorList.tsx +++ b/components/MentorList.tsx @@ -19,13 +19,13 @@ export default function MentorListComponent() { return (

All Mentors

-
+
{mentors?.map((m) => (

{m.name}

{m.email}

-

Specialization: {m.specialization}

+

{m.specialization.toUpperCase()}

- -
- ))} + ))} +
+ + + + {specializations.map((s) => ( +
+

{s.toUpperCase()}

+
+ {mentors?.filter((ms) => ms.specialization.includes(s)).map((m) => ( +
+
+

{m.name}

+

{m.email}

+

{m.specialization.toUpperCase()}

+
+ +
+ ))} +
+
+ ))} + ) } \ No newline at end of file diff --git a/components/Separator.tsx b/components/Separator.tsx new file mode 100644 index 0000000..bce7f70 --- /dev/null +++ b/components/Separator.tsx @@ -0,0 +1,3 @@ +export default function Separator() { + return
+} \ No newline at end of file From 5def4d56a5c65ab0908d91f9a74f9f367ab19f0e Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 22:13:00 +0700 Subject: [PATCH 04/33] add mentoring availability --- components/MentorList.tsx | 28 ++++++++++++++++++++++++---- lib/types.ts | 9 +++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/components/MentorList.tsx b/components/MentorList.tsx index b929399..74e5adf 100644 --- a/components/MentorList.tsx +++ b/components/MentorList.tsx @@ -49,16 +49,36 @@ export default function MentorListComponent() { - {specializations.map((s) => ( -
+ {specializations.map((s, specializationIndex) => ( +

{s.toUpperCase()}

{mentors?.filter((ms) => ms.specialization.includes(s)).map((m) => (
-
+

{m.name}

{m.email}

-

{m.specialization.toUpperCase()}

+

{m.specialization.toUpperCase()}

+
+ {m.availableMentorings.map((availableMentoring, index) => { + const startDate = new Date(availableMentoring.startTime * 1000) + const startDay = startDate.toLocaleDateString() + const startTimestamp = startDate.toLocaleString('id-ID', { timeStyle: 'short' }) + const start = `${startDay} ${startTimestamp}` + + const endDate = new Date(availableMentoring.startTime * 1000) + const endDay = endDate.toLocaleDateString() + const endTimestamp = endDate.toLocaleString('id-ID', { timeStyle: 'short' }) + const end = `${endDay} ${endTimestamp}` + + return ( +
+ {start} WIB - {end} WIB +
+ ) + } + )} +
+
+ ) +} \ No newline at end of file diff --git a/components/MentorList.tsx b/components/MentorList.tsx index 74e5adf..e355467 100644 --- a/components/MentorList.tsx +++ b/components/MentorList.tsx @@ -2,6 +2,7 @@ import { fetchMentors } from "@/lib/firebaseUtils" import { FirestoreMentor, FirestoreUser } from "@/lib/types" import { useEffect, useState } from "react" import Separator from "./Separator" +import MentorItemComponent from "./MentorItemComponent" export default function MentorListComponent() { const [mentors, setMentors] = useState() @@ -54,36 +55,7 @@ export default function MentorListComponent() {

{s.toUpperCase()}

{mentors?.filter((ms) => ms.specialization.includes(s)).map((m) => ( -
-
-

{m.name}

-

{m.email}

-

{m.specialization.toUpperCase()}

-
- {m.availableMentorings.map((availableMentoring, index) => { - const startDate = new Date(availableMentoring.startTime * 1000) - const startDay = startDate.toLocaleDateString() - const startTimestamp = startDate.toLocaleString('id-ID', { timeStyle: 'short' }) - const start = `${startDay} ${startTimestamp}` - - const endDate = new Date(availableMentoring.startTime * 1000) - const endDay = endDate.toLocaleDateString() - const endTimestamp = endDate.toLocaleString('id-ID', { timeStyle: 'short' }) - const end = `${endDay} ${endTimestamp}` - - return ( -
- {start} WIB - {end} WIB -
- ) - } - )} -
-
- -
+ ))}
From 28e57307d91dd7eb0727b31bef3f794ef3900290 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 22:18:30 +0700 Subject: [PATCH 06/33] better item component --- components/MentorItemComponent.tsx | 41 ++++++++++++++++-------------- components/MentorList.tsx | 13 ++-------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/components/MentorItemComponent.tsx b/components/MentorItemComponent.tsx index ccef089..9ba2bf7 100644 --- a/components/MentorItemComponent.tsx +++ b/components/MentorItemComponent.tsx @@ -1,32 +1,35 @@ import { FirestoreMentor } from "@/lib/types" -export default function MentorItemComponent({mentor: m}: {mentor: FirestoreMentor}) { +export default function MentorItemComponent({ mentor: m }: { mentor: FirestoreMentor }) { return (

{m.name}

{m.email}

{m.specialization.toUpperCase()}

-
- {m.availableMentorings.map((availableMentoring, index) => { - const startDate = new Date(availableMentoring.startTime * 1000) - const startDay = startDate.toLocaleDateString() - const startTimestamp = startDate.toLocaleString('id-ID', { timeStyle: 'short' }) - const start = `${startDay} ${startTimestamp}` - const endDate = new Date(availableMentoring.startTime * 1000) - const endDay = endDate.toLocaleDateString() - const endTimestamp = endDate.toLocaleString('id-ID', { timeStyle: 'short' }) - const end = `${endDay} ${endTimestamp}` + {m.availableMentorings && ( +
+ {m.availableMentorings.map((availableMentoring, index) => { + const startDate = new Date(availableMentoring.startTime * 1000) + const startDay = startDate.toLocaleDateString() + const startTimestamp = startDate.toLocaleString('id-ID', { timeStyle: 'short' }) + const start = `${startDay} ${startTimestamp}` - return ( -
- {start} WIB - {end} WIB -
- ) - } - )} -
+ const endDate = new Date(availableMentoring.startTime * 1000) + const endDay = endDate.toLocaleDateString() + const endTimestamp = endDate.toLocaleString('id-ID', { timeStyle: 'short' }) + const end = `${endDay} ${endTimestamp}` + + return ( +
+ {start} WIB - {end} WIB +
+ ) + } + )} +
+ )}
-
+ {mentors?.map((m, index) => ( + ))}
From d04699beec0f9fab2d5f136ff536bbb33eeb02b5 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 22:31:08 +0700 Subject: [PATCH 07/33] add back button --- app/mentorship/[mentorId]/layout.tsx | 23 +++++++++++++++++++++++ app/mentorship/[mentorId]/page.tsx | 18 ++++++++++++++++++ components/MentorItemComponent.tsx | 5 +++-- 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 app/mentorship/[mentorId]/layout.tsx create mode 100644 app/mentorship/[mentorId]/page.tsx diff --git a/app/mentorship/[mentorId]/layout.tsx b/app/mentorship/[mentorId]/layout.tsx new file mode 100644 index 0000000..f157aa4 --- /dev/null +++ b/app/mentorship/[mentorId]/layout.tsx @@ -0,0 +1,23 @@ +"use client" + +import { ChevronLeft } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { ReactNode } from "react"; + +export default function MentorDetailLayout( + { children }: { children: ReactNode } +) { + const router = useRouter() + + return ( +
+
+ +
+ {children} +
+ ) +} \ No newline at end of file diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx new file mode 100644 index 0000000..cab31ac --- /dev/null +++ b/app/mentorship/[mentorId]/page.tsx @@ -0,0 +1,18 @@ +"use client" + +import { useParams } from "next/navigation" +import { useEffect } from "react" + +export default function MentorDetailPage() { + const params = useParams<{ mentorId: string }>() + + useEffect(() => { + console.log("prams", params.mentorId) + }, []) + + return ( +
+ {params.mentorId} +
+ ) +} \ No newline at end of file diff --git a/components/MentorItemComponent.tsx b/components/MentorItemComponent.tsx index 9ba2bf7..294d0fb 100644 --- a/components/MentorItemComponent.tsx +++ b/components/MentorItemComponent.tsx @@ -1,8 +1,9 @@ import { FirestoreMentor } from "@/lib/types" +import Link from "next/link" export default function MentorItemComponent({ mentor: m }: { mentor: FirestoreMentor }) { return ( -
+

{m.name}

{m.email}

@@ -34,6 +35,6 @@ export default function MentorItemComponent({ mentor: m }: { mentor: FirestoreMe -
+ ) } \ No newline at end of file From 74a3e635a354ba52376ea610e59df727462e5bd9 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 22:43:43 +0700 Subject: [PATCH 08/33] init mentor detail --- app/mentorship/[mentorId]/page.tsx | 33 +++++++++++--- lib/firebaseUtils.ts | 71 +++++++++++++++++++----------- 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index cab31ac..9423625 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -1,18 +1,41 @@ "use client" +import { fetchMentorById } from "@/lib/firebaseUtils" +import { FirestoreMentor } from "@/lib/types" import { useParams } from "next/navigation" -import { useEffect } from "react" +import { useEffect, useState } from "react" export default function MentorDetailPage() { const params = useParams<{ mentorId: string }>() + const [mentor, setMentor] = useState() + useEffect(() => { - console.log("prams", params.mentorId) - }, []) + fetchMentorById(params.mentorId).then((m) => { + if (m) { + setMentor(m) + } + }) + }, [params.mentorId]) return ( -
- {params.mentorId} +
+
+

Mentor Details

+
+

{mentor?.name}

+

{mentor?.email}

+

Specialization: {mentor?.specialization.toUpperCase()}

+
+ +
+ +
+

Available Slots

+
+ +
+
) } \ No newline at end of file diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index acc9418..d765245 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -33,7 +33,7 @@ export async function fetchAllApplications(): Promise { const applicationsRef = collection(db, 'applications'); const q = query(applicationsRef, orderBy('createdAt', 'desc')); const querySnapshot = await getDocs(q); - + const applications: FirestoreApplication[] = []; querySnapshot.forEach((doc) => { applications.push({ @@ -41,7 +41,7 @@ export async function fetchAllApplications(): Promise { ...doc.data() } as FirestoreApplication); }); - + return applications; } catch (error) { console.error('Error fetching applications:', error); @@ -57,7 +57,7 @@ export async function fetchAllUsers(status?: string): Promise { const usersRef = collection(db, 'users'); const firebaseQuery = status ? query(usersRef, where('status', '==', status)) : usersRef; const querySnapshot = await getDocs(firebaseQuery); - + const users: FirestoreUser[] = []; querySnapshot.forEach((doc) => { users.push({ @@ -65,7 +65,7 @@ export async function fetchAllUsers(status?: string): Promise { ...doc.data() } as FirestoreUser); }); - + return users; } catch { throw new Error('Failed to fetch users'); @@ -79,7 +79,7 @@ export async function fetchUserById(userId: string): Promise { usersMap.set(user.id, user); }); - + const combinedData: CombinedApplicationData[] = applications .map(application => { const user = usersMap.get(application.id); - + if (!user) { return null; } - + return { id: application.id, accommodations: application.accommodations, @@ -167,7 +167,7 @@ export async function fetchApplicationsWithUsers(status?: string, minScore?: num .filter((item): item is CombinedApplicationData => item !== null); return combinedData; - + } catch { throw new Error('Failed to fetch applications with users'); } @@ -183,7 +183,7 @@ export function getEducationLevel(education: string): string { 'University / College (Graduate)': 'Graduate', 'Other': 'Other' }; - + return educationMap[education] || education; } @@ -221,7 +221,7 @@ export async function getPortalConfig(): Promise { try { const configRef = doc(db, 'config', 'portalConfig'); const configSnap = await getDoc(configRef); - + if (!configSnap.exists()) { return null; } @@ -239,7 +239,7 @@ export async function getPortalConfig(): Promise { }; return config; - + } catch { return null; } @@ -252,7 +252,7 @@ export async function getPortalConfig(): Promise { export async function updatePortalConfig(config: PortalConfig): Promise { try { const configRef = doc(db, 'config', 'portalConfig'); - + // Convert Date objects to Firestore timestamps const firestoreData = { applicationCloseDate: Timestamp.fromDate(config.applicationCloseDate), @@ -265,7 +265,7 @@ export async function updatePortalConfig(config: PortalConfig): Promise await updateDoc(configRef, firestoreData); return true; - + } catch { return false; } @@ -293,22 +293,22 @@ export async function updateUserStatus(userId: string, status: string): Promise< * @param evaluationNotes - Optional evaluation notes */ export async function updateApplicationScore( - applicationId: string, - score: number, + applicationId: string, + score: number, evaluationNotes?: string ): Promise { try { const applicationRef = doc(db, 'applications', applicationId); - + const updateData: { score: number; evaluationNotes?: string; updatedAt: string } = { score, updatedAt: new Date().toISOString() }; - + if (evaluationNotes && evaluationNotes.trim() !== '') { updateData.evaluationNotes = evaluationNotes.trim(); } - + await updateDoc(applicationRef, updateData); return true; } catch { @@ -349,7 +349,7 @@ export async function fetchQuestions(): Promise> { try { const questionsRef = collection(db, 'questions'); const querySnapshot = await getDocs(questionsRef); - + const questions = new Map(); querySnapshot.forEach((doc) => { questions.set(doc.id, { @@ -357,7 +357,7 @@ export async function fetchQuestions(): Promise> { ...doc.data() } as Question); }); - + questionsCache = questions; return questions; } catch (error) { @@ -373,11 +373,11 @@ export async function getQuestionText(questionId: string): Promise { try { const questions = await fetchQuestions(); const question = questions.get(questionId); - + if (question) { return question.text; } - + // Fallback: format the ID as a readable title return formatQuestionId(questionId); } catch (error) { @@ -440,7 +440,7 @@ export async function fetchMentors(): Promise { const usersRef = collection(db, 'users'); const firebaseQuery = query(usersRef, where('mentor', '==', true)); const querySnapshot = await getDocs(firebaseQuery); - + const users: FirestoreMentor[] = []; querySnapshot.forEach((doc) => { users.push({ @@ -448,9 +448,30 @@ export async function fetchMentors(): Promise { ...doc.data() } as FirestoreMentor); }); - + return users; } catch { throw new Error('Failed to fetch mentors'); } +} + +/** + * Fetch mentor from db provided user id. + */ +export async function fetchMentorById(uid: string): Promise { + try { + const userRef = doc(db, 'users', uid); + const userSnap = await getDoc(userRef); + + if (userSnap.exists()) { + return { + id: userSnap.id, + ...userSnap.data() + } as FirestoreMentor; + } else { + return null; + } + } catch { + return null; + } } \ No newline at end of file From a5963caf9616fa615e1072764e910b54464a3d54 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 22:57:50 +0700 Subject: [PATCH 09/33] init mentorings time slots --- app/mentorship/[mentorId]/page.tsx | 12 +++++++++--- components/MentorItemComponent.tsx | 13 ++----------- lib/helpers.ts | 7 +++++++ 3 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 lib/helpers.ts diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 9423625..2e099a4 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -1,7 +1,8 @@ "use client" import { fetchMentorById } from "@/lib/firebaseUtils" -import { FirestoreMentor } from "@/lib/types" +import { epochToStringDate } from "@/lib/helpers" +import { AvailableMentoring, FirestoreMentor } from "@/lib/types" import { useParams } from "next/navigation" import { useEffect, useState } from "react" @@ -9,11 +10,13 @@ export default function MentorDetailPage() { const params = useParams<{ mentorId: string }>() const [mentor, setMentor] = useState() + const [availableMentorings, setAvailableMentorings] = useState() useEffect(() => { fetchMentorById(params.mentorId).then((m) => { if (m) { setMentor(m) + setAvailableMentorings(m.availableMentorings) } }) }, [params.mentorId]) @@ -27,13 +30,16 @@ export default function MentorDetailPage() {

{mentor?.email}

Specialization: {mentor?.specialization.toUpperCase()}

-

Available Slots

- + {availableMentorings?.map((availableMentoring, availableMentoringIndex) => { + return ( +
{epochToStringDate(availableMentoring.startTime)}
+ ) + })}
diff --git a/components/MentorItemComponent.tsx b/components/MentorItemComponent.tsx index 294d0fb..728cac3 100644 --- a/components/MentorItemComponent.tsx +++ b/components/MentorItemComponent.tsx @@ -1,3 +1,4 @@ +import { epochToStringDate } from "@/lib/helpers" import { FirestoreMentor } from "@/lib/types" import Link from "next/link" @@ -12,19 +13,9 @@ export default function MentorItemComponent({ mentor: m }: { mentor: FirestoreMe {m.availableMentorings && (
{m.availableMentorings.map((availableMentoring, index) => { - const startDate = new Date(availableMentoring.startTime * 1000) - const startDay = startDate.toLocaleDateString() - const startTimestamp = startDate.toLocaleString('id-ID', { timeStyle: 'short' }) - const start = `${startDay} ${startTimestamp}` - - const endDate = new Date(availableMentoring.startTime * 1000) - const endDay = endDate.toLocaleDateString() - const endTimestamp = endDate.toLocaleString('id-ID', { timeStyle: 'short' }) - const end = `${endDay} ${endTimestamp}` - return (
- {start} WIB - {end} WIB + {epochToStringDate(availableMentoring.startTime)} WIB - {epochToStringDate(availableMentoring.endTime)} WIB
) } diff --git a/lib/helpers.ts b/lib/helpers.ts new file mode 100644 index 0000000..59d1317 --- /dev/null +++ b/lib/helpers.ts @@ -0,0 +1,7 @@ +export function epochToStringDate(epochSecond: number) { + const startDate = new Date(epochSecond * 1000) + const startDay = startDate.toLocaleDateString() + const startTimestamp = startDate.toLocaleString('id-ID', { timeStyle: 'short' }) + const start = `${startDay} ${startTimestamp}` + return start +} \ No newline at end of file From 21b197851d8e515534fc702bb54338d06e9faaa6 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Wed, 16 Jul 2025 23:07:47 +0700 Subject: [PATCH 10/33] available mentorings --- app/mentorship/[mentorId]/page.tsx | 5 +++++ components/MentoringSlots.tsx | 23 +++++++++++++++++++++++ lib/helpers.ts | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 components/MentoringSlots.tsx diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 2e099a4..7f78154 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -1,5 +1,6 @@ "use client" +import MentoringSlotsComponent from "@/components/MentoringSlots" import { fetchMentorById } from "@/lib/firebaseUtils" import { epochToStringDate } from "@/lib/helpers" import { AvailableMentoring, FirestoreMentor } from "@/lib/types" @@ -34,6 +35,10 @@ export default function MentorDetailPage() {

Available Slots

+ + {/* {availableMentorings && } */} + +
{availableMentorings?.map((availableMentoring, availableMentoringIndex) => { return ( diff --git a/components/MentoringSlots.tsx b/components/MentoringSlots.tsx new file mode 100644 index 0000000..3c19097 --- /dev/null +++ b/components/MentoringSlots.tsx @@ -0,0 +1,23 @@ +import { epochToStringDate } from "@/lib/helpers" +import { AvailableMentoring } from "@/lib/types" + +interface MentoringSlotsComponentProps { + availableMentorings: AvailableMentoring[] +} + +export default function MentoringSlotsComponent( + { availableMentorings }: MentoringSlotsComponentProps +) { + return ( +
+ {availableMentorings.map((availableMentoring, index) => { + return ( +
+ {epochToStringDate(availableMentoring.startTime)} WIB - {epochToStringDate(availableMentoring.endTime)} WIB +
+ ) + } + )} +
+ ) +} \ No newline at end of file diff --git a/lib/helpers.ts b/lib/helpers.ts index 59d1317..9ec54cf 100644 --- a/lib/helpers.ts +++ b/lib/helpers.ts @@ -1,7 +1,7 @@ export function epochToStringDate(epochSecond: number) { const startDate = new Date(epochSecond * 1000) const startDay = startDate.toLocaleDateString() - const startTimestamp = startDate.toLocaleString('id-ID', { timeStyle: 'short' }) + const startTimestamp = startDate.toLocaleString('en-US', { timeStyle: 'short' }) const start = `${startDay} ${startTimestamp}` return start } \ No newline at end of file From f671fd798c140fd55f6ec1946cbacba87059e097 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 00:19:07 +0700 Subject: [PATCH 11/33] add mentoring slots for mentor --- app/mentorship/[mentorId]/page.tsx | 10 +---- components/MentoringSlots.tsx | 68 +++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 7f78154..8f39be2 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -36,16 +36,8 @@ export default function MentorDetailPage() {

Available Slots

- {/* {availableMentorings && } */} + {availableMentorings && } - -
- {availableMentorings?.map((availableMentoring, availableMentoringIndex) => { - return ( -
{epochToStringDate(availableMentoring.startTime)}
- ) - })} -
) diff --git a/components/MentoringSlots.tsx b/components/MentoringSlots.tsx index 3c19097..5058368 100644 --- a/components/MentoringSlots.tsx +++ b/components/MentoringSlots.tsx @@ -8,16 +8,70 @@ interface MentoringSlotsComponentProps { export default function MentoringSlotsComponent( { availableMentorings }: MentoringSlotsComponentProps ) { + const EPOCH_START_MENTORING = 1753340400; + const EPOCH_ENDS_MENTORING = 1753448400; + const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; + const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; + const PIXELS_PER_MINUTE = 1.2; + const TOTAL_HEIGHT = TOTAL_DURATION_MINUTES * PIXELS_PER_MINUTE; + const HOUR_HEIGHT = 60 * PIXELS_PER_MINUTE; + + // Generate hour marks + const hourMarks = []; + const totalHours = Math.ceil(TOTAL_DURATION_MINUTES / 60); + + for (let i = 0; i <= totalHours; i++) { + const hourEpoch = EPOCH_START_MENTORING + (i * 3600); // 3600 seconds = 1 hour + const topPosition = i * HOUR_HEIGHT; + + hourMarks.push( +
+ {epochToStringDate(hourEpoch)} +
+ ); + } + return ( -
+
+ {/* Hour marks */} +
+ {hourMarks} +
+ + {/* Mentoring slots */} {availableMentorings.map((availableMentoring, index) => { + const startOffset = (availableMentoring.startTime - EPOCH_START_MENTORING) / 60; // minutes from start + const duration = (availableMentoring.endTime - availableMentoring.startTime) / 60; // duration in minutes + const topPosition = startOffset * PIXELS_PER_MINUTE; + const height = duration * PIXELS_PER_MINUTE; + return ( -
- {epochToStringDate(availableMentoring.startTime)} WIB - {epochToStringDate(availableMentoring.endTime)} WIB +
+ + {epochToStringDate(availableMentoring.startTime)} - {epochToStringDate(availableMentoring.endTime)} WIB +
- ) - } - )} + ); + })}
- ) + ); } \ No newline at end of file From 8ffb4bd3929143d2d09d70f262ed78a7c1921b4c Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 00:46:44 +0700 Subject: [PATCH 12/33] use mentoring container --- app/mentorship/[mentorId]/page.tsx | 4 +-- components/MentoringSlots.tsx | 42 ++++------------------ components/MentoringSlotsContainer.tsx | 50 ++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 38 deletions(-) create mode 100644 components/MentoringSlotsContainer.tsx diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 8f39be2..fdc3b7c 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -34,10 +34,8 @@ export default function MentorDetailPage() {
-

Available Slots

- +

Mentoring Slots

{availableMentorings && } -
) diff --git a/components/MentoringSlots.tsx b/components/MentoringSlots.tsx index 5058368..3544202 100644 --- a/components/MentoringSlots.tsx +++ b/components/MentoringSlots.tsx @@ -1,5 +1,6 @@ import { epochToStringDate } from "@/lib/helpers" import { AvailableMentoring } from "@/lib/types" +import MentoringSlotsContainer from "./MentoringSlotsContainer"; interface MentoringSlotsComponentProps { availableMentorings: AvailableMentoring[] @@ -10,54 +11,25 @@ export default function MentoringSlotsComponent( ) { const EPOCH_START_MENTORING = 1753340400; const EPOCH_ENDS_MENTORING = 1753448400; + const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; const PIXELS_PER_MINUTE = 1.2; const TOTAL_HEIGHT = TOTAL_DURATION_MINUTES * PIXELS_PER_MINUTE; const HOUR_HEIGHT = 60 * PIXELS_PER_MINUTE; - - // Generate hour marks - const hourMarks = []; - const totalHours = Math.ceil(TOTAL_DURATION_MINUTES / 60); - - for (let i = 0; i <= totalHours; i++) { - const hourEpoch = EPOCH_START_MENTORING + (i * 3600); // 3600 seconds = 1 hour - const topPosition = i * HOUR_HEIGHT; - - hourMarks.push( -
- {epochToStringDate(hourEpoch)} -
- ); - } return ( -
- {/* Hour marks */} -
- {hourMarks} -
- + {/* Mentoring slots */} {availableMentorings.map((availableMentoring, index) => { const startOffset = (availableMentoring.startTime - EPOCH_START_MENTORING) / 60; // minutes from start const duration = (availableMentoring.endTime - availableMentoring.startTime) / 60; // duration in minutes const topPosition = startOffset * PIXELS_PER_MINUTE; const height = duration * PIXELS_PER_MINUTE; - + return ( -
); })} -
+
); } \ No newline at end of file diff --git a/components/MentoringSlotsContainer.tsx b/components/MentoringSlotsContainer.tsx new file mode 100644 index 0000000..9b14bed --- /dev/null +++ b/components/MentoringSlotsContainer.tsx @@ -0,0 +1,50 @@ +import { epochToStringDate } from "@/lib/helpers"; +import { ReactNode } from "react"; + +export default function MentoringSlotsContainer( + { children }: { children: ReactNode } +) { + const EPOCH_START_MENTORING = 1753340400; + const EPOCH_ENDS_MENTORING = 1753448400; + + const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; + const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; + const PIXELS_PER_MINUTE = 1.2; + const TOTAL_HEIGHT = TOTAL_DURATION_MINUTES * PIXELS_PER_MINUTE; + const HOUR_HEIGHT = 60 * PIXELS_PER_MINUTE; + + // Generate hour marks + const hourMarks = []; + const totalHours = Math.ceil(TOTAL_DURATION_MINUTES / 60); + + for (let i = 0; i <= totalHours; i++) { + const hourEpoch = EPOCH_START_MENTORING + (i * 3600); // 3600 seconds = 1 hour + const topPosition = i * HOUR_HEIGHT; + + hourMarks.push( +
+ {epochToStringDate(hourEpoch)} +
+ ); + } + return ( +
+
+
+ {hourMarks} +
+ {children} +
+
+ ) +} \ No newline at end of file From 7a030b5ef31c8b6a918a5098a39119249709b31e Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 01:09:37 +0700 Subject: [PATCH 13/33] improve ui --- components/MentoringSlots.tsx | 14 ++++++++++---- lib/types.ts | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/MentoringSlots.tsx b/components/MentoringSlots.tsx index 3544202..5518b30 100644 --- a/components/MentoringSlots.tsx +++ b/components/MentoringSlots.tsx @@ -1,6 +1,7 @@ import { epochToStringDate } from "@/lib/helpers" import { AvailableMentoring } from "@/lib/types" import MentoringSlotsContainer from "./MentoringSlotsContainer"; +import { MapPin, Video } from "lucide-react"; interface MentoringSlotsComponentProps { availableMentorings: AvailableMentoring[] @@ -30,7 +31,7 @@ export default function MentoringSlotsComponent( return (
- - {epochToStringDate(availableMentoring.startTime)} - {epochToStringDate(availableMentoring.endTime)} WIB - +
+ + {epochToStringDate(availableMentoring.startTime)} - {epochToStringDate(availableMentoring.endTime)} WIB + +

+ Mentor availability is {availableMentoring.location.toUpperCase()} +

+
); })} diff --git a/lib/types.ts b/lib/types.ts index 9cb8cf0..795283b 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -56,6 +56,7 @@ export interface FirestoreMentor { export interface AvailableMentoring { startTime: number; endTime: number; + location: string; } /** From 6ca6b89b2d6e9254f3c928e71b2fe9015724a3e6 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 01:24:59 +0700 Subject: [PATCH 14/33] define mentorship slot --- components/DrawMentorshipSlot.tsx | 39 ++++++++++++++++++++++++++++ components/MentoringSlots.tsx | 42 ++++--------------------------- config.ts | 8 ++++++ 3 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 components/DrawMentorshipSlot.tsx create mode 100644 config.ts diff --git a/components/DrawMentorshipSlot.tsx b/components/DrawMentorshipSlot.tsx new file mode 100644 index 0000000..d3c7199 --- /dev/null +++ b/components/DrawMentorshipSlot.tsx @@ -0,0 +1,39 @@ +import { EPOCH_START_MENTORING, PIXELS_PER_MINUTE } from "@/config"; +import { epochToStringDate } from "@/lib/helpers"; +import { ReactNode } from "react"; + +interface DrawMentorshipSlotProps { + index: number + startTime: number + endTime: number + children?: ReactNode +} + +export default function DrawMentorshipSlot( + { startTime, endTime, index, children }: DrawMentorshipSlotProps +) { + const startOffset = (startTime - EPOCH_START_MENTORING) / 60; // minutes from start + const duration = (endTime - startTime) / 60; // duration in minutes + const topPosition = startOffset * PIXELS_PER_MINUTE; + const height = duration * PIXELS_PER_MINUTE; + + return ( +
+
+ + {epochToStringDate(startTime)} - {epochToStringDate(endTime)} WIB + + {children} +
+
+ ); +} \ No newline at end of file diff --git a/components/MentoringSlots.tsx b/components/MentoringSlots.tsx index 5518b30..be11b37 100644 --- a/components/MentoringSlots.tsx +++ b/components/MentoringSlots.tsx @@ -2,6 +2,7 @@ import { epochToStringDate } from "@/lib/helpers" import { AvailableMentoring } from "@/lib/types" import MentoringSlotsContainer from "./MentoringSlotsContainer"; import { MapPin, Video } from "lucide-react"; +import DrawMentorshipSlot from "./DrawMentorshipSlot"; interface MentoringSlotsComponentProps { availableMentorings: AvailableMentoring[] @@ -10,46 +11,13 @@ interface MentoringSlotsComponentProps { export default function MentoringSlotsComponent( { availableMentorings }: MentoringSlotsComponentProps ) { - const EPOCH_START_MENTORING = 1753340400; - const EPOCH_ENDS_MENTORING = 1753448400; - - const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; - const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; - const PIXELS_PER_MINUTE = 1.2; - const TOTAL_HEIGHT = TOTAL_DURATION_MINUTES * PIXELS_PER_MINUTE; - const HOUR_HEIGHT = 60 * PIXELS_PER_MINUTE; return ( - {/* Mentoring slots */} - {availableMentorings.map((availableMentoring, index) => { - const startOffset = (availableMentoring.startTime - EPOCH_START_MENTORING) / 60; // minutes from start - const duration = (availableMentoring.endTime - availableMentoring.startTime) / 60; // duration in minutes - const topPosition = startOffset * PIXELS_PER_MINUTE; - const height = duration * PIXELS_PER_MINUTE; - - return ( -
-
- - {epochToStringDate(availableMentoring.startTime)} - {epochToStringDate(availableMentoring.endTime)} WIB - -

- Mentor availability is {availableMentoring.location.toUpperCase()} -

-
-
- ); - })} + {availableMentorings.map((availableMentoring, index) => ( + + + ))}
); } \ No newline at end of file diff --git a/config.ts b/config.ts new file mode 100644 index 0000000..b78c60a --- /dev/null +++ b/config.ts @@ -0,0 +1,8 @@ +export const EPOCH_START_MENTORING = 1753340400; +export const EPOCH_ENDS_MENTORING = 1753448400; + +export const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; +export const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; +export const PIXELS_PER_MINUTE = 1.2; +export const TOTAL_HEIGHT = TOTAL_DURATION_MINUTES * PIXELS_PER_MINUTE; +export const HOUR_HEIGHT = 60 * PIXELS_PER_MINUTE; \ No newline at end of file From 6261d4fbcf505137819c23e8bc89af95f14dc264 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 01:28:44 +0700 Subject: [PATCH 15/33] better draw mentorship slot --- components/DrawMentorshipSlot.tsx | 6 +++--- components/MentoringSlots.tsx | 1 + components/MentoringSlotsContainer.tsx | 9 +-------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/components/DrawMentorshipSlot.tsx b/components/DrawMentorshipSlot.tsx index d3c7199..21b4ba2 100644 --- a/components/DrawMentorshipSlot.tsx +++ b/components/DrawMentorshipSlot.tsx @@ -20,12 +20,12 @@ export default function DrawMentorshipSlot( return (
diff --git a/components/MentoringSlots.tsx b/components/MentoringSlots.tsx index be11b37..a2ac8b5 100644 --- a/components/MentoringSlots.tsx +++ b/components/MentoringSlots.tsx @@ -16,6 +16,7 @@ export default function MentoringSlotsComponent( {availableMentorings.map((availableMentoring, index) => ( +

Mentor is available {availableMentoring.location}

))}
diff --git a/components/MentoringSlotsContainer.tsx b/components/MentoringSlotsContainer.tsx index 9b14bed..02b4832 100644 --- a/components/MentoringSlotsContainer.tsx +++ b/components/MentoringSlotsContainer.tsx @@ -1,17 +1,10 @@ +import { EPOCH_START_MENTORING, HOUR_HEIGHT, TOTAL_DURATION_MINUTES, TOTAL_HEIGHT } from "@/config"; import { epochToStringDate } from "@/lib/helpers"; import { ReactNode } from "react"; export default function MentoringSlotsContainer( { children }: { children: ReactNode } ) { - const EPOCH_START_MENTORING = 1753340400; - const EPOCH_ENDS_MENTORING = 1753448400; - - const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; - const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; - const PIXELS_PER_MINUTE = 1.2; - const TOTAL_HEIGHT = TOTAL_DURATION_MINUTES * PIXELS_PER_MINUTE; - const HOUR_HEIGHT = 60 * PIXELS_PER_MINUTE; // Generate hour marks const hourMarks = []; From d028dbc74bb2d396d98e08be4077f7a5e1d9c61c Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 02:03:25 +0700 Subject: [PATCH 16/33] better mentorship schedule view --- app/mentorship/[mentorId]/page.tsx | 31 +++++++++++++++++++++----- components/DrawMentorshipSlot.tsx | 12 +++++----- components/MentoringSlots.tsx | 24 -------------------- components/MentoringSlotsContainer.tsx | 16 ++++++++----- config.ts | 2 +- lib/firebaseUtils.ts | 24 ++++++++++++++++++++ lib/types.ts | 12 ++++++++++ 7 files changed, 78 insertions(+), 43 deletions(-) delete mode 100644 components/MentoringSlots.tsx diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index fdc3b7c..2a4805e 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -1,9 +1,9 @@ "use client" -import MentoringSlotsComponent from "@/components/MentoringSlots" -import { fetchMentorById } from "@/lib/firebaseUtils" -import { epochToStringDate } from "@/lib/helpers" -import { AvailableMentoring, FirestoreMentor } from "@/lib/types" +import DrawMentorshipSlot from "@/components/DrawMentorshipSlot" +import MentoringSlotsContainer from "@/components/MentoringSlotsContainer" +import { fetchBookedMentorshipSlotsByMentorId, fetchMentorById } from "@/lib/firebaseUtils" +import { AvailableMentoring, FirestoreMentor, MentorshipAppointment } from "@/lib/types" import { useParams } from "next/navigation" import { useEffect, useState } from "react" @@ -13,6 +13,8 @@ export default function MentorDetailPage() { const [mentor, setMentor] = useState() const [availableMentorings, setAvailableMentorings] = useState() + const [bookedSlots, setBookedSlots] = useState() + useEffect(() => { fetchMentorById(params.mentorId).then((m) => { if (m) { @@ -20,6 +22,12 @@ export default function MentorDetailPage() { setAvailableMentorings(m.availableMentorings) } }) + + fetchBookedMentorshipSlotsByMentorId(params.mentorId).then((m) => { + if (m) { + setBookedSlots(m) + } + }) }, [params.mentorId]) return ( @@ -34,8 +42,19 @@ export default function MentorDetailPage() {
-

Mentoring Slots

- {availableMentorings && } +

Mentoring Schedule

+ + {availableMentorings && availableMentorings.map((availableMentoring, index) => ( + +

Mentor is available {availableMentoring.location}

+
+ ))} + + {bookedSlots?.map((bookedSlot, index) => ( + + + ))} +
) diff --git a/components/DrawMentorshipSlot.tsx b/components/DrawMentorshipSlot.tsx index 21b4ba2..c702bc9 100644 --- a/components/DrawMentorshipSlot.tsx +++ b/components/DrawMentorshipSlot.tsx @@ -7,10 +7,11 @@ interface DrawMentorshipSlotProps { startTime: number endTime: number children?: ReactNode + asBooking?: boolean } export default function DrawMentorshipSlot( - { startTime, endTime, index, children }: DrawMentorshipSlotProps + { startTime, endTime, index, children, asBooking }: DrawMentorshipSlotProps ) { const startOffset = (startTime - EPOCH_START_MENTORING) / 60; // minutes from start const duration = (endTime - startTime) / 60; // duration in minutes @@ -20,18 +21,17 @@ export default function DrawMentorshipSlot( return (
- - {epochToStringDate(startTime)} - {epochToStringDate(endTime)} WIB - +

{epochToStringDate(startTime)} - {epochToStringDate(endTime)} WIB

{children}
diff --git a/components/MentoringSlots.tsx b/components/MentoringSlots.tsx deleted file mode 100644 index a2ac8b5..0000000 --- a/components/MentoringSlots.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { epochToStringDate } from "@/lib/helpers" -import { AvailableMentoring } from "@/lib/types" -import MentoringSlotsContainer from "./MentoringSlotsContainer"; -import { MapPin, Video } from "lucide-react"; -import DrawMentorshipSlot from "./DrawMentorshipSlot"; - -interface MentoringSlotsComponentProps { - availableMentorings: AvailableMentoring[] -} - -export default function MentoringSlotsComponent( - { availableMentorings }: MentoringSlotsComponentProps -) { - - return ( - - {availableMentorings.map((availableMentoring, index) => ( - -

Mentor is available {availableMentoring.location}

-
- ))} -
- ); -} \ No newline at end of file diff --git a/components/MentoringSlotsContainer.tsx b/components/MentoringSlotsContainer.tsx index 02b4832..85d6704 100644 --- a/components/MentoringSlotsContainer.tsx +++ b/components/MentoringSlotsContainer.tsx @@ -17,26 +17,30 @@ export default function MentoringSlotsContainer( hourMarks.push(
- {epochToStringDate(hourEpoch)} +
+ {epochToStringDate(hourEpoch)} +
+
+
); } return (
-
- {hourMarks} + {hourMarks} +
+ {children}
- {children}
) diff --git a/config.ts b/config.ts index b78c60a..4a8f19a 100644 --- a/config.ts +++ b/config.ts @@ -3,6 +3,6 @@ export const EPOCH_ENDS_MENTORING = 1753448400; export const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; export const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; -export const PIXELS_PER_MINUTE = 1.2; +export const PIXELS_PER_MINUTE = 2; export const TOTAL_HEIGHT = TOTAL_DURATION_MINUTES * PIXELS_PER_MINUTE; export const HOUR_HEIGHT = 60 * PIXELS_PER_MINUTE; \ No newline at end of file diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index d765245..5728092 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -17,6 +17,7 @@ import { PortalConfig, Question, FirestoreMentor, + MentorshipAppointment, } from "./types"; export { APPLICATION_STATUS } from "./types"; @@ -474,4 +475,27 @@ export async function fetchMentorById(uid: string): Promise { + users.push({ + id: doc.id, + ...doc.data() + } as MentorshipAppointment); + }); + + return users; + } catch { + throw new Error('Failed to fetch mentors'); + } } \ No newline at end of file diff --git a/lib/types.ts b/lib/types.ts index 795283b..21dd3b9 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -59,6 +59,18 @@ export interface AvailableMentoring { location: string; } +/** + * Define appointment booked by hackers for a mentor. Related to collection `mentorships`. + */ +export interface MentorshipAppointment { + id: string; + startTime: number; + endTime: number; + mentorId: string; + hackerId: string; // a hacker book for one team + hackerDescription: string; // inquiry needed by hacker +} + /** * Graded applications are applications that have been scored. * This category is exclusive to the admin portal, and is not recorded in the DB. From a9fcb9052e7bcdf6a73e9b253f03fecd6b1bb659 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 02:20:30 +0700 Subject: [PATCH 17/33] better layouting --- app/mentorship/[mentorId]/page.tsx | 1 + components/DrawMentorshipSlot.tsx | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 2a4805e..a1c33d9 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -52,6 +52,7 @@ export default function MentorDetailPage() { {bookedSlots?.map((bookedSlot, index) => ( +

{bookedSlot.hackerDescription}

))} diff --git a/components/DrawMentorshipSlot.tsx b/components/DrawMentorshipSlot.tsx index c702bc9..8053e4f 100644 --- a/components/DrawMentorshipSlot.tsx +++ b/components/DrawMentorshipSlot.tsx @@ -1,6 +1,7 @@ import { EPOCH_START_MENTORING, PIXELS_PER_MINUTE } from "@/config"; import { epochToStringDate } from "@/lib/helpers"; -import { ReactNode } from "react"; +import { ChevronDown, ChevronUp } from "lucide-react"; +import { ReactNode, useState } from "react"; interface DrawMentorshipSlotProps { index: number @@ -18,6 +19,8 @@ export default function DrawMentorshipSlot( const topPosition = startOffset * PIXELS_PER_MINUTE; const height = duration * PIXELS_PER_MINUTE; + const [openDetail, setOpenDetail] = useState(false) + return (
setOpenDetail(!openDetail)} >
-

{epochToStringDate(startTime)} - {epochToStringDate(endTime)} WIB

- {children} +
+

{asBooking && Booked{" -- "} }{epochToStringDate(startTime)} - {epochToStringDate(endTime)} WIB

+ +
+
+ {children} +
); From 6e2ad266d6d4abc6f59108431bf4c50781a3449a Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 14:01:29 +0700 Subject: [PATCH 18/33] change mentorship impl; edit type --- app/mentorship/[mentorId]/page.tsx | 30 ++++++++---------------------- lib/firebaseUtils.ts | 2 +- lib/types.ts | 12 +++--------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index a1c33d9..16f6e66 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -1,9 +1,7 @@ "use client" -import DrawMentorshipSlot from "@/components/DrawMentorshipSlot" -import MentoringSlotsContainer from "@/components/MentoringSlotsContainer" -import { fetchBookedMentorshipSlotsByMentorId, fetchMentorById } from "@/lib/firebaseUtils" -import { AvailableMentoring, FirestoreMentor, MentorshipAppointment } from "@/lib/types" +import { fetchMentorshipAppointmentsByMentorId, fetchMentorById } from "@/lib/firebaseUtils" +import { FirestoreMentor, MentorshipAppointment } from "@/lib/types" import { useParams } from "next/navigation" import { useEffect, useState } from "react" @@ -11,21 +9,19 @@ export default function MentorDetailPage() { const params = useParams<{ mentorId: string }>() const [mentor, setMentor] = useState() - const [availableMentorings, setAvailableMentorings] = useState() - const [bookedSlots, setBookedSlots] = useState() + const [mentorshipAppointments, setMentorshipAppointments] = useState() useEffect(() => { fetchMentorById(params.mentorId).then((m) => { if (m) { setMentor(m) - setAvailableMentorings(m.availableMentorings) } }) - fetchBookedMentorshipSlotsByMentorId(params.mentorId).then((m) => { + fetchMentorshipAppointmentsByMentorId(params.mentorId).then((m) => { if (m) { - setBookedSlots(m) + setMentorshipAppointments(m) } }) }, [params.mentorId]) @@ -43,19 +39,9 @@ export default function MentorDetailPage() {

Mentoring Schedule

- - {availableMentorings && availableMentorings.map((availableMentoring, index) => ( - -

Mentor is available {availableMentoring.location}

-
- ))} - - {bookedSlots?.map((bookedSlot, index) => ( - -

{bookedSlot.hackerDescription}

-
- ))} -
+
+ +
) diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index 5728092..30470e2 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -480,7 +480,7 @@ export async function fetchMentorById(uid: string): Promise Date: Thu, 17 Jul 2025 14:18:18 +0700 Subject: [PATCH 19/33] add better layouting --- app/mentorship/[mentorId]/page.tsx | 5 +++- .../MentorshipAppointmentCardComponent.tsx | 27 +++++++++++++++++++ lib/firebaseUtils.ts | 12 ++++++--- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 components/MentorshipAppointmentCardComponent.tsx diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 16f6e66..f89eb98 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -1,5 +1,6 @@ "use client" +import MentorshipAppointmentCardComponent from "@/components/MentorshipAppointmentCardComponent" import { fetchMentorshipAppointmentsByMentorId, fetchMentorById } from "@/lib/firebaseUtils" import { FirestoreMentor, MentorshipAppointment } from "@/lib/types" import { useParams } from "next/navigation" @@ -40,7 +41,9 @@ export default function MentorDetailPage() {

Mentoring Schedule

- + {mentorshipAppointments?.map((mentorshipAppointment) => ( + + ))}
diff --git a/components/MentorshipAppointmentCardComponent.tsx b/components/MentorshipAppointmentCardComponent.tsx new file mode 100644 index 0000000..646bee5 --- /dev/null +++ b/components/MentorshipAppointmentCardComponent.tsx @@ -0,0 +1,27 @@ +import { epochToStringDate } from "@/lib/helpers" +import { MentorshipAppointment } from "@/lib/types" +import Separator from "./Separator" + +interface MentorshipAppointmentCardComponentProps { + mentorshipAppointment: MentorshipAppointment +} + +export default function MentorshipAppointmentCardComponent( + { mentorshipAppointment }: MentorshipAppointmentCardComponentProps +) { + return ( +
+
+ {mentorshipAppointment.hackerId ? Booked : Available} +

Mentoring ID : {mentorshipAppointment.id}

+
+
+ {epochToStringDate(mentorshipAppointment.startTime)} - {epochToStringDate(mentorshipAppointment.endTime)} {(mentorshipAppointment.endTime - mentorshipAppointment.startTime) / 60} minutes +
+ +
+ {mentorshipAppointment.hackerDescription} +
+
+ ) +} \ No newline at end of file diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index 30470e2..9a97e85 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -5,9 +5,9 @@ import { getDoc, updateDoc, query, - orderBy, Timestamp, where, + orderBy, } from "firebase/firestore"; import { db, auth } from "./firebase"; import { @@ -483,7 +483,12 @@ export async function fetchMentorById(uid: string): Promise Date: Thu, 17 Jul 2025 14:22:17 +0700 Subject: [PATCH 20/33] change mentor layouting --- app/mentorship/[mentorId]/page.tsx | 8 +++++++- components/MentorshipAppointmentCardComponent.tsx | 11 +++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index f89eb98..5169494 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -3,6 +3,7 @@ import MentorshipAppointmentCardComponent from "@/components/MentorshipAppointmentCardComponent" import { fetchMentorshipAppointmentsByMentorId, fetchMentorById } from "@/lib/firebaseUtils" import { FirestoreMentor, MentorshipAppointment } from "@/lib/types" +import { Plus } from "lucide-react" import { useParams } from "next/navigation" import { useEffect, useState } from "react" @@ -39,7 +40,12 @@ export default function MentorDetailPage() {
-

Mentoring Schedule

+
+

Mentoring Schedule

+ +
{mentorshipAppointments?.map((mentorshipAppointment) => ( diff --git a/components/MentorshipAppointmentCardComponent.tsx b/components/MentorshipAppointmentCardComponent.tsx index 646bee5..49b629c 100644 --- a/components/MentorshipAppointmentCardComponent.tsx +++ b/components/MentorshipAppointmentCardComponent.tsx @@ -13,15 +13,18 @@ export default function MentorshipAppointmentCardComponent(
{mentorshipAppointment.hackerId ? Booked : Available} -

Mentoring ID : {mentorshipAppointment.id}

+

Mentoring ID : {mentorshipAppointment.id}

{epochToStringDate(mentorshipAppointment.startTime)} - {epochToStringDate(mentorshipAppointment.endTime)} {(mentorshipAppointment.endTime - mentorshipAppointment.startTime) / 60} minutes
-
- {mentorshipAppointment.hackerDescription} -
+ {mentorshipAppointment.hackerDescription && ( +
+

Hacker's Inquiry:

+

{mentorshipAppointment.hackerDescription}

+
+ )}
) } \ No newline at end of file From fd462a708b4e5694a385ad37411ae756aeadd16e Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 14:34:29 +0700 Subject: [PATCH 21/33] add mentorship page --- app/mentorship/[mentorId]/page.tsx | 13 +++++++---- app/mentorship/add/page.tsx | 26 ++++++++++++++++++++++ app/mentorship/{[mentorId] => }/layout.tsx | 0 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 app/mentorship/add/page.tsx rename app/mentorship/{[mentorId] => }/layout.tsx (100%) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 5169494..9a6e497 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -4,14 +4,13 @@ import MentorshipAppointmentCardComponent from "@/components/MentorshipAppointme import { fetchMentorshipAppointmentsByMentorId, fetchMentorById } from "@/lib/firebaseUtils" import { FirestoreMentor, MentorshipAppointment } from "@/lib/types" import { Plus } from "lucide-react" -import { useParams } from "next/navigation" +import { useParams, useRouter } from "next/navigation" import { useEffect, useState } from "react" export default function MentorDetailPage() { const params = useParams<{ mentorId: string }>() - + const router = useRouter() const [mentor, setMentor] = useState() - const [mentorshipAppointments, setMentorshipAppointments] = useState() useEffect(() => { @@ -28,6 +27,10 @@ export default function MentorDetailPage() { }) }, [params.mentorId]) + const handleOnClickAddAppointment = (mentorId: string) => { + router.push(`/mentorship/add?mentorId=${mentorId}`) + } + return (
@@ -42,7 +45,9 @@ export default function MentorDetailPage() {

Mentoring Schedule

-
diff --git a/app/mentorship/add/page.tsx b/app/mentorship/add/page.tsx new file mode 100644 index 0000000..c828871 --- /dev/null +++ b/app/mentorship/add/page.tsx @@ -0,0 +1,26 @@ +"use client" + +import { useParams } from "next/navigation" +import { useEffect } from "react" + +export default function AddMentorshipAppointmentPage() { + const params = useParams<{ mentorId: string }>() + + useEffect(() => { + + }, [params.mentorId]) + + return ( +
+
+

Add Mentorship Appointment Slot

+

Add a mentorship slot for a mentor that can be booked immediately by hackers.

+
+ +
+ +
+ +
+ ) +} \ No newline at end of file diff --git a/app/mentorship/[mentorId]/layout.tsx b/app/mentorship/layout.tsx similarity index 100% rename from app/mentorship/[mentorId]/layout.tsx rename to app/mentorship/layout.tsx From d725dafecb7771d2c81d6845914ca2c475e1507b Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 15:20:47 +0700 Subject: [PATCH 22/33] better add mentorship slot ui --- app/mentorship/add/page.tsx | 85 +++++++++++++++++++++++++++++---- lib/helpers.ts | 4 ++ package-lock.json | 94 +++++++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 175 insertions(+), 9 deletions(-) diff --git a/app/mentorship/add/page.tsx b/app/mentorship/add/page.tsx index c828871..0b53b68 100644 --- a/app/mentorship/add/page.tsx +++ b/app/mentorship/add/page.tsx @@ -1,26 +1,93 @@ "use client" -import { useParams } from "next/navigation" -import { useEffect } from "react" +import { fetchMentorById } from "@/lib/firebaseUtils" +import { dateToStringTime, epochToStringDate } from "@/lib/helpers" +import { FirestoreMentor } from "@/lib/types" +import { useSearchParams } from "next/navigation" +import { useEffect, useState } from "react" +import DatePicker from "react-datepicker" +import "react-datepicker/dist/react-datepicker.css"; export default function AddMentorshipAppointmentPage() { - const params = useParams<{ mentorId: string }>() + const TIME_INTERVAL = 15 + const params = useSearchParams() + const mentorId = params.get('mentorId') + + const [mentor, setMentor] = useState() + const [mentorName, setMentorName] = useState('') + + const [startDate, setStartDate] = useState(new Date()); + const [nTime, setNTime] = useState(1) + const [endDate, setEndDate] = useState(new Date()); + const [appointmentType, setAppointmentType] = useState(''); + const [error, setError] = useState('') + + const handleTypeChange = (event: React.ChangeEvent) => { + setAppointmentType(event.target.value); + }; + + const handleSubmit = async () => { + } useEffect(() => { + if (mentorId) { + fetchMentorById(mentorId).then((result) => { + setMentor(result) + setMentorName(result?.name) + }) + } + }, [mentorId]) - }, [params.mentorId]) return ( -
+

Add Mentorship Appointment Slot

-

Add a mentorship slot for a mentor that can be booked immediately by hackers.

+

Add mentorship slots that can be booked immediately by hackers.

- -
-
+
+
+ Mentor Name + setMentorName(e.target.value)} disabled type="text" className="p-2 rounded-xl bg-zinc-50/20" /> +
+
+ Start Time +
+ date && setStartDate(date)} + showTimeSelect + timeIntervals={15} + className="bg-zinc-50/20 p-2 rounded-lg" + /> + {dateToStringTime(startDate || new Date())} +
+
+ +
+ Create For N Times + setNTime(Number(e.target.value))} type="number" className="p-2 rounded-xl bg-zinc-50/20" /> + {startDate &&

This will create {nTime} slot(s) with interval {TIME_INTERVAL} minutes starting from {epochToStringDate(startDate.getTime() / 1000)}

} + +
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+
) } \ No newline at end of file diff --git a/lib/helpers.ts b/lib/helpers.ts index 9ec54cf..4ea888a 100644 --- a/lib/helpers.ts +++ b/lib/helpers.ts @@ -1,3 +1,7 @@ +export function dateToStringTime(date: Date) { + return date.toLocaleString('en-US', { timeStyle: 'short'}) +} + export function epochToStringDate(epochSecond: number) { const startDate = new Date(epochSecond * 1000) const startDay = startDate.toLocaleDateString() diff --git a/package-lock.json b/package-lock.json index 8c4e5ca..ba9c4f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "next": "15.1.6", "nodemailer": "^7.0.3", "react": "^19.0.0", + "react-datepicker": "^8.4.0", "react-dom": "^19.0.0", "react-hot-toast": "^2.5.2" }, @@ -734,6 +735,59 @@ "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz", "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==" }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.13", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.13.tgz", + "integrity": "sha512-Qmj6t9TjgWAvbygNEu1hj4dbHI9CY0ziCMIJrmYoDIn9TUAH5lRmiIeZmRd4c6QEZkzdoH7jNnoNyoY1AIESiA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.4", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz", + "integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@grpc/grpc-js": { "version": "1.9.15", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", @@ -2372,6 +2426,15 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -2514,6 +2577,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -5169,6 +5242,21 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.4.0.tgz", + "integrity": "sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.3", + "clsx": "^2.1.1", + "date-fns": "^4.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", @@ -5946,6 +6034,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", diff --git a/package.json b/package.json index 70f2398..4534af0 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "next": "15.1.6", "nodemailer": "^7.0.3", "react": "^19.0.0", + "react-datepicker": "^8.4.0", "react-dom": "^19.0.0", "react-hot-toast": "^2.5.2" }, From 1e07e8f7b5c4c778101fe0b00e9ed3601a9764ce Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 15:43:43 +0700 Subject: [PATCH 23/33] proper add mentorship slots --- app/mentorship/add/page.tsx | 64 +++++++++++++++++++++++++++++-------- config.ts | 2 ++ lib/firebaseUtils.ts | 27 ++++++++++++++-- lib/types.ts | 6 ++-- 4 files changed, 81 insertions(+), 18 deletions(-) diff --git a/app/mentorship/add/page.tsx b/app/mentorship/add/page.tsx index 0b53b68..ac45fc9 100644 --- a/app/mentorship/add/page.tsx +++ b/app/mentorship/add/page.tsx @@ -1,32 +1,66 @@ "use client" -import { fetchMentorById } from "@/lib/firebaseUtils" +import { addMentorshipAppointment, fetchMentorById } from "@/lib/firebaseUtils" import { dateToStringTime, epochToStringDate } from "@/lib/helpers" -import { FirestoreMentor } from "@/lib/types" -import { useSearchParams } from "next/navigation" +import { FirestoreMentor, MentorshipAppointment } from "@/lib/types" +import { Loader2 } from "lucide-react" +import { useRouter, useSearchParams } from "next/navigation" import { useEffect, useState } from "react" import DatePicker from "react-datepicker" import "react-datepicker/dist/react-datepicker.css"; export default function AddMentorshipAppointmentPage() { + const router = useRouter() const TIME_INTERVAL = 15 const params = useSearchParams() const mentorId = params.get('mentorId') const [mentor, setMentor] = useState() const [mentorName, setMentorName] = useState('') - const [startDate, setStartDate] = useState(new Date()); - const [nTime, setNTime] = useState(1) - const [endDate, setEndDate] = useState(new Date()); - const [appointmentType, setAppointmentType] = useState(''); + const [nTime, setNTime] = useState(1) + const [appointmentType, setAppointmentType] = useState('online'); const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + const handleTypeChange = (event: React.ChangeEvent) => { setAppointmentType(event.target.value); }; const handleSubmit = async () => { + setLoading(true) + + try { + if (nTime <= 0) { + setError('Must create minimum 1 slot') + return + } else { + setError('') + } + + if (!startDate) { + setError('Start date cannot be empty') + return + } + + if (!mentorId) { + setError('Mentor Id cannot be empty') + return + } + + if (!location) { + setError('Location cannot be empty') + return + } + addMentorshipAppointment(startDate.getTime() / 1000, mentorId, appointmentType).then((res) => { + router.replace(`/mentorship/${mentorId}`) + }) + } catch (error) { + console.log(error) + } finally { + setLoading(false) + } } useEffect(() => { @@ -74,18 +108,22 @@ export default function AddMentorshipAppointmentPage() {
-
- - -
+
+ + +
-
- +
+ {error && {error}} +
diff --git a/config.ts b/config.ts index 4a8f19a..acc7954 100644 --- a/config.ts +++ b/config.ts @@ -1,6 +1,8 @@ export const EPOCH_START_MENTORING = 1753340400; export const EPOCH_ENDS_MENTORING = 1753448400; +export const ONE_SLOT_INTERVAL_MINUTES = 15; // one mentorship slot is defined as 15 minutes + export const TOTAL_DURATION_SECONDS = EPOCH_ENDS_MENTORING - EPOCH_START_MENTORING; export const TOTAL_DURATION_MINUTES = TOTAL_DURATION_SECONDS / 60; export const PIXELS_PER_MINUTE = 2; diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index 9a97e85..8dd6e5c 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -8,6 +8,7 @@ import { Timestamp, where, orderBy, + addDoc, } from "firebase/firestore"; import { db, auth } from "./firebase"; import { @@ -19,6 +20,7 @@ import { FirestoreMentor, MentorshipAppointment, } from "./types"; +import { ONE_SLOT_INTERVAL_MINUTES } from "@/config"; export { APPLICATION_STATUS } from "./types"; export type { CombinedApplicationData } from "./types"; @@ -482,10 +484,10 @@ export async function fetchMentorById(uid: string): Promise Date: Thu, 17 Jul 2025 15:46:11 +0700 Subject: [PATCH 24/33] add batch add slot --- app/mentorship/[mentorId]/page.tsx | 2 +- app/mentorship/add/page.tsx | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 9a6e497..a82f9c1 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -51,7 +51,7 @@ export default function MentorDetailPage() {
-
+
{mentorshipAppointments?.map((mentorshipAppointment) => ( ))} diff --git a/app/mentorship/add/page.tsx b/app/mentorship/add/page.tsx index ac45fc9..a39cba2 100644 --- a/app/mentorship/add/page.tsx +++ b/app/mentorship/add/page.tsx @@ -1,5 +1,6 @@ "use client" +import { ONE_SLOT_INTERVAL_MINUTES } from "@/config" import { addMentorshipAppointment, fetchMentorById } from "@/lib/firebaseUtils" import { dateToStringTime, epochToStringDate } from "@/lib/helpers" import { FirestoreMentor, MentorshipAppointment } from "@/lib/types" @@ -53,9 +54,12 @@ export default function AddMentorshipAppointmentPage() { setError('Location cannot be empty') return } - addMentorshipAppointment(startDate.getTime() / 1000, mentorId, appointmentType).then((res) => { - router.replace(`/mentorship/${mentorId}`) - }) + for (let index = 0; index < nTime; index++) { + const INTERVALS_SECONDS = ONE_SLOT_INTERVAL_MINUTES * 60 * index; + addMentorshipAppointment((startDate.getTime() / 1000) + INTERVALS_SECONDS, mentorId, appointmentType).then((res) => { + router.replace(`/mentorship/${mentorId}`) + }) + } } catch (error) { console.log(error) } finally { From 643e2775c7b5b8118cf295c33bbf167343e3db1e Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 15:58:40 +0700 Subject: [PATCH 25/33] remove unused code --- components/MentorItemComponent.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/components/MentorItemComponent.tsx b/components/MentorItemComponent.tsx index 728cac3..55f1ba6 100644 --- a/components/MentorItemComponent.tsx +++ b/components/MentorItemComponent.tsx @@ -9,19 +9,6 @@ export default function MentorItemComponent({ mentor: m }: { mentor: FirestoreMe

{m.name}

{m.email}

{m.specialization.toUpperCase()}

- - {m.availableMentorings && ( -
- {m.availableMentorings.map((availableMentoring, index) => { - return ( -
- {epochToStringDate(availableMentoring.startTime)} WIB - {epochToStringDate(availableMentoring.endTime)} WIB -
- ) - } - )} -
- )}
+
+ + {/* Modal for confirmation */} + {deleteModalActive && ( +
+
+

Confirm Deletion

+

+ Are you sure you want to delete this appointment? This action cannot be undone. +

+
+ + +
+
+
+ )}
- ) + ); } \ No newline at end of file diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index 8dd6e5c..b229ac8 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -9,6 +9,7 @@ import { where, orderBy, addDoc, + deleteDoc, } from "firebase/firestore"; import { db, auth } from "./firebase"; import { @@ -21,6 +22,7 @@ import { MentorshipAppointment, } from "./types"; import { ONE_SLOT_INTERVAL_MINUTES } from "@/config"; +import { error } from "console"; export { APPLICATION_STATUS } from "./types"; export type { CombinedApplicationData } from "./types"; @@ -527,4 +529,18 @@ export async function addMentorshipAppointment(startDate: number, mentorId: stri console.log(error) throw new Error('Failed to add a new mentorship appointment slot') } +} + +/** + * Delete mentorship slot. + */ +export async function deleteMentorshipAppointment(mentorshipId: string) { + try { + const docRef = doc(db, 'mentorships', mentorshipId); + await deleteDoc(docRef); + return true + } catch (error) { + console.log(error) + throw new Error('Error when deleting a mentorship appointment') + } } \ No newline at end of file From a6702fff352d772731597590b6aa19ea59f30a96 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 19:41:38 +0700 Subject: [PATCH 27/33] better mentor layouting --- .gitignore | 4 + app/mentorship/[mentorId]/page.tsx | 7 + lib/types.ts | 4 +- package-lock.json | 1435 +++++++++++++++++++++++++++- package.json | 3 + tsconfig.json | 3 +- 6 files changed, 1419 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 5ef6a52..388da46 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# for dev only - Ryan +/app/api/create-mentors/** +*adminsdk*.* diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index a82f9c1..431ac1f 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -38,7 +38,13 @@ export default function MentorDetailPage() {

{mentor?.name}

{mentor?.email}

+

+ Discord: {mentor?.discordUsername}

Specialization: {mentor?.specialization.toUpperCase()}

+
+

Intro

+

{mentor?.intro}

+
@@ -51,6 +57,7 @@ export default function MentorDetailPage() {
+
{mentorshipAppointments?.map((mentorshipAppointment) => ( diff --git a/lib/types.ts b/lib/types.ts index d313982..7d55419 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -45,11 +45,13 @@ export interface FirestoreUser { * Mentor type of user. */ export interface FirestoreMentor { - id: string; + id?: string; email: string; name: string; mentor: boolean; specialization: string; + discordUsername: string; + intro: string; // introduction given by mentor } /** diff --git a/package-lock.json b/package-lock.json index ba9c4f8..471b7f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@vercel/analytics": "^1.5.0", "firebase": "^11.8.1", + "firebase-admin": "^13.4.0", "lucide-react": "^0.522.0", "next": "15.1.6", "nodemailer": "^7.0.3", @@ -28,6 +29,8 @@ "eslint-config-next": "15.1.6", "postcss": "^8", "tailwindcss": "^3.4.1", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", "typescript": "^5" } }, @@ -43,6 +46,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -171,6 +198,12 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fastify/busboy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", + "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", + "license": "MIT" + }, "node_modules/@firebase/ai": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.3.0.tgz", @@ -788,6 +821,94 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@google-cloud/firestore": { + "version": "7.11.3", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.3.tgz", + "integrity": "sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.16.0.tgz", + "integrity": "sha512-7/5LRgykyOfQENcm6hDKP8SX/u9XxE5YOiWOkgkwcoO+cG8xT/cyOvp9wwN3IxfdYgpHs8CE7Nq2PKX2lNaEXw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "<4.1.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@grpc/grpc-js": { "version": "1.9.15", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", @@ -1285,6 +1406,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@next/env": { "version": "15.1.6", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.6.tgz", @@ -1463,6 +1595,16 @@ "node": ">=12.4.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1552,12 +1694,106 @@ "tslib": "^2.8.0" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1568,7 +1804,37 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" }, "node_modules/@types/node": { "version": "20.17.17", @@ -1588,6 +1854,18 @@ "@types/node": "*" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.0.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", @@ -1606,6 +1884,47 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", + "optional": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz", @@ -1868,6 +2187,19 @@ } } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -1889,6 +2221,28 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2120,6 +2474,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -2135,6 +2499,23 @@ "node": ">= 0.4" } }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "optional": true + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2174,6 +2555,35 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2208,6 +2618,12 @@ "node": ">=8" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -2241,7 +2657,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, + "devOptional": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2474,6 +2890,19 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2489,6 +2918,13 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2591,7 +3027,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -2644,6 +3079,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2659,6 +3104,16 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -2681,7 +3136,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, + "devOptional": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -2691,18 +3146,50 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -2785,7 +3272,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" } @@ -2794,7 +3281,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" } @@ -2830,7 +3317,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, + "devOptional": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -2842,7 +3329,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, + "devOptional": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -3136,6 +3623,19 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -3145,6 +3645,19 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", @@ -3331,11 +3844,36 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "devOptional": true }, "node_modules/fast-glob": { "version": "3.3.1", @@ -3377,6 +3915,25 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", @@ -3472,6 +4029,46 @@ "@firebase/util": "1.12.0" } }, + "node_modules/firebase-admin": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.4.0.tgz", + "integrity": "sha512-Y8DcyKK+4pl4B93ooiy1G8qvdyRMkcNFfBSh+8rbVcw4cW8dgG0VXCCTp5NUwub8sn9vSPsOwpb9tE2OuFmcfQ==", + "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "@types/node": "^22.8.7", + "farmhash-modern": "^1.1.0", + "google-auth-library": "^9.14.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "node-forge": "^1.3.1", + "uuid": "^11.0.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.11.0", + "@google-cloud/storage": "^7.14.0" + } + }, + "node_modules/firebase-admin/node_modules/@types/node": { + "version": "22.16.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.4.tgz", + "integrity": "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/firebase-admin/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -3522,6 +4119,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", + "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3540,7 +4154,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, + "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3565,6 +4179,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", + "optional": true + }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -3574,6 +4195,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3586,7 +4250,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, + "devOptional": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", @@ -3610,7 +4274,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, + "devOptional": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3741,11 +4405,89 @@ "csstype": "^3.0.10" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", + "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/@grpc/grpc-js": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -3765,6 +4507,19 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -3817,7 +4572,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -3829,7 +4584,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, + "devOptional": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -3844,7 +4599,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, + "devOptional": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -3852,11 +4607,69 @@ "node": ">= 0.4" } }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==" }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/idb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", @@ -3896,6 +4709,13 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4208,6 +5028,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -4352,6 +5184,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4370,6 +5211,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4389,15 +5239,59 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, + "license": "MIT", "bin": { "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" } }, "node_modules/jsx-ast-utils": { @@ -4415,6 +5309,44 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4467,6 +5399,11 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4493,12 +5430,60 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -4522,6 +5507,28 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/lucide-react": { "version": "0.522.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.522.0.tgz", @@ -4531,11 +5538,18 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" } @@ -4562,6 +5576,42 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4579,6 +5629,7 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4595,8 +5646,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mz": { "version": "2.7.0", @@ -4712,6 +5762,35 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/nodemailer": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", @@ -4743,7 +5822,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 6" } @@ -4853,6 +5932,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4891,7 +5980,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -5182,6 +6271,19 @@ "react-is": "^16.13.1" } }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/protobufjs": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", @@ -5300,6 +6402,21 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5400,6 +6517,31 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -5513,7 +6655,6 @@ "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "devOptional": true, "bin": { "semver": "bin/semver.js" }, @@ -5734,6 +6875,23 @@ "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", "dev": true }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", + "optional": true + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -5742,6 +6900,16 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -5966,6 +7134,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", + "optional": true + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -6114,6 +7302,64 @@ "node": ">=6" } }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6147,6 +7393,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", @@ -6165,16 +7417,70 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", + "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/tslib": { @@ -6317,13 +7623,39 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "devOptional": true + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" }, "node_modules/web-vitals": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -6345,6 +7677,16 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6541,6 +7883,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6549,6 +7898,12 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yaml": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", @@ -6623,11 +7978,21 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 4534af0..26a432e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@vercel/analytics": "^1.5.0", "firebase": "^11.8.1", + "firebase-admin": "^13.4.0", "lucide-react": "^0.522.0", "next": "15.1.6", "nodemailer": "^7.0.3", @@ -29,6 +30,8 @@ "eslint-config-next": "15.1.6", "postcss": "^8", "tailwindcss": "^3.4.1", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", "typescript": "^5" } } diff --git a/tsconfig.json b/tsconfig.json index d8b9323..d11a925 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": ["./*"], + "@/lib/*": ["./lib/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], From a7b013c20f6ffacbd29ff46c4ba6d53888ad6b08 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 21:10:37 +0700 Subject: [PATCH 28/33] add image support --- app/mentorship/[mentorId]/page.tsx | 27 +++++++++++++++++++++++++-- lib/firebase.ts | 4 ++++ lib/firebaseUtils.ts | 19 ++++++++++++++++--- next.config.js | 3 +++ next.config.ts | 3 ++- 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 431ac1f..1f411c2 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -1,22 +1,31 @@ "use client" import MentorshipAppointmentCardComponent from "@/components/MentorshipAppointmentCardComponent" -import { fetchMentorshipAppointmentsByMentorId, fetchMentorById } from "@/lib/firebaseUtils" +import { fetchMentorshipAppointmentsByMentorId, fetchMentorById, getMentorProfilePicture } from "@/lib/firebaseUtils" import { FirestoreMentor, MentorshipAppointment } from "@/lib/types" import { Plus } from "lucide-react" +import Image from "next/image" import { useParams, useRouter } from "next/navigation" import { useEffect, useState } from "react" +import ghq from "@/public/assets/ghq.png" export default function MentorDetailPage() { const params = useParams<{ mentorId: string }>() const router = useRouter() const [mentor, setMentor] = useState() const [mentorshipAppointments, setMentorshipAppointments] = useState() + const [mentorUrl, setMentorUrl] = useState('') useEffect(() => { fetchMentorById(params.mentorId).then((m) => { if (m) { setMentor(m) + + getMentorProfilePicture(m.name).then((pp) => { + if (pp) { + setMentorUrl(pp) + } + }) } }) @@ -25,6 +34,8 @@ export default function MentorDetailPage() { setMentorshipAppointments(m) } }) + + }, [params.mentorId]) const handleOnClickAddAppointment = (mentorId: string) => { @@ -36,6 +47,18 @@ export default function MentorDetailPage() {

Mentor Details

+ {mentorUrl && ( + {`Profile { + setMentorUrl(ghq.src) + }} + className="rounded-full" + /> + )}

{mentor?.name}

{mentor?.email}

@@ -52,7 +75,7 @@ export default function MentorDetailPage() {

Mentoring Schedule

diff --git a/lib/firebase.ts b/lib/firebase.ts index 32e2258..2a88e7d 100644 --- a/lib/firebase.ts +++ b/lib/firebase.ts @@ -1,6 +1,7 @@ import { initializeApp } from 'firebase/app'; import { getAuth, GoogleAuthProvider } from 'firebase/auth'; import { getFirestore } from 'firebase/firestore'; +import { getStorage } from 'firebase/storage'; const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, @@ -21,6 +22,9 @@ export const auth = getAuth(app); // Initialize Firestore export const db = getFirestore(app); +// Initialize storage +export const storage = getStorage(app) + export const googleProvider = new GoogleAuthProvider(); googleProvider.setCustomParameters({ hd: "garudahacks.com", diff --git a/lib/firebaseUtils.ts b/lib/firebaseUtils.ts index b229ac8..b612c53 100644 --- a/lib/firebaseUtils.ts +++ b/lib/firebaseUtils.ts @@ -11,7 +11,7 @@ import { addDoc, deleteDoc, } from "firebase/firestore"; -import { db, auth } from "./firebase"; +import { db, auth, storage } from "./firebase"; import { FirestoreApplication, FirestoreUser, @@ -22,7 +22,7 @@ import { MentorshipAppointment, } from "./types"; import { ONE_SLOT_INTERVAL_MINUTES } from "@/config"; -import { error } from "console"; +import { getDownloadURL, ref } from "firebase/storage"; export { APPLICATION_STATUS } from "./types"; export type { CombinedApplicationData } from "./types"; @@ -540,7 +540,20 @@ export async function deleteMentorshipAppointment(mentorshipId: string) { await deleteDoc(docRef); return true } catch (error) { - console.log(error) + console.error(error) throw new Error('Error when deleting a mentorship appointment') } +} + +/** + * Fetch mentor image + */ +export async function getMentorProfilePicture(mentor_name: string) { + try{ + const imageRef = ref(storage, `/mentors/${mentor_name}.png`) + const url = await getDownloadURL(imageRef) + return url + } catch (error) { + return '' + } } \ No newline at end of file diff --git a/next.config.js b/next.config.js index 1571804..0c5ce58 100644 --- a/next.config.js +++ b/next.config.js @@ -4,4 +4,7 @@ module.exports = { // your project has ESLint errors. ignoreDuringBuilds: true, }, + images: { + domains: ['firebasestorage.googleapis.com'] + } }; diff --git a/next.config.ts b/next.config.ts index 08b1758..3c01b12 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,7 +4,8 @@ const nextConfig: NextConfig = { output: 'export', trailingSlash: true, images: { - unoptimized: true + unoptimized: true, + domains: ['firebasestorage.googleapis.com'] } }; From 8db90d678e31dd7950772aec14b2beb1c68a9e10 Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 21:17:20 +0700 Subject: [PATCH 29/33] minor fix mentorship --- app/mentorship/[mentorId]/page.tsx | 22 ++++++++++------------ components/MentorList.tsx | 2 +- next.config.js | 5 ++++- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 1f411c2..97cb548 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -47,18 +47,16 @@ export default function MentorDetailPage() {

Mentor Details

- {mentorUrl && ( - {`Profile { - setMentorUrl(ghq.src) - }} - className="rounded-full" - /> - )} + {`Profile { + setMentorUrl(ghq.src) + }} + className="rounded-full w-64 aspect-square" + />

{mentor?.name}

{mentor?.email}

diff --git a/components/MentorList.tsx b/components/MentorList.tsx index bc32fbd..44f5e72 100644 --- a/components/MentorList.tsx +++ b/components/MentorList.tsx @@ -31,7 +31,7 @@ export default function MentorListComponent() { return (

-

All Mentors

+

All Mentors ({mentors?.length})

{mentors?.map((m, index) => ( diff --git a/next.config.js b/next.config.js index 0c5ce58..9e427e4 100644 --- a/next.config.js +++ b/next.config.js @@ -5,6 +5,9 @@ module.exports = { ignoreDuringBuilds: true, }, images: { - domains: ['firebasestorage.googleapis.com'] + domains: [ + 'firebasestorage.googleapis.com', + 'garudahacks.com' + ] } }; From 2515fdeae6f4dbd880cd6effd8e763285b1346da Mon Sep 17 00:00:00 2001 From: Heryan Djaruma Date: Thu, 17 Jul 2025 21:27:24 +0700 Subject: [PATCH 30/33] better mentorship layout --- app/mentorship/[mentorId]/page.tsx | 2 +- components/MentorshipAppointmentCardComponent.tsx | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/mentorship/[mentorId]/page.tsx b/app/mentorship/[mentorId]/page.tsx index 97cb548..5b31b40 100644 --- a/app/mentorship/[mentorId]/page.tsx +++ b/app/mentorship/[mentorId]/page.tsx @@ -71,7 +71,7 @@ export default function MentorDetailPage() {
-

Mentoring Schedule

+

Mentoring Schedule ({mentorshipAppointments?.length})