From f32430973e0ee9a3f590be13bf5b4cb8e8141794 Mon Sep 17 00:00:00 2001 From: Malik Hisyam Date: Sat, 18 May 2024 22:26:25 +0700 Subject: [PATCH 1/2] Initial Commit --- .github/dependabot.yml | 11 - .github/workflows/docker-image.yml | 53 - .husky/pre-commit | 4 - .vscode/launch.json | 21 - .vscode/settings.json | 9 - components.json | 17 + components/AdminLayout.tsx | 17 + components/AdminSidebar.tsx | 58 + components/AuditoriumCards.tsx | 37 + components/Checkbox.tsx | 31 + components/CustomSelect.tsx | 39 + components/DefautLayout.tsx | 222 +-- components/FloatingNavigation.tsx | 73 + components/Footer.tsx | 44 + components/Navbar.tsx | 93 ++ components/SportCards.tsx | 19 + components/ui/accordion.tsx | 56 + components/ui/avatar.tsx | 48 + components/ui/button.tsx | 56 + components/ui/calendar.tsx | 66 + components/ui/card.tsx | 79 ++ components/ui/carousel.tsx | 260 ++++ components/ui/checkbox.tsx | 28 + components/ui/form.tsx | 176 +++ components/ui/input.tsx | 26 + components/ui/label.tsx | 24 + components/ui/popover.tsx | 29 + components/ui/select.tsx | 158 +++ lib/utils.ts | 6 + package-lock.json | 1999 +++++++++++++++++++++------ package.json | 27 +- pages/admin/dashboard/index.tsx | 41 + pages/admin/directors/create.tsx | 93 ++ pages/admin/directors/index.tsx | 53 + pages/admin/genres/create.tsx | 87 ++ pages/admin/genres/index.tsx | 52 + pages/admin/movies/create.tsx | 308 +++++ pages/admin/movies/index.tsx | 52 + pages/admin/regions/create.tsx | 87 ++ pages/admin/regions/index.tsx | 52 + pages/admin/starrings/index.tsx | 52 + pages/auditoriums/4dx.tsx | 16 + pages/auditoriums/gold-class.tsx | 15 + pages/auditoriums/private-box.tsx | 15 + pages/auditoriums/satin-class.tsx | 15 + pages/auditoriums/screenx.tsx | 15 + pages/auditoriums/spherex.tsx | 15 + pages/auditoriums/starium.tsx | 15 + pages/auditoriums/sweet-box.tsx | 15 + pages/auditoriums/velvet-class.tsx | 16 + pages/content/about-us/index.tsx | 57 + pages/content/careers/index.tsx | 40 + pages/content/contact-us/index.tsx | 28 + pages/content/feedback/index.tsx | 20 + pages/content/legal-term/index.tsx | 95 ++ pages/dashboard/index.tsx | 91 -- pages/events/all/index.tsx | 10 + pages/index.tsx | 194 ++- pages/membership/index.tsx | 44 + pages/movies/[id].tsx | 73 + pages/movies/now-playing/index.tsx | 73 + pages/promotion/all/index.tsx | 18 + pages/schedule/cinema/index.tsx | 59 + pages/snack/index.tsx | 10 + pages/special_audi/index.tsx | 48 + pages/user/login/index.tsx | 105 ++ pages/user/register/index.tsx | 260 ++++ public/favicon.ico | Bin 15086 -> 6714 bytes public/images/brick-bg.png | Bin 0 -> 8485 bytes public/images/brick-pattern.jpg | Bin 0 -> 14962 bytes public/images/cgv-logo.png | Bin 0 -> 42393 bytes public/images/cultureplex.png | Bin 0 -> 9930 bytes public/images/navbar-background.png | Bin 0 -> 4145 bytes schemas/loginSchema.tsx | 8 + schemas/registerSchema.tsx | 15 + styles/globals.css | 50 +- tailwind.config.js | 16 - tailwind.config.ts | 43 + tsconfig.json | 58 +- 79 files changed, 5325 insertions(+), 890 deletions(-) delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/docker-image.yml delete mode 100644 .husky/pre-commit delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json create mode 100644 components.json create mode 100644 components/AdminLayout.tsx create mode 100644 components/AdminSidebar.tsx create mode 100644 components/AuditoriumCards.tsx create mode 100644 components/Checkbox.tsx create mode 100644 components/CustomSelect.tsx create mode 100644 components/FloatingNavigation.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Navbar.tsx create mode 100644 components/SportCards.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/select.tsx create mode 100644 lib/utils.ts create mode 100644 pages/admin/dashboard/index.tsx create mode 100644 pages/admin/directors/create.tsx create mode 100644 pages/admin/directors/index.tsx create mode 100644 pages/admin/genres/create.tsx create mode 100644 pages/admin/genres/index.tsx create mode 100644 pages/admin/movies/create.tsx create mode 100644 pages/admin/movies/index.tsx create mode 100644 pages/admin/regions/create.tsx create mode 100644 pages/admin/regions/index.tsx create mode 100644 pages/admin/starrings/index.tsx create mode 100644 pages/auditoriums/4dx.tsx create mode 100644 pages/auditoriums/gold-class.tsx create mode 100644 pages/auditoriums/private-box.tsx create mode 100644 pages/auditoriums/satin-class.tsx create mode 100644 pages/auditoriums/screenx.tsx create mode 100644 pages/auditoriums/spherex.tsx create mode 100644 pages/auditoriums/starium.tsx create mode 100644 pages/auditoriums/sweet-box.tsx create mode 100644 pages/auditoriums/velvet-class.tsx create mode 100644 pages/content/about-us/index.tsx create mode 100644 pages/content/careers/index.tsx create mode 100644 pages/content/contact-us/index.tsx create mode 100644 pages/content/feedback/index.tsx create mode 100644 pages/content/legal-term/index.tsx delete mode 100644 pages/dashboard/index.tsx create mode 100644 pages/events/all/index.tsx create mode 100644 pages/membership/index.tsx create mode 100644 pages/movies/[id].tsx create mode 100644 pages/movies/now-playing/index.tsx create mode 100644 pages/promotion/all/index.tsx create mode 100644 pages/schedule/cinema/index.tsx create mode 100644 pages/snack/index.tsx create mode 100644 pages/special_audi/index.tsx create mode 100644 pages/user/login/index.tsx create mode 100644 pages/user/register/index.tsx create mode 100644 public/images/brick-bg.png create mode 100644 public/images/brick-pattern.jpg create mode 100644 public/images/cgv-logo.png create mode 100644 public/images/cultureplex.png create mode 100644 public/images/navbar-background.png create mode 100644 schemas/loginSchema.tsx create mode 100644 schemas/registerSchema.tsx delete mode 100644 tailwind.config.js create mode 100644 tailwind.config.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 3a3cce5..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index fd78f4e..0000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ master, main ] - tags: - - 'v*' - pull_request: - branches: [ master, main ] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - - build: - - runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - - steps: - - uses: actions/checkout@v2 - - - name: Log in to the Container registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Build and push Docker image - uses: docker/build-push-action@v2 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index 2b796ec..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npm run pre-commit-check diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index d9811ba..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "configurations": [ - { - "name": "Next.js: Debug Client-Side", - "type": "msedge", - "request": "launch", - "url": "http://localhost:3000" - }, - { - "name": "Next.js: Debug Full-Stack", - "type": "node-terminal", - "request": "launch", - "command": "npm run debug", - "serverReadyAction": { - "pattern": "started server on .+, url: (https?://.+)", - "uriFormat": "%s", - "action": "debugWithEdge" - } - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1e535ef..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "files.associations": { - "*.css": "tailwindcss" - }, - "editor.quickSuggestions": { - "strings": "on" - }, - "typescript.tsdk": "./node_modules/typescript/lib" -} \ No newline at end of file diff --git a/components.json b/components.json new file mode 100644 index 0000000..c17080c --- /dev/null +++ b/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "styles/globals.css", + "baseColor": "slate", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/components/AdminLayout.tsx b/components/AdminLayout.tsx new file mode 100644 index 0000000..286f407 --- /dev/null +++ b/components/AdminLayout.tsx @@ -0,0 +1,17 @@ +import AdminSidebar from "./AdminSidebar"; + +const AdminLayout: React.FC<{ + children: React.ReactNode +}> = ({ children }) => { + return ( + <> +
+ + {children} +
+ + ); + +} + +export const WithAdminLayout = (page: React.ReactElement) => {page}; \ No newline at end of file diff --git a/components/AdminSidebar.tsx b/components/AdminSidebar.tsx new file mode 100644 index 0000000..6f90c0b --- /dev/null +++ b/components/AdminSidebar.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Avatar, AvatarImage } from "@/components/ui/avatar"; +import Link from 'next/link'; + +const AdminSidebar = () => { + return ( +
+
+ Admin Panel Logo +
+ + + + +
+ ); +}; + +export default AdminSidebar; diff --git a/components/AuditoriumCards.tsx b/components/AuditoriumCards.tsx new file mode 100644 index 0000000..5053a88 --- /dev/null +++ b/components/AuditoriumCards.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import Link from 'next/link'; + +const AuditoriumCards = () => { + const auditoriums = [ + { name: "Gold Class", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041819584206.jpg", link: "/auditoriums/gold-class" }, + { name: "Velvet Class", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041826292094.jpg", link: "/auditoriums/velvet-class" }, + { name: "Satin Class", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041827191534.jpg", link: "/auditoriums/satin-class" }, + { name: "Sweet Box", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041828077499.jpg", link: "/auditoriums/sweet-box" }, + { name: "4DX", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041830356804.jpg", link: "/auditoriums/4dx" }, + { name: "ScreenX", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041831366877.jpg", link: "/auditoriums/screenx" }, + { name: "Starium", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041829003710.jpg", link: "/auditoriums/starium" }, + { name: "SphereX", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2308/SA202308041829541327.jpg", link: "/auditoriums/spherex" }, + { name: "Private Box", src: "https://cdn.cgv.id/uploads_v2/special_audi/web/thumbnails/2402/SA202402170700368566.png", link: "/auditoriums/private-box" } + ]; + + return ( +
+ {auditoriums.map(auditorium => ( + +
+ {auditorium.name} +

{auditorium.name}

+
+ + ))} +
+ ); +}; + +export default AuditoriumCards; + + diff --git a/components/Checkbox.tsx b/components/Checkbox.tsx new file mode 100644 index 0000000..72ab754 --- /dev/null +++ b/components/Checkbox.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { useController } from 'react-hook-form'; + +const IsShowingCheckBox = ({ control, name, label }) => { + const { field: { onChange, onBlur, value, ref } } = useController({ + control, + name, + defaultValue: true, + }); + + const handleChange = (event) => { + onChange(event.target.checked); + }; + + return ( +
+ + +
+ ); +}; + +export default IsShowingCheckBox; diff --git a/components/CustomSelect.tsx b/components/CustomSelect.tsx new file mode 100644 index 0000000..9278c82 --- /dev/null +++ b/components/CustomSelect.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { useController } from "react-hook-form"; + +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; + +export function CustomSelect({ control, name, label, getOptions }) { + const [, setSelectedLabel] = React.useState(label); + + const { field: { value, onChange } } = useController({ + control, + name, + }); + + const handleChange = (event) => { + onChange(event.target.value); + const selectedOption = options.find((option) => option.value === event.target.value); + setSelectedLabel(selectedOption?.label || label); + }; + + const options = getOptions(); + + return ( + + ); +} diff --git a/components/DefautLayout.tsx b/components/DefautLayout.tsx index 973c396..4b6aae8 100644 --- a/components/DefautLayout.tsx +++ b/components/DefautLayout.tsx @@ -1,222 +1,20 @@ -import React, { useState } from "react"; -import Head from 'next/head'; -import { Avatar, Button, ConfigProvider, Drawer, Layout, Menu, MenuProps } from "antd"; -import { faBars, faSignOut, faSignIn, faHome, faCubes, faUser, faUsers, faFlaskVial } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useRouter } from "next/router"; -import { useSession, signIn, signOut } from "next-auth/react"; -import nProgress from "nprogress"; - -const { Content, Sider } = Layout; - -const sidebarBackgroundColor = '#001529'; -const sidebarMenuSelectedItemBackgroundColor = '#1677ff'; +import FloatingNavigation from "./FloatingNavigation"; +import Footer from "./Footer"; +import Navbar from "./Navbar"; const DefaultLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { - - const [drawerOpen, setDrawerOpen] = useState(false); - const router = useRouter(); - const { data: session, status } = useSession(); - - // menu.key must match the router.pathname, see example below: "/dashboard" - const [selected, setSelected] = useState([router.pathname]); - - // key must also be unique, for obvious reason - function getMenu(): MenuProps['items'] { - const menu: MenuProps['items'] = []; - - menu.push({ - key: '/', - label: 'Home', - icon: , - onClick: () => router.push('/') - }); - - menu.push( - { - key: '#menu-1', - label: 'Menu 1', - icon: , - children: [ - { - key: '/dashboard', - label: 'Dashboard', - onClick: () => router.push('/dashboard') - }, - { - key: '/sub-menu-b', - label: 'Sub Menu B', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-c', - label: 'Sub Menu C', - onClick: () => router.push('/') - } - ] - }, - { - key: '#menu-2', - label: 'Menu 2', - icon: , - children: [ - { - key: '/sub-menu-d', - label: 'Sub Menu D', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-e', - label: 'Sub Menu E', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-f', - label: 'Sub Menu F', - onClick: () => router.push('/') - } - ] - }, - { - key: '#menu-3', - label: 'Menu 3', - icon: , - children: [ - { - key: '/sub-menu-g', - label: 'Sub Menu G', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-h', - label: 'Sub Menu H', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-i', - label: 'Sub Menu I', - onClick: () => router.push('/') - } - ] - } - ); - - if (status === 'authenticated') { - menu.push({ - key: '/sign-out', - label: 'Sign out', - icon: , - onClick: () => { - nProgress.start(); - signOut(); - // HINT: use this method call if need to end SSO server authentication session: - // signOut({ - // callbackUrl: '/api/end-session' - // }); - } - }); - } else { - menu.push({ - key: '/sign-in', - label: 'Sign in', - icon: , - onClick: () => { - nProgress.start(); - signIn('oidc'); - } - }); - } - - return menu; - } - - const displayUserName = session?.user?.name; - - function renderAvatar() { - if (status === 'authenticated') { - return ( -
-
- } /> -
-
- Hello, {displayUserName} -
-
- ); - } - - return null; - } - return ( - - - - - - - +
+ + - -
Logo
- {renderAvatar()} - - setSelected(e.selectedKeys)} /> - - - setDrawerOpen(false)}> - - setSelected(e.selectedKeys)} /> - - - -
-
- -
-
- Logo -
-
-
- - {children} - -
- - + {children} +
+
); + } export const WithDefaultLayout = (page: React.ReactElement) => {page}; diff --git a/components/FloatingNavigation.tsx b/components/FloatingNavigation.tsx new file mode 100644 index 0000000..249ccde --- /dev/null +++ b/components/FloatingNavigation.tsx @@ -0,0 +1,73 @@ +import React, { useState, useEffect } from "react"; +import Link from "next/link"; + +const FloatingNavigation = () => { + const [yPos, setYPos] = useState(0); + const [lastScrollTop, setLastScrollTop] = useState(0); + + useEffect(() => { + const handleScroll = () => { + const scrollTop = + window.pageYOffset || document.documentElement.scrollTop; + const delta = scrollTop - lastScrollTop; + + // Update position with slight delay and physics-like animation + setYPos((prevYPos) => prevYPos + delta * 0.1); + + setLastScrollTop(scrollTop); + }; + + window.addEventListener("scroll", handleScroll); + + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, [lastScrollTop]); + + return ( +
+
    +
  • + + Movies + +
  • +
  • + + Cinemas + +
  • +
  • + + Promotions + +
  • +
  • + + Features + +
  • +
+
+ ); +}; + +export default FloatingNavigation; diff --git a/components/Footer.tsx b/components/Footer.tsx new file mode 100644 index 0000000..1a66110 --- /dev/null +++ b/components/Footer.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import Link from 'next/link'; + +const Footer = () => { + return ( +
+
+
    + {['4DX', 'SCREENX', 'SPHEREX', 'STARIUM', 'DOLBY ATMOS', 'VELVET', 'GOLD CLASS', 'SATIN SUITE', 'SWEET BOX', 'PRIVATE BOX'].map((item, index) => ( +
  • {item}
  • + ))} +
+
+
+
+
    + {['About Us', 'Legal Term', 'Careers', 'Feedback', 'Contact Us', 'Investor Relation'].map((text, index) => ( +
  • + {text} +
  • + ))} +
+
+
+
+ CGV Logo +

COPYRIGHT 2024 CJ CGV ALL RIGHTS RESERVED.

+
+
+ + + + + + +
+
+
+ Footer Image Pattern +
+ ); +}; + +export default Footer; diff --git a/components/Navbar.tsx b/components/Navbar.tsx new file mode 100644 index 0000000..61bf61f --- /dev/null +++ b/components/Navbar.tsx @@ -0,0 +1,93 @@ +import React from "react"; +import Link from "next/link"; +import { FaFacebook, FaInstagram, FaTiktok, FaTwitter } from "react-icons/fa6"; + +const Navbar = () => { + return ( +
+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ + news + + + login + + + signup + + + logout + +
+
+ +
+ ); +}; + +export default Navbar; diff --git a/components/SportCards.tsx b/components/SportCards.tsx new file mode 100644 index 0000000..5a92c84 --- /dev/null +++ b/components/SportCards.tsx @@ -0,0 +1,19 @@ +import React from "react"; + +const SportCards = () => { + return ( +
+
+ Sports Hall FX Sudirman +

Sports Hall FX Sudirman

+
+ {/* Add more images with similar structure if needed */} +
+ ); +}; + +export default SportCards; diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..e6a723d --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..14bcc11 --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..0ba4277 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx new file mode 100644 index 0000000..fed3799 --- /dev/null +++ b/components/ui/calendar.tsx @@ -0,0 +1,66 @@ +import * as React from "react" +import { ChevronLeft, ChevronRight } from "lucide-react" +import { DayPicker } from "react-day-picker" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +export type CalendarProps = React.ComponentProps + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + , + // eslint-disable-next-line @typescript-eslint/no-unused-vars + IconRight: ({ ...props }) => , + }} + {...props} + /> + ) +} +Calendar.displayName = "Calendar" + +export { Calendar } diff --git a/components/ui/card.tsx b/components/ui/card.tsx new file mode 100644 index 0000000..afa13ec --- /dev/null +++ b/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +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< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + 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, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx new file mode 100644 index 0000000..9c2b9bf --- /dev/null +++ b/components/ui/carousel.tsx @@ -0,0 +1,260 @@ +import * as React from "react" +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react" +import { ArrowLeft, ArrowRight } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin + orientation?: "horizontal" | "vertical" + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return + } + + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) { + return + } + + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) { + return + } + + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) + } +) +Carousel.displayName = "Carousel" + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +}) +CarouselContent.displayName = "CarouselContent" + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel() + + return ( +
+ ) +}) +CarouselItem.displayName = "CarouselItem" + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +}) +CarouselPrevious.displayName = "CarouselPrevious" + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +}) +CarouselNext.displayName = "CarouselNext" + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx new file mode 100644 index 0000000..7e8c96a --- /dev/null +++ b/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/components/ui/form.tsx b/components/ui/form.tsx new file mode 100644 index 0000000..0481e02 --- /dev/null +++ b/components/ui/form.tsx @@ -0,0 +1,176 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/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, formState } = useFormContext() + + 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 +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +