Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c5c5db47b | ||
|
|
583b6b224e | ||
|
|
ce0579de5d | ||
|
|
7df4592a47 | ||
|
|
77eb755783 | ||
|
|
e2be5105e9 | ||
|
|
eb5ff2e46c | ||
|
|
e1269f4daa | ||
|
|
487623e4fb | ||
|
|
eff44c42a1 | ||
|
|
aa52b62ae7 | ||
|
|
3dec77bd0a | ||
|
|
ec81922001 | ||
|
|
d7b70b21d1 | ||
|
|
a7cf85dd66 | ||
|
|
bb22068856 | ||
|
|
209fa3fff1 | ||
|
|
935d23887c | ||
|
|
363f1582c9 | ||
|
|
1a9b47953a | ||
|
|
182ff7facc | ||
|
|
af8968ac01 | ||
|
|
4bd66af0ff | ||
|
|
73826fb4c3 | ||
|
|
2f9407e258 | ||
|
|
534bf0e10a | ||
|
|
df479a0a8d | ||
|
|
1948d2c67f |
@@ -1,9 +1,19 @@
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button } from "@imphnen-frontend-service/ui/atoms";
|
||||
import { cn } from "@imphnen-frontend-service/utils";
|
||||
import { cn, For, Show } from "@imphnen-frontend-service/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetArticleBySlug } from "@imphnen-frontend-service/service";
|
||||
|
||||
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 (
|
||||
<main>
|
||||
<motion.section
|
||||
@@ -12,76 +22,83 @@ export default function DetailArticle() {
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
>
|
||||
<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]"
|
||||
>
|
||||
<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>
|
||||
<Show
|
||||
condition={!isLoading && !isError && !!article}
|
||||
fallback={
|
||||
<div className="w-full max-w-[900px] mx-auto text-center text-neutral-500 py-20">
|
||||
{isError ? "Artikel tidak ditemukan." : "Memuat artikel..."}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<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]"
|
||||
>
|
||||
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’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 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>
|
||||
|
||||
<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 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>
|
||||
<For data={paragraphs}>
|
||||
{(paragraph) => (
|
||||
<p key={paragraph.slice(0, 40)} className="leading-tight">
|
||||
{paragraph}
|
||||
</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</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>
|
||||
</Show>
|
||||
</motion.section>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -2,12 +2,26 @@ import { Button } from "@imphnen-frontend-service/ui/atoms"
|
||||
import { cn, Show } from "@imphnen-frontend-service/utils"
|
||||
import { FC } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import { TArticleListItem } from "@imphnen-frontend-service/service"
|
||||
|
||||
export type ArticleCardProps = {
|
||||
variant?: "default" | "recent" | "featured"
|
||||
article?: TArticleListItem
|
||||
}
|
||||
|
||||
export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default' }) => {
|
||||
function formatDate(iso?: string) {
|
||||
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 (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -22,25 +36,25 @@ export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default' }) => {
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src="/image/59de8a682dfbe42455613ea07477ff3e30532c1f.webp"
|
||||
alt="Cover's Article"
|
||||
src={cover ?? "/image/59de8a682dfbe42455613ea07477ff3e30532c1f.webp"}
|
||||
alt={title}
|
||||
className={cn("w-full object-cover", variant === "recent" && "h-full")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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]">
|
||||
5 min read
|
||||
{article?.category ?? "Artikel"}
|
||||
</div>
|
||||
<Link
|
||||
to="/articles/detail"
|
||||
to={slug ? `/articles/${slug}` : "/articles"}
|
||||
className={cn(
|
||||
"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 === "recent" && "mb-7 xl:text-[27px]"
|
||||
)}
|
||||
>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
{title}
|
||||
</Link>
|
||||
<Show condition={variant !== "recent"}>
|
||||
<p
|
||||
@@ -49,7 +63,7 @@ export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default' }) => {
|
||||
variant === "featured" && "text-[15px] md:text-[19px] md:mb-6",
|
||||
)}
|
||||
>
|
||||
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.
|
||||
{excerpt}
|
||||
</p>
|
||||
</Show>
|
||||
<div
|
||||
@@ -59,14 +73,16 @@ export const ArticleCard: FC<ArticleCardProps> = ({ variant = 'default' }) => {
|
||||
variant === "featured" && "text-[15px] md:text-[19px] xl:text-base",
|
||||
)}
|
||||
>
|
||||
<p>Writer’s Name</p>
|
||||
<p>14 March 2025</p>
|
||||
<p>{author}</p>
|
||||
<p>{date}</p>
|
||||
</div>
|
||||
|
||||
<Show condition={variant === "default"}>
|
||||
<Button type="button" size="sm" className="px-2.5 py-2">
|
||||
Baca Artikel
|
||||
</Button>
|
||||
<Link to={slug ? `/articles/${slug}` : "/articles"}>
|
||||
<Button type="button" size="sm" className="px-2.5 py-2">
|
||||
Baca Artikel
|
||||
</Button>
|
||||
</Link>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import { ReactElement, useRef, useState } from "react";
|
||||
import { ArticleCard } from "./_components/card/article";
|
||||
import { cn, For } from "@imphnen-frontend-service/utils";
|
||||
import { cn, For, Show } from "@imphnen-frontend-service/utils";
|
||||
import { Button } from "@imphnen-frontend-service/ui/atoms";
|
||||
import { motion, useInView, Variants } from "framer-motion";
|
||||
|
||||
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]
|
||||
import { useGetArticleCategories, useGetArticles } from "@imphnen-frontend-service/service";
|
||||
|
||||
export default function Components(): ReactElement {
|
||||
const [activeTab, setActiveTab] = useState<Category>('UI/UX Design')
|
||||
const [activeTab, setActiveTab] = useState<string>("Semua")
|
||||
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 isInView = useInView(ref, { once: true, amount: 0.2 })
|
||||
@@ -52,29 +60,34 @@ export default function Components(): ReactElement {
|
||||
Featuring Articles
|
||||
</motion.h1>
|
||||
|
||||
<div className="grid gap-12 xl:grid-cols-2">
|
||||
<motion.div variants={childVariants}>
|
||||
<ArticleCard variant="featured" />
|
||||
</motion.div>
|
||||
<Show
|
||||
condition={!isLoading}
|
||||
fallback={<p className="text-neutral-500">Memuat artikel...</p>}
|
||||
>
|
||||
<div className="grid gap-12 xl:grid-cols-2">
|
||||
<motion.div variants={childVariants}>
|
||||
<ArticleCard variant="featured" article={featured} />
|
||||
</motion.div>
|
||||
|
||||
<div>
|
||||
<motion.h1
|
||||
className="text-[19px] text-neutral-800 font-semibold mb-10 md:text-[29px] xl:text-[37px] xl:mb-12"
|
||||
variants={childVariants}
|
||||
>
|
||||
Recent Articles
|
||||
</motion.h1>
|
||||
<div className="grid gap-10">
|
||||
<For data={Array.from({ length: 3 })}>
|
||||
{(_, index) => (
|
||||
<motion.div key={index} variants={childVariants}>
|
||||
<ArticleCard variant="recent" />
|
||||
</motion.div>
|
||||
)}
|
||||
</For>
|
||||
<div>
|
||||
<motion.h1
|
||||
className="text-[19px] text-neutral-800 font-semibold mb-10 md:text-[29px] xl:text-[37px] xl:mb-12"
|
||||
variants={childVariants}
|
||||
>
|
||||
Recent Articles
|
||||
</motion.h1>
|
||||
<div className="grid gap-10">
|
||||
<For data={recent}>
|
||||
{(article, index) => (
|
||||
<motion.div key={article.id ?? index} variants={childVariants}>
|
||||
<ArticleCard variant="recent" article={article} />
|
||||
</motion.div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto">
|
||||
@@ -86,7 +99,7 @@ export default function Components(): ReactElement {
|
||||
</motion.h1>
|
||||
<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}>
|
||||
<For data={CATEGORIES}>
|
||||
<For data={tabs}>
|
||||
{(category) => (
|
||||
<Button
|
||||
key={category}
|
||||
@@ -106,15 +119,20 @@ export default function Components(): ReactElement {
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 xl:grid-cols-3">
|
||||
<For data={Array.from({ length: 3 })}>
|
||||
{(_, index) => (
|
||||
<motion.div key={index} variants={childVariants}>
|
||||
<ArticleCard />
|
||||
</motion.div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Show
|
||||
condition={filtered.length > 0}
|
||||
fallback={<p className="text-neutral-500 text-center py-10">Belum ada artikel di kategori ini.</p>}
|
||||
>
|
||||
<div className="grid gap-8 xl:grid-cols-3">
|
||||
<For data={filtered}>
|
||||
{(article, index) => (
|
||||
<motion.div key={article.id ?? index} variants={childVariants}>
|
||||
<ArticleCard article={article} />
|
||||
</motion.div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</motion.section>
|
||||
</main>
|
||||
|
||||
+29
-5
@@ -10,6 +10,7 @@ import { QrisPaymentStep } from "./steps/qris-payement"
|
||||
import { VAPaymentStep } from "./steps/va-payment"
|
||||
import { SuccessStep } from "./steps/success"
|
||||
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
|
||||
type Step = typeof STEPS[number]
|
||||
@@ -17,13 +18,32 @@ type Step = typeof STEPS[number]
|
||||
type Props = {
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
mentorId?: string
|
||||
mentor?: { fullname?: string | null; current_role?: string; current_company?: string }
|
||||
}
|
||||
|
||||
export const AppointmentModal: FC<Props> = ({ open, setOpen }) => {
|
||||
export const AppointmentModal: FC<Props> = ({ open, setOpen, mentorId, mentor }) => {
|
||||
const [step, setStep] = useState<Step>('topic')
|
||||
const [selectedTopics, setSelectedTopics] = useState<number[]>([])
|
||||
const [booking, setBooking] = useState<TBookSessionRequest | null>(null)
|
||||
const [error, setError] = useState("")
|
||||
const bookMutation = usePostBookSession(mentorId ?? "")
|
||||
|
||||
const handleStep = (action: 'next' | 'prev') => {
|
||||
const handleStep = async (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') {
|
||||
setOpen(false)
|
||||
} else if (action === 'next') {
|
||||
@@ -113,13 +133,17 @@ export const AppointmentModal: FC<Props> = ({ open, setOpen }) => {
|
||||
|
||||
<AnimatePresence>
|
||||
{step === 'topic' && <TopicStep selectedTopics={selectedTopics} setSelectedTopics={setSelectedTopics} />}
|
||||
{step === 'schedule' && <ScheduleStep />}
|
||||
{step === 'schedule' && <ScheduleStep onChange={(data) => setBooking(data)} />}
|
||||
{step === 'profile' && <ProfileStep />}
|
||||
{step === 'payment' && <PaymentStep selectedTopics={selectedTopics} />}
|
||||
{step === 'payment' && <PaymentStep selectedTopics={selectedTopics} mentor={mentor} booking={booking} />}
|
||||
{step === 'qr-payment' && <QrisPaymentStep />}
|
||||
{step === 'va-payment' && <VAPaymentStep />}
|
||||
{step === 'success' && <SuccessStep />}
|
||||
</AnimatePresence>
|
||||
|
||||
{error && (
|
||||
<p className="text-danger-600 text-xs mt-4 font-medium">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Show condition={step !== 'success'}>
|
||||
@@ -148,7 +172,7 @@ export const AppointmentModal: FC<Props> = ({ open, setOpen }) => {
|
||||
condition={step !== 'success'}
|
||||
fallback="Halman Booking"
|
||||
>
|
||||
<Show condition={step !== 'payment'} fallback="Bayar Sekarang">
|
||||
<Show condition={step !== 'payment'} fallback={bookMutation.isPending ? "Memproses..." : "Bayar Sekarang"}>
|
||||
Selanjutnya
|
||||
</Show>
|
||||
</Show>
|
||||
|
||||
+17
-6
@@ -2,12 +2,18 @@ import { For, Show } from "@imphnen-frontend-service/utils"
|
||||
import { FC } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
import { TOPICS } from "../../../sections/topics"
|
||||
import { TBookSessionRequest } from "@imphnen-frontend-service/service"
|
||||
|
||||
type Props = {
|
||||
selectedTopics: number[]
|
||||
mentor?: { fullname?: string | null; current_role?: string; current_company?: string } | null
|
||||
booking?: TBookSessionRequest | null
|
||||
}
|
||||
|
||||
export const PaymentStep: FC<Props> = ({ selectedTopics }) => {
|
||||
export const PaymentStep: FC<Props> = ({ selectedTopics, mentor, booking }) => {
|
||||
const rate = 50000
|
||||
const serviceFee = 2000
|
||||
|
||||
return (
|
||||
<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"
|
||||
@@ -26,11 +32,16 @@ export const PaymentStep: FC<Props> = ({ selectedTopics }) => {
|
||||
</div>
|
||||
<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]">
|
||||
Muhammad Firdaus Oi Oi Oi, S.H., M.H.
|
||||
{mentor?.fullname || "Mentor"}
|
||||
</h3>
|
||||
<p className="text-[10px] text-neutral-600 md:text-xs">
|
||||
UI Designer at Oray orayan Studios
|
||||
{mentor?.current_role || "Mentor"} at {mentor?.current_company || "IMPHNEN"}
|
||||
</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>
|
||||
@@ -104,14 +115,14 @@ export const PaymentStep: FC<Props> = ({ selectedTopics }) => {
|
||||
</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">
|
||||
<p className="font-normal">Subtotal :</p>
|
||||
<p className="text-right">Rp. 50.000</p>
|
||||
<p className="text-right">Rp. {rate.toLocaleString("id-ID")}</p>
|
||||
<p className="font-normal">Service Fee :</p>
|
||||
<p className="text-right">Rp. 2.000</p>
|
||||
<p className="text-right">Rp. {serviceFee.toLocaleString("id-ID")}</p>
|
||||
|
||||
<hr className="border-neutral-200 col-span-full mt-1" />
|
||||
|
||||
<p className="text-neutral-700">Total :</p>
|
||||
<p className="text-right text-neutral-700">Rp. 52.000</p>
|
||||
<p className="text-right text-neutral-700">Rp. {(rate + serviceFee).toLocaleString("id-ID")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+30
-6
@@ -1,9 +1,11 @@
|
||||
import { Input, Select, Textarea } from "@imphnen-frontend-service/ui/atoms"
|
||||
import { cn } from "@imphnen-frontend-service/utils"
|
||||
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.
|
||||
|
||||
|
||||
Saat ini, saya tertarik untuk mengejar __. Tujuan saya untuk sesi ini adalah __.
|
||||
|
||||
Saya ingin tahu secara khusus tentang ___.
|
||||
@@ -13,7 +15,27 @@ 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]')
|
||||
|
||||
export const ScheduleStep = () => {
|
||||
type Props = {
|
||||
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 (
|
||||
<motion.div
|
||||
className="bg-white px-6 py-5 rounded-md md:px-6"
|
||||
@@ -31,15 +53,15 @@ export const ScheduleStep = () => {
|
||||
<div className="grid gap-2.5 md:grid-cols-2 md:gap-5">
|
||||
<div>
|
||||
<label className={labelClass}>Tanggal</label>
|
||||
<Input type="date" className="min-w-full w-full" />
|
||||
<Input type="date" className="min-w-full w-full" value={date} onChange={(e) => setDate(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Waktu</label>
|
||||
<Input type="time" className="min-w-full w-full" />
|
||||
<Input type="time" className="min-w-full w-full" value={time} onChange={(e) => setTime(e.target.value)} />
|
||||
</div>
|
||||
<div className="relative md:col-span-full">
|
||||
<label className={labelClass}>Lokasi</label>
|
||||
<Select className="min-w-full w-full">
|
||||
<Select className="min-w-full w-full" value={sessionType} onChange={(e) => setSessionType(e.target.value)}>
|
||||
<option value="online">Online</option>
|
||||
<option value="offline">Offline</option>
|
||||
</Select>
|
||||
@@ -48,7 +70,9 @@ export const ScheduleStep = () => {
|
||||
<label className={labelClass}>Pertanyaan Untuk Senpai</label>
|
||||
<Textarea
|
||||
className="min-w-full w-full h-40"
|
||||
placeholder={placeholder} />
|
||||
placeholder={placeholder}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { StarFilled } from "@ant-design/icons"
|
||||
import { Button } from "@imphnen-frontend-service/ui/atoms"
|
||||
import { cn, For } from "@imphnen-frontend-service/utils"
|
||||
import { TMentorDetail } from "@imphnen-frontend-service/service"
|
||||
|
||||
type Props = {
|
||||
onBook: () => void
|
||||
mentor?: TMentorDetail
|
||||
}
|
||||
|
||||
export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
export const ProfileSection: React.FC<Props> = ({ onBook, mentor }) => {
|
||||
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 (
|
||||
<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
|
||||
@@ -33,10 +41,10 @@ export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
"md:text-[19px] md:mb-2 md:text-start xl:text-[23px]",
|
||||
)}
|
||||
>
|
||||
Muhammad Firdaus Oi Oi Oi, S.H., M.H.
|
||||
{name}
|
||||
</h1>
|
||||
<p className="text-xs mb-4 text-neutral-600 md:mb-5 md:text-[15px] xl:text-[19px] xl:mb-5">
|
||||
UI Designer at Oray orayan Studios
|
||||
{role} at {company}
|
||||
</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
|
||||
@@ -56,7 +64,7 @@ export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
<div>
|
||||
<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">
|
||||
<For data={['UI Design', 'UX Reseacrh']}>
|
||||
<For data={expertise}>
|
||||
{(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">
|
||||
{item}
|
||||
@@ -68,7 +76,7 @@ export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
<div>
|
||||
<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">
|
||||
<For data={['Design Thinking', 'Communication', 'Problem Solving', '19:00 WIB']}>
|
||||
<For data={softSkills}>
|
||||
{(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">
|
||||
{item}
|
||||
|
||||
+63
-54
@@ -1,68 +1,77 @@
|
||||
import { Button } from "@imphnen-frontend-service/ui/atoms"
|
||||
import { cn, For } from "@imphnen-frontend-service/utils"
|
||||
import { For, Show } from "@imphnen-frontend-service/utils"
|
||||
import { useGetMentorAvailability } from "@imphnen-frontend-service/service"
|
||||
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 = {
|
||||
onBook: () => void
|
||||
mentorId?: string
|
||||
}
|
||||
|
||||
export const SenpaiScheduleSection: FC<Props> = ({ onBook }) => {
|
||||
const DAY_NAMES = ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"]
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<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">
|
||||
<For data={SCHEDULES}>
|
||||
{(item) => (
|
||||
<div key={item.date}>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<p className="text-[15px] font-medium">{item.date}</p>
|
||||
<p
|
||||
className={cn(
|
||||
"rounded-4xl py-1 px-2.5 text-xs font-medium",
|
||||
item.availableCount > 5 ? "bg-primary-200 text-primary-500" : "bg-danger-100 text-danger-600"
|
||||
)}
|
||||
>
|
||||
{item.availableCount} slot tersisa
|
||||
</p>
|
||||
<Show
|
||||
condition={!isLoading && days.length > 0}
|
||||
fallback={
|
||||
<p className="text-xs text-neutral-500">
|
||||
{isLoading ? "Memuat jadwal senpai..." : "Belum ada jadwal tersedia."}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<For data={days}>
|
||||
{([date, daySlots]) => (
|
||||
<div key={date}>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<p className="text-[15px] font-medium">{formatDate(date)}</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 className="flex gap-x-3">
|
||||
<For data={item.schedule}>
|
||||
{(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>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
||||
<hr className="border-primary-200" />
|
||||
|
||||
@@ -72,4 +81,4 @@ export const SenpaiScheduleSection: FC<Props> = ({ onBook }) => {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { ProfileSection } from './_components/sections/profile'
|
||||
import { StatisticsSection } from './_components/sections/senpai-statistics'
|
||||
import { TopicsSection } from './_components/sections/topics'
|
||||
@@ -7,44 +8,61 @@ import { EducationSection } from './_components/sections/education'
|
||||
import { SenpaiScheduleSection } from './_components/sections/senpai-schedule'
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms'
|
||||
import { AppointmentModal } from './_components/modals/appointment'
|
||||
import { useGetMentorDetail } from '@imphnen-frontend-service/service'
|
||||
import { Show } from '@imphnen-frontend-service/utils'
|
||||
|
||||
export const Components: FC = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { id } = useParams()
|
||||
const { data: mentor, isLoading, isError } = useGetMentorDetail(id ?? '')
|
||||
|
||||
const resume = mentor?.bio ?? ''
|
||||
|
||||
return (
|
||||
<main>
|
||||
<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)} />
|
||||
<Show
|
||||
condition={!isLoading && !isError}
|
||||
fallback={
|
||||
<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">
|
||||
{isError ? 'Mentor tidak ditemukan' : 'Memuat profil mentor...'}
|
||||
</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)}>
|
||||
Book Your Senpai!
|
||||
</Button>
|
||||
<Button type="button" size="sm" className="w-full md:hidden" onClick={() => setOpen(true)}>
|
||||
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="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 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>
|
||||
|
||||
<ExperienceSection />
|
||||
<EducationSection />
|
||||
</div>
|
||||
|
||||
<div className="hidden xl:block xl:w-[400px]">
|
||||
<SenpaiScheduleSection onBook={() => setOpen(true)} />
|
||||
<div className="hidden xl:block xl:w-[400px]">
|
||||
<SenpaiScheduleSection onBook={() => setOpen(true)} mentorId={mentor?.id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<AppointmentModal open={open} setOpen={setOpen} />
|
||||
<AppointmentModal open={open} setOpen={setOpen} mentorId={mentor?.id} mentor={mentor} />
|
||||
</Show>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,40 +1,70 @@
|
||||
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 { Link } from 'react-router-dom';
|
||||
|
||||
export const MentorCard: FC = () => {
|
||||
type TMentorCardProps = {
|
||||
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 (
|
||||
<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">
|
||||
<img src="/image/testimonial.webp" alt="Mentor" className="w-full object-cover" />
|
||||
<img src="/image/testimonial.webp" alt={mentor.fullname ?? 'Mentor'} className="w-full object-cover" />
|
||||
</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">
|
||||
3-5 Years Experience
|
||||
{experience}
|
||||
</Button>
|
||||
<h2 className="mb-1">
|
||||
<Link to="/mentoring/detail" className="text-xs font-semibold text-primary-500 md:text-[15px] md:font-semibold lg:text-[19px]">
|
||||
Fullname
|
||||
<Link to={`/mentoring/${mentor.id}`} className="text-xs font-semibold text-primary-500 md:text-[15px] md:font-semibold lg:text-[19px]">
|
||||
{mentor.fullname || mentor.legal_name || 'Mentor'}
|
||||
</Link>
|
||||
</h2>
|
||||
<p className="text-[8px] text-neutral-500 mb-3 md:text-[10px] md:font-medium md:mb-4 lg:text-xs">
|
||||
Full Stack Enjoyer at name company
|
||||
{role} at {company}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p className="text-[8px] text-neutral-500 mb-1 md:mb-2 lg:text-[10px]">Soft Skills :</p>
|
||||
<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]">
|
||||
Communication
|
||||
</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]">
|
||||
Communication
|
||||
</Button>
|
||||
<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]">
|
||||
+2
|
||||
</Button>
|
||||
{softSkills.length > 0 && (
|
||||
<div>
|
||||
<p className="text-[8px] text-neutral-500 mb-1 md:mb-2 lg:text-[10px]">Soft Skills :</p>
|
||||
<div className="space-x-2">
|
||||
<For data={shownSkills}>
|
||||
{(skill) => (
|
||||
<Button
|
||||
key={skill}
|
||||
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]"
|
||||
>
|
||||
{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>
|
||||
)
|
||||
|
||||
@@ -3,27 +3,24 @@ import { BannerSection } from './_components/banner-section';
|
||||
import { Topics } from './_components/topics';
|
||||
import { Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { For } from '@imphnen-frontend-service/utils';
|
||||
import { For, Show } from '@imphnen-frontend-service/utils';
|
||||
import { MentorCard } from './_components/mentor-card';
|
||||
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 { 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 => {
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 3,
|
||||
pageSize: 8,
|
||||
});
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const { data: mentors, isLoading } = useGetMentors({ search });
|
||||
|
||||
const table = useReactTable({
|
||||
data: TEMP_DATA,
|
||||
data: mentors ?? [],
|
||||
columns: [],
|
||||
state: { pagination },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
@@ -31,6 +28,8 @@ export const Components: FC = (): ReactElement => {
|
||||
onPaginationChange: setPagination,
|
||||
});
|
||||
|
||||
const pageMentors = table.getRowModel().rows.map((row) => row.original);
|
||||
|
||||
const ref = useRef(null)
|
||||
const isInView = useInView(ref, { once: true, amount: 0.2 })
|
||||
|
||||
@@ -91,6 +90,8 @@ export const Components: FC = (): ReactElement => {
|
||||
<Input
|
||||
placeholder="Cari berdasarkan nama, posisi/peran"
|
||||
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" />
|
||||
</motion.div>
|
||||
@@ -101,13 +102,31 @@ export const Components: FC = (): ReactElement => {
|
||||
initial="hidden"
|
||||
animate={isInView ? 'visible' : 'hidden'}
|
||||
>
|
||||
<For data={Array.from({ length: 8 })}>
|
||||
{(_, index) => (
|
||||
<motion.div key={index} variants={childVariants}>
|
||||
<MentorCard />
|
||||
</motion.div>
|
||||
)}
|
||||
</For>
|
||||
<Show
|
||||
condition={!isLoading}
|
||||
fallback={
|
||||
<motion.p variants={childVariants} className="text-center text-neutral-500 col-span-full py-10">
|
||||
Memuat data mentor...
|
||||
</motion.p>
|
||||
}
|
||||
>
|
||||
<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>
|
||||
|
||||
<Pagination table={table} />
|
||||
|
||||
@@ -1,11 +1,80 @@
|
||||
import { FC, ReactElement, useState } from 'react';
|
||||
import { ControlledInputField, RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
||||
import { Button, Select } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { RegisterResetBanner } from '@imphnen-frontend-service/ui/organisms';
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
|
||||
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 => {
|
||||
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 (
|
||||
<div className="flex flex-col justify-center items-center min-h-screen py-[60px] px-[80px]">
|
||||
@@ -13,13 +82,6 @@ export const Components: FC = (): ReactElement => {
|
||||
<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:w-[714px]">
|
||||
<Button
|
||||
className='xl:hidden gap-3'
|
||||
variant='secondary'
|
||||
>
|
||||
<ArrowLeftOutlined />
|
||||
Login
|
||||
</Button>
|
||||
<RegisterMentorStep step={step} />
|
||||
<h3 className="mt-5 text-3xl font-semibold text-primary-500">
|
||||
Register
|
||||
@@ -27,60 +89,44 @@ export const Components: FC = (): ReactElement => {
|
||||
<h5 className="text-primary-500 font-medium">
|
||||
Welcome to the team, Mentor - powered by IMPHNEN
|
||||
</h5>
|
||||
<form>
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<div className={`${step !== 1 ? 'hidden' : ''}`}>
|
||||
<div className='mt-7 mb-2'>
|
||||
<InputField
|
||||
label="Nama Lengkap"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
placeholder="Nama Lengkap"
|
||||
name={'fullname'}
|
||||
label="Nama Lengkap" size="lg" className="w-full"
|
||||
placeholder="Nama Lengkap" name="fullname"
|
||||
value={form.fullname} onChange={(e) => setF('fullname', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<SelectField
|
||||
label="Jenis Kelamin"
|
||||
defaultValue=""
|
||||
size="lg"
|
||||
error=""
|
||||
label="Jenis Kelamin" defaultValue="" size="lg" error=""
|
||||
value={form.gender} onChange={(e) => setF('gender', e.target.value)}
|
||||
>
|
||||
<option value="" disabled hidden>
|
||||
Pilih Jenis Kelamin
|
||||
</option>
|
||||
<option value="laki-laki">Laki - Laki</option>
|
||||
<option value="perempuan">Perempuan</option>
|
||||
<option value="" disabled hidden>Pilih Jenis Kelamin</option>
|
||||
<option value="Laki-laki">Laki - Laki</option>
|
||||
<option value="Perempuan">Perempuan</option>
|
||||
</SelectField>
|
||||
<div className='my-2'>
|
||||
<InputField
|
||||
label="No Telp/Whatsapp Only"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
placeholder="Nama Lengkap"
|
||||
name={'fullname'}
|
||||
label="No Telp/Whatsapp Only" size="lg" className="w-full"
|
||||
placeholder="Nomor WhatsApp" name="phone_number"
|
||||
value={form.phone_number} onChange={(e) => setF('phone_number', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<SelectField
|
||||
label="Domisili"
|
||||
defaultValue=""
|
||||
size="lg"
|
||||
error=""
|
||||
label="Domisili" defaultValue="" size="lg" error=""
|
||||
value={form.domicile} onChange={(e) => setF('domicile', e.target.value)}
|
||||
>
|
||||
<option value="" disabled hidden>
|
||||
Pilih Domisili
|
||||
</option>
|
||||
<option value="aceh">Aceh</option>
|
||||
<option value="jawa-selatan">Jawa Selatan</option>
|
||||
<option value="" disabled hidden>Pilih Domisili</option>
|
||||
<option value="Aceh">Aceh</option>
|
||||
<option value="Jawa Barat">Jawa Barat</option>
|
||||
</SelectField>
|
||||
<div className='mt-2'>
|
||||
<SelectField
|
||||
label="Pendidikan Terakhir"
|
||||
defaultValue=""
|
||||
size="lg"
|
||||
error=""
|
||||
label="Pendidikan Terakhir" defaultValue="" size="lg" error=""
|
||||
value={form.last_education} onChange={(e) => setF('last_education', e.target.value)}
|
||||
>
|
||||
<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="S1">S1</option>
|
||||
<option value="S2">S2</option>
|
||||
@@ -90,145 +136,85 @@ export const Components: FC = (): ReactElement => {
|
||||
</div>
|
||||
<div className={`${step !== 2 ? 'hidden' : ''}`}>
|
||||
<div className='mt-7 mb-2 gap-2 flex flex-col'>
|
||||
<InputField
|
||||
label="Perusahaan Saat Ini"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
placeholder="Masukkan Perusahaan tempat kerja, Senpai~! ✨ (Pastikan tidak typo, ya~ 😆)"
|
||||
name={'fullname'}
|
||||
/>
|
||||
<InputField
|
||||
label="Masukan jabatan senpai di perusahaan"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
placeholder="Masukan jabatan senpai di perusahaan"
|
||||
name={'fullname'}
|
||||
/>
|
||||
<SelectField
|
||||
label="Topik Keahlian Yang Ingin Diajarkan"
|
||||
defaultValue=""
|
||||
size="lg"
|
||||
error=""
|
||||
>
|
||||
<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'}
|
||||
/>
|
||||
<InputField label="Perusahaan Saat Ini" size="lg" className="w-full"
|
||||
placeholder="Masukkan Perusahaan tempat kerja, Senpai~! ✨" name="current_company"
|
||||
value={form.current_company} onChange={(e) => setF('current_company', e.target.value)} />
|
||||
<InputField label="Masukan jabatan senpai di perusahaan" size="lg" className="w-full"
|
||||
placeholder="Jabatan" name="current_role"
|
||||
value={form.current_role} onChange={(e) => setF('current_role', e.target.value)} />
|
||||
<InputField label="Topik Keahlian Yang Ingin Diajarkan (pisahkan koma)" size="lg" className="w-full"
|
||||
placeholder="mis. Backend Developer, Rust" name="expertise"
|
||||
value={form.expertise} onChange={(e) => setF('expertise', e.target.value)} />
|
||||
<InputField label="Industri (pisahkan koma)" size="lg" className="w-full"
|
||||
placeholder="mis. Software, Education" name="industries"
|
||||
value={form.industries} onChange={(e) => setF('industries', e.target.value)} />
|
||||
<InputField label="Skill Utama / Bahasa (pisahkan koma)" size="lg" className="w-full"
|
||||
placeholder="mis. Rust, TypeScript" name="languages"
|
||||
value={form.languages} onChange={(e) => setF('languages', e.target.value)} />
|
||||
<InputField label="Tahun Pengalaman" type="number" size="lg" className="w-full"
|
||||
placeholder="mis. 3" name="years_of_experience"
|
||||
value={form.years_of_experience} onChange={(e) => setF('years_of_experience', e.target.value)} />
|
||||
<InputField label="Bio Singkat" size="lg" className="w-full"
|
||||
placeholder="Ceritakan pengalaman Anda" name="bio"
|
||||
value={form.bio} onChange={(e) => setF('bio', e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${step !== 3 ? 'hidden' : ''}`}>
|
||||
<div className='mt-7 mb-2 gap-2 flex flex-col'>
|
||||
<SelectField
|
||||
label="Durasi Sesi Mentoring"
|
||||
defaultValue=""
|
||||
size="lg"
|
||||
error=""
|
||||
>
|
||||
<option value="" disabled hidden>
|
||||
Placeholder
|
||||
</option>
|
||||
<InputField label="Topik Mentoring (pisahkan koma)" size="lg" className="w-full"
|
||||
placeholder="mis. Rust Programming, Backend" name="topics_of_interest"
|
||||
value={form.topics_of_interest} onChange={(e) => setF('topics_of_interest', e.target.value)} />
|
||||
<SelectField label="Tingkat Mentee" defaultValue="" size="lg" error=""
|
||||
value={form.preferred_mentee_level} onChange={(e) => setF('preferred_mentee_level', e.target.value)}>
|
||||
<option value="" disabled hidden>Pilih tingkat mentee</option>
|
||||
<option value="beginner">Beginner</option>
|
||||
<option value="intermediate">Intermediate</option>
|
||||
<option value="advanced">Advanced</option>
|
||||
</SelectField>
|
||||
<SelectField
|
||||
label="Format Pengajaran yang Anda Sediakan"
|
||||
defaultValue=""
|
||||
size="lg"
|
||||
error=""
|
||||
>
|
||||
<option value="" disabled hidden>
|
||||
Placeholder
|
||||
</option>
|
||||
</SelectField>
|
||||
<InputField
|
||||
type="file"
|
||||
label="Unggah Curiculum Vitae"
|
||||
size="lg"
|
||||
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,5–3 jam"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
placeholder="Isi Nominalnya dong senpaiii!!!"
|
||||
/>
|
||||
<InputField label="Format Pengajaran (pisahkan koma, mis. online,offline)" size="lg" className="w-full"
|
||||
placeholder="online,offline" name="preferred_mentoring_formats"
|
||||
value={form.preferred_mentoring_formats} onChange={(e) => setF('preferred_mentoring_formats', e.target.value)} />
|
||||
<InputField label="Komitmen Ketersediaan" size="lg" className="w-full"
|
||||
placeholder="mis. 2 jam per minggu" name="availability_commitment"
|
||||
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"
|
||||
placeholder="mis. 500000" name="mentoring_rate_amount"
|
||||
value={form.mentoring_rate_amount} onChange={(e) => setF('mentoring_rate_amount', e.target.value)} />
|
||||
<InputField label="URL LinkedIn" size="lg" className="w-full"
|
||||
placeholder="https://linkedin.com/in/..." name="linkedin_url"
|
||||
value={form.linkedin_url} onChange={(e) => setF('linkedin_url', e.target.value)} />
|
||||
<InputField label="URL Portofolio" size="lg" className="w-full"
|
||||
placeholder="Google Drive, GitHub, Behance" name="portfolio_url"
|
||||
value={form.portfolio_url} onChange={(e) => setF('portfolio_url', e.target.value)} />
|
||||
<h6 className='text-xs'>Silahkan isi dalam angka (Contoh:500.000)</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${step !== 4 ? 'hidden' : ''}`}>
|
||||
<div className='mt-7 mb-2 gap-2 flex flex-col'>
|
||||
<InputField
|
||||
type='email'
|
||||
label="Email"
|
||||
size="lg"
|
||||
className="w-full"
|
||||
placeholder="isi emailnya dong senpai!!!"
|
||||
/>
|
||||
<InputField
|
||||
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"
|
||||
/>
|
||||
<InputField type='email' label="Email" size="lg" className="w-full"
|
||||
placeholder="isi emailnya dong senpai!!!" name="email"
|
||||
value={form.email} onChange={(e) => setF('email', e.target.value)} />
|
||||
<InputField type='password' label="Buat Password" size="lg" className="w-full"
|
||||
placeholder="Buat Password" name="password"
|
||||
value={form.password} onChange={(e) => setF('password', e.target.value)} />
|
||||
<InputField type='password' label="Konfirmasi Password" size="lg" className="w-full"
|
||||
placeholder="Ulangi Password" name="confirmPassword"
|
||||
value={form.confirmPassword} onChange={(e) => setF('confirmPassword', e.target.value)} />
|
||||
</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'>
|
||||
<Button
|
||||
variant='secondary'
|
||||
onClick={() => setStep(step - 1)}
|
||||
type='button'
|
||||
className={`${step === 1 ? 'hidden' : ''}`}
|
||||
>
|
||||
<ArrowLeftOutlined />
|
||||
Kembali
|
||||
<Button variant='secondary' onClick={() => setStep(step - 1)} type='button'
|
||||
className={`${step === 1 ? 'hidden' : ''}`}>
|
||||
<ArrowLeftOutlined /> Kembali
|
||||
</Button>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={() => setStep(step + 1)}
|
||||
type='button'
|
||||
className={`${step === 4 ? 'hidden' : ''}`}
|
||||
>
|
||||
Lanjutkan
|
||||
<ArrowRightOutlined />
|
||||
<Button variant='primary' onClick={() => setStep(step + 1)} type='button'
|
||||
className={`${step === 4 ? 'hidden' : ''}`}>
|
||||
Lanjutkan <ArrowRightOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={() => setStep(step + 1)}
|
||||
type='button'
|
||||
className={`${step === 4 ? '' : 'hidden'}`}
|
||||
>
|
||||
Selesaikan
|
||||
<ArrowRightOutlined />
|
||||
<Button variant='primary' onClick={handleSubmit} type='button'
|
||||
className={`${step === 4 ? '' : 'hidden'}`} disabled={submitting}>
|
||||
{submitting ? "Mengirim..." : "Selesaikan"} <ArrowRightOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -239,4 +225,4 @@ export const Components: FC = (): ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Components;
|
||||
export default Components;
|
||||
@@ -0,0 +1,12 @@
|
||||
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;
|
||||
@@ -17,7 +17,9 @@ const mappingPublicRoutes = [
|
||||
'/auth/register-mentor',
|
||||
'/auth/register-mentor/pending',
|
||||
'/auth/register-mentor/success',
|
||||
'/mentoring',
|
||||
'/resources',
|
||||
'/articles',
|
||||
];
|
||||
|
||||
const mappingRoutePermissions = [
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
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,6 +1,8 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
export * from './auth';
|
||||
export * from './dimentorin';
|
||||
export * from './gacha';
|
||||
export * from './users';
|
||||
|
||||
@@ -9,3 +11,24 @@ const config: AxiosRequestConfig = {
|
||||
};
|
||||
|
||||
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)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
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,3 +1,4 @@
|
||||
export * from './auth';
|
||||
export * from './dimentorin';
|
||||
export * from './gacha';
|
||||
export * from './users';
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
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,4 +1,5 @@
|
||||
export * from './auth';
|
||||
export * from './dimentorin';
|
||||
export * from './gacha';
|
||||
export * from './users';
|
||||
export * from './roles';
|
||||
|
||||
Generated
+71
-584
@@ -95,7 +95,7 @@
|
||||
"h3": "^1.8.2",
|
||||
"jiti": "2.4.2",
|
||||
"jsdom": "~22.1.0",
|
||||
"nx": "20.8.1",
|
||||
"nx": "^20.8.1",
|
||||
"openapi-typescript": "^7.8.0",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^2.6.2",
|
||||
@@ -2254,20 +2254,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
|
||||
"integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==",
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz",
|
||||
"integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.0.2",
|
||||
"@emnapi/wasi-threads": "1.0.4",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
|
||||
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
|
||||
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2275,9 +2275,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz",
|
||||
"integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz",
|
||||
"integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -7573,7 +7573,7 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz",
|
||||
"integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.52.0"
|
||||
@@ -10971,18 +10971,6 @@
|
||||
"@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": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz",
|
||||
@@ -11208,15 +11196,6 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "18.16.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz",
|
||||
@@ -11259,7 +11238,7 @@
|
||||
"version": "19.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
|
||||
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
@@ -11269,7 +11248,7 @@
|
||||
"version": "19.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz",
|
||||
"integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
@@ -13225,9 +13204,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@yarnpkg/parsers/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz",
|
||||
"integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -13353,7 +13332,6 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
@@ -14029,14 +14007,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
|
||||
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
"follow-redirects": "^1.16.0",
|
||||
"form-data": "^4.0.5",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
@@ -15311,7 +15290,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
@@ -16589,7 +16568,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cwd": {
|
||||
@@ -16830,7 +16809,6 @@
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -17543,9 +17521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -19367,9 +19345,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -19597,15 +19575,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
"hasown": "^2.0.4",
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -19724,9 +19703,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/front-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz",
|
||||
"integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -20385,9 +20364,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -20739,7 +20718,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
@@ -20844,7 +20822,7 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz",
|
||||
"integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
@@ -25005,7 +24983,6 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
@@ -25939,9 +25916,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nx/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -26020,9 +25997,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nx/node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"version": "7.8.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
|
||||
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -27413,7 +27390,7 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
|
||||
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0"
|
||||
@@ -27432,7 +27409,7 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
|
||||
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@@ -28308,10 +28285,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/prr": {
|
||||
"version": "1.0.1",
|
||||
@@ -28818,7 +28798,7 @@
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
@@ -29483,7 +29463,7 @@
|
||||
"version": "1.87.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz",
|
||||
"integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
@@ -32153,9 +32133,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
|
||||
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
|
||||
"integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -32450,502 +32430,6 @@
|
||||
"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": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@@ -35359,16 +34843,19 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
|
||||
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-ast-parser": {
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@
|
||||
"h3": "^1.8.2",
|
||||
"jiti": "2.4.2",
|
||||
"jsdom": "~22.1.0",
|
||||
"nx": "20.8.1",
|
||||
"nx": "^20.8.1",
|
||||
"openapi-typescript": "^7.8.0",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^2.6.2",
|
||||
|
||||
Reference in New Issue
Block a user