+
{value}
{label}
diff --git a/apps/landing/src/app/(public)/(home)/_components/testimonial-section.tsx b/apps/landing/src/app/(public)/(home)/_components/testimonial-section.tsx
new file mode 100644
index 0000000..bc4d169
--- /dev/null
+++ b/apps/landing/src/app/(public)/(home)/_components/testimonial-section.tsx
@@ -0,0 +1,102 @@
+'use client';
+
+import TESTIMONIALS from '@/data/testimonials.json';
+import { buttonVariants } from '@components';
+import { motion, useInView } from 'framer-motion';
+import Image from 'next/image';
+import Link from 'next/link';
+import { useRef } from 'react';
+import { FaQuoteLeft } from 'react-icons/fa';
+
+export function TestimonialSection() {
+ const ref = useRef(null);
+ const isInView = useInView(ref, { once: true, amount: 0.1 });
+
+ const containerVariants = {
+ hidden: { opacity: 0 },
+ visible: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.1,
+ delayChildren: 0.2,
+ },
+ },
+ };
+
+ const itemVariants = {
+ hidden: { y: 20, opacity: 0 },
+ visible: {
+ y: 0,
+ opacity: 1,
+ transition: {
+ duration: 0.4,
+ ease: [0.25, 0.46, 0.45, 0.94],
+ },
+ },
+ };
+
+ return (
+
+
+
+
+
+ Apa Kata Mereka Tentang
+
+ Komunitas Kami?
+
+
+
+
+
+
+ {TESTIMONIALS.map((testimonial) => (
+
+
+
+
+
+
+ {testimonial.name}
+
+
{testimonial.role}
+
+
+
+
+
+ ))}
+
+
+
+
+ Tulis Testimonimu
+
+
+
+
+ );
+}
diff --git a/apps/landing/src/app/(public)/(home)/page.tsx b/apps/landing/src/app/(public)/(home)/page.tsx
new file mode 100644
index 0000000..535f374
--- /dev/null
+++ b/apps/landing/src/app/(public)/(home)/page.tsx
@@ -0,0 +1,14 @@
+import { CommunitySection } from './_components/community-section';
+import { CTASection } from './_components/cta-section';
+import { HeroSection } from './_components/hero-section';
+import { TestimonialSection } from './_components/testimonial-section';
+export default function Page() {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/apps/landing/src/app/(public)/_components/footer.tsx b/apps/landing/src/app/(public)/_components/footer.tsx
new file mode 100644
index 0000000..a6d1257
--- /dev/null
+++ b/apps/landing/src/app/(public)/_components/footer.tsx
@@ -0,0 +1,102 @@
+import { LogoSimple } from '@/app/_components/logo';
+import NAVIGATIONS from '@/data/navigations.json';
+import SOCIALS from '@/data/socials.json';
+import Link from 'next/link';
+import {
+ FaDiscord,
+ FaFacebook,
+ FaInstagram,
+ FaLinkedinIn,
+ FaTiktok,
+} from 'react-icons/fa';
+
+export default function Footer() {
+ return (
+
+
+
+
+
+
+
+
+ Ingin Menjadi Programmer Handal Namun Enggan Ngoding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Halaman
+
+ {NAVIGATIONS.map(({ link, title }) => (
+
+
+ {title}
+
+
+ ))}
+
+
+
+
Link
+
+ {SOCIALS.map(({ link, name }) => (
+
+
+ {name}
+
+
+ ))}
+
+
+
+
+
+
+ © {new Date().getFullYear()} IMPHNEN - Ingin Menjadi Programmer
+ Handal, Namun Enggan Ngoding. All rights reserved.
+
+
+
+
+ );
+}
diff --git a/apps/landing/src/app/(public)/_components/header.tsx b/apps/landing/src/app/(public)/_components/header.tsx
new file mode 100644
index 0000000..30b40d7
--- /dev/null
+++ b/apps/landing/src/app/(public)/_components/header.tsx
@@ -0,0 +1,110 @@
+'use client';
+
+import { LogoSimple } from '@/app/_components/logo';
+import NAVIGATIONS from '@/data/navigations.json';
+import { Button } from '@components';
+import { cn } from '@utils';
+import Link from 'next/link';
+import { useRouter } from 'next/navigation';
+import { useEffect, useState } from 'react';
+import { LuMenu, LuX } from 'react-icons/lu';
+
+export function Header() {
+ const router = useRouter();
+
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsScrolled(window.scrollY > 10);
+ };
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
+
+ return (
+
+
+
+
+ {/* Desktop Navigation */}
+
+ {NAVIGATIONS.map(({ link, title }) => (
+
+
+ {title}
+
+
+
+ ))}
+
+
+
+ router.push('/signin')}
+ className="hidden md:flex"
+ >
+ Masuk
+
+ router.push('/signup')}
+ className="hidden md:flex"
+ >
+ Daftar
+
+
+ {/* Mobile Menu Button */}
+ setMobileMenuOpen(!mobileMenuOpen)}
+ className="flex md:hidden"
+ >
+ {mobileMenuOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* Mobile Menu */}
+ {mobileMenuOpen && (
+
+
+ {NAVIGATIONS.map(({ link, title }) => (
+ router.push(link)}
+ >
+ {title}
+
+ ))}
+
+ router.push('/signin')}>Login
+
+
+ )}
+
+ );
+}
diff --git a/apps/landing/src/app/_components/simple-theme-toggle.tsx b/apps/landing/src/app/(public)/_components/theme-toggle.tsx
similarity index 92%
rename from apps/landing/src/app/_components/simple-theme-toggle.tsx
rename to apps/landing/src/app/(public)/_components/theme-toggle.tsx
index a4c65ce..6eac564 100644
--- a/apps/landing/src/app/_components/simple-theme-toggle.tsx
+++ b/apps/landing/src/app/(public)/_components/theme-toggle.tsx
@@ -4,7 +4,7 @@ import { Button, MoonIcon, SunIcon } from '@components';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
-export function SimpleThemeToggle() {
+export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
@@ -22,7 +22,7 @@ export function SimpleThemeToggle() {
return (
{
+ const date = new Date(dateString);
+ return date.toLocaleDateString('id-ID', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ });
+};
+
+const formatTime = (dateString: string) => {
+ const date = new Date(dateString);
+ return date.toLocaleTimeString('id-ID', {
+ hour: '2-digit',
+ minute: '2-digit',
+ timeZone: 'Asia/Jakarta',
+ });
+};
+
+const getEventStatus = (endDate: string) => {
+ const now = new Date();
+ const end = new Date(endDate);
+ return end > now ? 'upcoming' : 'past';
+};
+
+export default function EventsPage() {
+ const sortedEvents = [...events].sort(
+ (a, b) =>
+ new Date(b.start_date).getTime() - new Date(a.start_date).getTime()
+ );
+
+ return (
+
+
+ {/* Featured Card */}
+
+
+
+
+
+
+
+
+
+ Event Terbaru
+
+
+ {getEventStatus(sortedEvents[0].end_date) === 'upcoming'
+ ? 'Upcoming'
+ : 'Selesai'}
+
+
+
+ {sortedEvents[0].name}
+
+
+
+
+ {formatDate(sortedEvents[0].start_date)}
+
+
+
+
+ {formatTime(sortedEvents[0].start_date)} -{' '}
+ {formatTime(sortedEvents[0].end_date)} WIB
+
+
+
+
+
+ {sortedEvents[0].type === 'online'
+ ? 'Online'
+ : sortedEvents[0].location}
+
+
+ {sortedEvents[0].price > 0 && (
+
+ Rp {sortedEvents[0].price.toLocaleString('id-ID')}
+
+ )}
+
+
+ {sortedEvents[0].description}
+
+
+ Lihat Detail
+
+
+
+
+
+
+ {/* Regular Cards */}
+ {sortedEvents.slice(1).map((event) => (
+
+
+
+
+
+
+
+ {getEventStatus(event.end_date) === 'upcoming'
+ ? 'Upcoming'
+ : 'Selesai'}
+
+
+
+ {event.name}
+
+
+
+
+ {formatDate(event.start_date)}
+
+
+
+
+ {event.type === 'online' ? 'Online' : event.location}
+
+
+ {event.price > 0 && (
+
+ Rp {event.price.toLocaleString('id-ID')}
+
+ )}
+
+
+ {event.description}
+
+
+ Lihat Detail
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/landing/src/app/(public)/layout.tsx b/apps/landing/src/app/(public)/layout.tsx
new file mode 100644
index 0000000..b09f5e5
--- /dev/null
+++ b/apps/landing/src/app/(public)/layout.tsx
@@ -0,0 +1,12 @@
+import Footer from './_components/footer';
+import { Header } from './_components/header';
+
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/apps/landing/src/app/(public)/roadmaps/page.tsx b/apps/landing/src/app/(public)/roadmaps/page.tsx
new file mode 100644
index 0000000..7d1e0b7
--- /dev/null
+++ b/apps/landing/src/app/(public)/roadmaps/page.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+ return <>>;
+}
diff --git a/apps/landing/src/app/(public)/testimonials/page.tsx b/apps/landing/src/app/(public)/testimonials/page.tsx
new file mode 100644
index 0000000..424fc93
--- /dev/null
+++ b/apps/landing/src/app/(public)/testimonials/page.tsx
@@ -0,0 +1,56 @@
+import TESTIMONIALS from '@/data/testimonials.json';
+import { Button } from '@components';
+import Image from 'next/image';
+import Link from 'next/link';
+import { BsChatLeftQuote } from 'react-icons/bs';
+import { FaQuoteLeft } from 'react-icons/fa';
+
+export default function Page() {
+ return (
+
+
+
+
Testimonial
+
+ Menampilkan pengalaman dan cerita para member yang telah join ke dalam
+ komunitas kami
+
+
+
Tulis Testimonialmu
+
+
+
+
+ {TESTIMONIALS.map((testimonial) => (
+
+
+
+
+
+
+ {testimonial.name}
+
+
{testimonial.role}
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/landing/src/app/(public)/testimonials/submit/page.tsx b/apps/landing/src/app/(public)/testimonials/submit/page.tsx
new file mode 100644
index 0000000..7d1e0b7
--- /dev/null
+++ b/apps/landing/src/app/(public)/testimonials/submit/page.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+ return <>>;
+}
diff --git a/apps/landing/src/app/_components/footer.tsx b/apps/landing/src/app/_components/footer.tsx
deleted file mode 100644
index 6f5b4ba..0000000
--- a/apps/landing/src/app/_components/footer.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-import Link from 'next/link';
-
-export default function Footer() {
- return (
-
-
-
-
-
-
- IMPHNEN
-
-
-
- Ingin Menjadi Programmer Handal merupakan komunitas Programmer
- Indonesia.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Tautan Cepat
-
-
-
- Fitur
-
-
-
-
- Komunitas
-
-
-
-
- Sumber Belajar
-
-
-
-
- Testimoni
-
-
-
-
-
-
Sumber Belajar
-
-
-
- Video Tutorial
-
-
-
-
- Artikel
-
-
-
-
- Tantangan Koding
-
-
-
-
- Sharing Session
-
-
-
-
-
-
Bahasa Pemrograman
-
-
- PHP
-
-
- JavaScript
-
-
- Python
-
-
- C#
-
-
- Java
-
-
- Go
-
-
- Rust
-
-
- HTML
-
-
-
-
-
Newsletter
-
- Dapatkan update terbaru dari kami
-
-
-
-
- Daftar
-
-
-
-
-
-
-
- © {new Date().getFullYear()} IMPHNEN - Ingin Menjadi Programmer
- Handal, Namun Enggan Ngoding. All rights reserved.
-
-
-
-
- );
-}
diff --git a/apps/landing/src/app/_components/header.tsx b/apps/landing/src/app/_components/header.tsx
deleted file mode 100644
index 8a7fb09..0000000
--- a/apps/landing/src/app/_components/header.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-'use client';
-
-import { Button, MenuIcon, XIcon } from '@components';
-import { cn } from '@utils';
-import Image from 'next/image';
-import Link from 'next/link';
-import { useRouter } from 'next/navigation';
-import { useEffect, useState } from 'react';
-import { SimpleThemeToggle } from './simple-theme-toggle';
-
-export function Header() {
- const router = useRouter();
-
- const [isScrolled, setIsScrolled] = useState(false);
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
-
- useEffect(() => {
- const handleScroll = () => {
- setIsScrolled(window.scrollY > 10);
- };
- window.addEventListener('scroll', handleScroll);
- return () => window.removeEventListener('scroll', handleScroll);
- }, []);
-
- return (
-
-
-
-
- {/* Desktop Navigation */}
-
-
- Fitur
-
-
-
-
- Komunitas
-
-
-
-
-
- Sumber Belajar
-
-
-
-
-
- Testimoni
-
-
-
-
-
-
-
-
- router.push('/signin')}
- >
- Login
-
-
- {/* Mobile Menu Button */}
- setMobileMenuOpen(!mobileMenuOpen)}
- >
- {mobileMenuOpen ? (
-
- ) : (
-
- )}
-
-
-
-
- {/* Mobile Menu */}
- {mobileMenuOpen && (
-
-
- setMobileMenuOpen(false)}
- >
- Fitur
-
- setMobileMenuOpen(false)}
- >
- Komunitas
-
- setMobileMenuOpen(false)}
- >
- Sumber Belajar
-
- setMobileMenuOpen(false)}
- >
- Testimoni
-
-
- router.push('/signin')}
- className="group relative w-full sm:w-auto bg-gradient-to-r from-primary to-blue-600 hover:from-primary/90 hover:to-blue-600/90 transition-all duration-300 font-bold text-white hover:text-white/90 shadow-lg cursor-pointer"
- >
- Login
-
-
-
- )}
-
- );
-}
diff --git a/apps/landing/src/app/_components/logo.tsx b/apps/landing/src/app/_components/logo.tsx
new file mode 100644
index 0000000..d7fdfa7
--- /dev/null
+++ b/apps/landing/src/app/_components/logo.tsx
@@ -0,0 +1,71 @@
+export function Logo(props: React.SVGProps) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function LogoSimple(props: React.SVGProps) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/landing/src/app/_components/providers.tsx b/apps/landing/src/app/_components/providers.tsx
new file mode 100644
index 0000000..6f21b25
--- /dev/null
+++ b/apps/landing/src/app/_components/providers.tsx
@@ -0,0 +1,17 @@
+'use client';
+
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import type { ThemeProviderProps } from 'next-themes';
+import { ReactNode } from 'react';
+
+export function Providers({ children }: ThemeProviderProps) {
+ return {children} ;
+}
+
+export const queryClient = new QueryClient();
+
+function QueryProvider({ children }: { children: ReactNode }) {
+ return (
+ {children}
+ );
+}
diff --git a/apps/landing/src/app/_components/theme-provider.tsx b/apps/landing/src/app/_components/theme-provider.tsx
deleted file mode 100644
index 4db6c3c..0000000
--- a/apps/landing/src/app/_components/theme-provider.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-'use client';
-
-import type { ThemeProviderProps } from 'next-themes';
-import { ThemeProvider as NextThemesProvider } from 'next-themes';
-
-export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
- return {children} ;
-}
diff --git a/apps/landing/src/app/_components/toaster.tsx b/apps/landing/src/app/_components/toaster.tsx
new file mode 100644
index 0000000..ee824a2
--- /dev/null
+++ b/apps/landing/src/app/_components/toaster.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { Toaster as Sonner } from 'sonner';
+
+type ToasterProps = React.ComponentProps;
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ return (
+
+ );
+};
+
+export { Toaster };
diff --git a/apps/landing/src/app/layout.tsx b/apps/landing/src/app/layout.tsx
index 95903a5..d5484f1 100644
--- a/apps/landing/src/app/layout.tsx
+++ b/apps/landing/src/app/layout.tsx
@@ -1,11 +1,9 @@
+import { poppinsFont } from '@/lib/fonts';
import '@/styles/globals.css';
+import { cn } from '@utils';
import { type Metadata } from 'next';
-import { Inter } from 'next/font/google';
-import Footer from './_components/footer';
-import { Header } from './_components/header';
-import { ThemeProvider } from './_components/theme-provider';
-
-const inter = Inter({ subsets: ['latin'] });
+import { Providers } from './_components/providers';
+import { Toaster } from './_components/toaster';
export const metadata: Metadata = {
title: 'IMPHNEN - Ingin Menjadi Programmer Handal?',
@@ -19,19 +17,16 @@ export default function RootLayout({
}) {
return (
-
-
+
-
-
- {children}
-
-
-
+ {children}
+
+
);
diff --git a/apps/landing/src/data/communities-stats.json b/apps/landing/src/data/communities-stats.json
deleted file mode 100644
index 689484b..0000000
--- a/apps/landing/src/data/communities-stats.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- { "value": "100K+", "label": "Member Aktif" },
- { "value": "50+", "label": "Event Bulanan" },
- { "value": "100+", "label": "Mentor Profesional" },
- { "value": "5K+", "label": "Diskusi Mingguan" }
-]
diff --git a/apps/landing/src/data/communities.json b/apps/landing/src/data/communities.json
deleted file mode 100644
index a9f179f..0000000
--- a/apps/landing/src/data/communities.json
+++ /dev/null
@@ -1,23 +0,0 @@
-[
- {
- "iconName": "tabler:brand-facebook",
- "title": "Facebook Group",
- "description": "Bergabunglah dengan grup Facebook kami untuk diskusi santai dan berbagi artikel menarik.",
- "buttonText": "Gabung Sekarang",
- "buttonLink": "https://facebook.com/groups/programmerhandal"
- },
- {
- "iconName": "tabler:brand-instagram",
- "title": "Instagram",
- "description": "Ikuti kami di Instagram untuk tips programming, konten inspiratif, dan info event terbaru.",
- "buttonText": "Follow Kami",
- "buttonLink": "https://www.instagram.com/imphnen.dev"
- },
- {
- "iconName": "tabler:brand-discord-filled",
- "title": "Discord Server",
- "description": "Diskusikan langsung dengan sesama programmer dan dapatkan bantuan langsung dari para ahli.",
- "buttonText": "Join Server",
- "buttonLink": "https://discord.com/invite/imphnen"
- }
-]
diff --git a/apps/landing/src/data/events.json b/apps/landing/src/data/events.json
new file mode 100644
index 0000000..9778e48
--- /dev/null
+++ b/apps/landing/src/data/events.json
@@ -0,0 +1,46 @@
+[
+ {
+ "name": "IMPHNEN X GDG Medan",
+ "description": "Join us for the Google Cloud Roadshow: Build with AI in Medan! This is your chance to dive deep into the latest advancements in cloud technology and AI, guided by industry experts.",
+ "start_date": "2025-04-26T13:00:00+07:00",
+ "end_date": "2025-04-26T17:00:00+07:00",
+ "thumbnail": "https://scontent.fsoc1-2.fna.fbcdn.net/v/t39.30808-6/492363939_2132158393873766_2304913620264481070_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=127cfc&_nc_eui2=AeFe43oCdy2FOIVI6pr_grkO_q5XyVVxt5v-rlfJVXG3m9KgINIp9lo_vzXP4CtAAFJviDNJdDPaPyGnz7B8-gfE&_nc_ohc=bv3GExoL4pUQ7kNvwEpV7xQ&_nc_oc=Adk45dIpzgFxCHgtQKDTo99Sewj0ZMO4LX1cNEI2Dj7VeQDp8jd1RzYnG_UwsKkaTug&_nc_zt=23&_nc_ht=scontent.fsoc1-2.fna&_nc_gid=ZPKg-Y9FLRXcDiSak5WcLA&oh=00_AfL7BFg5g-q6MALMRFUlgaUWK8jHxQVTHdZ_1ezxBthXfw&oe=68354615",
+ "type": "onsite",
+ "price": 0,
+ "location": "Magnificient, Medan",
+ "detail_link": "https://n8n.gdgmedan.com/form/8466f60d-94b0-42a6-93aa-8738ae4ab5df?fbclid=IwY2xjawKcQoNleHRuA2FlbQIxMABicmlkETFnQ1RvSU5aWVhpWldaOXBjAR7LUWa_npCLb-QSfk1QFTl12tyw_eBhNO8oI8N2LvhwweeE5_41uQsARsEQGw_aem_1IP2vqrQcvjsvLCBy2C6Hg"
+ },
+ {
+ "name": "IMPHNEN X Connect Citcom",
+ "description": "Unleash the Future with AI: Decode the potential of artificial intelligence to revolutionize your strategies, unchain innovation, and gain a powerful competitive edge in today's fast-evolving business landscape. Harness the transformative capabilities of AI to outsmart competitors, optimize operations, and drive sustainable growth.",
+ "start_date": "2025-04-22T13:00:00+07:00",
+ "end_date": "2025-04-22T17:00:00+07:00",
+ "thumbnail": "https://scontent.fsoc1-2.fna.fbcdn.net/v/t39.30808-6/490432625_2122941998128739_5372979458124718061_n.jpg?stp=dst-jpg_p526x296_tt6&_nc_cat=105&ccb=1-7&_nc_sid=aa7b47&_nc_eui2=AeGc_W_0Gd3XY9X9ImKz1h1HpYKlinPYX-qlgqWKc9hf6k75HObQiJ8A8m6BZOIe2OrVQjjyXnHq08Rc4JuQWeCQ&_nc_ohc=uoSae0n37k0Q7kNvwHch8E5&_nc_oc=AdngSKtrCdZ4H3hlHh-9cttOnaKyrwiFEcm88kjp0gaflvQ65LUp4OT45-XVGDAoIo8&_nc_zt=23&_nc_ht=scontent.fsoc1-2.fna&_nc_gid=YGkoLGAxNuB0uF3C9hSYGQ&oh=00_AfLRkrzG7DRYa3HRuy9aXfUfG6zLkRBDj9LxqXKOfF9x7Q&oe=683546E0",
+ "type": "onsite",
+ "price": 0,
+ "location": "El Hotel, Bandung",
+ "detail_link": "/events/1"
+ },
+ {
+ "name": "JVM Meetup #64",
+ "description": "Join us for an insightful talk where we’ll explore how AI is reshaping the payment industry. Whether you’re into tech, AI, or just curious about the future of transactions, this is a must-attend event!",
+ "start_date": "2025-04-30T13:00:00+07:00",
+ "end_date": "2025-04-30T17:00:00+07:00",
+ "thumbnail": "https://scontent.fsoc1-1.fna.fbcdn.net/v/t39.30808-6/494158983_2136830150073257_5259003432367969457_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=833d8c&_nc_eui2=AeGcneO8ihnXyQ_7WV93z7Onz0UPVTr-bf_PRQ9VOv5t_wI-el8xouZ-Z4TKGMMlBPi21d9AA55SP54MHkzO4rx1&_nc_ohc=3Q9NKGIkpT4Q7kNvwHKzwXr&_nc_oc=AdkRJ--zwRGcfX_F2eIgx0qhK-N3OkhwqRwujFhgSRWy5mH_SdIyBmwW8PHHXp71ZUM&_nc_zt=23&_nc_ht=scontent.fsoc1-1.fna&_nc_gid=Xi30SjzSBAU1XN9rRxp0VA&oh=00_AfKvjd9LTMfOwfPLD43U8lKYEr4KABecj336J8hAY8ekMg&oe=68353105",
+ "type": "onsite",
+ "price": 0,
+ "location": "El Hotel, Bandung",
+ "detail_link": "https://lu.ma/422jkgz0?utm_campaign=jvmmeetup&utm_medium=social%2C%20event%2C%20meetup&utm_source=google%2C%20facebook%2C%20linkedin%2C%20instagram"
+ },
+ {
+ "name": "JVM Meetup #65",
+ "description": "Di era digital yang terus berkembang, Artificial Intelligence (AI) bukan lagi teknologi masa depantapi alat bantu masa kini yang siap mendongkrak efisiensi, kreativitas, dan produktivitas kita semua, terutama para penggiat IT dan pelaku usaha!",
+ "start_date": "2025-05-15T13:00:00+07:00",
+ "end_date": "2025-05-15T17:00:00+07:00",
+ "thumbnail": "https://scontent.fsoc1-1.fna.fbcdn.net/v/t39.30808-6/495382435_2144684952621110_8304851620615091109_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=833d8c&_nc_eui2=AeHwnU4KixfUsslo3M90ahT8GwMEI7zWSnQbAwQjvNZKdP3RHiUjdDZtuF1dIMvorL0nW0rDGOD6tPDi0i9ERK96&_nc_ohc=R1NyXnu-FKEQ7kNvwGbO-iN&_nc_oc=Adluf-uw1tmzkNME9vNhwkTv5-x_GHMfsLj1bzGyYds_ODq3UnWMq2DthyUisF9e3gU&_nc_zt=23&_nc_ht=scontent.fsoc1-1.fna&_nc_gid=kykMk9nPtcrtk-InL6tF6w&oh=00_AfLZSrEPpV83lVFRMz19n9RzfnsM4o7K7TijpdAjWYViiA&oe=6835231B",
+ "type": "onsite",
+ "price": 0,
+ "location": "Telkom Landmark Tower (Lt.31)",
+ "detail_link": "s.id/jvm65"
+ }
+]
diff --git a/apps/landing/src/data/features.json b/apps/landing/src/data/features.json
deleted file mode 100644
index 5163089..0000000
--- a/apps/landing/src/data/features.json
+++ /dev/null
@@ -1,22 +0,0 @@
-[
- {
- "iconName": "tabler:device-laptop",
- "title": "Belajar Tanpa Koding",
- "description": "Pelajari konsep programming dengan cara yang mudah dipahami tanpa harus menulis kode yang rumit."
- },
- {
- "iconName": "tabler:users",
- "title": "Komunitas Supportif",
- "description": "Bergabunglah dengan komunitas programmer Indonesia yang siap membantu dan berbagi pengalaman."
- },
- {
- "iconName": "tabler:book",
- "title": "Tutorial Interaktif",
- "description": "Akses tutorial interaktif yang membuat konsep programming lebih mudah untuk dipahami."
- },
- {
- "iconName": "tabler:code",
- "title": "Proyek Praktis",
- "description": "Terapkan pengetahuan Anda dalam proyek nyata dengan panduan langkah demi langkah."
- }
-]
diff --git a/apps/landing/src/data/hero-content.json b/apps/landing/src/data/hero-content.json
new file mode 100644
index 0000000..82d86cd
--- /dev/null
+++ b/apps/landing/src/data/hero-content.json
@@ -0,0 +1,12 @@
+{
+ "communityLabel": "Komunitas Programmer Indonesia",
+ "headingLine1": "Programmer Handal",
+ "headingLine2": "Enggan Ngoding",
+ "description": "Komunitas programmer terbesar di Indonesia, tempat berbagi meme, tutorial, pengalaman dan yapping",
+ "buttons": {
+ "join": "Join Komunitas",
+ "joinUrl": "#community",
+ "explore": "Explore Event",
+ "exploreUrl": "/events"
+ }
+}
diff --git a/apps/landing/src/data/hero-stats.json b/apps/landing/src/data/hero-stats.json
index 2e98bf5..70232a3 100644
--- a/apps/landing/src/data/hero-stats.json
+++ b/apps/landing/src/data/hero-stats.json
@@ -1,5 +1,5 @@
[
{ "value": "180K+", "label": "Member" },
- { "value": "500+", "label": "Tutorial" },
+ { "value": "500+", "label": "Meme Harian" },
{ "value": "24/7", "label": "Yapping" }
]
diff --git a/apps/landing/src/data/learning-resources.json b/apps/landing/src/data/learning-resources.json
deleted file mode 100644
index ec8dd98..0000000
--- a/apps/landing/src/data/learning-resources.json
+++ /dev/null
@@ -1,30 +0,0 @@
-[
- {
- "icon": "tabler:video",
- "title": "Video Tutorial",
- "description": "Belajar melalui tutorial video dari langkah awal hingga mahir.",
- "buttonText": "Lihat Semua Video",
- "buttonLink": "#"
- },
- {
- "icon": "tabler:article",
- "title": "Artikel & Tutorial",
- "description": "Pelajari konsep programming melalui artikel yang disusun secara terstruktur.",
- "buttonText": "Baca Artikel",
- "buttonLink": "#"
- },
- {
- "icon": "tabler:brand-vscode",
- "title": "Tantangan Koding",
- "description": "Uji kemampuan koding kamu dengan tantangan yang menyenangkan dan menantang.",
- "buttonText": "Mulai Tantangan",
- "buttonLink": "#"
- },
- {
- "icon": "tabler:device-desktop-share",
- "title": "Sharing Session",
- "description": "Ikuti sesi berbagi pengalaman dari programmer berpengalaman dan belajar dari pengalaman mereka.",
- "buttonText": "Jadwal Session",
- "buttonLink": "#"
- }
-]
diff --git a/apps/landing/src/data/navigations.json b/apps/landing/src/data/navigations.json
new file mode 100644
index 0000000..2db1c6d
--- /dev/null
+++ b/apps/landing/src/data/navigations.json
@@ -0,0 +1,22 @@
+[
+ {
+ "title": "Home",
+ "link": "/"
+ },
+ {
+ "title": "Event",
+ "link": "/events"
+ },
+ {
+ "title": "Testimoni",
+ "link": "/testimonials"
+ },
+ {
+ "title": "Roadmap",
+ "link": "/roadmaps"
+ },
+ {
+ "title": "Artikel",
+ "link": "/articles"
+ }
+]
diff --git a/apps/landing/src/data/socials.json b/apps/landing/src/data/socials.json
new file mode 100644
index 0000000..448b2b6
--- /dev/null
+++ b/apps/landing/src/data/socials.json
@@ -0,0 +1,37 @@
+[
+ {
+ "name": "Facebook",
+ "icon": "FaFacebook",
+ "color": "#1877F2",
+ "description": "Join discussions and share knowledge",
+ "link": "#"
+ },
+ {
+ "name": "Discord",
+ "icon": "FaDiscord",
+ "color": "#5865F2",
+ "description": "Real-time collaboration chat",
+ "link": "#"
+ },
+ {
+ "name": "Instagram",
+ "icon": "FaInstagram",
+ "color": "#E4405F",
+ "description": "Daily coding tips & showcases",
+ "link": "#"
+ },
+ {
+ "name": "TikTok",
+ "icon": "FaTiktok",
+ "color": "#000000",
+ "description": "Short coding tutorials",
+ "link": "#"
+ },
+ {
+ "name": "LinkedIn",
+ "icon": "FaLinkedin",
+ "color": "#0A66C2",
+ "description": "Professional networking",
+ "link": "#"
+ }
+]
diff --git a/apps/landing/src/data/testimonial-stats.json b/apps/landing/src/data/testimonial-stats.json
deleted file mode 100644
index 0e77dc9..0000000
--- a/apps/landing/src/data/testimonial-stats.json
+++ /dev/null
@@ -1,6 +0,0 @@
-[
- { "value": "98%", "label": "Tingkat Kemalasan" },
- { "value": "4.9/5", "label": "Rating Drama" },
- { "value": "85%", "label": "Mendapat Pekerjaan" },
- { "value": "24/7", "label": "Yapping" }
-]
diff --git a/apps/landing/src/data/testimonials.json b/apps/landing/src/data/testimonials.json
index 2c8ef6b..a587c16 100644
--- a/apps/landing/src/data/testimonials.json
+++ b/apps/landing/src/data/testimonials.json
@@ -1,20 +1,44 @@
[
{
- "quote": "Sebagai seseorang yang awalnya buta sama sekali tentang programming, kini saya bisa membuat website sendiri dengan percaya diri. IMPHNEN benar-benar jadi pintu gerbang saya ke dunia web development!",
- "name": "Ega",
- "role": "Web Developer",
- "avatar": "https://avatars.githubusercontent.com/u/97678571?v=4"
+ "id": 1,
+ "name": "Andi Pratama",
+ "role": "Backend Developer",
+ "text": "Komunitas ini sangat membantu perkembangan karir saya. Saya bisa belajar teknologi terbaru dan berkolaborasi dengan developer lain.",
+ "image": "https://picsum.photos/100/100?random=1"
},
{
- "quote": "IMPHNEN bukan cuma komunitas, tapi juga jadi tempat saya berkembang sebagai backend engineer. Diskusi teknisnya berbobot, dan respon dari komunitas selalu cepat dan tepat sasaran.",
- "name": "Maulana",
- "role": "Backend Engineer",
- "avatar": "https://avatars.githubusercontent.com/u/53475078?v=4"
+ "id": 2,
+ "name": "Sarah Wijaya",
+ "role": "Frontend Engineer",
+ "text": "Acara sharing session-nya sangat inspiratif. Saya jadi termotivasi untuk terus mengembangkan skill di bidang frontend development.",
+ "image": "https://picsum.photos/100/100?random=2"
},
{
- "quote": "Sebagai fullstack engineer pemula, saya merasa sangat terbantu dengan komunitas Discord IMPHNEN. Materinya praktikal, pembahasannya jelas, dan member-nya selalu siap membantu.",
- "name": "Rasyid",
- "role": "Fullstack Engineer",
- "avatar": "https://avatars.githubusercontent.com/u/49753444?v=4"
+ "id": 3,
+ "name": "Rizal Fauzi",
+ "role": "Fullstack Developer",
+ "text": "Bergabung di komunitas ini membuka banyak kesempatan networking dan project menarik. Sangat recommended untuk developer semua level!",
+ "image": "https://picsum.photos/100/100?random=5"
+ },
+ {
+ "id": 4,
+ "name": "Dewi Lestari",
+ "role": "Mobile Developer",
+ "text": "Materi workshop-nya praktis dan langsung applicable. Mentor-mentornya juga berpengalaman di industri.",
+ "image": "https://picsum.photos/100/100?random=9"
+ },
+ {
+ "id": 5,
+ "name": "Fajar Setiawan",
+ "role": "DevOps Engineer",
+ "text": "Komunitas yang solid dan saling support. Tidak pernah ragu untuk bertanya karena semua anggota sangat responsif.",
+ "image": "https://picsum.photos/100/100?random=10"
+ },
+ {
+ "id": 6,
+ "name": "Budi Santoso",
+ "role": "UI/UX Designer",
+ "text": "Kolaborasi antara designer dan developer di komunitas ini sangat smooth. Banyak belajar best practices untuk workflow yang lebih baik.",
+ "image": "https://picsum.photos/100/100?random=6"
}
]
diff --git a/apps/landing/src/lib/cookies.ts b/apps/landing/src/lib/cookies.ts
new file mode 100644
index 0000000..c6ddfea
--- /dev/null
+++ b/apps/landing/src/lib/cookies.ts
@@ -0,0 +1,24 @@
+import { cookies } from 'next/headers';
+
+const REFRESH_TOKEN_NAME = '__imphnen_refresh_token__';
+const ACCESS_TOKEN_NAME = '__imphnen_access_token__';
+
+export async function setRefreshToken(refreshToken: string) {
+ const cookieStore = await cookies();
+ cookieStore.set(REFRESH_TOKEN_NAME, refreshToken);
+}
+
+export async function setAccessToken(accessToken: string) {
+ const cookieStore = await cookies();
+ cookieStore.set(ACCESS_TOKEN_NAME, accessToken);
+}
+
+export async function getRefreshToken() {
+ const cookieStore = await cookies();
+ return cookieStore.get(REFRESH_TOKEN_NAME)?.value;
+}
+
+export async function getAccessToken() {
+ const cookieStore = await cookies();
+ return cookieStore.get(ACCESS_TOKEN_NAME)?.value;
+}
diff --git a/apps/landing/src/lib/fetcher.ts b/apps/landing/src/lib/fetcher.ts
new file mode 100644
index 0000000..41264c1
--- /dev/null
+++ b/apps/landing/src/lib/fetcher.ts
@@ -0,0 +1,6 @@
+import type { paths } from '@/openapi-types';
+import createClient from 'openapi-fetch';
+
+export const fetcher = createClient({
+ baseUrl: process.env.NEXT_PUBLIC_API_URL,
+});
diff --git a/apps/landing/src/lib/fonts.ts b/apps/landing/src/lib/fonts.ts
new file mode 100644
index 0000000..f6296c1
--- /dev/null
+++ b/apps/landing/src/lib/fonts.ts
@@ -0,0 +1,13 @@
+import { Bai_Jamjuree, Poppins } from 'next/font/google';
+
+export const baiJamjureeFont = Bai_Jamjuree({
+ subsets: ['latin'],
+ weight: ['300', '400', '500', '600', '700'],
+ display: 'swap',
+});
+
+export const poppinsFont = Poppins({
+ subsets: ['latin'],
+ weight: ['300', '400', '500', '600', '700'],
+ display: 'swap',
+});
diff --git a/apps/landing/src/lib/headers.ts b/apps/landing/src/lib/headers.ts
new file mode 100644
index 0000000..fdf0bdb
--- /dev/null
+++ b/apps/landing/src/lib/headers.ts
@@ -0,0 +1,11 @@
+import { headers } from 'next/headers';
+
+export async function getRemoteIp() {
+ const hdrs = await headers();
+ const xff = hdrs.get('x-forwarded-for');
+ if (!xff) return undefined;
+
+ // 'x-forwarded-for' can be a comma-separated list of IPs
+ const ips = xff.split(',').map((ip) => ip.trim());
+ return ips[0] || undefined;
+}
diff --git a/apps/landing/src/lib/rpc.ts b/apps/landing/src/lib/rpc.ts
new file mode 100644
index 0000000..96f1b19
--- /dev/null
+++ b/apps/landing/src/lib/rpc.ts
@@ -0,0 +1,8 @@
+import type { paths } from '@/openapi-types';
+import createFetchClient from 'openapi-fetch';
+import createClient from 'openapi-react-query';
+
+const fetchClient = createFetchClient({
+ baseUrl: process.env.NEXT_PUBLIC_API_URL,
+});
+export const rpc = createClient(fetchClient);
diff --git a/apps/landing/src/openapi-types.ts b/apps/landing/src/openapi-types.ts
new file mode 100644
index 0000000..8b0ed89
--- /dev/null
+++ b/apps/landing/src/openapi-types.ts
@@ -0,0 +1,2109 @@
+/**
+ * This file was auto-generated by openapi-typescript.
+ * Do not make direct changes to the file.
+ */
+
+export interface paths {
+ "/v1/auth/forgot": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_forgot_password"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/auth/login": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_login"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/auth/new-password": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_new_password"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/auth/refresh": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_refresh_token"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/auth/register": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_register"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/auth/send-otp": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_resend_otp"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/auth/verify-email": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_verify_email"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/cms/landing/events": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_event_list"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/cms/landing/events/create": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_create_event"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/cms/landing/events/delete/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete: operations["delete_event"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/cms/landing/events/detail/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_event_by_id"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/cms/landing/events/update/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch: operations["patch_update_event"];
+ trace?: never;
+ };
+ "/v1/gacha/claims/create": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_create_gacha_claim"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/claims/detail/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_detail_gacha_claim"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/items": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_gacha_item_list"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/items/create": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_create_gacha_item"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/items/delete/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete: operations["delete_gacha_item"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/items/detail/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_gacha_item_by_id"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/items/update/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put: operations["put_update_gacha_item"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/rolls/create": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_create_gacha_roll"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/rolls/detail/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_detail_gacha_roll"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/gacha/rolls/execute": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_execute_gacha_roll"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/permissions": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_permission_list"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/permissions/create": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_create_permission"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/permissions/delete/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete: operations["delete_permission"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/permissions/detail/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_permission_by_id"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/permissions/update/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put: operations["put_update_permission"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/roles": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_role_list"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/roles/create": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_create_role"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/roles/delete/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete: operations["delete_role"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/roles/detail/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_role_by_id"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/roles/update/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put: operations["put_update_role"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_user_list"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users/activate/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put: operations["patch_user_active_status"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users/create": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["post_create_user"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users/delete/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ delete: operations["delete_user"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users/detail/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_user_by_id"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users/me": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["get_user_me"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users/update/me": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put: operations["put_update_user_me"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/users/update/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put: operations["put_update_user"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+}
+export type webhooks = Record;
+export interface components {
+ schemas: {
+ AuthLoginRequestDto: {
+ email: string;
+ password: string;
+ };
+ AuthLoginResponsetDto: {
+ token: components["schemas"]["TokenDto"];
+ user: components["schemas"]["UsersDetailItemDto"];
+ };
+ AuthNewPasswordRequestDto: {
+ password: string;
+ token: string;
+ };
+ AuthRefreshTokenRequestDto: {
+ refresh_token: string;
+ };
+ AuthRegisterRequestDto: {
+ email: string;
+ fullname: string;
+ password: string;
+ phone_number: string;
+ };
+ AuthResendOtpRequestDto: {
+ email: string;
+ };
+ AuthVerifyEmailRequestDto: {
+ email: string;
+ /** Format: int32 */
+ otp: number;
+ };
+ EventsCreateRequestDto: {
+ description: string;
+ detail_link: string;
+ /** @example 2025-09-20T13:00:00Z */
+ end_date: string;
+ is_online: boolean;
+ location?: string | null;
+ name: string;
+ /** Format: double */
+ price: number;
+ /** @example 2025-09-20T13:00:00Z */
+ start_date: string;
+ };
+ EventsDetailItemDto: {
+ created_at: string;
+ description: string;
+ detail_link: string;
+ end_date: string;
+ id: string;
+ is_online: boolean;
+ location?: string | null;
+ name: string;
+ /** Format: double */
+ price: number;
+ start_date: string;
+ updated_at: string;
+ };
+ EventsListItemDto: {
+ created_at: string;
+ description: string;
+ detail_link: string;
+ end_date: string;
+ id: string;
+ is_deleted: boolean;
+ is_online: boolean;
+ location?: string | null;
+ name: string;
+ /** Format: double */
+ price: number;
+ start_date: string;
+ };
+ EventsUpdateRequestDto: {
+ description: string;
+ detail_link: string;
+ /** @example 2025-09-20T13:00:00Z */
+ end_date: string;
+ is_online: boolean;
+ location?: string | null;
+ name: string;
+ /** Format: double */
+ price: number;
+ /** @example 2025-09-20T13:00:00Z */
+ start_date: string;
+ };
+ GachaClaimItemDto: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ item: components["schemas"]["GachaItemDto"];
+ updated_at?: string | null;
+ user: components["schemas"]["UsersDetailItemDto"];
+ };
+ GachaClaimRequestDto: {
+ item_id: string;
+ user_id: string;
+ };
+ GachaItemDto: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ name: string;
+ updated_at?: string | null;
+ };
+ GachaItemRequestDto: {
+ image_url: string;
+ name: string;
+ };
+ GachaRollItemDto: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ item: components["schemas"]["GachaItemDto"];
+ /** Format: int32 */
+ quantity: number;
+ updated_at?: string | null;
+ /** Format: float */
+ weight: number;
+ };
+ GachaRollRequestDto: {
+ item_id: string;
+ /** Format: int32 */
+ quantity: number;
+ /** Format: float */
+ weight: number;
+ };
+ MessageResponseDto: {
+ message: string;
+ version: string;
+ };
+ MetaRequestDto: {
+ filter?: string | null;
+ filter_by?: string | null;
+ order?: string | null;
+ /** Format: int64 */
+ page?: number | null;
+ /** Format: int64 */
+ per_page?: number | null;
+ search?: string | null;
+ sort_by?: string | null;
+ };
+ MetaResponseDto: {
+ /** Format: int64 */
+ page?: number | null;
+ /** Format: int64 */
+ per_page?: number | null;
+ /** Format: int64 */
+ total?: number | null;
+ };
+ PermissionsItemDto: {
+ created_at?: string | null;
+ id: string;
+ name: string;
+ updated_at?: string | null;
+ };
+ PermissionsRequestDto: {
+ name: string;
+ };
+ ResponseListSuccessDto_Vec_EventsListItemDto: {
+ data: {
+ created_at: string;
+ description: string;
+ detail_link: string;
+ end_date: string;
+ id: string;
+ is_deleted: boolean;
+ is_online: boolean;
+ location?: string | null;
+ name: string;
+ /** Format: double */
+ price: number;
+ start_date: string;
+ }[];
+ meta?: null | components["schemas"]["MetaResponseDto"];
+ };
+ ResponseListSuccessDto_Vec_GachaItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ name: string;
+ updated_at?: string | null;
+ }[];
+ meta?: null | components["schemas"]["MetaResponseDto"];
+ };
+ ResponseListSuccessDto_Vec_PermissionsItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ name: string;
+ updated_at?: string | null;
+ }[];
+ meta?: null | components["schemas"]["MetaResponseDto"];
+ };
+ ResponseListSuccessDto_Vec_RolesListItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ name: string;
+ permissions_count: number;
+ updated_at?: string | null;
+ }[];
+ meta?: null | components["schemas"]["MetaResponseDto"];
+ };
+ ResponseListSuccessDto_Vec_UsersListItemDto: {
+ data: {
+ avatar?: string | null;
+ created_at: string;
+ email: string;
+ fullname: string;
+ id: string;
+ is_active: boolean;
+ phone_number: string;
+ role: string;
+ updated_at: string;
+ }[];
+ meta?: null | components["schemas"]["MetaResponseDto"];
+ };
+ ResponseSuccessDto_AuthLoginResponsetDto: {
+ data: {
+ token: components["schemas"]["TokenDto"];
+ user: components["schemas"]["UsersDetailItemDto"];
+ };
+ };
+ ResponseSuccessDto_EventsDetailItemDto: {
+ data: {
+ created_at: string;
+ description: string;
+ detail_link: string;
+ end_date: string;
+ id: string;
+ is_online: boolean;
+ location?: string | null;
+ name: string;
+ /** Format: double */
+ price: number;
+ start_date: string;
+ updated_at: string;
+ };
+ };
+ ResponseSuccessDto_GachaClaimItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ item: components["schemas"]["GachaItemDto"];
+ updated_at?: string | null;
+ user: components["schemas"]["UsersDetailItemDto"];
+ };
+ };
+ ResponseSuccessDto_GachaItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ name: string;
+ updated_at?: string | null;
+ };
+ };
+ ResponseSuccessDto_GachaRollItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ item: components["schemas"]["GachaItemDto"];
+ /** Format: int32 */
+ quantity: number;
+ updated_at?: string | null;
+ /** Format: float */
+ weight: number;
+ };
+ };
+ ResponseSuccessDto_PermissionsItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ name: string;
+ updated_at?: string | null;
+ };
+ };
+ ResponseSuccessDto_RolesDetailItemDto: {
+ data: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ name: string;
+ permissions: components["schemas"]["PermissionsItemDto"][];
+ updated_at?: string | null;
+ };
+ };
+ ResponseSuccessDto_TokenDto: {
+ data: {
+ access_token: string;
+ refresh_token: string;
+ };
+ };
+ ResponseSuccessDto_UsersDetailItemDto: {
+ data: {
+ avatar?: string | null;
+ birthdate?: string | null;
+ created_at: string;
+ email: string;
+ fullname: string;
+ gender?: string | null;
+ id: string;
+ is_active: boolean;
+ phone_number: string;
+ role: components["schemas"]["RolesDetailItemDto"];
+ updated_at: string;
+ };
+ };
+ RolesDetailItemDto: {
+ created_at?: string | null;
+ id: string;
+ is_deleted: boolean;
+ name: string;
+ permissions: components["schemas"]["PermissionsItemDto"][];
+ updated_at?: string | null;
+ };
+ RolesListItemDto: {
+ created_at?: string | null;
+ id: string;
+ name: string;
+ permissions_count: number;
+ updated_at?: string | null;
+ };
+ RolesRequestCreateDto: {
+ name: string;
+ permissions: string[];
+ };
+ RolesRequestUpdateDto: {
+ name?: string | null;
+ overwrite?: boolean | null;
+ permissions?: string[] | null;
+ };
+ TokenDto: {
+ access_token: string;
+ refresh_token: string;
+ };
+ UsersActiveInactiveRequestDto: {
+ is_active: boolean;
+ };
+ UsersCreateRequestDto: {
+ email: string;
+ fullname: string;
+ is_active: boolean;
+ password: string;
+ phone_number: string;
+ role_id: string;
+ };
+ UsersDetailItemDto: {
+ avatar?: string | null;
+ birthdate?: string | null;
+ created_at: string;
+ email: string;
+ fullname: string;
+ gender?: string | null;
+ id: string;
+ is_active: boolean;
+ phone_number: string;
+ role: components["schemas"]["RolesDetailItemDto"];
+ updated_at: string;
+ };
+ UsersListItemDto: {
+ avatar?: string | null;
+ created_at: string;
+ email: string;
+ fullname: string;
+ id: string;
+ is_active: boolean;
+ phone_number: string;
+ role: string;
+ updated_at: string;
+ };
+ UsersUpdateRequestDto: {
+ avatar?: string | null;
+ birthdate?: string | null;
+ email: string;
+ fullname: string;
+ gender?: string | null;
+ is_active: boolean;
+ phone_number: string;
+ role_id: string;
+ };
+ };
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
+}
+export type $defs = Record;
+export interface operations {
+ post_forgot_password: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthResendOtpRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Forgot password request successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ /** @description Forgot password request failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_login: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthLoginRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Login successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_AuthLoginResponsetDto"];
+ };
+ };
+ /** @description Login failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_new_password: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthNewPasswordRequestDto"];
+ };
+ };
+ responses: {
+ /** @description New password request successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ /** @description New password request failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_refresh_token: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthRefreshTokenRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Refresh token request successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ /** @description Refresh token request failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_register: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthRegisterRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Register successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ /** @description Register failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_resend_otp: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthResendOtpRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Resend otp successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ /** @description Resend otp failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_verify_email: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthVerifyEmailRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Verify email successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ /** @description Verify email failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_event_list: {
+ parameters: {
+ query?: {
+ /** @description Page number */
+ page?: number;
+ /** @description Items per page */
+ per_page?: number;
+ /** @description Search keyword */
+ search?: string;
+ /** @description Sort by field */
+ sort_by?: string;
+ /** @description Order ASC or DESC */
+ order?: string;
+ /** @description Filter value */
+ filter?: string;
+ /** @description Field to filter by */
+ filter_by?: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get event list */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseListSuccessDto_Vec_EventsListItemDto"];
+ };
+ };
+ };
+ };
+ post_create_event: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["EventsCreateRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Create new event */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ delete_event: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Event ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Soft delete event */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_event_by_id: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Event ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get event by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_EventsDetailItemDto"];
+ };
+ };
+ };
+ };
+ patch_update_event: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Event ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["EventsUpdateRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Update event */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_create_gacha_claim: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["GachaClaimRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Create new gacha claim */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_detail_gacha_claim: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Gacha Claim ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get Gacha Claim by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_GachaClaimItemDto"];
+ };
+ };
+ };
+ };
+ get_gacha_item_list: {
+ parameters: {
+ query?: {
+ /** @description Page number */
+ page?: number;
+ /** @description Items per page */
+ per_page?: number;
+ /** @description Search keyword */
+ search?: string;
+ /** @description Sort by field */
+ sort_by?: string;
+ /** @description Order ASC or DESC */
+ order?: string;
+ /** @description Filter value */
+ filter?: string;
+ /** @description Field to filter by */
+ filter_by?: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get gacha item list */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseListSuccessDto_Vec_GachaItemDto"];
+ };
+ };
+ };
+ };
+ post_create_gacha_item: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["GachaItemRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Create gacha item */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ delete_gacha_item: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Delete gacha item */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_gacha_item_by_id: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Gacha Item ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get gacha item by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_GachaItemDto"];
+ };
+ };
+ };
+ };
+ put_update_gacha_item: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["GachaItemRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Update gacha item */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_create_gacha_roll: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["GachaRollRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Create new gacha roll */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_detail_gacha_roll: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Gacha Roll ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get Gacha Roll by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_GachaRollItemDto"];
+ };
+ };
+ };
+ };
+ post_execute_gacha_roll: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Execute and get 1 gacha result */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_GachaRollItemDto"];
+ };
+ };
+ };
+ };
+ get_permission_list: {
+ parameters: {
+ query?: {
+ /** @description Page number */
+ page?: number;
+ /** @description Items per page */
+ per_page?: number;
+ /** @description Search keyword */
+ search?: string;
+ /** @description Sort by field */
+ sort_by?: string;
+ /** @description Order ASC or DESC */
+ order?: string;
+ /** @description Filter value */
+ filter?: string;
+ /** @description Field to filter by */
+ filter_by?: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get permission list */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseListSuccessDto_Vec_PermissionsItemDto"];
+ };
+ };
+ };
+ };
+ post_create_permission: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["PermissionsRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Create new permission */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ delete_permission: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Delete permission */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_permission_by_id: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Permission ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get permission by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_PermissionsItemDto"];
+ };
+ };
+ };
+ };
+ put_update_permission: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["PermissionsRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Update permission */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_role_list: {
+ parameters: {
+ query?: {
+ /** @description Page number */
+ page?: number;
+ /** @description Items per page */
+ per_page?: number;
+ /** @description Search keyword */
+ search?: string;
+ /** @description Sort by field */
+ sort_by?: string;
+ /** @description Order ASC or DESC */
+ order?: string;
+ /** @description Filter value */
+ filter?: string;
+ /** @description Field to filter by */
+ filter_by?: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get role list */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseListSuccessDto_Vec_RolesListItemDto"];
+ };
+ };
+ };
+ };
+ post_create_role: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["RolesRequestCreateDto"];
+ };
+ };
+ responses: {
+ /** @description Create new role */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ delete_role: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Delete role */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_role_by_id: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Role ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get role by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_RolesDetailItemDto"];
+ };
+ };
+ };
+ };
+ put_update_role: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["RolesRequestUpdateDto"];
+ };
+ };
+ responses: {
+ /** @description Update role */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_user_list: {
+ parameters: {
+ query?: {
+ /** @description Page number */
+ page?: number;
+ /** @description Items per page */
+ per_page?: number;
+ /** @description Search keyword */
+ search?: string;
+ /** @description Sort by field */
+ sort_by?: string;
+ /** @description Order ASC or DESC */
+ order?: string;
+ /** @description Filter value */
+ filter?: string;
+ /** @description Field to filter by */
+ filter_by?: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get user list */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseListSuccessDto_Vec_UsersListItemDto"];
+ };
+ };
+ };
+ };
+ patch_user_active_status: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UsersActiveInactiveRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Set user active/inactive */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ post_create_user: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UsersCreateRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Create new user */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ delete_user: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Soft delete user */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ get_user_by_id: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description User ID */
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get user by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_UsersDetailItemDto"];
+ };
+ };
+ };
+ };
+ get_user_me: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get user by ID */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ResponseSuccessDto_UsersDetailItemDto"];
+ };
+ };
+ };
+ };
+ put_update_user_me: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UsersUpdateRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Update user me */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+ put_update_user: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UsersUpdateRequestDto"];
+ };
+ };
+ responses: {
+ /** @description Update user */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["MessageResponseDto"];
+ };
+ };
+ };
+ };
+}
diff --git a/libs/shadcn-ui/package-lock.json b/libs/shadcn-ui/package-lock.json
index 652dc97..50115cc 100644
--- a/libs/shadcn-ui/package-lock.json
+++ b/libs/shadcn-ui/package-lock.json
@@ -8,14 +8,18 @@
"name": "@imphnen-frontend-service/shadcn-ui",
"version": "0.0.1",
"dependencies": {
- "@radix-ui/react-slot": "^1.2.0",
+ "@hookform/resolvers": "^5.0.1",
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.503.0",
+ "react-hook-form": "^7.56.4",
"react-icons": "^5.5.0",
"tailwind-merge": "^3.2.0",
- "tailwindcss": "^4.1.4"
+ "tailwindcss": "^4.1.4",
+ "zod": "^3.25.28"
},
"devDependencies": {
"tw-animate-css": "^1.2.8"
@@ -446,6 +450,18 @@
"node": ">=18"
}
},
+ "node_modules/@hookform/resolvers": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz",
+ "integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -461,10 +477,56 @@
}
}
},
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
+ "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
- "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
@@ -759,6 +821,12 @@
],
"peer": true
},
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
"node_modules/@tailwindcss/node": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.4.tgz",
@@ -1464,6 +1532,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-dom": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.56.4",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.4.tgz",
+ "integrity": "sha512-Rob7Ftz2vyZ/ZGsQZPaRdIefkgOSrQSPXfqBdvOPwJfoGnjwRJUs7EM7Kc1mcoDv3NOtqBzPGbcMB8CGn9CKgw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
@@ -1513,6 +1610,13 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1649,6 +1753,15 @@
"optional": true
}
}
+ },
+ "node_modules/zod": {
+ "version": "3.25.28",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.28.tgz",
+ "integrity": "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/libs/shadcn-ui/package.json b/libs/shadcn-ui/package.json
index 374ee3c..7616d4a 100644
--- a/libs/shadcn-ui/package.json
+++ b/libs/shadcn-ui/package.json
@@ -10,14 +10,18 @@
}
},
"dependencies": {
- "@radix-ui/react-slot": "^1.2.0",
+ "@hookform/resolvers": "^5.0.1",
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.503.0",
+ "react-hook-form": "^7.56.4",
"react-icons": "^5.5.0",
"tailwind-merge": "^3.2.0",
- "tailwindcss": "^4.1.4"
+ "tailwindcss": "^4.1.4",
+ "zod": "^3.25.28"
},
"devDependencies": {
"tw-animate-css": "^1.2.8"
diff --git a/libs/shadcn-ui/src/atoms/button.tsx b/libs/shadcn-ui/src/atoms/button.tsx
index d8c89fc..6da2a3c 100644
--- a/libs/shadcn-ui/src/atoms/button.tsx
+++ b/libs/shadcn-ui/src/atoms/button.tsx
@@ -3,23 +3,21 @@
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
-import { cn } from '../lib/cn';
+import { cn } from '../lib';
const buttonVariants = cva(
- 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
+ 'inline-flex items-center justify-center font-[600] rounded-md px-[16px] py-[10px] transition-colors duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed',
{
variants: {
variant: {
- default:
- 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
- destructive:
- 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
- outline:
- 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
+ primary: 'bg-primary-500 hover:bg-primary-600 text-white shadow-md',
secondary:
- 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
- ghost: 'hover:bg-accent hover:text-accent-foreground',
- link: 'text-primary underline-offset-4 hover:underline',
+ 'bg-white hover:text-primary-600 hover:bg-gray-50 text-primary-500 shadow-md',
+ text: 'bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500',
+ bordered:
+ 'border border-primary-500 hover:border-primary-600 bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500',
+ success: 'bg-success-500 hover:bg-success-600 text-white shadow-md',
+ danger: 'bg-danger-100 hover:bg-danger-200 text-danger-500 shadow-md',
},
size: {
default: 'h-9 px-4 py-2',
@@ -29,7 +27,7 @@ const buttonVariants = cva(
},
},
defaultVariants: {
- variant: 'default',
+ variant: 'primary',
size: 'default',
},
}
diff --git a/libs/shadcn-ui/src/atoms/card.tsx b/libs/shadcn-ui/src/atoms/card.tsx
new file mode 100644
index 0000000..78e70ea
--- /dev/null
+++ b/libs/shadcn-ui/src/atoms/card.tsx
@@ -0,0 +1,82 @@
+import * as React from 'react';
+import { cn } from '../lib';
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+Card.displayName = 'Card';
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardHeader.displayName = 'CardHeader';
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardTitle.displayName = 'CardTitle';
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardDescription.displayName = 'CardDescription';
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardContent.displayName = 'CardContent';
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardFooter.displayName = 'CardFooter';
+
+export {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+};
diff --git a/libs/shadcn-ui/src/atoms/form.tsx b/libs/shadcn-ui/src/atoms/form.tsx
new file mode 100644
index 0000000..cca75e1
--- /dev/null
+++ b/libs/shadcn-ui/src/atoms/form.tsx
@@ -0,0 +1,167 @@
+'use client';
+
+import * as LabelPrimitive from '@radix-ui/react-label';
+import { Slot } from '@radix-ui/react-slot';
+import * as React from 'react';
+import {
+ Controller,
+ FormProvider,
+ useFormContext,
+ useFormState,
+ type ControllerProps,
+ type FieldPath,
+ type FieldValues,
+} from 'react-hook-form';
+import { cn } from '../lib';
+import { Label } from './label';
+
+const Form = FormProvider;
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> = {
+ name: TName;
+};
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+);
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ );
+};
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext);
+ const itemContext = React.useContext(FormItemContext);
+ const { getFieldState } = useFormContext();
+ const formState = useFormState({ name: fieldContext.name });
+ const fieldState = getFieldState(fieldContext.name, formState);
+
+ if (!fieldContext) {
+ throw new Error('useFormField should be used within ');
+ }
+
+ const { id } = itemContext;
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ };
+};
+
+type FormItemContextValue = {
+ id: string;
+};
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue
+);
+
+function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
+ const id = React.useId();
+
+ return (
+
+
+
+ );
+}
+
+function FormLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ const { error, formItemId } = useFormField();
+
+ return (
+
+ );
+}
+
+function FormControl({ ...props }: React.ComponentProps) {
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField();
+
+ return (
+
+ );
+}
+
+function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
+ const { formDescriptionId } = useFormField();
+
+ return (
+
+ );
+}
+
+function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
+ const { error, formMessageId } = useFormField();
+ const body = error ? String(error?.message ?? '') : props.children;
+
+ if (!body) {
+ return null;
+ }
+
+ return (
+
+ {body}
+
+ );
+}
+
+export {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ useFormField,
+};
diff --git a/libs/shadcn-ui/src/atoms/index.ts b/libs/shadcn-ui/src/atoms/index.ts
index e54f955..82b2d15 100644
--- a/libs/shadcn-ui/src/atoms/index.ts
+++ b/libs/shadcn-ui/src/atoms/index.ts
@@ -1,2 +1,6 @@
export * from './button';
+export * from './card';
+export * from './form';
export * from './icons';
+export * from './input';
+export * from './label';
diff --git a/libs/shadcn-ui/src/atoms/input.tsx b/libs/shadcn-ui/src/atoms/input.tsx
new file mode 100644
index 0000000..a5b26ee
--- /dev/null
+++ b/libs/shadcn-ui/src/atoms/input.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import * as React from 'react';
+import { cn } from '../lib';
+
+const Input = React.forwardRef>(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+Input.displayName = 'Input';
+
+export { Input };
diff --git a/libs/shadcn-ui/src/atoms/label.tsx b/libs/shadcn-ui/src/atoms/label.tsx
new file mode 100644
index 0000000..c44bbce
--- /dev/null
+++ b/libs/shadcn-ui/src/atoms/label.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import * as LabelPrimitive from '@radix-ui/react-label';
+import * as React from 'react';
+import { cn } from '../lib';
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Label };
diff --git a/libs/shadcn-ui/src/index.css b/libs/shadcn-ui/src/index.css
index d3e908b..607f2b7 100644
--- a/libs/shadcn-ui/src/index.css
+++ b/libs/shadcn-ui/src/index.css
@@ -1,9 +1,73 @@
@import 'tailwindcss';
-@import "tw-animate-css";
+@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
@theme inline {
+ --color-primary-50: #f0f8ff;
+ --color-primary-100: #e1f0fd;
+ --color-primary-200: #bce1fb;
+ --color-primary-300: #81cbf8;
+ --color-primary-400: #3eb0f2;
+ --color-primary-500: #23a1eb;
+ --color-primary-600: #0877c1;
+ --color-primary-700: #085f9c;
+ --color-primary-800: #0b5181;
+ --color-primary-900: #0f446b;
+ --color-primary-950: #0a2b47;
+
+ --color-neutral-50: #f6f6f6;
+ --color-neutral-100: #e7e7e7;
+ --color-neutral-200: #d1d1d1;
+ --color-neutral-300: #b0b0b0;
+ --color-neutral-400: #888888;
+ --color-neutral-500: #6d6d6d;
+ --color-neutral-600: #5d5d5d;
+ --color-neutral-700: #4f4f4f;
+ --color-neutral-800: #454545;
+ --color-neutral-900: #3d3d3d;
+ --color-neutral-950: #2b2b2b;
+
+ --color-success-100: #e0fbd8;
+ --color-success-200: #bcf8b0;
+ --color-success-300: #8eea85;
+ --color-success-400: #63d564;
+ --color-success-500: #35ba43;
+ --color-success-600: #269f3e;
+ --color-success-700: #1a8439;
+ --color-success-800: #106b32;
+ --color-success-900: #0b592f;
+
+ --color-info-100: #ccfcfe;
+ --color-info-200: #9bf3fd;
+ --color-info-300: #67e3fb;
+ --color-info-400: #42cdf8;
+ --color-info-500: #04acf3;
+ --color-info-600: #0185d0;
+ --color-info-700: #0264af;
+ --color-info-800: #01478d;
+ --color-info-900: #003375;
+
+ --color-warning-100: #fffcd3;
+ --color-warning-200: #fffaa9;
+ --color-warning-300: #fff67d;
+ --color-warning-400: #fff25d;
+ --color-warning-500: #ffed27;
+ --color-warning-600: #dbc91d;
+ --color-warning-700: #b7a714;
+ --color-warning-800: #93850b;
+ --color-warning-900: #7a6d07;
+
+ --color-danger-100: #ffe8da;
+ --color-danger-200: #ffcbb3;
+ --color-danger-300: #ffaa8d;
+ --color-danger-400: #ff8870;
+ --color-danger-500: #ff5242;
+ --color-danger-600: #da3030;
+ --color-danger-700: #b7212d;
+ --color-danger-800: #93152a;
+ --color-danger-900: #7a0c27;
+
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
@@ -76,40 +140,6 @@
--sidebar-ring: oklch(0.623 0.214 259.815);
}
-.dark {
- --background: oklch(0.141 0.005 285.823);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.21 0.006 285.885);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.21 0.006 285.885);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.546 0.245 262.881);
- --primary-foreground: oklch(0.379 0.146 265.522);
- --secondary: oklch(0.274 0.006 286.033);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.274 0.006 286.033);
- --muted-foreground: oklch(0.705 0.015 286.067);
- --accent: oklch(0.274 0.006 286.033);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.488 0.243 264.376);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.21 0.006 285.885);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.546 0.245 262.881);
- --sidebar-primary-foreground: oklch(0.379 0.146 265.522);
- --sidebar-accent: oklch(0.274 0.006 286.033);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.488 0.243 264.376);
-}
-
@layer base {
* {
@apply border-border outline-ring/50;
@@ -127,6 +157,7 @@
padding-right: 2rem;
width: 100%;
}
+
@media (min-width: 1536px) {
.container {
max-width: 1400px;
diff --git a/package-lock.json b/package-lock.json
index fdbd228..b1a656b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,10 @@
"@ant-design/icons": "^6.0.0",
"@hookform/resolvers": "^5.0.1",
"@iconify/react": "^6.0.0",
+ "@marsidev/react-turnstile": "^1.1.0",
+ "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.0",
+ "@redocly/ajv": "^8.11.2",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-store": "^0.7.0",
"@tanstack/react-table": "^8.21.2",
@@ -24,6 +27,8 @@
"js-cookie": "^3.0.5",
"next": "~15.2.4",
"next-themes": "^0.4.6",
+ "openapi-fetch": "^0.14.0",
+ "openapi-react-query": "^0.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.56.4",
@@ -90,6 +95,7 @@
"jiti": "2.4.2",
"jsdom": "~22.1.0",
"nx": "20.8.1",
+ "openapi-typescript": "^7.8.0",
"postcss": "^8.5.3",
"prettier": "^2.6.2",
"serve": "^14.2.4",
@@ -4157,6 +4163,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@marsidev/react-turnstile": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-1.1.0.tgz",
+ "integrity": "sha512-X7bP9ZYutDd+E+klPYF+/BJHqEyyVkN4KKmZcNRr84zs3DcMoftlMAuoKqNSnqg0HE7NQ1844+TLFSJoztCdSA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^17.0.2 || ^18.0.0 || ^19.0",
+ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0"
+ }
+ },
"node_modules/@mdx-js/react": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz",
@@ -7590,10 +7606,56 @@
}
}
},
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
+ "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
- "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
@@ -7627,6 +7689,111 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
+ "node_modules/@redocly/ajv": {
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
+ "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js-replace": "^1.0.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@redocly/ajv/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/@redocly/config": {
+ "version": "0.22.2",
+ "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.2.tgz",
+ "integrity": "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/openapi-core": {
+ "version": "1.34.3",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.3.tgz",
+ "integrity": "sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/ajv": "^8.11.2",
+ "@redocly/config": "^0.22.0",
+ "colorette": "^1.2.0",
+ "https-proxy-agent": "^7.0.5",
+ "js-levenshtein": "^1.1.6",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^5.0.1",
+ "pluralize": "^8.0.0",
+ "yaml-ast-parser": "0.0.43"
+ },
+ "engines": {
+ "node": ">=18.17.0",
+ "npm": ">=9.5.0"
+ }
+ },
+ "node_modules/@redocly/openapi-core/node_modules/agent-base": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@redocly/openapi-core/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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@redocly/openapi-core/node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/openapi-core/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@redocly/openapi-core/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@rollup/pluginutils": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
@@ -10826,7 +10993,7 @@
"version": "19.1.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz",
"integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.0.0"
@@ -14825,6 +14992,13 @@
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
+ "node_modules/change-case": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
+ "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/char-regex": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
@@ -18394,7 +18568,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/fast-fifo": {
@@ -20438,6 +20611,19 @@
"node": ">=8"
}
},
+ "node_modules/index-to-position": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz",
+ "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -22808,6 +22994,16 @@
"node": ">=14"
}
},
+ "node_modules/js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -26121,6 +26317,99 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/openapi-fetch": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.14.0.tgz",
+ "integrity": "sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==",
+ "license": "MIT",
+ "dependencies": {
+ "openapi-typescript-helpers": "^0.0.15"
+ }
+ },
+ "node_modules/openapi-react-query": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/openapi-react-query/-/openapi-react-query-0.5.0.tgz",
+ "integrity": "sha512-VtyqiamsbWsdSWtXmj/fAR+m9nNxztsof6h8ZIsjRj8c8UR/x9AIwHwd60IqwgymmFwo7qfSJQ1ZzMJrtqjQVg==",
+ "license": "MIT",
+ "dependencies": {
+ "openapi-typescript-helpers": "^0.0.15"
+ },
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.25.0",
+ "openapi-fetch": "^0.14.0"
+ }
+ },
+ "node_modules/openapi-typescript": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.8.0.tgz",
+ "integrity": "sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/openapi-core": "^1.34.3",
+ "ansi-colors": "^4.1.3",
+ "change-case": "^5.4.4",
+ "parse-json": "^8.3.0",
+ "supports-color": "^10.0.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "openapi-typescript": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "typescript": "^5.x"
+ }
+ },
+ "node_modules/openapi-typescript-helpers": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.15.tgz",
+ "integrity": "sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==",
+ "license": "MIT"
+ },
+ "node_modules/openapi-typescript/node_modules/parse-json": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
+ "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "index-to-position": "^1.1.0",
+ "type-fest": "^4.39.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/openapi-typescript/node_modules/supports-color": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.0.0.tgz",
+ "integrity": "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/openapi-typescript/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
@@ -26864,6 +27153,16 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/polished": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz",
@@ -28404,7 +28703,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -32768,6 +33066,12 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/uri-js-replace": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
+ "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
+ "license": "MIT"
+ },
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
@@ -34639,6 +34943,13 @@
"node": ">= 14"
}
},
+ "node_modules/yaml-ast-parser": {
+ "version": "0.0.43",
+ "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
+ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
diff --git a/package.json b/package.json
index 7bf87ec..c35b39d 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"landing:dev": "nx serve landing",
"landing:build": "nx build landing",
"landing:prod": "cd ./dist/apps/landing && npx next start",
+ "landing:openapi": "npx openapi-typescript https://api.imphnen.dev/openapi.json -o ./apps/landing/src/openapi-types.ts",
"ui:test": "nx test ui",
"ui:build": "nx build ui",
"ui:storybook": "nx storybook ui"
@@ -27,7 +28,10 @@
"@ant-design/icons": "^6.0.0",
"@hookform/resolvers": "^5.0.1",
"@iconify/react": "^6.0.0",
+ "@marsidev/react-turnstile": "^1.1.0",
+ "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.0",
+ "@redocly/ajv": "^8.11.2",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-store": "^0.7.0",
"@tanstack/react-table": "^8.21.2",
@@ -39,6 +43,8 @@
"js-cookie": "^3.0.5",
"next": "~15.2.4",
"next-themes": "^0.4.6",
+ "openapi-fetch": "^0.14.0",
+ "openapi-react-query": "^0.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.56.4",
@@ -105,6 +111,7 @@
"jiti": "2.4.2",
"jsdom": "~22.1.0",
"nx": "20.8.1",
+ "openapi-typescript": "^7.8.0",
"postcss": "^8.5.3",
"prettier": "^2.6.2",
"serve": "^14.2.4",