diff options
| author | l3wdfut4pwr <l3wdfut4pwr@gmail.com> | 2025-12-30 13:46:39 +0200 |
|---|---|---|
| committer | l3wdfut4pwr <l3wdfut4pwr@gmail.com> | 2025-12-30 13:46:39 +0200 |
| commit | c3dcb9c827df6d80ad1b0b1a7c6155561527b39d (patch) | |
| tree | 76d8b9e706f9e8fcf7acc157a633905ff16c6b74 /src | |
init
Diffstat (limited to 'src')
26 files changed, 683 insertions, 0 deletions
diff --git a/src/app/favicon.ico b/src/app/favicon.ico Binary files differnew file mode 100644 index 0000000..718d6fe --- /dev/null +++ b/src/app/favicon.ico diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..51cd92c --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,106 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: #05040a; + --foreground: #ffffff; + + --violet: #464199; + --light-violet: #8784c9; + --dark-indigo: #0d0c1c; + --red: #e64c4f; + + --radius: 0.625rem; + --card: oklch(1 0 0); + --card-foreground: oklch(0.13 0.028 261.692); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.13 0.028 261.692); + --primary: oklch(0.21 0.034 264.665); + --primary-foreground: oklch(0.985 0.002 247.839); + --secondary: oklch(0.967 0.003 264.542); + --secondary-foreground: oklch(0.21 0.034 264.665); + --muted: oklch(0.967 0.003 264.542); + --muted-foreground: oklch(0.551 0.027 264.364); + --accent: oklch(0.967 0.003 264.542); + --accent-foreground: oklch(0.21 0.034 264.665); + --destructive: oklch(0.577 0.245 27.325); + --input: oklch(0.928 0.006 264.531); + --ring: oklch(0.707 0.022 261.325); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0.002 247.839); + --sidebar-foreground: oklch(0.13 0.028 261.692); + --sidebar-primary: oklch(0.21 0.034 264.665); + --sidebar-primary-foreground: oklch(0.985 0.002 247.839); + --sidebar-accent: oklch(0.967 0.003 264.542); + --sidebar-accent-foreground: oklch(0.21 0.034 264.665); + --sidebar-border: oklch(0.928 0.006 264.531); + --sidebar-ring: oklch(0.707 0.022 261.325); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-violet: var(--violet); + --color-light-violet: var(--light-violet); + --color-dark-indigo: var(--dark-indigo); + --color-red: var(--red); + + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + --radius-3xl: calc(var(--radius) + 12px); + --radius-4xl: calc(var(--radius) + 16px); +} + +@layer base { + * { + @apply border-violet outline-ring/50; + } + html { + @apply scroll-smooth; + } + body { + @apply bg-background text-foreground; + } + button:not(:disabled), + [role='button']:not(:disabled) { + cursor: pointer; + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..194dca2 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,36 @@ +import type { Metadata } from 'next'; +import { Nunito } from 'next/font/google'; +import './globals.css'; +import { Header } from '../components/header'; +import { GlobalContextProvider } from '../lib/contexts'; +import { Footer } from '../components/footer'; + +const nunito = Nunito(); + +export const metadata: Metadata = { + title: 'Artberry', + description: 'Happy gooning!', + icons: '/icons/logo.svg', +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + <html lang="en"> + <body className={`${nunito.className} antialiased px-37.5`}> + <div className="max-w-375 min-h-dvh flex flex-col mx-auto"> + <GlobalContextProvider> + <Header /> + <main className="mt-[50px] mb-[80px] grow"> + {children} + </main> + <Footer /> + </GlobalContextProvider> + </div> + </body> + </html> + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..b073f26 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,5 @@ +import Image from 'next/image'; + +export default function Home() { + return null; +} diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx new file mode 100644 index 0000000..d0f096f --- /dev/null +++ b/src/app/upload/page.tsx @@ -0,0 +1,38 @@ +'use client'; + +import { Button } from '@/components/ui'; +import { + UploadMenu, + FileDropzone, + TagsInput, + SourceInput, +} from '@/components/upload'; +import { PUBLISH_DISCLAIMER } from '@/lib/consts'; + +export default function Upload() { + return ( + <div className="flex justify-between w-full"> + <UploadMenu /> + <form + className="flex flex-col grow max-w-[900px] gap-5" + onSubmit={(e) => { + console.log('SUBMIT'); + e.preventDefault(); + + const formData = new FormData(e.currentTarget); + const data: any = Object.fromEntries(formData.entries()); + + console.log(data.files); + }} + > + <FileDropzone /> + <TagsInput /> + <SourceInput /> + <span className="text-center text-sm"> + {PUBLISH_DISCLAIMER} + </span> + <Button>Опубликовать</Button> + </form> + </div> + ); +} diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx new file mode 100644 index 0000000..9b2a3f2 --- /dev/null +++ b/src/components/footer/Footer.tsx @@ -0,0 +1,24 @@ +import Logo from '@icons/logo.svg'; +import ArtberryIcon from '@icons/artberry.svg'; + +export function Footer() { + return ( + <footer className="flex w-full justify-between py-[50px]"> + <div className="flex flex-col justify-between w-[259px] h-[192px]"> + <div className="text-right"> + <div className="flex items-center gap-2.5 w-full"> + <Logo /> + <ArtberryIcon /> + </div> + <span className="font-medium">Лучшее — здесь.</span> + </div> + <div className="flex flex-col gap-1.25"> + <span className="underline text-sm"> + Terms and Conditions · Privacy Policy + </span> + <span className="text-xs">© 2025 artberry.xyz</span> + </div> + </div> + </footer> + ); +} diff --git a/src/components/footer/index.ts b/src/components/footer/index.ts new file mode 100644 index 0000000..ddcc5a9 --- /dev/null +++ b/src/components/footer/index.ts @@ -0,0 +1 @@ +export * from './Footer'; diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx new file mode 100644 index 0000000..b096bdb --- /dev/null +++ b/src/components/header/Header.tsx @@ -0,0 +1,10 @@ +import { NavBar, SubNav } from '.'; + +export function Header() { + return ( + <div className="flex flex-col w-full"> + <NavBar /> + <SubNav /> + </div> + ); +} diff --git a/src/components/header/NavBar.tsx b/src/components/header/NavBar.tsx new file mode 100644 index 0000000..f186904 --- /dev/null +++ b/src/components/header/NavBar.tsx @@ -0,0 +1,75 @@ +//import { ShuffleIcon } from 'lucide-react'; +import ShuffleIcon from '@icons/ShuffleIcon.svg'; +import Image from 'next/image'; +import { Button, Input } from '../ui'; +import { ProfileOrLogin } from './ProfileOrLogin'; +import Link from 'next/link'; +import DiscordIcon from '@icons/discord.svg'; +import { Switch } from '@/components/ui/switch'; +export function NavBar() { + return ( + <div className="flex w-full justify-between py-7.5 h-[110] items-center"> + <div className="flex min-w-[267] max-w-[750] grow items-center gap-7.5"> + <Link href="/" className="shrink-0 "> + <Image + src="/icons/logo.svg" + alt="ARTBERRY" + width={46} + height={50} + className="h-[50]" + /> + </Link> + <div className="flex gap-3.75 grow items-center"> + <Search /> + <Button variant={'icon'} size={'icon'} asChild> + <ShuffleIcon width={20} height={19} strokeWidth={2} /> + </Button> + <div className="gap-[20] flex items-center shrink-0 w-[185]"> + <div className="flex h-[22] gap-2.5 opacity-100 items-center w-[66]"> + <span>AI</span> + <Switch /> + </div> + <div className="flex h-[22] gap-2.5 opacity-100 items-center w-[66]"> + <span>NSFW</span> + <Switch /> + </div> + </div> + </div> + </div> + + <Sections /> + <div className="flex gap-3.75 items-center shrink-0"> + <Link href={'#'} className="hover:[&_path]:fill-light-violet"> + <DiscordIcon /> + </Link> + <ProfileOrLogin /> + </div> + </div> + ); +} + +function Search() { + return ( + <div className="flex w-[409] h-[50] border-2 items-center rounded-4xl p-3.75"> + <div className="flex w-full gap-1.25"> + <Image + src="/icons/search.svg" + alt="" + width={24} + height={24} + className="h-[24] w-[24]" + /> + <Input + placeholder="Поиск" + className="text-light-violet placeholder:text-light-violet text-sm" + /> + </div> + </div> + ); +} + +function Sections() { + return ( + <div className="flex grow min-w-75 max-w-[450] justify-between px-7.5 font-medium"></div> + ); +} diff --git a/src/components/header/ProfileOrLogin.tsx b/src/components/header/ProfileOrLogin.tsx new file mode 100644 index 0000000..38c1eb1 --- /dev/null +++ b/src/components/header/ProfileOrLogin.tsx @@ -0,0 +1,25 @@ +'use client'; + +import Image from 'next/image'; +import { useUser } from '../../lib/contexts'; +import { Button } from '../ui'; +import Link from 'next/link'; + +export function ProfileOrLogin() { + const user = useUser(); + + if (!user) { + return <Button className="py-2.5 px-3.75">ВОЙТИ</Button>; + } + + return ( + <Link href={'/profile'}> + <Image + src={user?.avatar ?? 'icons/avatar.svg'} + alt="" + width={60} + height={60} + /> + </Link> + ); +} diff --git a/src/components/header/SubNav.tsx b/src/components/header/SubNav.tsx new file mode 100644 index 0000000..a446ff0 --- /dev/null +++ b/src/components/header/SubNav.tsx @@ -0,0 +1,43 @@ +import Link from 'next/link'; +import { Button } from '../ui'; +import React from 'react'; +import { cn } from '@/lib/utils'; + +export function SubNav() { + return ( + <div className="flex justify-between w-full"> + <SectionLink href="#" className="rounded-tl-[5px] rounded-bl-4xl"> + ТЕГИ + </SectionLink> + <SectionLink href="#" className="border-x border-light-violet"> + КАТЕГОРИИ + </SectionLink> + <SectionLink href="#" className="border-x border-light-violet"> + ПЕРСОНАЖИ + </SectionLink> + <SectionLink href="#" className="rounded-tr-[5px] rounded-br-4xl"> + КОЛЛЕКЦИИ + </SectionLink> + </div> + ); +} + +function SectionLink({ + children, + href, + className, +}: React.PropsWithChildren & { href: string; className?: string }) { + return ( + <Button + asChild + className={cn( + 'bg-violet grow py-3.5 w-full hover:bg-light-violet', + className, + )} + variant={'ghost'} + size={'text'} + > + <Link href={href}>{children}</Link> + </Button> + ); +} diff --git a/src/components/header/index.ts b/src/components/header/index.ts new file mode 100644 index 0000000..3a75160 --- /dev/null +++ b/src/components/header/index.ts @@ -0,0 +1,4 @@ +export * from './Header'; +export * from './ProfileOrLogin'; +export * from './NavBar'; +export * from './SubNav'; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..65c1e84 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +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 gap-2 whitespace-nowrap transition-colors duration-200 ease-out', + { + variants: { + variant: { + default: 'bg-violet rounded-4xl hover:bg-light-violet', + outline: '', + ghost: '', + link: 'hover:text-light-violet', + icon: 'bg-violet hover:bg-light-violet rounded-[10px] cursor-pointer', + menu: 'w-[350px] hover:bg-light-violet active:bg-violet active:border-violet rounded-4xl border-2 border-light-violet justify-start', + }, + size: { + sm: 'py-1.75 px-3.75', + default: 'py-2.5 px-7.5', + lg: 'py-2.5 px-7.5 w-full', + icon: 'size-12.5 p-3.75', + text: '', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); + +function Button({ + className, + variant = 'default', + size = 'default', + asChild = false, + ...props +}: React.ComponentProps<'button'> & + VariantProps<typeof buttonVariants> & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : 'button'; + + return ( + <Comp + data-slot="button" + data-variant={variant} + data-size={size} + className={cn(buttonVariants({ variant, size, className }))} + {...props} + /> + ); +} + +export { Button, buttonVariants }; diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts new file mode 100644 index 0000000..4d2a1a0 --- /dev/null +++ b/src/components/ui/index.ts @@ -0,0 +1,2 @@ +export * from './input'; +export * from './button'; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..0424772 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,9 @@ +import { cn } from '@/lib/utils'; +import React from 'react'; + +export function Input({ + className, + ...props +}: React.InputHTMLAttributes<HTMLInputElement>) { + return <input className={cn('outline-none', className)} {...props} />; +} diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..26fe2c3 --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,30 @@ +'use client'; + +import * as React from 'react'; +import * as SwitchPrimitive from '@radix-ui/react-switch'; + +import { cn } from '@/lib/utils'; + +function Switch({ + className, + ...props +}: React.ComponentProps<typeof SwitchPrimitive.Root>) { + return ( + <SwitchPrimitive.Root + data-slot="switch" + className={cn( + 'peer data-[state=checked]:bg-violet data-[state=unchecked]:bg-light-violet focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[18px] w-10 shrink-0 items-center rounded-full border border-transparent transition-all duration-200 ease-out outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', + className, + )} + {...props} + > + <SwitchPrimitive.Thumb + data-slot="switch-thumb" + className={cn( + 'bg-light-violet data-[state=unchecked]:bg-violet dark:data-[state=checked]:bg-light-violet pointer-events-none block size-3.5 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-[2px]', + )} + />{' '} + </SwitchPrimitive.Root> + ); +} +export { Switch }; diff --git a/src/components/upload/Dropzone.tsx b/src/components/upload/Dropzone.tsx new file mode 100644 index 0000000..6980f5c --- /dev/null +++ b/src/components/upload/Dropzone.tsx @@ -0,0 +1,45 @@ +'use client'; + +import Dropzone from 'react-dropzone'; +import UploadIcon from '@icons/upload.svg'; +import { Button } from '@/components/ui'; +import { useRef, useState } from 'react'; + +export function FileDropzone() { + // const [files, setFiles] = useState<File[]>([]); + const hiddenInputRef = useRef<HTMLInputElement | null>(null); + return ( + <Dropzone + multiple + onDrop={(acceptedFiles: File[]) => { + // setFiles((prev) => [...prev, ...acceptedFiles]); + }} + > + {({ getRootProps, getInputProps }) => ( + <div + {...getRootProps({ + className: + 'w-full h-[460px] justify-between p-7.5 border flex flex-col text-sm items-center rounded-[10px]', + })} + > + <input name="files" type="file" ref={hiddenInputRef} /> + <input {...getInputProps()} /> + + <div className="flex flex-col justify-center items-center h-full w-full gap-2.5"> + <UploadIcon /> + <span> + Выбери файлы на компьютере или перетащи сюда. JPG, + PNG до 20MB. + </span> + <span> + {hiddenInputRef.current?.files?.length ?? 0} + </span> + </div> + <Button size={'lg'} className="justify-self-end"> + Выбрать + </Button> + </div> + )} + </Dropzone> + ); +} diff --git a/src/components/upload/SourceInput.tsx b/src/components/upload/SourceInput.tsx new file mode 100644 index 0000000..c93c4b6 --- /dev/null +++ b/src/components/upload/SourceInput.tsx @@ -0,0 +1,20 @@ +'use client'; +import { Input } from '@/components/ui'; + +export function SourceInput() { + return ( + <div className="flex flex-col gap-2.5 w-full"> + <span>ИСТОЧНИК</span> + <Input + name="source" + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + } + }} + className="w-full h-10 py-2.5 px-5 border rounded-4xl" + type="text" + /> + </div> + ); +} diff --git a/src/components/upload/TagsInput.tsx b/src/components/upload/TagsInput.tsx new file mode 100644 index 0000000..1b88fa2 --- /dev/null +++ b/src/components/upload/TagsInput.tsx @@ -0,0 +1,83 @@ +'use client'; + +import React, { useState } from 'react'; +import { Tag, WithContext } from 'react-tag-input'; +import ExclamationIcon from '@icons/exclamation.svg'; +import XIcon from '@icons/x.svg'; +import { Button } from '@/components/ui'; + +export function TagsInput() { + const [tags, setTags] = useState<Tag[]>([]); + + return ( + <div className="flex flex-col w-full gap-2.5"> + <div className="flex gap-2.5 font-semibold"> + <span>ТЕГИ</span> + <span className="text-light-violet">{tags.length}/50</span> + <span className="flex gap-1.25 items-center text-light-violet"> + <ExclamationIcon /> Введите минимум 5 тегов через пробел + </span> + </div> + <div + id="tags-container" + className="w-full min-h-[100px] border p-5 rounded-4xl" + > + <input + name="tags" + className="hidden" + defaultValue={tags.map((tag) => tag.id)} + /> + <WithContext + id="tags-input" + tags={tags} + placeholder={'Введите теги'} + maxTags={50} + handleAddition={(tag) => { + setTags((prev) => { + return [...prev, tag]; + }); + }} + handleDelete={(index: number, e) => { + setTags(tags.filter((_, i) => i !== index)); + }} + handleDrag={(tag: Tag, currPos: number, newPos: number) => { + const newTags = tags.slice(); + + newTags.splice(currPos, 1); + newTags.splice(newPos, 0, tag); + + setTags(newTags); + }} + inputFieldPosition="inline" + classNames={{ + tagInputField: 'outline-none text-light-violet', + tag: 'bg-violet px-2.5 py-1.25 flex gap-1.25 rounded-[10px] w-fit items-center', + selected: 'flex gap-2.5 flex-wrap items-center', + }} + removeComponent={RemoveTag} + /> + </div> + </div> + ); +} + +class RemoveTag extends React.Component { + render(): React.ReactNode { + // @ts-expect-error ... + const { className, onRemove } = this.props; + return ( + <Button + variant={'ghost'} + size={'text'} + onClick={onRemove} + className={className} + onFocus={(e) => { + // Это говно здесь из-за того, что без него после удаления бэкспейсом, фокусится крестик + document.getElementById('tags-input')?.focus(); + }} + > + <XIcon /> + </Button> + ); + } +} diff --git a/src/components/upload/UploadMenu.tsx b/src/components/upload/UploadMenu.tsx new file mode 100644 index 0000000..22ebafd --- /dev/null +++ b/src/components/upload/UploadMenu.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { Button } from '@/components/ui'; + +export function UploadMenu() { + return ( + <div className=""> + <Button variant={'menu'}>АРТЫ</Button> + </div> + ); +} diff --git a/src/components/upload/index.ts b/src/components/upload/index.ts new file mode 100644 index 0000000..022fe11 --- /dev/null +++ b/src/components/upload/index.ts @@ -0,0 +1,4 @@ +export * from './UploadMenu'; +export * from './Dropzone'; +export * from './TagsInput'; +export * from './SourceInput'; diff --git a/src/lib/consts.ts b/src/lib/consts.ts new file mode 100644 index 0000000..7dea370 --- /dev/null +++ b/src/lib/consts.ts @@ -0,0 +1,2 @@ +export const PUBLISH_DISCLAIMER = + 'Прочитайте правила, прежде чем загружать. После публикации требуется одобрение модератора. Одобрение может занять до 14 дней, не связывайтесь с модерацией по этому поводу. Нажимая "опубликовать", вы подтверждаете, что прочитали наши правила и понимаете, что не можете удалить свою собственную загрузку, если вы не являетесь автором контента.'; diff --git a/src/lib/contexts/Auth.context.tsx b/src/lib/contexts/Auth.context.tsx new file mode 100644 index 0000000..ff2d369 --- /dev/null +++ b/src/lib/contexts/Auth.context.tsx @@ -0,0 +1,37 @@ +'use client'; + +import React, { createContext, use } from 'react'; + +type User = { + id: string; + avatar?: string; + // Чёто там ещё +}; + +interface AuthContext { + user: User | null; +} + +const AuthContext = createContext<AuthContext | null>(null); + +export const AuthContextProvider = ({ children }: React.PropsWithChildren) => { + // TODO: подключить бэк + const user = null; + return ( + <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider> + ); +}; + +export const useAuthContext = () => { + const context = use(AuthContext); + + if (!context) { + throw new Error( + 'useAuthContext must be used within AuthContextProvider', + ); + } + + return context; +}; + +export const useUser = () => useAuthContext().user; diff --git a/src/lib/contexts/Global.context.tsx b/src/lib/contexts/Global.context.tsx new file mode 100644 index 0000000..3f8b61b --- /dev/null +++ b/src/lib/contexts/Global.context.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { AuthContextProvider } from './Auth.context'; + +export const GlobalContextProvider = ({ + children, +}: React.PropsWithChildren) => { + return <AuthContextProvider>{children}</AuthContextProvider>; +}; diff --git a/src/lib/contexts/index.ts b/src/lib/contexts/index.ts new file mode 100644 index 0000000..61ebbe8 --- /dev/null +++ b/src/lib/contexts/index.ts @@ -0,0 +1,2 @@ +export * from './Auth.context'; +export * from './Global.context'; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} |
