Commit a9a4088f authored by Vũ Đình Nguyên's avatar Vũ Đình Nguyên

first commit

parent e4003351
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.cjs",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
import { defineConfig } from 'orval'
import axios from 'axios'
import fs from 'fs'
import path from 'path'
const linksPath = path.resolve(process.cwd(), 'src/links/index.ts')
const linksContent = fs.readFileSync(linksPath, 'utf8')
const backendHostMatch = linksContent.match(/const\s+backendHost\s*=\s*['"`]([^'"`]+)['"`]/)
const backendHost = backendHostMatch ? backendHostMatch[1] : ''
const siteURLMatch = linksContent.match(/siteURL\s*:\s*['"`]([^'"`]+)['"`]/)
const siteURL = siteURLMatch ? siteURLMatch[1] : ''
const imageEndpointMatch = linksContent.match(/imageEndpoint\s*:\s*`([^`]*)`/)
let imageEndpoint = ''
if (imageEndpointMatch) {
imageEndpoint = imageEndpointMatch[1].replace(/\${\s*backendHost\s*}/g, backendHost)
} else {
const simpleMatch = linksContent.match(/imageEndpoint\s*:\s*['"`]([^'"`]+)['"`]/)
imageEndpoint = simpleMatch ? simpleMatch[1] : ''
}
const orvalConfig = async () => {
const { data: swagger } = await axios.get(`${imageEndpoint}/swagger/swagger-output.json`, {
headers: { Origin: siteURL }
})
return defineConfig({
'saigon-business': {
output: {
mode: 'tags',
target: 'src/api/endpoints/index.ts',
schemas: 'src/api/models',
client: 'react-query',
prettier: true,
override: {
query: {
useQuery: true,
useInfinite: true,
usePrefetch: true
// useSuspenseQuery: true
},
mutator: {
path: './src/api/mutator/custom-client.ts',
name: 'useCustomClient'
}
}
},
input: {
target: swagger,
filters: {
tags: [
'Auth',
'WebsiteConfig',
'Event',
'Files',
'Footer',
'Order',
'OrganizationCategory',
'Organizations',
'PageConfig',
'Permisions',
'Products',
'Schedule',
'Status',
'Users',
'Validator',
'Contact',
'Statistic',
'Notification',
'MembershipFee',
'PermisionFunction',
'Department',
'UserDepartment',
'UserHistory',
'Approvals',
'News',
'Category'
]
}
}
}
})
}
export default orvalConfig
{
"name": "vcci-fontend-v2",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --webpack",
"build": "next build --webpack",
"start": "next start",
"lint": "eslint",
"generate:api": "orval -c orval.config.ts"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@tanstack/react-query": "^5.90.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.548.0",
"next": "16.0.1",
"next-nprogress-bar": "^2.4.7",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-hook-form": "^7.65.0",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^4.1.12",
"zustand": "^5.0.8"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"axios": "^1.13.1",
"eslint": "^9",
"eslint-config-next": "16.0.1",
"orval": "8.0.0-rc.0",
"tailwindcss": "^4",
"ts-node": "^10.9.2",
"typescript": "^5"
}
}
This diff is collapsed.
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
\ No newline at end of file
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
\ No newline at end of file
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
\ No newline at end of file
// Core
import { AxiosError, isAxiosError } from 'axios'
import { QueryClient } from '@tanstack/react-query'
// App
// import router from '@/router'
import useAuthStore from '@/store/useAuthStore'
// import useProfileStore from '@stores/profile'
import { QueryData } from '@/lib/types/base-api'
// import { BASE_PATHS } from '@/constants/path'
// Constants
const RETRY_COUNT = 3
const EXPIRED_TOKEN_ERROR = 401
const DENIED_PERMISSION_ERROR = 403
const INTERNAL_SERVER_ERROR = 500
// Utils
// Handle check base retry logical
const handleCheckBaseRetryLogical = (failureCount: number, error: Error) => {
// Check retry count and is axios error
if (failureCount > RETRY_COUNT || !isAxiosError<QueryData>(error)) {
return false
}
// Expired token error
if (error.response?.status === EXPIRED_TOKEN_ERROR) {
handleUnAuthorizationError()
return false
}
// Denied permission error
if (error.response?.status === DENIED_PERMISSION_ERROR) {
// router.navigate('/')
window.location.href = process ? '/' : '/admin'
return false
}
return true
}
// Handle un authorization error
const handleUnAuthorizationError = () => {
useAuthStore.getState().resetStore()
// useProfileStore.getState().resetStore()
// const languageAwarePath = addLanguageToPath({
// path: BASE_PATHS.authSignIn
// })
// router.navigate('')
window.location.href = process ? '/' : '/admin'
}
// Handle delay value
const handleDelayRetry = (failureCount: number) => failureCount * 1000 + Math.random() * 1000
// Query client
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
placeholderData: (previousData: unknown) => previousData,
retry(failureCount, error) {
if (!handleCheckBaseRetryLogical(failureCount, error)) return false
return true
},
retryDelay: handleDelayRetry
},
mutations: {
retry: (failureCount, error) => {
if (!handleCheckBaseRetryLogical(failureCount, error)) {
return false
}
if ((error as AxiosError<QueryData>).response?.status === INTERNAL_SERVER_ERROR) {
return true
}
return false
},
retryDelay: handleDelayRetry
}
}
})
export default queryClient
import Axios, { AxiosError, AxiosRequestConfig } from 'axios'
import config from '@/links/index'
import useAuthStore from '@/store/useAuthStore'
const { siteURL } = config
const AXIOS_INSTANCE = Axios.create({ baseURL: config.apiEndpoint })
AXIOS_INSTANCE.interceptors.request.use(async (config) => {
config.headers.Authorization = `Bearer ${useAuthStore.getState().appAccessToken ?? ''}`
if (typeof window !== 'undefined') {
config.headers['IsAdmin'] = window.location.pathname.includes('/admin/')
} else {
config.headers['Origin'] = siteURL
}
if (config.method === 'post' && config.url?.includes('/exportJoinedOrgs')) {
config.responseType = 'blob'
config.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
}
return config
})
AXIOS_INSTANCE.interceptors.response.use(
async (response) => response,
(error) => Promise.reject(error)
)
const useCustomClient = <T>(config: AxiosRequestConfig, options?: AxiosRequestConfig): Promise<T> => {
const source = Axios.CancelToken.source()
const promise = AXIOS_INSTANCE({
...config,
...options,
cancelToken: source.token
}).then(({ data, status }) => {
return data instanceof Blob ? data : { ...data, statusCode: status }
})
// @ts-expect-error not exist cancel
promise.cancel = () => {
source.cancel('Query was cancelled')
}
return promise
}
export { useCustomClient }
// In some case with react-query and swr you want to be able to override the return error type so you can also do it here like this
export type ErrorType<Error> = AxiosError<Error>
export type BodyType<BodyData> = BodyData
// Or, in case you want to wrap the body type (optional)
// (if the custom instance is processing data before sending it, like changing the case for example)
// export type BodyType<BodyData> = CamelCase<BodyData>;
import { LayoutProps } from '@/lib/types/layout'
import { ReactQueryProvider } from './react-query'
// import { AOSProvider } from './aos'
import ProgressBarProvider from './progress-bar'
export function Providers({ children }: LayoutProps) {
return (
<ReactQueryProvider>
<ProgressBarProvider>{children}</ProgressBarProvider>
</ReactQueryProvider>
)
}
'use client'
import { AppProgressBar as ProgressBar } from 'next-nprogress-bar'
import { Fragment, startTransition, useEffect, useState } from 'react'
import { cssVar } from '@/lib/utils/css-var'
export const ProgressBarProvider = ({ children }: { children: React.ReactNode }) => {
const [isClient, setIsClient] = useState(false)
useEffect(() => startTransition(() => setIsClient(true)), [])
if (!isClient) return children
return (
<Fragment>
{children}
<ProgressBar
height='4px'
color={`hsl(${cssVar('--secondary')})`}
options={{ showSpinner: false }}
shallowRouting
/>
</Fragment>
)
}
export default ProgressBarProvider
'use client'
import { LayoutProps } from '@/lib/types/layout'
import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import queryClient from '@/api/config/query-client'
let browserQueryClient: QueryClient | undefined = undefined
function getQueryClient() {
// Server: always make a new query client
if (isServer) return queryClient
// Browser: make a new query client if we don't already have one
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = queryClient
return browserQueryClient
}
export default ReactQueryProvider
export function ReactQueryProvider({ children }: LayoutProps) {
// NOTE: Avoid useState when initializing the query client if you don't
// have a suspense boundary between this and the code that may
// suspend because React will throw away the client on the initial
// render if it suspends and there is no boundary
return <QueryClientProvider client={getQueryClient()}>{children}</QueryClientProvider>
}
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import './styles.css'
import{Providers} from './_providers'
import React from 'react'
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Providers>{children}</Providers>
</body>
</html>
);
}
import Image from 'next/image'
import { Button, Card } from '@/components/ui'
export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image className="dark:invert" src="/next.svg" alt="Next.js logo" width={100} height={20} priority />
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-primary dark:text-zinc-50">
To get started, edit the page.tsx file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{' '}
<a href="https://vercel.com/templates?framework=next.js" className="font-medium text-zinc-950 dark:text-zinc-50">
Templates
</a>{' '}
or the{' '}
<a href="https://nextjs.org/learn?utm_source=create-next-app" className="font-medium text-zinc-950 dark:text-zinc-50">
Learning
</a>{' '}
center.
</p>
</div>
<div className="flex w-full gap-4 text-base font-medium sm:flex-row">
<Card className="flex-1 md:w-[158px]">
<a href="https://vercel.com/new?utm_source=create-next-app" target="_blank" rel="noopener noreferrer">
<Button className="w-full" variant="secondary">
<Image className="dark:invert" src="/vercel.svg" alt="Vercel logomark" width={16} height={16} />
<span className="ml-2">Deploy Now</span>
</Button>
</a>
</Card>
<Card className="flex-1 md:w-[158px]">
<a href="https://nextjs.org/docs?utm_source=create-next-app" target="_blank" rel="noopener noreferrer">
<Button className="w-full" variant="outline">
Documentation
</Button>
</a>
</Card>
</div>
</main>
</div>
)
}
@import '../styles/index.css';
/* shared styles are in `src/styles/index.css` */
:root {
--primary-color: #0043ce;
--secondary-color: #193c3e;
--background-color: #f7f7f7;
--text-color: #333333;
--border-color: #eeeeee;
/* Tiptap text colors */
--tiptap-stone-foreground: oklch(0.27 0.01 34);
--tiptap-zinc-foreground: oklch(0.27 0.01 286);
--tiptap-slate-foreground: oklch(0.28 0.04 260);
--tiptap-red-foreground: oklch(0.44 0.16 27);
--tiptap-orange-foreground: oklch(0.47 0.14 37);
--tiptap-amber-foreground: oklch(0.47 0.12 46);
--tiptap-yellow-foreground: oklch(0.48 0.1 62);
--tiptap-lime-foreground: oklch(0.45 0.11 131);
--tiptap-green-foreground: oklch(0.45 0.11 151);
--tiptap-emerald-foreground: oklch(0.43 0.09 167);
--tiptap-teal-foreground: oklch(0.44 0.07 188);
--tiptap-cyan-foreground: oklch(0.45 0.08 224);
--tiptap-sky-foreground: oklch(0.44 0.1 241);
--tiptap-blue-foreground: oklch(0.42 0.18 266);
--tiptap-indigo-foreground: oklch(0.4 0.18 277);
--tiptap-violet-foreground: oklch(0.43 0.21 293);
--tiptap-purple-foreground: oklch(0.44 0.2 304);
--tiptap-fuchsia-foreground: oklch(0.45 0.19 325);
--tiptap-pink-foreground: oklch(0.46 0.17 4);
--tiptap-rose-foreground: oklch(0.45 0.17 14);
--tiptap-stone: oklch(0.72 0.01 56);
--tiptap-zinc: oklch(0.71 0.01 286);
--tiptap-slate: oklch(0.71 0.04 257);
--tiptap-red: oklch(0.71 0.17 22);
--tiptap-orange: oklch(0.76 0.16 56);
--tiptap-amber: oklch(0.84 0.16 84);
--tiptap-yellow: oklch(0.86 0.17 92);
--tiptap-lime: oklch(0.85 0.21 129);
--tiptap-green: oklch(0.8 0.18 152);
--tiptap-emerald: oklch(0.77 0.15 163);
--tiptap-teal: oklch(0.78 0.13 182);
--tiptap-cyan: oklch(0.8 0.13 212);
--tiptap-sky: oklch(0.75 0.14 233);
--tiptap-blue: oklch(0.71 0.14 255);
--tiptap-indigo: oklch(0.68 0.16 277);
--tiptap-violet: oklch(0.71 0.16 294);
--tiptap-purple: oklch(0.72 0.18 306);
--tiptap-fuchsia: oklch(0.75 0.21 322);
--tiptap-pink: oklch(0.73 0.18 350);
--tiptap-rose: oklch(0.72 0.17 13);
}
@layer components {
.tiptap {
@apply border-gray-200;
mark {
@apply rounded-md p-1;
}
a {
@apply cursor-pointer text-blue-600 underline-offset-4 hover:underline;
}
blockquote {
@apply mx-2 border-l-4 pl-2;
}
ul,
ol {
@apply my-1 ms-4 me-0.5 py-1;
li p {
@apply mt-0.5 mb-0.5;
}
}
ul {
@apply list-disc;
}
ol {
@apply list-decimal;
}
ul[data-type='taskList'] {
@apply ml-0 list-none p-0;
li {
@apply flex items-start gap-2;
label {
@apply pt-0.5 select-none;
}
div {
@apply flex-1;
}
p {
@apply m-0;
}
&[data-checked='true'] {
p {
@apply line-through opacity-40;
}
}
}
input[type='checkbox'] {
@apply cursor-pointer;
}
ul[data-type='taskList'] {
@apply m-0;
}
}
table {
@apply m-0 w-full table-fixed border-collapse overflow-hidden;
th,
td {
@apply relative box-border min-w-1 border p-4 align-top font-normal;
}
th {
@apply bg-primary text-primary-foreground whitespace-nowrap;
}
.selectedCell:after {
@apply pointer-events-none absolute top-0 right-0 bottom-0 left-0 bg-gray-200/40 content-[''];
}
.column-resize-handle {
@apply bg-secondary pointer-events-none absolute top-0 -right-0.5 -bottom-0.5 w-1;
}
}
.tableWrapper {
@apply m-0 overflow-x-auto;
}
&.resize-cursor {
@apply cursor-col-resize;
}
/* Placeholder /
p.is-editor-empty:first-child::before {
@apply text-muted-foreground pointer-events-none content-[attr(data-placeholder)];
}
/ Image */
img {
@apply rounded-md;
&.ProseMirror-selectednode {
@apply outline-2 outline-offset-2;
}
}
}
}
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
import React from 'react'
import cn from './utils'
type BadgeProps = React.HTMLAttributes<HTMLSpanElement> & {
variant?: 'default' | 'secondary'
}
export function Badge({ className, variant = 'default', ...props }: BadgeProps) {
const variantClass = variant === 'secondary' ? 'badge-secondary' : 'badge'
return <span className={cn(variantClass, className)} {...props} />
}
export default Badge
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 rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
import React from 'react'
import cn from './utils'
type CardProps = React.HTMLAttributes<HTMLDivElement>
export function Card({ className, children, ...props }: CardProps) {
return (
<div className={cn('card', className)} {...props}>
{children}
</div>
)
}
export default Card
"use client"
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<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("grid place-content-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
FormProvider,
useFormContext,
type ControllerProps,
type FieldPath,
type FieldValues,
} 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<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue | null>(null)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
if (!itemContext) {
throw new Error("useFormField should be used within <FormItem>")
}
const fieldState = getFieldState(fieldContext.name, formState)
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<FormItemContextValue | null>(null)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? "") : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
import * as React from 'react'
import { Check, Sun, Moon } from 'lucide-react'
export const Icons = {
Check,
Sun,
Moon,
}
export default Icons
export { Button } from './button'
export { Input } from './input'
export { Label } from './label'
export { Badge } from './badge'
export { Card } from './card'
export { Icons } from './icons'
export { cn } from './utils'
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }
export declare function cn(...inputs: Array<string | false | null | undefined>): string;
export default cn;
export function cn(...inputs: Array<string | false | null | undefined>) {
return inputs.filter(Boolean).join(' ')
}
export default cn
// Query data
export interface QueryData<Data = unknown> {
message: string | null
responseData: Data
// data: Data
// success: boolean
status: 'success' | 'fail'
}
export type QueryParams = {
filters?: string
sortField?: string
sortOrder?: 'ASC' | 'DESC' | 'asc' | 'desc'
currentPage?: number
pageSize?: number
[key: string]: string | number | undefined
}
// Pagination query data
export type PaginationQueryData<Row = unknown> = QueryData<{
count: number
currentPage: number | string
totalPages: number
rows: Row[]
}>
// Mutation data
export interface MutationData<Data = unknown> {
message: string | null
message_en: string | null
responseData: Data
status: 'fail' | 'success'
timeStamp: string
violations: Array<{
key: string
code: 'DUPLICATE' | string
message: string
}>
}
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }
export type GoogleRedirectResponse<Flow = AuthorizationCodeFlow | ImplicitGrantFlow> =
| GoogleResponseSuccess<Flow>
| GoogleResponseError
export type AuthorizationCodeFlow = { code: string }
export type ImplicitGrantFlow = { access_token: string; token_type: string; expires_in: string }
export type GoogleResponseSuccess<Flow = AuthorizationCodeFlow | ImplicitGrantFlow> = {
state: string
scope: string
authuser: string
prompt: string
} & Flow
export type GoogleResponseError = { error: string }
export type GoogleRedirectResponseQueryKeys =
| keyof GoogleResponseSuccess<AuthorizationCodeFlow>
| keyof GoogleResponseSuccess<ImplicitGrantFlow>
| keyof GoogleResponseError
export interface GenerateAuthUrlOpts {
/**
* Recommended. Indicates whether your application can refresh access tokens
* when the user is not present at the browser. Valid parameter values are
* 'online', which is the default value, and 'offline'. Set the value to
* 'offline' if your application needs to refresh access tokens when the user
* is not present at the browser. This value instructs the Google
* authorization server to return a refresh token and an access token the
* first time that your application exchanges an authorization code for
* tokens.
*/
access_type?: string
/**
* The hd (hosted domain) parameter streamlines the login process for G Suite
* hosted accounts. By including the domain of the G Suite user (for example,
* mycollege.edu), you can indicate that the account selection UI should be
* optimized for accounts at that domain. To optimize for G Suite accounts
* generally instead of just one domain, use an asterisk: hd=*.
* Don't rely on this UI optimization to control who can access your app,
* as client-side requests can be modified. Be sure to validate that the
* returned ID token has an hd claim value that matches what you expect
* (e.g. mycolledge.edu). Unlike the request parameter, the ID token claim is
* contained within a security token from Google, so the value can be trusted.
*/
hd?: string
/**
* The 'response_type' will always be set to 'CODE'.
*/
response_type?: string
/**
* The client ID for your application. The value passed into the constructor
* will be used if not provided. You can find this value in the API Console.
*/
client_id?: string
/**
* Determines where the API server redirects the user after the user
* completes the authorization flow. The value must exactly match one of the
* 'redirect_uri' values listed for your project in the API Console. Note that
* the http or https scheme, case, and trailing slash ('/') must all match.
* The value passed into the constructor will be used if not provided.
*/
redirect_uri?: string
/**
* Required. A space-delimited list of scopes that identify the resources that
* your application could access on the user's behalf. These values inform the
* consent screen that Google displays to the user. Scopes enable your
* application to only request access to the resources that it needs while
* also enabling users to control the amount of access that they grant to your
* application. Thus, there is an inverse relationship between the number of
* scopes requested and the likelihood of obtaining user consent. The
* OAuth 2.0 API Scopes document provides a full list of scopes that you might
* use to access Google APIs. We recommend that your application request
* access to authorization scopes in context whenever possible. By requesting
* access to user data in context, via incremental authorization, you help
* users to more easily understand why your application needs the access it is
* requesting.
*/
scope?: string[] | string
/**
* Recommended. Specifies any string value that your application uses to
* maintain state between your authorization request and the authorization
* server's response. The server returns the exact value that you send as a
* name=value pair in the hash (#) fragment of the 'redirect_uri' after the
* user consents to or denies your application's access request. You can use
* this parameter for several purposes, such as directing the user to the
* correct resource in your application, sending nonces, and mitigating
* cross-site request forgery. Since your redirect_uri can be guessed, using a
* state value can increase your assurance that an incoming connection is the
* result of an authentication request. If you generate a random string or
* encode the hash of a cookie or another value that captures the client's
* state, you can validate the response to additionally ensure that the
* request and response originated in the same browser, providing protection
* against attacks such as cross-site request forgery. See the OpenID Connect
* documentation for an example of how to create and confirm a state token.
*/
state?: string
/**
* Optional. Enables applications to use incremental authorization to request
* access to additional scopes in context. If you set this parameter's value
* to true and the authorization request is granted, then the new access token
* will also cover any scopes to which the user previously granted the
* application access. See the incremental authorization section for examples.
*/
include_granted_scopes?: boolean
/**
* Optional. If your application knows which user is trying to authenticate,
* it can use this parameter to provide a hint to the Google Authentication
* Server. The server uses the hint to simplify the login flow either by
* prefilling the email field in the sign-in form or by selecting the
* appropriate multi-login session. Set the parameter value to an email
* address or sub identifier, which is equivalent to the user's Google ID.
*/
login_hint?: string
/**
* Optional. A space-delimited, case-sensitive list of prompts to present the
* user. If you don't specify this parameter, the user will be prompted only
* the first time your app requests access. Possible values are:
*
* 'none' - Donot display any authentication or consent screens. Must not be
* specified with other values.
* 'consent' - Prompt the user for consent.
* 'select_account' - Prompt the user to select an account.
*/
prompt?: string
}
export type LayoutProps = Readonly<{
children: React.ReactNode
}>
export interface SearchParams<Key extends string = string> extends URLSearchParams {
get: (name: Key) => string | null
}
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// Core
import { twMerge } from 'tailwind-merge'
import { clsx, type ClassValue } from 'clsx'
// Cn
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
import { Buffer } from 'buffer'
export async function encrypt(dataToEncrypt: string, keyString: string, bufferString: string) {
const ivBuffer = Buffer.from(bufferString, 'base64')
const key = await getKey(keyString)
const dataBuffer = new TextEncoder().encode(dataToEncrypt)
const encryptedData = await encryptData(dataBuffer, key, ivBuffer)
return Buffer.from(encryptedData).toString('base64')
}
export async function decrypt(encryptedDataString: string, keyString: string, bufferString: string) {
const key = await getKey(keyString)
const encryptedBuffer = Buffer.from(encryptedDataString, 'base64')
const ivBuffer = Buffer.from(bufferString, 'base64')
const decryptedBuffer = await decryptData(encryptedBuffer, key, ivBuffer)
return new TextDecoder().decode(decryptedBuffer)
}
// ======================================= HELPER FUNCTIONS ================================
const getKey = async (encryptionKey: string) => {
return await crypto.subtle.importKey(
'raw',
Buffer.from(encryptionKey),
{
name: 'AES-GCM',
length: 256
},
true,
['encrypt', 'decrypt']
)
}
const encryptData = async (dataBuffer: BufferSource, key: CryptoKey, ivBuffer: BufferSource) => {
return await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: ivBuffer
},
key,
dataBuffer
)
}
const decryptData = async (encryptedDataBuffer: BufferSource, key: CryptoKey, ivBuffer: BufferSource) => {
return await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: ivBuffer
},
key,
encryptedDataBuffer
)
}
export const cssVar = (name: string) => {
return getComputedStyle(document.documentElement).getPropertyValue(name)
}
// import { z } from 'zod'
// const envConfigSchema = z.object({
// BUFFER_KEY: z.string().min(1),
// SECRET_KEY: z.string().min(1)
// })
// Process env is for nextjs, import meta for vite
// const envConfigParser = envConfigSchema.safeParse({
// BUFFER_KEY: typeof process != 'undefined' ? process.env.FOSCO_BUFFER : import.meta.env.FOSCO_BUFFER,
// SECRET_KEY: typeof process != 'undefined' ? process.env.FOSCO_SECRET : import.meta.env.FOSCO_SECRET
// })
// if (!envConfigParser.success) {
// console.error(envConfigParser.error.issues)
// throw new Error('Invalid .env variable values')
// }
// export const envConfig = envConfigParser.data
// export default envConfig
// Core
// import { isAxiosError } from 'axios'
// App
// import i18n, { Language } from './i18n'
// Utils
// Extract error message
// export const extractErrorMessage = (error: unknown) => {
// const axiosErrorResponse = isAxiosError(error) ? error.response : null
// const errorMessage =
// i18n.language === Language.Vi
// ? (axiosErrorResponse?.data.message ?? 'Có lỗi xảy ra, vui lòng thử lại sau')
// : (axiosErrorResponse?.data.message_en ?? 'An error occurred, please try again later')
// return errorMessage
// }
// import slugify from 'slugify'
// export const generateSlug = (text: string) => {
// return slugify(text, {
// replacement: '-',
// lower: true,
// locale: 'vi',
// trim: true,
// strict: true
// })
// }
// // Core
// import { decrypt, encrypt } from './crypto'
// import { PersistStorage, StorageValue } from 'zustand/middleware'
// import { getCookie, removeCookie, setCookie } from 'typescript-cookie'
// // App
// import envConfig from './env-config'
// export async function getItem(name: string) {
// const cipherText = getCookie(name)
// // const cipherText = localStorage.getItem(name)
// if (!cipherText) return null
// const originalText = await decrypt(cipherText, envConfig.SECRET_KEY, envConfig.BUFFER_KEY)
// if (!originalText) return null
// return JSON.parse(originalText)
// }
// export async function setItem<S>(name: string, value: StorageValue<S>) {
// const cipherText = await encrypt(JSON.stringify(value), envConfig.SECRET_KEY, envConfig.BUFFER_KEY)
// setCookie(name, cipherText)
// localStorage.setItem(name, cipherText)
// }
// export async function removeItem(name: string) {
// removeCookie(name)
// localStorage.removeItem(name)
// }
// // Utils
// // Create storage
// export const createStorage = <S>(): PersistStorage<S> => {
// return {
// getItem,
// setItem,
// removeItem
// }
// }
declare const links: {
analyticsGoogle: string
apiEndpoint: string
imageEndpoint: string
backendHost: string
}
export default links
const backendHost = 'gateway.dev.meu-solutions.com'
const links = {
analyticsGoogle: 'G-C9TEK9BS4C',
// apiEndpoint: "http://localhost:3000/api/v1.0",
apiEndpoint: `https://${backendHost}/vcci/api/v1.0`,
// imageEndpoint: 'https://utc2.erp.meu-solutions.com',
imageEndpoint: `https://${backendHost}/vcci`,
backendHost,
siteURL: 'https://vcci.erp.meu-solutions.com/',
}
export default links
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface AuthStoreStateType {
// States
appIsLoggedIn: boolean
appAccessToken: string | null
appAccessTokenExpired: number | null
appRefreshToken: string | null
appUserRemember: {
username: string
password: string
remember: boolean
} | null
_hasHydrated: boolean
// Actions
setHasHydrated: (state: AuthStoreStateType) => void
setAppIsLoggedIn: (isLoggedIn: boolean) => void
setAppToken: (accessToken: string, accessTokenExpired: number, refreshToken?: string) => void
removeAppToken: () => void
setAppUserRemember: (username: string, password: string, remember: boolean) => void
resetStore: () => void
}
// Define store
const useAuthStore = create<AuthStoreStateType>()(
devtools(
persist(
(set, get) => ({
// States
appIsLoggedIn: false,
appAccessToken: null,
appAccessTokenExpired: null,
appRefreshToken: null,
appUserRemember: null,
// Methods
setAppIsLoggedIn: (isLoggedIn: boolean) => set(() => ({ appIsLoggedIn: isLoggedIn })),
setAppToken: (accessToken: string, accessTokenExpired: number, refreshToken?: string) =>
set(() => ({
appAccessToken: accessToken,
appAccessTokenExpired: Date.now() + (accessTokenExpired / 60 - 5) * 60 * 1000,
appRefreshToken: refreshToken ?? get().appRefreshToken
})),
removeAppToken: () => {
set(() => ({
appAccessToken: null,
appAccessTokenExpired: null,
appRefreshToken: null
}))
},
setAppUserRemember: (username, password, remember) =>
set(() => ({
appUserRemember: {
username,
password,
remember
}
})),
resetStore: () => {
// Clear in-memory state
set(() => ({
appIsLoggedIn: false,
appAccessToken: null,
appAccessTokenExpired: null,
appRefreshToken: null,
appUserRemember: null,
_hasHydrated: false
}))
// Remove persisted storage
try {
localStorage.removeItem('app-auth-storage')
} catch {
// ignore
}
},
_hasHydrated: false,
setHasHydrated: (state: AuthStoreStateType) =>
set(() => ({
_hasHydrated: state != undefined
}))
}),
{
name: 'app-auth-storage',
onRehydrateStorage: () => {
return (state: AuthStoreStateType | undefined, error: unknown) => {
if (error || state == undefined) return
state.setHasHydrated(state)
return
}
}
}
)
)
)
export default useAuthStore
@import 'tailwindcss/preflight' layer(base);
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
/* COLOR */
:root {
/* Default background color of <body />...etc */
--background: 210 20% 98%;
--foreground: 0 0% 11%;
--primary-orange: 36 100% 55%;
/* Background color for <Card /> */
--card: 0 0% 98%;
--card-foreground: 0 0% 14%;
/* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */
--popover: 0, 0%, 98%;
--popover-foreground: 0 0% 15%;
/* Primary colors for <Button /> */
--primary: 155 100% 12%;
--primary-foreground: 156 100% 99%;
--primary-orange: 36 100% 55%;
/* Secondary colors for <Button /> */
--secondary: 175 36% 88%;
--secondary-foreground: 158 83% 14%;
/* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */
--muted: 0 0% 91%;
--muted-foreground: 156 2% 53%;
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 175 32% 92%;
--accent-foreground: 159 83% 7%;
/* Used for destructive actions such as <Button variant="destructive"> */
--destructive: 357 76% 49%;
--destructive-foreground: 0 0% 100%;
/* Used for success actions such as <Button variant="success"> */
--success: 138 75% 22%;
--success-foreground: 136 100% 95%;
/* Used for info actions such as <Button variant="info"> */
--info: 220 68% 52%;
--info-foreground: 217 100% 91%;
/* Used for warning actions such as <Button variant="warning"> */
--warning: 25 100% 58%;
--warning-foreground: 46 92% 95%;
/* Default border color */
--border: 0 0% 90%;
/* Border color for inputs such as <Input />, <Select />, <Textarea /> */
--input: 214, 30%, 91%;
/* Used for focus ring */
--ring: 158 83% 14%;
/* Border radius for card, input and buttons */
--radius: 0.5rem;
/* Charts */
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
/* Sidebar */
--sidebar-background: 210 20% 98%;
--sidebar-foreground: 0 0% 11%;
--sidebar-primary: 158 83% 14%;
--sidebar-primary-foreground: 156 100% 99%;
--sidebar-accent: 175 32% 92%;
--sidebar-accent-foreground: 159 83% 7%;
--sidebar-border: 0 0% 89.8%;
--sidebar-ring: 158 83% 14%;
/* Header */
--header-primary: 158 100% 12%;
}
.dark {
/* Default background color of <body />...etc */
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
/* Background color for <Card /> */
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
/* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
/* Primary colors for <Button /> */
--primary: 0 0% 98%;
--primary-foreground: 227 53% 28%;
--primary-orange: 36 100% 55%;
/* USER DEFINED */
/* Secondary colors for <Button /> */
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
/* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
/* Used for destructive actions such as <Button variant="destructive"> */
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
/* Used for success actions */
--success: 142.1 76.2% 36.3%;
--success-foreground: 210 40% 98%;
/* Used for warning actions */
--warning: 48 96% 89%;
--warning-foreground: 38 92% 50%;
/* Default border color */
--border: 0 0% 14.9%;
/* Border color for inputs such as <Input />, <Select />, <Textarea /> */
--input: 0 0% 14.9%;
/* Used for focus ring */
--ring: 0 0% 83.1%;
/* Charts */
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
/* Sidebar */
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
/* Header */
--header-primary: 162, 15%, 17%;
}
}
@layer components {
/* Header */
.header-base {
@apply z-20;
}
.header-sticky {
@apply sticky top-0;
}
.header-fixed {
@apply fixed top-0 right-0 left-0;
}
/* Scrollbar */
.scrollbar {
@apply [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-400/60 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-400 [&::-webkit-scrollbar-track]:bg-transparent;
}
.list-screen-filter-container {
@apply flex flex-col items-stretch justify-between gap-6 xl:flex-row xl:items-start;
}
/* Typography */
.typography-h1 {
@apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
}
.typography-h2 {
@apply scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0;
}
.typography-h3 {
@apply scroll-m-20 text-2xl font-semibold tracking-tight;
}
.typography-h4 {
@apply scroll-m-20 text-xl font-semibold tracking-tight;
}
.typography-p {
@apply leading-7 [&:not(:first-child)]:mt-6;
}
.typography-table {
@apply my-6 w-full overflow-y-auto;
table {
@apply w-full;
tr {
@apply even:bg-muted m-0 border-t p-0;
}
th,
td {
@apply border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right;
}
th {
@apply font-bold;
}
}
}
.typography-blockquote {
@apply mt-6 border-l-2 pl-6 italic;
}
.typography-list {
@apply my-6 ml-6 list-disc [&>li]:mt-2;
}
.typography-inline-code {
@apply bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold;
}
.typography-lead {
@apply text-muted-foreground text-xl;
}
.typography-large {
@apply text-lg font-semibold;
}
.typography-small {
@apply text-sm leading-none font-medium;
}
.typography-muted {
@apply text-muted-foreground text-sm;
}
}
@import 'tailwindcss/theme' layer(theme);
@theme inline {
/* Fonts */
--font-reddit-sans: RedditSans, sans-serif;
--default-font-family: 'SF Pro Display', -apple-system;
/* Border Radius */
--radius-lg: hsl(var(--radius));
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
/* Colors */
/* Containers */
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
/* Cards */
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
/* Popovers */
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
/* Primaries */
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
/* Secondaries */
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
/* Muted */
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
/* Accent */
--color-accent: hsl(var(--accent));
--color-accent-secondary: hsl(var(--accent-secondary));
--color-accent-table-header: hsl(var(--accent-table-header));
--color-accent-table-header-foreground: hsl(var(--accent-table-header-foreground));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-accent-information: hsl(var(--accent-information));
/* Destructive */
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
/* Success */
--color-success: hsl(var(--success));
--color-success-foreground: hsl(var(--success-foreground));
/* Border */
--color-border: hsl(var(--border));
/* Input */
--color-input: hsl(var(--input));
/* Ring */
--color-ring: hsl(var(--ring));
/* Chart */
--color-chart-1: hsl(var(--chart-1));
--color-chart-2: hsl(var(--chart-2));
--color-chart-3: hsl(var(--chart-3));
--color-chart-4: hsl(var(--chart-4));
--color-chart-5: hsl(var(--chart-5));
/* Sidebar */
--color-sidebar: hsl(var(--sidebar-background));
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
--color-side-bar-primary: hsl(var(--sidebar-primary));
--color-side-bar-primary-foreground: hslvar(--sidebar-primary-foreground);
--color-side-bar-accent: hsl(var(--sidebar-accent));
--color-side-bar-accent-foreground: hslvar(--sidebar-accent-foreground);
--color-side-bar-border: hsl(var(--sidebar-border));
--color-side-bar-ring: hsl(var(--sidebar-ring));
/* Header */
--color-header-primary: hsl(var(--header-primary));
}
@layer utilities {
.container {
@apply mr-auto ml-auto px-4;
@media (width >= 80rem) {
@apply px-5;
}
}
.field-size-content {
field-sizing: 'content';
}
.sun-editor-editable {
font-family: 'Be Vietnam Pro', sans-serif;
font-size: 16px;
background-color: transparent;
}
.sun-editor-editable img {
height: auto !important;
}
.sun-editor-editable .se-image-container figure {
width: 100% !important;
display: flex;
justify-content: center;
}
.sun-editor-editable th,
.sun-editor-editable td {
padding: 6px !important;
}
}
@import 'tailwindcss';
@custom-variant dark (&:where(.dark, .dark *));
/* Configs */
@import './_base.css';
@import './_themes.css';
@import './_components.css';
@import './_utilities.css';
/* Plugins */
@plugin "tailwindcss-animate";
/* Source */
@source "../../modules/ui";
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment