Compare commits

..
3 Commits
Author SHA1 Message Date
BoboiAzumi dd1714a05f fix: conflict 2025-08-18 12:43:32 +07:00
BoboiAzumi 1081c78cce fix: conflict and add redirect 2025-08-18 12:24:41 +07:00
Muhamad Rijal f3a19fa460 Feat/dimentorin landing page (#49)
* feat(dimentorin): slicing landing page menu mentoring

* feat(dimentorin): slicing landing page detail mentor

* feat(dimentorin): slicing modal booking mentor

* feat(dimentorin): slicing landing - resources page

* feat(dimentorin): slicing landing - articles page

* feat(dimentorin): slicing landing - detail articles page
2025-07-28 23:26:22 +07:00
23 changed files with 1017 additions and 1185 deletions
@@ -1,19 +1,9 @@
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { Button } from "@imphnen-frontend-service/ui/atoms"; import { Button } from "@imphnen-frontend-service/ui/atoms";
import { cn, For, Show } from "@imphnen-frontend-service/utils"; import { cn } from "@imphnen-frontend-service/utils";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useParams } from "react-router-dom";
import { useGetArticleBySlug } from "@imphnen-frontend-service/service";
export default function DetailArticle() { export default function DetailArticle() {
const { slug } = useParams()
const { data: article, isLoading, isError } = useGetArticleBySlug(slug ?? "")
const paragraphs = article?.content.split("\n").filter((p) => p.trim()) ?? []
const date = article?.created_at
? new Date(article.created_at).toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" })
: ""
return ( return (
<main> <main>
<motion.section <motion.section
@@ -22,83 +12,76 @@ export default function DetailArticle() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ duration: 0.5, ease: "easeInOut" }} transition={{ duration: 0.5, ease: "easeInOut" }}
> >
<Show <div className="w-full max-w-[900px] mx-auto mb-8 md:mb-[60px]">
condition={!isLoading && !isError && !!article} <div className="mb-10 flex items-center justify-between">
fallback={ <Button
<div className="w-full max-w-[900px] mx-auto text-center text-neutral-500 py-20"> type="button"
{isError ? "Artikel tidak ditemukan." : "Memuat artikel..."} variant="text"
</div> className="flex gap-x-2 text-[10px] px-2.5 py-2 md:text-[15px]"
}
>
<div className="w-full max-w-[900px] mx-auto mb-8 md:mb-[60px]">
<div className="mb-10 flex items-center justify-between">
<Button
type="button"
variant="text"
className="flex gap-x-2 text-[10px] px-2.5 py-2 md:text-[15px]"
onClick={() => window.history.back()}
>
<Icon icon="material-symbols:arrow-back-rounded" />
<span>Back</span>
</Button>
<Button
type="button"
variant="text"
className="flex gap-x-2 text-[10px] px-2.5 py-2 md:text-[15px]"
>
<span>Read Next</span>
<Icon icon="material-symbols:arrow-forward-rounded" />
</Button>
</div>
<div className="text-center leading-tight">
<h1
className="text-[23px] text-neutral-800 font-semibold mb-6 md:text-[37px] md:mb-10 xl:text-[46px]"
>
{article?.title}
</h1>
<p
className="text-[15px] font-semibold text-neutral-800 mb-2 md:text-[19px] md:mb-1 xl:text-[23px] xl:mb-2"
>
{article?.author_name || "IMPHNEN Editorial"}
</p>
<p className="text-xs text-neutral-600 mb-6 md:text-[15px] xl:text-[19px]">
{date}
</p>
<div
className="text-[10px] text-primary-600 font-medium bg-primary-200 px-3 py-1 rounded-4xl w-max mx-auto md:text-xs xl:text-[15px]"
>
{article?.category}
</div>
</div>
</div>
<div className="w-full max-w-[900px] mx-auto">
<div className="mb-8 w-full h-auto aspect-16/10 overflow-hidden md:aspect-31/16 md:mb-[60px] xl:aspect-45/26">
<img
src={article?.cover_url ?? "/image/59de8a682dfbe42455613ea07477ff3e30532c1f.webp"}
alt="Cover Article"
className="size-full object-cover"
/>
</div>
<div
className={cn(
"text-balance text-[15px] font-medium text-neutral-600 space-y-8",
"md:space-y-12 md:text-[19px] xl:text-[23px] xl:max-w-[780px] xl:mx-auto",
)}
> >
<p className="leading-tight">{article?.excerpt}</p> <Icon icon="material-symbols:arrow-back-rounded" />
<For data={paragraphs}> <span>Back</span>
{(paragraph) => ( </Button>
<p key={paragraph.slice(0, 40)} className="leading-tight"> <Button
{paragraph} type="button"
</p> variant="text"
)} className="flex gap-x-2 text-[10px] px-2.5 py-2 md:text-[15px]"
</For> >
<span>Read Next</span>
<Icon icon="material-symbols:arrow-forward-rounded" />
</Button>
</div>
<div className="text-center leading-tight">
<h1
className="text-[23px] text-neutral-800 font-semibold mb-6 md:text-[37px] md:mb-10 xl:text-[46px]"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</h1>
<p
className="text-[15px] font-semibold text-neutral-800 mb-2 md:text-[19px] md:mb-1 xl:text-[23px] xl:mb-2"
>
Writer&rsquo;s Name
</p>
<p className="text-xs text-neutral-600 mb-6 md:text-[15px] xl:text-[19px]">
14 March 2025
</p>
<div
className="text-[10px] text-primary-600 font-medium bg-primary-200 px-3 py-1 rounded-4xl w-max mx-auto md:text-xs xl:text-[15px]"
>
5 min read
</div> </div>
</div> </div>
</Show> </div>
<div className="w-full max-w-[900px] mx-auto">
<div className="mb-8 w-full h-auto aspect-16/10 overflow-hidden md:aspect-31/16 md:mb-[60px] xl:aspect-45/26">
<img src="/image/59de8a682dfbe42455613ea07477ff3e30532c1f.webp" alt="Cover Article" className="size-full object-cover" />
</div>
<div
className={cn(
"text-balance text-[15px] font-medium text-neutral-600 space-y-8",
"md:space-y-12 md:text-[19px] xl:text-[23px] xl:max-w-[780px] xl:mx-auto",
)}
>
<h2 className="text-[19px] text-neutral-800 font-semibold leading-tight md:text-[23px] xl:text-[29px]">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi. Aliquam in hendrerit urna. Pellentesque sit amet sapien fringilla, mattis ligula consectetur, ultrices mauris. Maecenas vitae mattis tellus.
</h2>
<p className="leading-tight">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi. Aliquam in hendrerit urna. Pellentesque sit amet sapien fringilla, mattis ligula consectetur, ultrices mauris. Maecenas vitae mattis tellus. Nullam quis imperdiet augue. Vestibulum auctor ornare leo, non suscipit magna interdum eu. Curabitur pellentesque nibh nibh, at maximus ante fermentum sit amet. Pellentesque commodo lacus at sodales sodales. Quisque sagittis orci ut diam condimentum, vel euismod erat placerat. In iaculis arcu eros, eget tempus orci facilisis id.
</p>
<img src="/image/code-article.webp" alt="Code" className="w-full h-auto mb-12" />
<p className="leading-tight">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi. Aliquam in hendrerit urna. Pellentesque sit amet sapien fringilla, mattis ligula consectetur, ultrices mauris. Maecenas vitae mattis tellus. Nullam quis imperdiet augue. Vestibulum auctor ornare leo, non suscipit magna interdum eu. Curabitur pellentesque nibh nibh, at maximus ante fermentum sit amet. Pellentesque commodo lacus at sodales sodales. Quisque sagittis orci ut diam condimentum, vel euismod erat placerat. In iaculis arcu eros, eget tempus orci facilisis id.
</p>
<p className="leading-tight">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi. Aliquam in hendrerit urna. Pellentesque sit amet sapien fringilla, mattis ligula consectetur, ultrices mauris. Maecenas vitae mattis tellus. Nullam quis imperdiet augue. Vestibulum auctor ornare leo, non suscipit magna interdum eu. Curabitur pellentesque nibh nibh, at maximus ante fermentum sit amet. Pellentesque commodo lacus at sodales sodales. Quisque sagittis orci ut diam condimentum, vel euismod erat placerat. In iaculis arcu eros, eget tempus orci facilisis id.
</p>
</div>
</div>
</motion.section> </motion.section>
</main> </main>
) )
@@ -2,26 +2,12 @@ import { Button } from "@imphnen-frontend-service/ui/atoms"
import { cn, Show } from "@imphnen-frontend-service/utils" import { cn, Show } from "@imphnen-frontend-service/utils"
import { FC } from "react" import { FC } from "react"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { TArticleListItem } from "@imphnen-frontend-service/service"
export type ArticleCardProps = { export type ArticleCardProps = {
variant?: "default" | "recent" | "featured" variant?: "default" | "recent" | "featured"
article?: TArticleListItem
} }
function formatDate(iso?: string) { export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default' }) => {
if (!iso) return ""
return new Date(iso).toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" })
}
export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default', article }) => {
const title = article?.title ?? "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
const excerpt = article?.excerpt ?? "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
const author = article?.author_name ?? "IMPHNEN Editorial"
const date = formatDate(article?.created_at)
const slug = article?.slug ?? ""
const cover = article?.cover_url
return ( return (
<div <div
className={cn( className={cn(
@@ -36,25 +22,25 @@ export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default', article
)} )}
> >
<img <img
src={cover ?? "/image/59de8a682dfbe42455613ea07477ff3e30532c1f.webp"} src="/image/59de8a682dfbe42455613ea07477ff3e30532c1f.webp"
alt={title} alt="Cover's Article"
className={cn("w-full object-cover", variant === "recent" && "h-full")} className={cn("w-full object-cover", variant === "recent" && "h-full")}
/> />
</div> </div>
<div className={cn("leading-tight", variant === "recent" && "xl:flex-1")}> <div className={cn("leading-tight", variant === "recent" && "xl:flex-1")}>
<div className="w-max bg-primary-200 text-primary-600 px-3 py-1 text-xs font-medium rounded-4xl mb-4 xl:mb-[15px]"> <div className="w-max bg-primary-200 text-primary-600 px-3 py-1 text-xs font-medium rounded-4xl mb-4 xl:mb-[15px]">
{article?.category ?? "Artikel"} 5 min read
</div> </div>
<Link <Link
to={slug ? `/articles/${slug}` : "/articles"} to="/articles/detail"
className={cn( className={cn(
"text-[15px] text-neutral-800 font-semibold mb-4 line-clamp-2 md:text-[19px] xl:mb-[23px]", "text-[15px] text-neutral-800 font-semibold mb-4 line-clamp-2 md:text-[19px] xl:mb-[23px]",
variant === "featured" && "text-[19px] md:text-[23px]", variant === "featured" && "text-[19px] md:text-[23px]",
variant === "recent" && "mb-7 xl:text-[27px]" variant === "recent" && "mb-7 xl:text-[27px]"
)} )}
> >
{title} Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</Link> </Link>
<Show condition={variant !== "recent"}> <Show condition={variant !== "recent"}>
<p <p
@@ -63,7 +49,7 @@ export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default', article
variant === "featured" && "text-[15px] md:text-[19px] md:mb-6", variant === "featured" && "text-[15px] md:text-[19px] md:mb-6",
)} )}
> >
{excerpt} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi. Aliquam in hendrerit urna. Pellentesque sit amet sapien fringilla, mattis ligula consectetur, ultrices mauris.
</p> </p>
</Show> </Show>
<div <div
@@ -73,16 +59,14 @@ export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default', article
variant === "featured" && "text-[15px] md:text-[19px] xl:text-base", variant === "featured" && "text-[15px] md:text-[19px] xl:text-base",
)} )}
> >
<p>{author}</p> <p>Writers Name</p>
<p>{date}</p> <p>14 March 2025</p>
</div> </div>
<Show condition={variant === "default"}> <Show condition={variant === "default"}>
<Link to={slug ? `/articles/${slug}` : "/articles"}> <Button type="button" size="sm" className="px-2.5 py-2">
<Button type="button" size="sm" className="px-2.5 py-2"> Baca Artikel
Baca Artikel </Button>
</Button>
</Link>
</Show> </Show>
</div> </div>
</div> </div>
@@ -1,22 +1,14 @@
import { ReactElement, useRef, useState } from "react"; import { ReactElement, useRef, useState } from "react";
import { ArticleCard } from "./_components/card/article"; import { ArticleCard } from "./_components/card/article";
import { cn, For, Show } from "@imphnen-frontend-service/utils"; import { cn, For } from "@imphnen-frontend-service/utils";
import { Button } from "@imphnen-frontend-service/ui/atoms"; import { Button } from "@imphnen-frontend-service/ui/atoms";
import { motion, useInView, Variants } from "framer-motion"; import { motion, useInView, Variants } from "framer-motion";
import { useGetArticleCategories, useGetArticles } from "@imphnen-frontend-service/service";
const CATEGORIES = ['UI/UX Design', 'Software/Web Dev', 'Data & AI', 'Cloud & DevOps', 'Cybersecurity', 'IT & Network', 'Project Management', 'QA & Testing'] as const
type Category = typeof CATEGORIES[number]
export default function Components(): ReactElement { export default function Components(): ReactElement {
const [activeTab, setActiveTab] = useState<string>("Semua") const [activeTab, setActiveTab] = useState<Category>('UI/UX Design')
const { data: articles = [], isLoading } = useGetArticles({ per_page: 20 })
const { data: categories = [] } = useGetArticleCategories()
const tabs = ["Semua", ...categories]
const featured = articles[0]
const recent = articles.slice(1, 4)
const filtered = activeTab === "Semua"
? articles
: articles.filter((a) => a.category === activeTab)
const ref = useRef(null) const ref = useRef(null)
const isInView = useInView(ref, { once: true, amount: 0.2 }) const isInView = useInView(ref, { once: true, amount: 0.2 })
@@ -60,34 +52,29 @@ export default function Components(): ReactElement {
Featuring Articles Featuring Articles
</motion.h1> </motion.h1>
<Show <div className="grid gap-12 xl:grid-cols-2">
condition={!isLoading} <motion.div variants={childVariants}>
fallback={<p className="text-neutral-500">Memuat artikel...</p>} <ArticleCard variant="featured" />
> </motion.div>
<div className="grid gap-12 xl:grid-cols-2">
<motion.div variants={childVariants}>
<ArticleCard variant="featured" article={featured} />
</motion.div>
<div> <div>
<motion.h1 <motion.h1
className="text-[19px] text-neutral-800 font-semibold mb-10 md:text-[29px] xl:text-[37px] xl:mb-12" className="text-[19px] text-neutral-800 font-semibold mb-10 md:text-[29px] xl:text-[37px] xl:mb-12"
variants={childVariants} variants={childVariants}
> >
Recent Articles Recent Articles
</motion.h1> </motion.h1>
<div className="grid gap-10"> <div className="grid gap-10">
<For data={recent}> <For data={Array.from({ length: 3 })}>
{(article, index) => ( {(_, index) => (
<motion.div key={article.id ?? index} variants={childVariants}> <motion.div key={index} variants={childVariants}>
<ArticleCard variant="recent" article={article} /> <ArticleCard variant="recent" />
</motion.div> </motion.div>
)} )}
</For> </For>
</div>
</div> </div>
</div> </div>
</Show> </div>
</div> </div>
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
@@ -99,7 +86,7 @@ export default function Components(): ReactElement {
</motion.h1> </motion.h1>
<div className="scrollbar-hide mb-8 w-full overflow-auto mx-auto"> <div className="scrollbar-hide mb-8 w-full overflow-auto mx-auto">
<motion.div className="min-w-max w-auto flex gap-x-3" variants={childVariants}> <motion.div className="min-w-max w-auto flex gap-x-3" variants={childVariants}>
<For data={tabs}> <For data={CATEGORIES}>
{(category) => ( {(category) => (
<Button <Button
key={category} key={category}
@@ -119,20 +106,15 @@ export default function Components(): ReactElement {
</motion.div> </motion.div>
</div> </div>
<Show <div className="grid gap-8 xl:grid-cols-3">
condition={filtered.length > 0} <For data={Array.from({ length: 3 })}>
fallback={<p className="text-neutral-500 text-center py-10">Belum ada artikel di kategori ini.</p>} {(_, index) => (
> <motion.div key={index} variants={childVariants}>
<div className="grid gap-8 xl:grid-cols-3"> <ArticleCard />
<For data={filtered}> </motion.div>
{(article, index) => ( )}
<motion.div key={article.id ?? index} variants={childVariants}> </For>
<ArticleCard article={article} /> </div>
</motion.div>
)}
</For>
</div>
</Show>
</div> </div>
</motion.section> </motion.section>
</main> </main>
@@ -10,7 +10,6 @@ import { QrisPaymentStep } from "./steps/qris-payement"
import { VAPaymentStep } from "./steps/va-payment" import { VAPaymentStep } from "./steps/va-payment"
import { SuccessStep } from "./steps/success" import { SuccessStep } from "./steps/success"
import { PaymentStep } from "./steps/payment" import { PaymentStep } from "./steps/payment"
import { TBookSessionRequest, usePostBookSession } from "@imphnen-frontend-service/service"
const STEPS = ['topic', 'schedule', 'profile', 'payment', 'qr-payment', 'va-payment', 'success'] as const const STEPS = ['topic', 'schedule', 'profile', 'payment', 'qr-payment', 'va-payment', 'success'] as const
type Step = typeof STEPS[number] type Step = typeof STEPS[number]
@@ -18,32 +17,13 @@ type Step = typeof STEPS[number]
type Props = { type Props = {
open: boolean open: boolean
setOpen: (open: boolean) => void setOpen: (open: boolean) => void
mentorId?: string
mentor?: { fullname?: string | null; current_role?: string; current_company?: string }
} }
export const AppointmentModal: FC<Props> = ({ open, setOpen, mentorId, mentor }) => { export const AppointmentModal: FC<Props> = ({ open, setOpen }) => {
const [step, setStep] = useState<Step>('topic') const [step, setStep] = useState<Step>('topic')
const [selectedTopics, setSelectedTopics] = useState<number[]>([]) const [selectedTopics, setSelectedTopics] = useState<number[]>([])
const [booking, setBooking] = useState<TBookSessionRequest | null>(null)
const [error, setError] = useState("")
const bookMutation = usePostBookSession(mentorId ?? "")
const handleStep = async (action: 'next' | 'prev') => { const handleStep = (action: 'next' | 'prev') => {
setError("")
if (step === 'payment' && action === 'next') {
if (!mentorId || !booking) {
setError("Data booking tidak lengkap.")
return
}
try {
await bookMutation.mutateAsync({ ...booking, topic: booking.topic || `Mentoring #${selectedTopics[0] ?? 1}` })
setStep('success')
} catch {
setError("Gagal membuat sesi mentoring. Coba lagi.")
}
return
}
if (action === 'next' && step === 'success') { if (action === 'next' && step === 'success') {
setOpen(false) setOpen(false)
} else if (action === 'next') { } else if (action === 'next') {
@@ -133,17 +113,13 @@ export const AppointmentModal: FC<Props> = ({ open, setOpen, mentorId, mentor })
<AnimatePresence> <AnimatePresence>
{step === 'topic' && <TopicStep selectedTopics={selectedTopics} setSelectedTopics={setSelectedTopics} />} {step === 'topic' && <TopicStep selectedTopics={selectedTopics} setSelectedTopics={setSelectedTopics} />}
{step === 'schedule' && <ScheduleStep onChange={(data) => setBooking(data)} />} {step === 'schedule' && <ScheduleStep />}
{step === 'profile' && <ProfileStep />} {step === 'profile' && <ProfileStep />}
{step === 'payment' && <PaymentStep selectedTopics={selectedTopics} mentor={mentor} booking={booking} />} {step === 'payment' && <PaymentStep selectedTopics={selectedTopics} />}
{step === 'qr-payment' && <QrisPaymentStep />} {step === 'qr-payment' && <QrisPaymentStep />}
{step === 'va-payment' && <VAPaymentStep />} {step === 'va-payment' && <VAPaymentStep />}
{step === 'success' && <SuccessStep />} {step === 'success' && <SuccessStep />}
</AnimatePresence> </AnimatePresence>
{error && (
<p className="text-danger-600 text-xs mt-4 font-medium">{error}</p>
)}
</div> </div>
<Show condition={step !== 'success'}> <Show condition={step !== 'success'}>
@@ -172,7 +148,7 @@ export const AppointmentModal: FC<Props> = ({ open, setOpen, mentorId, mentor })
condition={step !== 'success'} condition={step !== 'success'}
fallback="Halman Booking" fallback="Halman Booking"
> >
<Show condition={step !== 'payment'} fallback={bookMutation.isPending ? "Memproses..." : "Bayar Sekarang"}> <Show condition={step !== 'payment'} fallback="Bayar Sekarang">
Selanjutnya Selanjutnya
</Show> </Show>
</Show> </Show>
@@ -2,18 +2,12 @@ import { For, Show } from "@imphnen-frontend-service/utils"
import { FC } from "react" import { FC } from "react"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { TOPICS } from "../../../sections/topics" import { TOPICS } from "../../../sections/topics"
import { TBookSessionRequest } from "@imphnen-frontend-service/service"
type Props = { type Props = {
selectedTopics: number[] selectedTopics: number[]
mentor?: { fullname?: string | null; current_role?: string; current_company?: string } | null
booking?: TBookSessionRequest | null
} }
export const PaymentStep: FC<Props> = ({ selectedTopics, mentor, booking }) => { export const PaymentStep: FC<Props> = ({ selectedTopics }) => {
const rate = 50000
const serviceFee = 2000
return ( return (
<motion.div <motion.div
className="bg-white px-6 py-5 rounded-md grid gap-6 md:grid-cols-2 md:gap-8 xl:grid-cols-9" className="bg-white px-6 py-5 rounded-md grid gap-6 md:grid-cols-2 md:gap-8 xl:grid-cols-9"
@@ -32,16 +26,11 @@ export const PaymentStep: FC<Props> = ({ selectedTopics, mentor, booking }) => {
</div> </div>
<div className="w-44 mx-auto text-center md:ms-0 md:text-left md:mb-2 md:text-[15px] md:w-auto"> <div className="w-44 mx-auto text-center md:ms-0 md:text-left md:mb-2 md:text-[15px] md:w-auto">
<h3 className="text-xs font-semibold mb-1 xl:text-[15px]"> <h3 className="text-xs font-semibold mb-1 xl:text-[15px]">
{mentor?.fullname || "Mentor"} Muhammad Firdaus Oi Oi Oi, S.H., M.H.
</h3> </h3>
<p className="text-[10px] text-neutral-600 md:text-xs"> <p className="text-[10px] text-neutral-600 md:text-xs">
{mentor?.current_role || "Mentor"} at {mentor?.current_company || "IMPHNEN"} UI Designer at Oray orayan Studios
</p> </p>
{booking?.scheduled_at && (
<p className="text-[10px] text-neutral-500 md:text-xs mt-1">
{new Date(booking.scheduled_at).toLocaleString("id-ID", { dateStyle: "full", timeStyle: "short" })}
</p>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -115,14 +104,14 @@ export const PaymentStep: FC<Props> = ({ selectedTopics, mentor, booking }) => {
</p> </p>
<div className="grid grid-cols-2 space-y-2.5 text-[10px] text-neutral-400 font-medium md:text-xs md:space-y-3"> <div className="grid grid-cols-2 space-y-2.5 text-[10px] text-neutral-400 font-medium md:text-xs md:space-y-3">
<p className="font-normal">Subtotal :</p> <p className="font-normal">Subtotal :</p>
<p className="text-right">Rp. {rate.toLocaleString("id-ID")}</p> <p className="text-right">Rp. 50.000</p>
<p className="font-normal">Service Fee :</p> <p className="font-normal">Service Fee :</p>
<p className="text-right">Rp. {serviceFee.toLocaleString("id-ID")}</p> <p className="text-right">Rp. 2.000</p>
<hr className="border-neutral-200 col-span-full mt-1" /> <hr className="border-neutral-200 col-span-full mt-1" />
<p className="text-neutral-700">Total :</p> <p className="text-neutral-700">Total :</p>
<p className="text-right text-neutral-700">Rp. {(rate + serviceFee).toLocaleString("id-ID")}</p> <p className="text-right text-neutral-700">Rp. 52.000</p>
</div> </div>
</div> </div>
</div> </div>
@@ -1,8 +1,6 @@
import { Input, Select, Textarea } from "@imphnen-frontend-service/ui/atoms" import { Input, Select, Textarea } from "@imphnen-frontend-service/ui/atoms"
import { cn } from "@imphnen-frontend-service/utils" import { cn } from "@imphnen-frontend-service/utils"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { useEffect, useState } from "react"
import { TBookSessionRequest } from "@imphnen-frontend-service/service"
const placeholder = `Hi [Nama Mentor], Saya [Nama Kamu] & saya berharap dapat memiliki sesi mentoring dengan Anda. const placeholder = `Hi [Nama Mentor], Saya [Nama Kamu] & saya berharap dapat memiliki sesi mentoring dengan Anda.
@@ -15,27 +13,7 @@ Saya ingin tahu secara khusus tentang ___.
const labelClass = cn('text-neutral-800 text-[10px] font-semibold mb-1.5 inline-block md:text-xs md:mb-2 xl:text-[15px]') const labelClass = cn('text-neutral-800 text-[10px] font-semibold mb-1.5 inline-block md:text-xs md:mb-2 xl:text-[15px]')
type Props = { export const ScheduleStep = () => {
onChange: (data: TBookSessionRequest) => void
}
export const ScheduleStep = ({ onChange }: Props) => {
const [date, setDate] = useState("")
const [time, setTime] = useState("")
const [sessionType, setSessionType] = useState("online")
const [description, setDescription] = useState("")
useEffect(() => {
if (!date || !time) return
onChange({
topic: "Mentoring Session",
scheduled_at: `${date}T${time}:00`,
description,
duration_minutes: 60,
session_type: sessionType,
})
}, [date, time, sessionType, description])
return ( return (
<motion.div <motion.div
className="bg-white px-6 py-5 rounded-md md:px-6" className="bg-white px-6 py-5 rounded-md md:px-6"
@@ -53,15 +31,15 @@ export const ScheduleStep = ({ onChange }: Props) => {
<div className="grid gap-2.5 md:grid-cols-2 md:gap-5"> <div className="grid gap-2.5 md:grid-cols-2 md:gap-5">
<div> <div>
<label className={labelClass}>Tanggal</label> <label className={labelClass}>Tanggal</label>
<Input type="date" className="min-w-full w-full" value={date} onChange={(e) => setDate(e.target.value)} /> <Input type="date" className="min-w-full w-full" />
</div> </div>
<div> <div>
<label className={labelClass}>Waktu</label> <label className={labelClass}>Waktu</label>
<Input type="time" className="min-w-full w-full" value={time} onChange={(e) => setTime(e.target.value)} /> <Input type="time" className="min-w-full w-full" />
</div> </div>
<div className="relative md:col-span-full"> <div className="relative md:col-span-full">
<label className={labelClass}>Lokasi</label> <label className={labelClass}>Lokasi</label>
<Select className="min-w-full w-full" value={sessionType} onChange={(e) => setSessionType(e.target.value)}> <Select className="min-w-full w-full">
<option value="online">Online</option> <option value="online">Online</option>
<option value="offline">Offline</option> <option value="offline">Offline</option>
</Select> </Select>
@@ -70,9 +48,7 @@ export const ScheduleStep = ({ onChange }: Props) => {
<label className={labelClass}>Pertanyaan Untuk Senpai</label> <label className={labelClass}>Pertanyaan Untuk Senpai</label>
<Textarea <Textarea
className="min-w-full w-full h-40" className="min-w-full w-full h-40"
placeholder={placeholder} placeholder={placeholder} />
value={description}
onChange={(e) => setDescription(e.target.value)} />
</div> </div>
</div> </div>
</motion.div> </motion.div>
@@ -1,20 +1,12 @@
import { StarFilled } from "@ant-design/icons" import { StarFilled } from "@ant-design/icons"
import { Button } from "@imphnen-frontend-service/ui/atoms" import { Button } from "@imphnen-frontend-service/ui/atoms"
import { cn, For } from "@imphnen-frontend-service/utils" import { cn, For } from "@imphnen-frontend-service/utils"
import { TMentorDetail } from "@imphnen-frontend-service/service"
type Props = { type Props = {
onBook: () => void onBook: () => void
mentor?: TMentorDetail
} }
export const ProfileSection: React.FC<Props> = ({ onBook, mentor }) => { export const ProfileSection: React.FC<Props> = ({ onBook }) => {
const name = mentor?.fullname || mentor?.legal_name || "Mentor"
const role = mentor?.current_role || "Mentor"
const company = mentor?.current_company || "IMPHNEN"
const expertise = mentor?.expertise ?? []
const softSkills = mentor?.languages ?? []
return ( return (
<div className="bg-white px-4 py-5 space-y-6 md:px-8 md:pt-6 md:pb-0 xl:space-y-0 xl:py-[30px] xl:flex xl:gap-x-9 xl:justify-between"> <div className="bg-white px-4 py-5 space-y-6 md:px-8 md:pt-6 md:pb-0 xl:space-y-0 xl:py-[30px] xl:flex xl:gap-x-9 xl:justify-between">
<div <div
@@ -41,10 +33,10 @@ export const ProfileSection: React.FC<Props> = ({ onBook, mentor }) => {
"md:text-[19px] md:mb-2 md:text-start xl:text-[23px]", "md:text-[19px] md:mb-2 md:text-start xl:text-[23px]",
)} )}
> >
{name} Muhammad Firdaus Oi Oi Oi, S.H., M.H.
</h1> </h1>
<p className="text-xs mb-4 text-neutral-600 md:mb-5 md:text-[15px] xl:text-[19px] xl:mb-5"> <p className="text-xs mb-4 text-neutral-600 md:mb-5 md:text-[15px] xl:text-[19px] xl:mb-5">
{role} at {company} UI Designer at Oray orayan Studios
</p> </p>
<div className="flex items-center gap-x-2.5 w-full max-w-max border border-primary-50 p-1.5 rounded-md mx-auto md:ms-0"> <div className="flex items-center gap-x-2.5 w-full max-w-max border border-primary-50 p-1.5 rounded-md mx-auto md:ms-0">
<div <div
@@ -64,7 +56,7 @@ export const ProfileSection: React.FC<Props> = ({ onBook, mentor }) => {
<div> <div>
<h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Expertise</h2> <h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Expertise</h2>
<div className="p-4 bg-primary-50 border border-primary-100 rounded-md flex flex-wrap gap-3"> <div className="p-4 bg-primary-50 border border-primary-100 rounded-md flex flex-wrap gap-3">
<For data={expertise}> <For data={['UI Design', 'UX Reseacrh']}>
{(item) => ( {(item) => (
<div key={item} className="bg-primary-300 text-primary-600 px-3 py-2 rounded-md text-[10px] md:font-medium xl:text-xs xl:font-semibold"> <div key={item} className="bg-primary-300 text-primary-600 px-3 py-2 rounded-md text-[10px] md:font-medium xl:text-xs xl:font-semibold">
{item} {item}
@@ -76,7 +68,7 @@ export const ProfileSection: React.FC<Props> = ({ onBook, mentor }) => {
<div> <div>
<h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Soft Skills</h2> <h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Soft Skills</h2>
<div className="p-4 bg-primary-50 border border-primary-100 rounded-md flex flex-wrap gap-3"> <div className="p-4 bg-primary-50 border border-primary-100 rounded-md flex flex-wrap gap-3">
<For data={softSkills}> <For data={['Design Thinking', 'Communication', 'Problem Solving', '19:00 WIB']}>
{(item) => ( {(item) => (
<div key={item} className="bg-primary-300 text-primary-600 px-3 py-2 rounded-md text-[10px] md:font-medium xl:text-xs xl:font-semibold"> <div key={item} className="bg-primary-300 text-primary-600 px-3 py-2 rounded-md text-[10px] md:font-medium xl:text-xs xl:font-semibold">
{item} {item}
@@ -1,77 +1,68 @@
import { Button } from "@imphnen-frontend-service/ui/atoms" import { Button } from "@imphnen-frontend-service/ui/atoms"
import { For, Show } from "@imphnen-frontend-service/utils" import { cn, For } from "@imphnen-frontend-service/utils"
import { useGetMentorAvailability } from "@imphnen-frontend-service/service"
import { FC } from "react" import { FC } from "react"
const SCHEDULES = [
{
date: "Kamis, 20 Maret 2025",
availableCount: 10,
schedule: [
{ time: "19:00 WIB", available: true },
{ time: "20:00 WIB", available: true },
{ time: "21:00 WIB", available: true },
{ time: "22:00 WIB", available: true },
]
},
{
date: "Jumat, 21 Maret 2025",
availableCount: 1,
schedule: [
{ time: "19:00 WIB", available: true },
{ time: "20:00 WIB", available: true },
{ time: "21:00 WIB", available: false },
{ time: "22:00 WIB", available: true },
]
}
]
type Props = { type Props = {
onBook: () => void onBook: () => void
mentorId?: string
} }
const DAY_NAMES = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"] export const SenpaiScheduleSection: FC<Props> = ({ onBook }) => {
function formatDate(dateStr: string) {
const d = new Date(dateStr + "T00:00:00")
return `${DAY_NAMES[d.getDay()]}, ${d.getDate()} ${d.toLocaleString("id-ID", { month: "long" })} ${d.getFullYear()}`
}
function formatTime(timeStr: string) {
const [h] = timeStr.split(":").map(Number)
return `${String(h).padStart(2, "0")}:00 WIB`
}
export const SenpaiScheduleSection: FC<Props> = ({ onBook, mentorId }) => {
const { data: availability, isLoading } = useGetMentorAvailability(mentorId ?? "")
const slots = availability?.slots ?? []
const byDate = new Map<string, typeof slots>()
for (const s of slots) {
if (!byDate.has(s.date)) byDate.set(s.date, [])
byDate.get(s.date)!.push(s)
}
const days = [...byDate.entries()]
return ( return (
<div> <div>
<h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Senpai Schedule</h2> <h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Senpai Schedule</h2>
<div className="p-4 bg-primary-50 border border-primary-100 rounded-md space-y-5"> <div className="p-4 bg-primary-50 border border-primary-100 rounded-md space-y-5">
<Show <For data={SCHEDULES}>
condition={!isLoading && days.length > 0} {(item) => (
fallback={ <div key={item.date}>
<p className="text-xs text-neutral-500"> <div className="flex justify-between items-center mb-4">
{isLoading ? "Memuat jadwal senpai..." : "Belum ada jadwal tersedia."} <p className="text-[15px] font-medium">{item.date}</p>
</p> <p
} className={cn(
> "rounded-4xl py-1 px-2.5 text-xs font-medium",
<For data={days}> item.availableCount > 5 ? "bg-primary-200 text-primary-500" : "bg-danger-100 text-danger-600"
{([date, daySlots]) => ( )}
<div key={date}> >
<div className="flex justify-between items-center mb-4"> {item.availableCount} slot tersisa
<p className="text-[15px] font-medium">{formatDate(date)}</p> </p>
<p className="rounded-4xl py-1 px-2.5 text-xs font-medium bg-primary-200 text-primary-500">
{daySlots.length} slot tersisa
</p>
</div>
<div className="flex flex-wrap gap-x-3 gap-y-2">
<For data={daySlots}>
{(schedule) => (
<div
key={`${schedule.date}-${schedule.time}`}
className={`px-3 py-2 text-xs rounded-md font-semibold ${
schedule.available
? "bg-primary-300 text-primary-600"
: "bg-neutral-200 text-neutral-400 line-through"
}`}
>
{formatTime(schedule.time)}
</div>
)}
</For>
</div>
</div> </div>
)} <div className="flex gap-x-3">
</For> <For data={item.schedule}>
</Show> {(schedule, index) => (
<div
key={index}
className="bg-primary-300 text-primary-600 px-3 py-2 text-xs rounded-md font-semibold"
>
{schedule.time}
</div>
)}
</For>
</div>
</div>
)}
</For>
<hr className="border-primary-200" /> <hr className="border-primary-200" />
@@ -1,5 +1,4 @@
import { FC, useState } from 'react' import { FC, useState } from 'react'
import { useParams } from 'react-router-dom'
import { ProfileSection } from './_components/sections/profile' import { ProfileSection } from './_components/sections/profile'
import { StatisticsSection } from './_components/sections/senpai-statistics' import { StatisticsSection } from './_components/sections/senpai-statistics'
import { TopicsSection } from './_components/sections/topics' import { TopicsSection } from './_components/sections/topics'
@@ -8,61 +7,44 @@ import { EducationSection } from './_components/sections/education'
import { SenpaiScheduleSection } from './_components/sections/senpai-schedule' import { SenpaiScheduleSection } from './_components/sections/senpai-schedule'
import { Button } from '@imphnen-frontend-service/ui/atoms' import { Button } from '@imphnen-frontend-service/ui/atoms'
import { AppointmentModal } from './_components/modals/appointment' import { AppointmentModal } from './_components/modals/appointment'
import { useGetMentorDetail } from '@imphnen-frontend-service/service'
import { Show } from '@imphnen-frontend-service/utils'
export const Components: FC = () => { export const Components: FC = () => {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { id } = useParams()
const { data: mentor, isLoading, isError } = useGetMentorDetail(id ?? '')
const resume = mentor?.bio ?? ''
return ( return (
<main> <main>
<Show <section className="w-full p-8 md:py-14 md:px-[60px] lg:py-16 lg:px-20">
condition={!isLoading && !isError} <div className="max-w-7xl mx-auto space-y-8 md:bg-white xl:bg-transparent">
fallback={ <ProfileSection onBook={() => setOpen(true)} />
<section className="w-full p-8 md:py-14 md:px-[60px] lg:py-16 lg:px-20">
<div className="max-w-7xl mx-auto text-center text-neutral-500 py-10"> <Button type="button" size="sm" className="w-full md:hidden" onClick={() => setOpen(true)}>
{isError ? 'Mentor tidak ditemukan' : 'Memuat profil mentor...'} Book Your Senpai!
</Button>
<div className="bg-white px-4 py-5 md:px-8 md:pb-6 md:pt-0 xl:py-7 xl:flex xl:gap-x-10">
<div className="space-y-10 md:space-y-7 xl:flex-1">
<StatisticsSection />
<TopicsSection />
<div className="px-6 py-8 rounded-md shadow-md">
<h2 className="text-xs text-neutral-800 font-semibold mb-5 md:text-[15px] xl:text-[19px]">Senpai Resume</h2>
<p className="text-[10px] font-medium text-neutral-600 text-pretty md:text-[15px]">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi. Aliquam in hendrerit urna. Pellentesque sit amet sapien fringilla, mattis ligula consectetur, ultrices mauris. Maecenas vitae mattis tellus. Nullam quis imperdiet augue. Vestibulum auctor ornare leo, non suscipit magna interdum eu. Curabitur pellentesque nibh nibh, at maximus ante fermentum sit amet. Pellentesque commodo lacus at sodales sodales. Quisque sagittis orci ut diam condimentum, vel euismod erat placerat. In iaculis arcu eros, eget tempus orci facilisis id.
</p>
</div>
<ExperienceSection />
<EducationSection />
</div> </div>
</section>
}
>
<section className="w-full p-8 md:py-14 md:px-[60px] lg:py-16 lg:px-20">
<div className="max-w-7xl mx-auto space-y-8 md:bg-white xl:bg-transparent">
<ProfileSection onBook={() => setOpen(true)} mentor={mentor} />
<Button type="button" size="sm" className="w-full md:hidden" onClick={() => setOpen(true)}> <div className="hidden xl:block xl:w-[400px]">
Book Your Senpai! <SenpaiScheduleSection onBook={() => setOpen(true)} />
</Button>
<div className="bg-white px-4 py-5 md:px-8 md:pb-6 md:pt-0 xl:py-7 xl:flex xl:gap-x-10">
<div className="space-y-10 md:space-y-7 xl:flex-1">
<StatisticsSection />
<TopicsSection />
<div className="px-6 py-8 rounded-md shadow-md">
<h2 className="text-xs text-neutral-800 font-semibold mb-5 md:text-[15px] xl:text-[19px]">Senpai Resume</h2>
<p className="text-[10px] font-medium text-neutral-600 text-pretty md:text-[15px]">
{resume || 'Belum ada deskripsi dari senpai ini.'}
</p>
</div>
<ExperienceSection />
<EducationSection />
</div>
<div className="hidden xl:block xl:w-[400px]">
<SenpaiScheduleSection onBook={() => setOpen(true)} mentorId={mentor?.id} />
</div>
</div> </div>
</div> </div>
</section> </div>
</section>
<AppointmentModal open={open} setOpen={setOpen} mentorId={mentor?.id} mentor={mentor} /> <AppointmentModal open={open} setOpen={setOpen} />
</Show>
</main> </main>
) )
} }
@@ -1,70 +1,40 @@
import { Button } from '@imphnen-frontend-service/ui/atoms'; import { Button } from '@imphnen-frontend-service/ui/atoms';
import { For } from '@imphnen-frontend-service/utils';
import { TMentorDetail } from '@imphnen-frontend-service/service';
import { FC } from 'react'; import { FC } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
type TMentorCardProps = { export const MentorCard: FC = () => {
mentor: TMentorDetail;
};
export const MentorCard: FC<TMentorCardProps> = ({ mentor }) => {
const experience =
mentor.years_of_experience > 0
? `${mentor.years_of_experience} Years Experience`
: 'New Mentor';
const role = mentor.current_role || 'Mentor';
const company = mentor.current_company || 'IMPHNEN';
const softSkills = mentor.expertise || [];
const shownSkills = softSkills.slice(0, 2);
const extraCount = Math.max(0, softSkills.length - shownSkills.length);
return ( return (
<div className="p-2.5 rounded-md bg-white shadow flex gap-x-4 items-start md:p-4 md:flex-col md:rounded-lg md:gap-y-4"> <div className="p-2.5 rounded-md bg-white shadow flex gap-x-4 items-start md:p-4 md:flex-col md:rounded-lg md:gap-y-4">
<div className="size-[60px] rounded-md overflow-hidden md:w-full md:h-auto md:aspect-square"> <div className="size-[60px] rounded-md overflow-hidden md:w-full md:h-auto md:aspect-square">
<img src="/image/testimonial.webp" alt={mentor.fullname ?? 'Mentor'} className="w-full object-cover" /> <img src="/image/testimonial.webp" alt="Mentor" className="w-full object-cover" />
</div> </div>
<div> <div>
<Button variant="text" size="sm" className="bg-primary-100 h-auto px-1.5 py-1 text-[8px] font-normal mb-2 hover:bg-primary-100 md:text-[10px] md:font-medium md:mb-4"> <Button variant="text" size="sm" className="bg-primary-100 h-auto px-1.5 py-1 text-[8px] font-normal mb-2 hover:bg-primary-100 md:text-[10px] md:font-medium md:mb-4">
{experience} 3-5 Years Experience
</Button> </Button>
<h2 className="mb-1"> <h2 className="mb-1">
<Link to={`/mentoring/${mentor.id}`} className="text-xs font-semibold text-primary-500 md:text-[15px] md:font-semibold lg:text-[19px]"> <Link to="/mentoring/detail" className="text-xs font-semibold text-primary-500 md:text-[15px] md:font-semibold lg:text-[19px]">
{mentor.fullname || mentor.legal_name || 'Mentor'} Fullname
</Link> </Link>
</h2> </h2>
<p className="text-[8px] text-neutral-500 mb-3 md:text-[10px] md:font-medium md:mb-4 lg:text-xs"> <p className="text-[8px] text-neutral-500 mb-3 md:text-[10px] md:font-medium md:mb-4 lg:text-xs">
{role} at {company} Full Stack Enjoyer at name company
</p> </p>
{softSkills.length > 0 && ( <div>
<div> <p className="text-[8px] text-neutral-500 mb-1 md:mb-2 lg:text-[10px]">Soft Skills :</p>
<p className="text-[8px] text-neutral-500 mb-1 md:mb-2 lg:text-[10px]">Soft Skills :</p> <div className="space-x-2">
<div className="space-x-2"> <Button variant="text" size="sm" className="bg-primary-200 h-auto px-1.5 py-1 text-[8px] font-normal hover:bg-primary-200 lg:text-[10px]">
<For data={shownSkills}> Communication
{(skill) => ( </Button>
<Button <Button variant="text" size="sm" className="bg-primary-200 h-auto px-1.5 py-1 text-[8px] font-normal hidden hover:bg-primary-200 md:inline-block lg:text-[10px]">
key={skill} Communication
variant="text" </Button>
size="sm" <Button variant="text" size="sm" className="h-auto px-1.5 py-1 text-[8px] font-normal text-neutral-500 hover:bg-transparent lg:text-[10px]">
className="bg-primary-200 h-auto px-1.5 py-1 text-[8px] font-normal hover:bg-primary-200 lg:text-[10px]" +2
> </Button>
{skill}
</Button>
)}
</For>
{extraCount > 0 && (
<Button
variant="text"
size="sm"
className="h-auto px-1.5 py-1 text-[8px] font-normal text-neutral-500 hover:bg-transparent lg:text-[10px]"
>
+{extraCount}
</Button>
)}
</div>
</div> </div>
)} </div>
</div> </div>
</div> </div>
) )
@@ -3,24 +3,27 @@ import { BannerSection } from './_components/banner-section';
import { Topics } from './_components/topics'; import { Topics } from './_components/topics';
import { Input } from '@imphnen-frontend-service/ui/atoms'; import { Input } from '@imphnen-frontend-service/ui/atoms';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { For, Show } from '@imphnen-frontend-service/utils'; import { For } from '@imphnen-frontend-service/utils';
import { MentorCard } from './_components/mentor-card'; import { MentorCard } from './_components/mentor-card';
import { Pagination } from '@imphnen-frontend-service/ui/molecules'; import { Pagination } from '@imphnen-frontend-service/ui/molecules';
import { useGetMentors } from '@imphnen-frontend-service/service';
import { getCoreRowModel, getPaginationRowModel, PaginationState, useReactTable } from '@tanstack/react-table'; import { getCoreRowModel, getPaginationRowModel, PaginationState, useReactTable } from '@tanstack/react-table';
import { motion, useInView, Variants } from 'framer-motion'; import { motion, useInView, Variants } from 'framer-motion';
const TEMP_DATA = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'John Doe' },
{ id: 3, name: 'John Doe' },
{ id: 4, name: 'John Doe' },
]
export const Components: FC = (): ReactElement => { export const Components: FC = (): ReactElement => {
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 8, pageSize: 3,
}); });
const [search, setSearch] = useState('');
const { data: mentors, isLoading } = useGetMentors({ search });
const table = useReactTable({ const table = useReactTable({
data: mentors ?? [], data: TEMP_DATA,
columns: [], columns: [],
state: { pagination }, state: { pagination },
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
@@ -28,8 +31,6 @@ export const Components: FC = (): ReactElement => {
onPaginationChange: setPagination, onPaginationChange: setPagination,
}); });
const pageMentors = table.getRowModel().rows.map((row) => row.original);
const ref = useRef(null) const ref = useRef(null)
const isInView = useInView(ref, { once: true, amount: 0.2 }) const isInView = useInView(ref, { once: true, amount: 0.2 })
@@ -90,8 +91,6 @@ export const Components: FC = (): ReactElement => {
<Input <Input
placeholder="Cari berdasarkan nama, posisi/peran" placeholder="Cari berdasarkan nama, posisi/peran"
className="relative min-w-full w-full" className="relative min-w-full w-full"
value={search}
onChange={(e) => setSearch(e.target.value)}
/> />
<SearchOutlined className="absolute right-2.5 top-1/2 -translate-y-1/2 text-primary-500 size-2.5 cursor-text md:me-12 lg:me-0" /> <SearchOutlined className="absolute right-2.5 top-1/2 -translate-y-1/2 text-primary-500 size-2.5 cursor-text md:me-12 lg:me-0" />
</motion.div> </motion.div>
@@ -102,31 +101,13 @@ export const Components: FC = (): ReactElement => {
initial="hidden" initial="hidden"
animate={isInView ? 'visible' : 'hidden'} animate={isInView ? 'visible' : 'hidden'}
> >
<Show <For data={Array.from({ length: 8 })}>
condition={!isLoading} {(_, index) => (
fallback={ <motion.div key={index} variants={childVariants}>
<motion.p variants={childVariants} className="text-center text-neutral-500 col-span-full py-10"> <MentorCard />
Memuat data mentor... </motion.div>
</motion.p> )}
} </For>
>
<Show
condition={pageMentors.length > 0}
fallback={
<motion.p variants={childVariants} className="text-center text-neutral-500 col-span-full py-10">
Tidak ada mentor ditemukan
</motion.p>
}
>
<For data={pageMentors}>
{(mentor, index) => (
<motion.div key={mentor.id ?? index} variants={childVariants}>
<MentorCard mentor={mentor} />
</motion.div>
)}
</For>
</Show>
</Show>
</motion.div> </motion.div>
<Pagination table={table} /> <Pagination table={table} />
@@ -1,80 +1,11 @@
import { FC, ReactElement, useState } from 'react'; import { FC, ReactElement, useState } from 'react';
import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms'; import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
import { Button } from '@imphnen-frontend-service/ui/atoms'; import { Button, Select } from '@imphnen-frontend-service/ui/atoms';
import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons'; import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
import { InputField, RegisterMentorStep, SelectField } from '@imphnen-frontend-service/ui/molecules'; import { InputField, RegisterMentorStep, SelectField } from '@imphnen-frontend-service/ui/molecules';
import { usePostRegisterMentor, TMentorRegisterRequest } from '@imphnen-frontend-service/service';
import { useNavigate } from 'react-router-dom';
export const Components: FC = (): ReactElement => { export const Components: FC = (): ReactElement => {
const [step, setStep] = useState(1) const [ step, setStep ] = useState(1)
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState("")
const [form, setForm] = useState({
email: "", password: "", confirmPassword: "",
fullname: "", phone_number: "", gender: "", domicile: "",
legal_name: "", phone_for_verification: "", identity_document_url: "",
last_education: "", bio: "", linkedin_url: "", github_url: "",
cv_url: "", portfolio_url: "", industries: "", expertise: "", languages: "",
current_company: "", current_role: "", years_of_experience: "",
topics_of_interest: "", preferred_mentee_level: "", preferred_mentoring_formats: "",
availability_commitment: "", mentoring_rate_amount: "",
})
const setF = (k: string, v: string) => setForm((f) => ({ ...f, [k]: v }))
const registerMutation = usePostRegisterMentor()
const navigate = useNavigate()
const handleSubmit = async () => {
setError("")
if (form.password !== form.confirmPassword) {
setError("Password dan konfirmasi tidak sama.")
return
}
const payload: TMentorRegisterRequest = {
email: form.email,
password: form.password,
fullname: form.fullname,
phone_number: form.phone_number || null,
identity_and_verification: {
legal_name: form.legal_name || form.fullname,
gender: form.gender || null,
domicile: form.domicile || null,
identity_document_url: form.identity_document_url || "https://example.com/identity.jpg",
phone_for_verification: form.phone_for_verification || null,
},
professional_profile: {
bio: form.bio,
last_education: form.last_education || null,
linkedin_url: form.linkedin_url || null,
github_url: form.github_url || null,
cv_url: form.cv_url || null,
portfolio_url: form.portfolio_url || null,
industries: form.industries.split(",").map((s) => s.trim()).filter(Boolean),
expertise: form.expertise.split(",").map((s) => s.trim()).filter(Boolean),
languages: form.languages.split(",").map((s) => s.trim()).filter(Boolean),
current_company: form.current_company,
current_role: form.current_role,
years_of_experience: Number(form.years_of_experience) || 0,
},
mentoring_logistics: {
topics_of_interest: form.topics_of_interest.split(",").map((s) => s.trim()).filter(Boolean),
preferred_mentee_level: [form.preferred_mentee_level].filter(Boolean),
preferred_mentoring_formats: form.preferred_mentoring_formats.split(",").map((s) => s.trim()).filter(Boolean),
availability_commitment: form.availability_commitment,
mentoring_rate_amount: Number(form.mentoring_rate_amount) || 0,
},
}
setSubmitting(true)
try {
await registerMutation.mutateAsync(payload)
navigate('/auth/register-mentor/pending')
} catch (e: unknown) {
const msg = (e as { response?: { data?: { message?: string } } })?.response?.data?.message
setError(msg || "Gagal mendaftar sebagai mentor. Coba lagi.")
} finally {
setSubmitting(false)
}
}
return ( return (
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]"> <div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
@@ -82,6 +13,13 @@ export const Components: FC = (): ReactElement => {
<RegisterResetBanner /> <RegisterResetBanner />
<div className="xl:border-2 xl:border-primary-500/50 xl:w-[726px] rounded-lg py-[32px] px-[48px] flex justify-center"> <div className="xl:border-2 xl:border-primary-500/50 xl:w-[726px] rounded-lg py-[32px] px-[48px] flex justify-center">
<div className="xl:w-[714px]"> <div className="xl:w-[714px]">
<Button
className='xl:hidden gap-3'
variant='secondary'
>
<ArrowLeftOutlined />
Login
</Button>
<RegisterMentorStep step={step} /> <RegisterMentorStep step={step} />
<h3 className="mt-5 text-3xl font-semibold text-primary-500"> <h3 className="mt-5 text-3xl font-semibold text-primary-500">
Register Register
@@ -89,44 +27,60 @@ export const Components: FC = (): ReactElement => {
<h5 className="text-primary-500 font-medium"> <h5 className="text-primary-500 font-medium">
Welcome to the team, Mentor - powered by IMPHNEN Welcome to the team, Mentor - powered by IMPHNEN
</h5> </h5>
<form onSubmit={(e) => e.preventDefault()}> <form>
<div className={`${step !== 1 ? 'hidden' : ''}`}> <div className={`${step !== 1 ? 'hidden' : ''}`}>
<div className='mt-7 mb-2'> <div className='mt-7 mb-2'>
<InputField <InputField
label="Nama Lengkap" size="lg" className="w-full" label="Nama Lengkap"
placeholder="Nama Lengkap" name="fullname" size="lg"
value={form.fullname} onChange={(e) => setF('fullname', e.target.value)} className="w-full"
placeholder="Nama Lengkap"
name={'fullname'}
/> />
</div> </div>
<SelectField <SelectField
label="Jenis Kelamin" defaultValue="" size="lg" error="" label="Jenis Kelamin"
value={form.gender} onChange={(e) => setF('gender', e.target.value)} defaultValue=""
size="lg"
error=""
> >
<option value="" disabled hidden>Pilih Jenis Kelamin</option> <option value="" disabled hidden>
<option value="Laki-laki">Laki - Laki</option> Pilih Jenis Kelamin
<option value="Perempuan">Perempuan</option> </option>
<option value="laki-laki">Laki - Laki</option>
<option value="perempuan">Perempuan</option>
</SelectField> </SelectField>
<div className='my-2'> <div className='my-2'>
<InputField <InputField
label="No Telp/Whatsapp Only" size="lg" className="w-full" label="No Telp/Whatsapp Only"
placeholder="Nomor WhatsApp" name="phone_number" size="lg"
value={form.phone_number} onChange={(e) => setF('phone_number', e.target.value)} className="w-full"
placeholder="Nama Lengkap"
name={'fullname'}
/> />
</div> </div>
<SelectField <SelectField
label="Domisili" defaultValue="" size="lg" error="" label="Domisili"
value={form.domicile} onChange={(e) => setF('domicile', e.target.value)} defaultValue=""
size="lg"
error=""
> >
<option value="" disabled hidden>Pilih Domisili</option> <option value="" disabled hidden>
<option value="Aceh">Aceh</option> Pilih Domisili
<option value="Jawa Barat">Jawa Barat</option> </option>
<option value="aceh">Aceh</option>
<option value="jawa-selatan">Jawa Selatan</option>
</SelectField> </SelectField>
<div className='mt-2'> <div className='mt-2'>
<SelectField <SelectField
label="Pendidikan Terakhir" defaultValue="" size="lg" error="" label="Pendidikan Terakhir"
value={form.last_education} onChange={(e) => setF('last_education', e.target.value)} defaultValue=""
size="lg"
error=""
> >
<option value="" disabled hidden>Apa pendidikan terakhir senpai</option> <option value="" disabled hidden>
Apa pendidikan terakhir senpai
</option>
<option value="SMA">SMA</option> <option value="SMA">SMA</option>
<option value="S1">S1</option> <option value="S1">S1</option>
<option value="S2">S2</option> <option value="S2">S2</option>
@@ -136,85 +90,145 @@ export const Components: FC = (): ReactElement => {
</div> </div>
<div className={`${step !== 2 ? 'hidden' : ''}`}> <div className={`${step !== 2 ? 'hidden' : ''}`}>
<div className='mt-7 mb-2 gap-2 flex flex-col'> <div className='mt-7 mb-2 gap-2 flex flex-col'>
<InputField label="Perusahaan Saat Ini" size="lg" className="w-full" <InputField
placeholder="Masukkan Perusahaan tempat kerja, Senpai~! ✨" name="current_company" label="Perusahaan Saat Ini"
value={form.current_company} onChange={(e) => setF('current_company', e.target.value)} /> size="lg"
<InputField label="Masukan jabatan senpai di perusahaan" size="lg" className="w-full" className="w-full"
placeholder="Jabatan" name="current_role" placeholder="Masukkan Perusahaan tempat kerja, Senpai~! ✨ (Pastikan tidak typo, ya~ 😆)"
value={form.current_role} onChange={(e) => setF('current_role', e.target.value)} /> name={'fullname'}
<InputField label="Topik Keahlian Yang Ingin Diajarkan (pisahkan koma)" size="lg" className="w-full" />
placeholder="mis. Backend Developer, Rust" name="expertise" <InputField
value={form.expertise} onChange={(e) => setF('expertise', e.target.value)} /> label="Masukan jabatan senpai di perusahaan"
<InputField label="Industri (pisahkan koma)" size="lg" className="w-full" size="lg"
placeholder="mis. Software, Education" name="industries" className="w-full"
value={form.industries} onChange={(e) => setF('industries', e.target.value)} /> placeholder="Masukan jabatan senpai di perusahaan"
<InputField label="Skill Utama / Bahasa (pisahkan koma)" size="lg" className="w-full" name={'fullname'}
placeholder="mis. Rust, TypeScript" name="languages" />
value={form.languages} onChange={(e) => setF('languages', e.target.value)} /> <SelectField
<InputField label="Tahun Pengalaman" type="number" size="lg" className="w-full" label="Topik Keahlian Yang Ingin Diajarkan"
placeholder="mis. 3" name="years_of_experience" defaultValue=""
value={form.years_of_experience} onChange={(e) => setF('years_of_experience', e.target.value)} /> size="lg"
<InputField label="Bio Singkat" size="lg" className="w-full" error=""
placeholder="Ceritakan pengalaman Anda" name="bio" >
value={form.bio} onChange={(e) => setF('bio', e.target.value)} /> <option value="" disabled hidden>
Apa yang ingin anda ajarkan senpaii
</option>
<option value="Frontend Developer">Frontend Developer</option>
<option value="Backend Developer">Backend Developer</option>
<option value="Other">Other</option>
</SelectField>
<InputField
label="Jika Mengisi “Other”, Tulis Topik yang Kamu Kuasai"
size="lg"
className="w-full"
placeholder="Tolong isi jika tidak ada di pilihan senpai"
name={'fullname'}
/>
<InputField
label="Skill Utama yang Kamu Miliki (boleh lebih dari satu)"
size="lg"
className="w-full"
placeholder="Isi Skill nya dong senpai"
name={'fullname'}
/>
</div> </div>
</div> </div>
<div className={`${step !== 3 ? 'hidden' : ''}`}> <div className={`${step !== 3 ? 'hidden' : ''}`}>
<div className='mt-7 mb-2 gap-2 flex flex-col'> <div className='mt-7 mb-2 gap-2 flex flex-col'>
<InputField label="Topik Mentoring (pisahkan koma)" size="lg" className="w-full" <SelectField
placeholder="mis. Rust Programming, Backend" name="topics_of_interest" label="Durasi Sesi Mentoring"
value={form.topics_of_interest} onChange={(e) => setF('topics_of_interest', e.target.value)} /> defaultValue=""
<SelectField label="Tingkat Mentee" defaultValue="" size="lg" error="" size="lg"
value={form.preferred_mentee_level} onChange={(e) => setF('preferred_mentee_level', e.target.value)}> error=""
<option value="" disabled hidden>Pilih tingkat mentee</option> >
<option value="beginner">Beginner</option> <option value="" disabled hidden>
<option value="intermediate">Intermediate</option> Placeholder
<option value="advanced">Advanced</option> </option>
</SelectField> </SelectField>
<InputField label="Format Pengajaran (pisahkan koma, mis. online,offline)" size="lg" className="w-full" <SelectField
placeholder="online,offline" name="preferred_mentoring_formats" label="Format Pengajaran yang Anda Sediakan"
value={form.preferred_mentoring_formats} onChange={(e) => setF('preferred_mentoring_formats', e.target.value)} /> defaultValue=""
<InputField label="Komitmen Ketersediaan" size="lg" className="w-full" size="lg"
placeholder="mis. 2 jam per minggu" name="availability_commitment" error=""
value={form.availability_commitment} onChange={(e) => setF('availability_commitment', e.target.value)} /> >
<InputField label="Honorarium yang Diharapkan per Sesi" type="number" size="lg" className="w-full" <option value="" disabled hidden>
placeholder="mis. 500000" name="mentoring_rate_amount" Placeholder
value={form.mentoring_rate_amount} onChange={(e) => setF('mentoring_rate_amount', e.target.value)} /> </option>
<InputField label="URL LinkedIn" size="lg" className="w-full" </SelectField>
placeholder="https://linkedin.com/in/..." name="linkedin_url" <InputField
value={form.linkedin_url} onChange={(e) => setF('linkedin_url', e.target.value)} /> type="file"
<InputField label="URL Portofolio" size="lg" className="w-full" label="Unggah Curiculum Vitae"
placeholder="Google Drive, GitHub, Behance" name="portfolio_url" size="lg"
value={form.portfolio_url} onChange={(e) => setF('portfolio_url', e.target.value)} /> className="w-full"
/>
<InputField
label="Unggah Portofolio"
size="lg"
className="w-full"
placeholder="Google Drive, Behance, GitHub, Notion, dan lainnya"
/>
<InputField
type='number'
label="Honorarium yang Diharapkan per Sesi (Online) Durasi rata-rata sesi: 2,53 jam"
size="lg"
className="w-full"
placeholder="Isi Nominalnya dong senpaiii!!!"
/>
<h6 className='text-xs'>Silahkan isi dalam angka (Contoh:500.000)</h6> <h6 className='text-xs'>Silahkan isi dalam angka (Contoh:500.000)</h6>
</div> </div>
</div> </div>
<div className={`${step !== 4 ? 'hidden' : ''}`}> <div className={`${step !== 4 ? 'hidden' : ''}`}>
<div className='mt-7 mb-2 gap-2 flex flex-col'> <div className='mt-7 mb-2 gap-2 flex flex-col'>
<InputField type='email' label="Email" size="lg" className="w-full" <InputField
placeholder="isi emailnya dong senpai!!!" name="email" type='email'
value={form.email} onChange={(e) => setF('email', e.target.value)} /> label="Email"
<InputField type='password' label="Buat Password" size="lg" className="w-full" size="lg"
placeholder="Buat Password" name="password" className="w-full"
value={form.password} onChange={(e) => setF('password', e.target.value)} /> placeholder="isi emailnya dong senpai!!!"
<InputField type='password' label="Konfirmasi Password" size="lg" className="w-full" />
placeholder="Ulangi Password" name="confirmPassword" <InputField
value={form.confirmPassword} onChange={(e) => setF('confirmPassword', e.target.value)} /> type='password'
label="Buat Password"
size="lg"
className="w-full"
placeholder="Buat Password"
/>
<InputField
type='password'
label="Buat Password"
size="lg"
className="w-full"
placeholder="Buat Password"
/>
</div> </div>
</div> </div>
{error && <p className="text-danger-600 text-xs mt-3 font-medium">{error}</p>}
<div className='flex justify-end mt-4 gap-2'> <div className='flex justify-end mt-4 gap-2'>
<Button variant='secondary' onClick={() => setStep(step - 1)} type='button' <Button
className={`${step === 1 ? 'hidden' : ''}`}> variant='secondary'
<ArrowLeftOutlined /> Kembali onClick={() => setStep(step - 1)}
type='button'
className={`${step === 1 ? 'hidden' : ''}`}
>
<ArrowLeftOutlined />
Kembali
</Button> </Button>
<Button variant='primary' onClick={() => setStep(step + 1)} type='button' <Button
className={`${step === 4 ? 'hidden' : ''}`}> variant='primary'
Lanjutkan <ArrowRightOutlined /> onClick={() => setStep(step + 1)}
type='button'
className={`${step === 4 ? 'hidden' : ''}`}
>
Lanjutkan
<ArrowRightOutlined />
</Button> </Button>
<Button variant='primary' onClick={handleSubmit} type='button' <Button
className={`${step === 4 ? '' : 'hidden'}`} disabled={submitting}> variant='primary'
{submitting ? "Mengirim..." : "Selesaikan"} <ArrowRightOutlined /> onClick={() => setStep(step + 1)}
type='button'
className={`${step === 4 ? '' : 'hidden'}`}
>
Selesaikan
<ArrowRightOutlined />
</Button> </Button>
</div> </div>
</form> </form>
-12
View File
@@ -1,12 +0,0 @@
import { Navbar } from '@imphnen-frontend-service/ui/organisms';
import { FC, ReactElement } from 'react';
export const Components: FC = (): ReactElement => {
return (
<div className='bg-blue-50'>
<Navbar />
</div>
);
};
export default Components;
-2
View File
@@ -17,9 +17,7 @@ const mappingPublicRoutes = [
'/auth/register-mentor', '/auth/register-mentor',
'/auth/register-mentor/pending', '/auth/register-mentor/pending',
'/auth/register-mentor/success', '/auth/register-mentor/success',
'/mentoring',
'/resources', '/resources',
'/articles',
]; ];
const mappingRoutePermissions = [ const mappingRoutePermissions = [
@@ -0,0 +1,6 @@
import { redirect } from 'next/navigation';
export default function Page() {
redirect('https://forms.gle/fNzQWSdqXoyFsf8V6');
return null;
}
-147
View File
@@ -1,147 +0,0 @@
import { api } from '../';
import {
TArticleDetail,
TArticleListItem,
TArticleListParams,
TMentorAvailability,
TMentorDetail,
TMentorListParams,
TMentorRegisterRequest,
TSessionFeedbackRequest,
TSessionListResponse,
TBookSessionRequest,
} from '../../types/dimentorin';
import { TResponseMessage } from '../../types/common';
export const getMentors = async (
params?: TMentorListParams
): Promise<TMentorDetail[]> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/mentors/public',
params,
});
return data.data;
};
export const getMentorDetail = async (id: string): Promise<TMentorDetail> => {
const { data } = await api({
method: 'GET',
url: `/dimentorin/mentors/public/${id}`,
});
return data.data;
};
export const getMentorMe = async (): Promise<TMentorDetail> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/mentors/me',
});
return data.data;
};
export const getMentorMeStatus = async (): Promise<{ status: string }> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/mentors/me/status',
});
return data.data;
};
export const getMentorAvailability = async (
id: string
): Promise<TMentorAvailability> => {
const { data } = await api({
method: 'GET',
url: `/dimentorin/mentors/${id}/availability`,
});
return data.data;
};
export const getMentorSessions = async (
id: string
): Promise<TSessionListResponse> => {
const { data } = await api({
method: 'GET',
url: `/dimentorin/mentors/${id}/sessions`,
});
return data.data;
};
export const getMySessions = async (): Promise<TSessionListResponse> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/sessions/me',
});
return data.data;
};
export const postBookSession = async (
mentorId: string,
payload: TBookSessionRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'POST',
url: `/dimentorin/mentors/${mentorId}/sessions/create`,
data: payload,
});
return data;
};
export const postSessionFeedback = async (
sessionId: string,
payload: TSessionFeedbackRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'POST',
url: `/dimentorin/sessions/${sessionId}/feedback/create`,
data: payload,
});
return data;
};
export const getArticles = async (
params?: TArticleListParams
): Promise<TArticleListItem[]> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/articles',
params,
});
return data.data;
};
export const getArticleCategories = async (): Promise<string[]> => {
const { data } = await api({
method: 'GET',
url: '/dimentorin/articles/categories',
});
return data.data;
};
export const getArticleBySlug = async (slug: string): Promise<TArticleDetail> => {
const { data } = await api({
method: 'GET',
url: `/dimentorin/articles/slug/${slug}`,
});
return data.data;
};
export const getArticleById = async (id: string): Promise<TArticleDetail> => {
const { data } = await api({
method: 'GET',
url: `/dimentorin/articles/${id}`,
});
return data.data;
};
export const postRegisterMentor = async (
payload: TMentorRegisterRequest
): Promise<TResponseMessage> => {
const { data } = await api({
method: 'POST',
url: '/dimentorin/mentors/create',
data: payload,
});
return data;
};
+1 -24
View File
@@ -1,8 +1,6 @@
import axios, { AxiosError, AxiosRequestConfig } from 'axios'; import axios, { AxiosRequestConfig } from 'axios';
import Cookies from 'js-cookie';
export * from './auth'; export * from './auth';
export * from './dimentorin';
export * from './gacha'; export * from './gacha';
export * from './users'; export * from './users';
@@ -11,24 +9,3 @@ const config: AxiosRequestConfig = {
}; };
export const api = axios.create(config); export const api = axios.create(config);
api.interceptors.request.use((config) => {
const raw = Cookies.get('token');
if (raw) {
try {
const parsed = JSON.parse(raw);
const token = parsed?.token?.access_token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
} catch {
// ignore malformed token
}
}
return config;
});
api.interceptors.response.use(
(response) => response,
(error: AxiosError) => Promise.reject(error)
);
-164
View File
@@ -1,164 +0,0 @@
import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
import {
getArticleById,
getArticleBySlug,
getArticleCategories,
getArticles,
getMentorAvailability,
getMentorDetail,
getMentorMe,
getMentorMeStatus,
getMentorSessions,
getMentors,
getMySessions,
postBookSession,
postRegisterMentor,
postSessionFeedback,
} from '../../api/dimentorin';
import {
TArticleDetail,
TArticleListItem,
TArticleListParams,
TBookSessionRequest,
TMentorAvailability,
TMentorDetail,
TMentorListParams,
TMentorRegisterRequest,
TSessionFeedbackRequest,
TSessionListResponse,
} from '../../types/dimentorin';
import { TResponseError, TResponseMessage } from '../../types/common';
export const useGetMentors = (
params?: TMentorListParams
): UseQueryResult<TMentorDetail[], TResponseError> => {
return useQuery({
queryKey: ['get-mentors', params],
queryFn: async () => await getMentors(params),
});
};
export const useGetMentorDetail = (
id: string
): UseQueryResult<TMentorDetail, TResponseError> => {
return useQuery({
queryKey: ['get-mentor-detail', id],
queryFn: async () => await getMentorDetail(id),
enabled: !!id,
});
};
export const useGetMentorMe = (): UseQueryResult<
TMentorDetail,
TResponseError
> => {
return useQuery({
queryKey: ['get-mentor-me'],
queryFn: async () => await getMentorMe(),
});
};
export const useGetMentorMeStatus = (): UseQueryResult<
{ status: string },
TResponseError
> => {
return useQuery({
queryKey: ['get-mentor-me-status'],
queryFn: async () => await getMentorMeStatus(),
});
};
export const useGetMentorAvailability = (
id: string
): UseQueryResult<TMentorAvailability, TResponseError> => {
return useQuery({
queryKey: ['get-mentor-availability', id],
queryFn: async () => await getMentorAvailability(id),
enabled: !!id,
});
};
export const useGetMentorSessions = (
id: string
): UseQueryResult<TSessionListResponse, TResponseError> => {
return useQuery({
queryKey: ['get-mentor-sessions', id],
queryFn: async () => await getMentorSessions(id),
enabled: !!id,
});
};
export const useGetMySessions = (): UseQueryResult<
TSessionListResponse,
TResponseError
> => {
return useQuery({
queryKey: ['get-my-sessions'],
queryFn: async () => await getMySessions(),
});
};
export const usePostBookSession = (mentorId: string) => {
return useMutation({
mutationKey: ['post-book-session', mentorId],
mutationFn: async (payload: TBookSessionRequest) =>
await postBookSession(mentorId, payload),
});
};
export const usePostSessionFeedback = (sessionId: string) => {
return useMutation({
mutationKey: ['post-session-feedback', sessionId],
mutationFn: async (payload: TSessionFeedbackRequest) =>
await postSessionFeedback(sessionId, payload),
});
};
export const usePostRegisterMentor = () => {
return useMutation({
mutationKey: ['post-register-mentor'],
mutationFn: async (payload: TMentorRegisterRequest) =>
await postRegisterMentor(payload),
});
};
export const useGetArticles = (
params?: TArticleListParams
): UseQueryResult<TArticleListItem[], TResponseError> => {
return useQuery({
queryKey: ['get-articles', params],
queryFn: async () => await getArticles(params),
});
};
export const useGetArticleCategories = (): UseQueryResult<
string[],
TResponseError
> => {
return useQuery({
queryKey: ['get-article-categories'],
queryFn: async () => await getArticleCategories(),
});
};
export const useGetArticleBySlug = (
slug: string
): UseQueryResult<TArticleDetail, TResponseError> => {
return useQuery({
queryKey: ['get-article-by-slug', slug],
queryFn: async () => await getArticleBySlug(slug),
enabled: !!slug,
});
};
export const useGetArticleById = (
id: string
): UseQueryResult<TArticleDetail, TResponseError> => {
return useQuery({
queryKey: ['get-article-by-id', id],
queryFn: async () => await getArticleById(id),
enabled: !!id,
});
};
export type { TResponseMessage };
-1
View File
@@ -1,4 +1,3 @@
export * from './auth'; export * from './auth';
export * from './dimentorin';
export * from './gacha'; export * from './gacha';
export * from './users'; export * from './users';
-157
View File
@@ -1,157 +0,0 @@
import { TResponseList } from '../common';
export type TMentorListItem = {
id: string;
user_id: string;
fullname: string | null;
email: string | null;
status: string;
created_at: string;
updated_at: string;
};
export type TMentorDetail = {
id: string;
user_id: string;
fullname: string | null;
email: string | null;
legal_name: string | null;
gender: string | null;
domicile: string | null;
bio: string | null;
last_education: string | null;
linkedin_url: string | null;
github_url: string | null;
cv_url: string | null;
portfolio_url: string | null;
phone_for_verification: string | null;
industries: string[];
expertise: string[];
languages: string[];
current_company: string;
current_role: string;
years_of_experience: number;
topics_of_interest: string[];
preferred_mentee_level: string[];
preferred_mentoring_formats: string[];
availability_commitment: string;
mentoring_rate: number;
status: string;
created_at: string;
updated_at: string;
};
export type TMentorListParams = {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
order?: string;
};
export type TMentorListResponse = TMentorListItem[] | TResponseList<TMentorListItem>;
export type TAvailabilitySlot = {
date: string;
time: string;
available: boolean;
};
export type TMentorAvailability = {
mentor_id: string;
availability_commitment: string;
preferred_formats: string[];
slots: TAvailabilitySlot[];
booked_dates: string[];
};
export type TSessionListItem = {
id: string;
mentor_id: string;
mentee_id: string;
topic: string;
scheduled_at: string;
duration_minutes: number;
session_type: string;
status: string;
rating: number | null;
created_at: string;
mentee_email: string | null;
mentee_fullname: string | null;
};
export type TSessionListResponse = {
sessions: TSessionListItem[];
total: number;
};
export type TBookSessionRequest = {
topic: string;
scheduled_at: string;
description?: string | null;
duration_minutes?: number | null;
session_type?: string | null;
};
export type TSessionFeedbackRequest = {
feedback: string;
rating: number;
};
export type TArticleListItem = {
id: string;
title: string;
slug: string;
category: string;
excerpt: string;
cover_url: string | null;
author_name: string | null;
created_at: string;
};
export type TArticleDetail = TArticleListItem & {
content: string;
is_published: boolean;
updated_at: string;
};
export type TArticleListParams = {
page?: number;
per_page?: number;
category?: string;
};
export type TMentorRegisterRequest = {
email: string;
password: string;
fullname: string;
phone_number?: string | null;
identity_and_verification: {
legal_name: string;
gender?: string | null;
domicile?: string | null;
identity_document_url: string;
phone_for_verification?: string | null;
};
professional_profile: {
bio: string;
last_education?: string | null;
linkedin_url?: string | null;
github_url?: string | null;
cv_url?: string | null;
portfolio_url?: string | null;
industries: string[];
expertise: string[];
languages: string[];
current_company: string;
current_role: string;
years_of_experience: number;
};
mentoring_logistics: {
topics_of_interest: string[];
preferred_mentee_level: string[];
preferred_mentoring_formats: string[];
availability_commitment: string;
mentoring_rate_amount: number;
};
};
-1
View File
@@ -1,5 +1,4 @@
export * from './auth'; export * from './auth';
export * from './dimentorin';
export * from './gacha'; export * from './gacha';
export * from './users'; export * from './users';
export * from './roles'; export * from './roles';
+584 -71
View File
@@ -95,7 +95,7 @@
"h3": "^1.8.2", "h3": "^1.8.2",
"jiti": "2.4.2", "jiti": "2.4.2",
"jsdom": "~22.1.0", "jsdom": "~22.1.0",
"nx": "^20.8.1", "nx": "20.8.1",
"openapi-typescript": "^7.8.0", "openapi-typescript": "^7.8.0",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"prettier": "^2.6.2", "prettier": "^2.6.2",
@@ -2254,20 +2254,20 @@
} }
}, },
"node_modules/@emnapi/core": { "node_modules/@emnapi/core": {
"version": "1.4.5", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
"integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@emnapi/wasi-threads": "1.0.4", "@emnapi/wasi-threads": "1.0.2",
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.4.5", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2275,9 +2275,9 @@
} }
}, },
"node_modules/@emnapi/wasi-threads": { "node_modules/@emnapi/wasi-threads": {
"version": "1.0.4", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz",
"integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -7573,7 +7573,7 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz",
"integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright": "1.52.0" "playwright": "1.52.0"
@@ -10971,6 +10971,18 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/doctrine": { "node_modules/@types/doctrine": {
"version": "0.0.9", "version": "0.0.9",
"resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz",
@@ -11196,6 +11208,15 @@
"dev": true, "dev": true,
"license": "MIT" "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==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.16.9", "version": "18.16.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz",
@@ -11238,7 +11259,7 @@
"version": "19.1.2", "version": "19.1.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
@@ -11248,7 +11269,7 @@
"version": "19.1.2", "version": "19.1.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz",
"integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
@@ -13204,9 +13225,9 @@
} }
}, },
"node_modules/@yarnpkg/parsers/node_modules/js-yaml": { "node_modules/@yarnpkg/parsers/node_modules/js-yaml": {
"version": "3.15.1", "version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -13332,6 +13353,7 @@
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "4" "debug": "4"
@@ -14007,15 +14029,14 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.18.1", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.16.0", "follow-redirects": "^1.15.6",
"form-data": "^4.0.5", "form-data": "^4.0.0",
"https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0"
"proxy-from-env": "^2.1.0"
} }
}, },
"node_modules/axobject-query": { "node_modules/axobject-query": {
@@ -15290,7 +15311,7 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"readdirp": "^4.0.1" "readdirp": "^4.0.1"
@@ -16568,7 +16589,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cwd": { "node_modules/cwd": {
@@ -16809,6 +16830,7 @@
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -17521,9 +17543,9 @@
} }
}, },
"node_modules/end-of-stream": { "node_modules/end-of-stream": {
"version": "1.4.5", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -19345,9 +19367,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.16.0", "version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -19575,16 +19597,15 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.6", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0", "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4", "mime-types": "^2.1.12"
"mime-types": "^2.1.35"
}, },
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@@ -19703,9 +19724,9 @@
} }
}, },
"node_modules/front-matter/node_modules/js-yaml": { "node_modules/front-matter/node_modules/js-yaml": {
"version": "3.15.1", "version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -20364,9 +20385,9 @@
} }
}, },
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.4", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -20718,6 +20739,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"agent-base": "6", "agent-base": "6",
@@ -20822,7 +20844,7 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz",
"integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/import-fresh": { "node_modules/import-fresh": {
@@ -24983,6 +25005,7 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/muggle-string": { "node_modules/muggle-string": {
@@ -25916,9 +25939,9 @@
} }
}, },
"node_modules/nx/node_modules/brace-expansion": { "node_modules/nx/node_modules/brace-expansion": {
"version": "2.1.4", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -25997,9 +26020,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nx/node_modules/semver": { "node_modules/nx/node_modules/semver": {
"version": "7.8.4", "version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@@ -27390,7 +27413,7 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.52.0" "playwright-core": "1.52.0"
@@ -27409,7 +27432,7 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
@@ -28285,13 +28308,10 @@
} }
}, },
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "2.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT", "license": "MIT"
"engines": {
"node": ">=10"
}
}, },
"node_modules/prr": { "node_modules/prr": {
"version": "1.0.1", "version": "1.0.1",
@@ -28798,7 +28818,7 @@
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 14.18.0" "node": ">= 14.18.0"
@@ -29463,7 +29483,7 @@
"version": "1.87.0", "version": "1.87.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz",
"integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==", "integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chokidar": "^4.0.0", "chokidar": "^4.0.0",
@@ -32133,9 +32153,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/tmp": { "node_modules/tmp": {
"version": "0.2.7", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -32430,6 +32450,502 @@
"node": ">=0.6.x" "node": ">=0.6.x"
} }
}, },
"node_modules/tsx": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
"integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"esbuild": "~0.23.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/esbuild": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.23.1",
"@esbuild/android-arm": "0.23.1",
"@esbuild/android-arm64": "0.23.1",
"@esbuild/android-x64": "0.23.1",
"@esbuild/darwin-arm64": "0.23.1",
"@esbuild/darwin-x64": "0.23.1",
"@esbuild/freebsd-arm64": "0.23.1",
"@esbuild/freebsd-x64": "0.23.1",
"@esbuild/linux-arm": "0.23.1",
"@esbuild/linux-arm64": "0.23.1",
"@esbuild/linux-ia32": "0.23.1",
"@esbuild/linux-loong64": "0.23.1",
"@esbuild/linux-mips64el": "0.23.1",
"@esbuild/linux-ppc64": "0.23.1",
"@esbuild/linux-riscv64": "0.23.1",
"@esbuild/linux-s390x": "0.23.1",
"@esbuild/linux-x64": "0.23.1",
"@esbuild/netbsd-x64": "0.23.1",
"@esbuild/openbsd-arm64": "0.23.1",
"@esbuild/openbsd-x64": "0.23.1",
"@esbuild/sunos-x64": "0.23.1",
"@esbuild/win32-arm64": "0.23.1",
"@esbuild/win32-ia32": "0.23.1",
"@esbuild/win32-x64": "0.23.1"
}
},
"node_modules/tunnel-agent": { "node_modules/tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -34843,19 +35359,16 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.9.0", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
}, },
"engines": { "engines": {
"node": ">= 14.6" "node": ">= 14"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
} }
}, },
"node_modules/yaml-ast-parser": { "node_modules/yaml-ast-parser": {
+1 -1
View File
@@ -111,7 +111,7 @@
"h3": "^1.8.2", "h3": "^1.8.2",
"jiti": "2.4.2", "jiti": "2.4.2",
"jsdom": "~22.1.0", "jsdom": "~22.1.0",
"nx": "^20.8.1", "nx": "20.8.1",
"openapi-typescript": "^7.8.0", "openapi-typescript": "^7.8.0",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"prettier": "^2.6.2", "prettier": "^2.6.2",