This commit is contained in:
monoid 2024-04-20 19:58:18 +09:00
parent 50a62794ed
commit 550865f643
31 changed files with 3335 additions and 272 deletions

2
.gitignore vendored
View File

@ -34,3 +34,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
order.sqlite3

View File

@ -1,5 +1,3 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
@ -28,9 +26,3 @@ To learn more about Next.js, take a look at the following resources:
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -1,33 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
}

View File

@ -1,8 +1,12 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { Inter, Noto_Sans_KR } from "next/font/google";
import "./globals.css";
import { cn } from "@/lib/utils";
const inter = Inter({ subsets: ["latin"] });
const NotoSansKR = Noto_Sans_KR({
subsets: ["latin"],
variable: "--font-sans",
});
export const metadata: Metadata = {
title: "Create Next App",
@ -15,8 +19,9 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<html lang="ko">
<head />
<body className={cn("min-h-dvh bg-background font-sans antialiased", NotoSansKR.variable)}>{children}</body>
</html>
);
}

31
app/order/action.ts Normal file
View File

@ -0,0 +1,31 @@
"use server";
import { Order } from "@/hooks/useOrder";
import { saveOrder, Payment, cancelOrder, completeOrder } from "@/lib/db";
import { revalidatePath } from "next/cache";
export async function saveOrderApi(orders: Order[], payment: Payment){
console.log(orders, "주문");
// db에 주문 저장
const id = await saveOrder(orders, payment);
// cache revaildation
revalidatePath("/", "page");
return {
id,
completed : false,
orders: orders,
};
}
export async function cancelOrderApi(uid: string){
await cancelOrder(uid);
revalidatePath("/", "page");
}
export async function completeOrderApi(uid: string, completed: boolean = true){
await completeOrder(uid, completed);
revalidatePath("/", "page");
revalidatePath("/stat", "page");
}

20
app/order/page.tsx Normal file
View File

@ -0,0 +1,20 @@
"use server";
import { readMenu } from "../../lib/readCsv";
import OrderComponent from "@/components/order";
import NavMenu from "@/components/NavMenu";
export default async function Order() {
//read csv file
const menu = await readMenu();
const categories = [...(new Set(menu.map(item => item.category)))];
return (
<div className="">
<NavMenu />
<div className="p-4">
<OrderComponent menus={menu} categories={categories} />
</div>
</div>
);
}

View File

@ -1,113 +1,22 @@
import Image from "next/image";
import OrderCard from "@/components/orderCard";
import { loadOrder } from "@/lib/db";
import NavMenu from "@/components/NavMenu";
export default function Home() {
export default async function Home() {
const orders = await loadOrder();
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
Get started by editing&nbsp;
<code className="font-mono font-bold">app/page.tsx</code>
</p>
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none">
<a
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{" "}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className="dark:invert"
width={100}
height={24}
priority
/>
</a>
<main className="min-h-screen">
<NavMenu />
{orders.length === 0 && (
<div className="flex items-center justify-center h-[calc(100vh-4rem)]">
<p className="text-muted-foreground text-2xl"> .</p>
</div>
</div>
<div className="relative z-[-1] flex place-items-center before:absolute before:h-[300px] before:w-full before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 sm:before:w-[480px] sm:after:w-[240px] before:lg:h-[360px]">
<Image
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className="mb-32 grid text-center lg:mb-0 lg:w-full lg:max-w-5xl lg:grid-cols-4 lg:text-left">
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Docs{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Learn{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Learn about Next.js in an interactive course with&nbsp;quizzes!
</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Templates{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Explore starter templates for Next.js.
</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Deploy{" "}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
-&gt;
</span>
</h2>
<p className="m-0 max-w-[30ch] text-balance text-sm opacity-50">
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
)}
<section className="grid grid-cols-[repeat(auto-fill,_minmax(300px,_auto))] p-4 gap-2">
{orders.map((order) => (
<OrderCard key={order.id} order={order} />
))}
</section>
</main>
);
}

65
app/stat/page.tsx Normal file
View File

@ -0,0 +1,65 @@
import NavMenu from "@/components/NavMenu";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Order } from "@/hooks/useOrder";
import { loadOrder } from "@/lib/db";
function StatItem({title, value}: {title: string, value: string}) {
return <div className="flex flex-col">
<p className="text-muted-foreground text-sm">{title}</p>
<p className="text-lg">{value}</p>
</div>
}
function sum (...args: number[]) {
return args.reduce((acc, cur) => acc + cur, 0);
}
function ordersSum(orders: Order[]){
return orders.reduce((acc, cur) => acc + cur.price * cur.quantity * 1000, 0);
}
export default async function Stat() {
const orders = await loadOrder();
const completed_order= orders.filter(order => order.completed === 1);
const cash_order = completed_order.filter(order => order.payment === "cash");
const account_order = completed_order.filter(order => order.payment === "account");
return <div className="">
<NavMenu />
<div className="p-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2">
<StatItem title="총 주문 수" value={orders.length.toString()} />
<StatItem title="완료된 주문 수" value={completed_order.length.toString()} />
<StatItem title="현금 결제 주문 수" value={cash_order.length.toString()} />
<StatItem title="계좌 이체 주문 수" value={account_order.length.toString()} />
</div>
<hr className="my-2" />
<div className="grid grid-cols-2">
<StatItem title="총 매출" value={sum(...completed_order.map(order=> ordersSum(order.orders)))
.toLocaleString("ko-KR", {
style: "currency",
currency: "KRW",
})
} />
<StatItem title="현금 매출" value={sum(...cash_order.map(order=> ordersSum(order.orders)))
.toLocaleString("ko-KR", {
style: "currency",
currency: "KRW",
})
} />
<StatItem title="계좌 매출" value={sum(...account_order.map(order=> ordersSum(order.orders)))
.toLocaleString("ko-KR", {
style: "currency",
currency: "KRW",
})
} />
</div>
</CardContent>
</Card>
</div>
</div>
}

17
components.json Normal file
View File

@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

37
components/NavMenu.tsx Normal file
View File

@ -0,0 +1,37 @@
"use client";
import Link from "next/link";
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
navigationMenuTriggerStyle
} from "./ui/navigation-menu";
export default function NavMenu() {
return <header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-14 max-w-screen-xl">
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<Link href="/" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}></NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/order" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}></NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href="/stat" legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}></NavigationMenuLink>
</Link>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
</header>
}

78
components/menu.tsx Normal file
View File

@ -0,0 +1,78 @@
import { MenuItem } from "@/lib/readCsv";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
import { Dispatch } from "react";
import { OrderAction } from "@/hooks/useOrder";
import { cn } from "@/lib/utils";
import { Button } from "./ui/button";
export default function Menu({
menus,
categories,
dispatch
}: {
menus: MenuItem[];
categories: string[];
dispatch: Dispatch<OrderAction>
}) {
const menu = menus;
return <Tabs defaultValue={categories[0]}>
<TabsList>
{[...categories].map(category => (
<TabsTrigger key={category} value={category}>{category}</TabsTrigger>
))}
</TabsList>
{
[...categories].map(category => (
<TabsContent key={category} value={category}>
<Card>
<CardHeader>
<CardTitle>{category}</CardTitle>
</CardHeader>
<CardContent>
<ul className="w-full flex flex-col">
<li className="flex gap-2">
<span className="w-80 text-lg">Name</span>
<span className="w-12 text-lg">HOT</span>
<span className="w-12 text-lg">COLD</span>
</li>
<hr className=""/>
{menu.filter(item => item.category === category).map((item,index) => (
<li key={item.name} className={cn("flex h-10 items-center gap-2", index % 2 === 0 ? "bg-accent" : "")}>
<span className="w-80">{item.name}</span>
<span className="w-12">{item.HOT}</span>
<span className="w-12">{item.COLD}</span>
<Button variant="outline" className="bg-sky-400 hover:bg-sky-500"
disabled={!item.COLD}
onClick={() => {
dispatch({ type: "ADD_ORDER", order: {
name: item.name,
HOT: false,
price: item.COLD ?? 0,
quantity: 1,
}});
}}
>ICE Order</Button>
<Button variant="outline" className="bg-red-400 hover:bg-red-500"
disabled={!item.HOT}
onClick={() => {
dispatch({ type: "ADD_ORDER", order: {
name: item.name,
HOT: true,
price: item.HOT ?? 0,
quantity: 1,
}});
}}
>HOT Order</Button>
</li>
))}
</ul>
</CardContent>
</Card>
</TabsContent>
))
}
</Tabs>
}

95
components/order.tsx Normal file
View File

@ -0,0 +1,95 @@
"use client";
import { MenuItem } from "@/lib/readCsv";
import { useOrder } from "../hooks/useOrder";
import Menu from "./menu";
import { Button } from "./ui/button";
import { Drawer, DrawerClose, DrawerContent, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "./ui/drawer";
import { useRouter } from "next/navigation";
import { saveOrderApi } from "@/app/order/action";
export default function Order({
menus,
categories,
}: {
menus: MenuItem[];
categories: string[];
}) {
const { state, dispatch } = useOrder();
const router = useRouter();
return (
<>
<Menu menus={menus} categories={categories} dispatch={dispatch} />
<Drawer>
<DrawerTrigger asChild>
<Button className="w-full mt-2 flex items-center">Order <span className="
ml-1 flex items-center justify-center rounded-full h-6 px-1 text-center bg-background text-foreground">{state.orders.reduce(
(acc, order) => acc + order.quantity, 0)}</span></Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle> ?</DrawerTitle>
</DrawerHeader>
<hr className="mx-4"/>
<ul className="flex flex-col gap-2 justify-center m-4">
{state.orders.map(order => (
<li key={`${order.HOT ? "HOT" : "COLD"} ${order.name}`} className="flex items-center">
<span className="w-12">{order.HOT ? "HOT " : "COLD"}</span>
<span className="flex-1">{order.name}</span>
<span className="flex-1"> {order.price * 1000}</span>
<span className="flex-1">{order.quantity}</span>
<span className="flex-1"> {order.price * order.quantity * 1000}</span>
<div className="flex space-x-2">
<Button variant="outline"
className="rounded-full"
onClick={() => dispatch({ type: "ADD_ORDER",
order:{
...order,
quantity: 1
}})} >+1</Button>
<Button variant="outline"
className="rounded-full"
onClick={() => dispatch({ type: "ADD_ORDER",
order: {
...order,
quantity: -1
}})}>-1</Button>
<Button variant="outline" onClick={() => dispatch({ type: "REMOVE_ORDER", order })}></Button>
</div>
</li>
))}
</ul>
<hr className="mx-4" />
<div className="flex mx-4 flex-col items-end">
<span className="text-sm text-muted-foreground"> </span>
<span className="flex-1 text-lg">{state.orders.map(order => order.price * order.quantity)
.reduce((acc, price) => acc + price, 0) * 1000}
</span>
</div>
<DrawerFooter>
<DrawerClose asChild>
<Button onClick={async ()=>{
console.log("order complete", router);
const d = await saveOrderApi(state.orders, "account")
console.log(d);
console.log("a complete", router);
router.push("/");
}}> </Button>
</DrawerClose>
<DrawerClose asChild>
<Button onClick={()=>{
saveOrderApi(state.orders, "cash").then(()=>{
console.log("order complete", router);
router.push("/");
})
}}> </Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</>
);
}

63
components/orderCard.tsx Normal file
View File

@ -0,0 +1,63 @@
"use client";
import { OrderStateColumns } from "@/lib/types";
import { cancelOrderApi, completeOrderApi } from "@/app/order/action";
import { Button } from "./ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger } from "./ui/drawer";
import { Pencil2Icon, TrashIcon } from "@radix-ui/react-icons";
export default function OrderCard({
order,
}: {
order: OrderStateColumns;
}) {
return <Card className="flex flex-col h-60">
<CardHeader className="relative">
<div className="absolute right-2">
<Drawer>
<DrawerTrigger asChild>
<Button variant="ghost" disabled={order.completed == 1}>
<TrashIcon />
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>
</DrawerTitle>
<DrawerDescription>
?
</DrawerDescription>
</DrawerHeader>
<DrawerClose asChild>
<Button onClick={() => {
cancelOrderApi(order.id);
}}></Button>
</DrawerClose>
</DrawerContent>
</Drawer>
</div>
<CardTitle className={order.completed == 1 ? "text-muted-foreground" : ""} >{order.id.split("/")[1]} </CardTitle>
<CardDescription>{order.completed ? "완료" : "대기중..."}</CardDescription>
</CardHeader>
<CardContent className="flex-1 overflow-scroll">
<hr />
{order.orders.map((item) => (
<div key={item.name}>
<span>{item.HOT ? "HOT" : "COLD"} {item.name} {item.quantity}</span>
</div>
))}
</CardContent>
<CardFooter>
<Button onClick={() => {
if (order.completed) {
completeOrderApi(order.id, false);
}
else {
completeOrderApi(order.id);
}
}}>{order.completed == 0 ? "완료" : "완료 취소"}</Button>
</CardFooter>
</Card>
}

57
components/ui/button.tsx Normal file
View File

@ -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 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

76
components/ui/card.tsx Normal file
View File

@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

118
components/ui/drawer.tsx Normal file
View File

@ -0,0 +1,118 @@
"use client"
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
)
Drawer.displayName = "Drawer"
const DrawerTrigger = DrawerPrimitive.Trigger
const DrawerPortal = DrawerPrimitive.Portal
const DrawerClose = DrawerPrimitive.Close
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
))
DrawerContent.displayName = "DrawerContent"
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
)
DrawerHeader.displayName = "DrawerHeader"
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
DrawerFooter.displayName = "DrawerFooter"
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}

View File

@ -0,0 +1,128 @@
import * as React from "react"
import { ChevronDownIcon } from "@radix-ui/react-icons"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}

55
components/ui/tabs.tsx Normal file
View File

@ -0,0 +1,55 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

69
hooks/useOrder.ts Normal file
View File

@ -0,0 +1,69 @@
import { useReducer } from "react";
export interface Order {
name: string;
quantity: number;
HOT: boolean;
price: number;
};
interface OrderState {
orders: Order[];
};
const initialState = {
orders: [],
} as OrderState;
export type OrderAction =
| { type: "ADD_ORDER"; order: Order }
| { type: "REMOVE_ORDER"; order: Order }
| { type: "UPDATE_ORDER"; order: Order };
function compareOrder(a: Order, b: Order) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
if (a.HOT && !b.HOT) {
return -1;
}
if (!a.HOT && b.HOT) {
return 1;
}
return 0;
}
function reducer(state: OrderState, action: OrderAction): OrderState {
switch (action.type) {
case "ADD_ORDER":
if (state.orders.find(order => compareOrder(order, action.order) === 0)) {
return {
orders: state.orders.map(order => compareOrder(order, action.order) === 0 ? {
...order,
quantity: Math.max(order.quantity + action.order.quantity, 0)
} : order),
};
}
return {
orders: [...state.orders, action.order],
};
case "REMOVE_ORDER":
return {
orders: state.orders.filter(order => compareOrder(order, action.order) !== 0),
};
case "UPDATE_ORDER":
return {
orders: state.orders.map(order => compareOrder(order, action.order) === 0 ? action.order : order),
};
default:
return state;
}
}
export function useOrder() {
const [state, dispatch] = useReducer(reducer, initialState);
return { state, dispatch };
}

97
lib/db.ts Normal file
View File

@ -0,0 +1,97 @@
import { Order } from "@/hooks/useOrder";
import { Database, NewOrderState } from './types' // this is the Database interface we defined earlier
import SQLite from 'better-sqlite3'
import { Kysely, SqliteDialect } from 'kysely'
const dialect = new SqliteDialect({
database: new SQLite('order.sqlite3'),
})
const db = new Kysely<Database>({
dialect,
});
export type Payment = "account" | "cash";
export async function requestOrderNumber() : Promise<number> {
const date = new Date().toISOString().split("T")[0];
return await db.transaction().execute(async trx=> {
const order_number = await trx.selectFrom("Order_Number")
.where("id", "==", date)
.selectAll()
.executeTakeFirst();
if (!order_number) {
await trx.insertInto("Order_Number")
.values({
id: date,
number: 1,
})
.execute();
return 1;
}
const num = order_number.number + 1;
await trx.updateTable("Order_Number")
.set("number", num)
.where("id", "==", date)
.execute();
return num;
});
}
export async function saveOrder(order: Order[], payment: Payment) {
const date = new Date().toISOString().split("T")[0];
const num = await requestOrderNumber();
const id = `${date}/${num}`;
await db.insertInto("Order_State")
.values({
id,
orders: JSON.stringify(order),
completed: 0,
payment,
} as NewOrderState)
.execute();
return id;
}
export async function loadOrder() {
const orders = (await db.selectFrom("Order_State")
.selectAll()
.orderBy(["completed","id desc"])
.execute())
.map(order => ({
...order,
orders: JSON.parse(order.orders as unknown as string) as Order[],
}));
return orders;
}
export async function clearOrder() {
await db.deleteFrom("Order_State")
.execute();
}
export async function cancelOrder(order_id: string) {
await db.deleteFrom("Order_State")
.where("id", "==", order_id)
.execute();
}
export async function completeOrder(uid: string, completed: boolean = true) {
await db.updateTable("Order_State")
.set("completed", completed ? 1 : 0)
.where("id", "==", uid)
.execute();
}
export async function loadOrderById(uid: string) {
const order = await db.selectFrom("Order_State")
.where("id", "==", uid)
.selectAll()
.executeTakeFirst();
return order ? {
...order,
orders: JSON.parse(order.orders as unknown as string) as Order[],
} : null;
}

25
lib/readCsv.tsx Normal file
View File

@ -0,0 +1,25 @@
import { readFile } from "fs/promises";
export type MenuItem = {
name: string;
HOT: number | null;
COLD: number | null;
category: string;
};
export const readMenu = async () => {
const file = await readFile('public/menu.csv', 'utf-8');
const [headersRaw, ...rows] = file.split('\n').map(row => row.trim()).filter(row => row.length > 0);
const headers = headersRaw.split(',').map(header => header.trim());
const data = rows.map(row => {
const items = row.split(',');
return {
name: items[0].trim(),
HOT: Number.parseFloat(items[1].trim()) || null,
COLD: Number.parseFloat(items[2].trim()) || null,
category: items[3].trim(),
} satisfies MenuItem;
});
return data;
};

34
lib/types.ts Normal file
View File

@ -0,0 +1,34 @@
import {
ColumnType,
Generated,
Insertable,
JSONColumnType,
Selectable,
Updateable
} from 'kysely';
import { Order } from "@/hooks/useOrder";
export interface OrderState {
id: string;
orders: JSONColumnType<Order[]>;
completed: 0 | 1;
payment: string;
}
export type NewOrderState = Insertable<OrderState>;
export type OrderStateColumns = Selectable<OrderState>;
export type OrderStateUpdate = Updateable<OrderState>;
export interface OrderNumber {
id: string;
number: number;
}
export type NewOrderNumber = Insertable<OrderNumber>;
export type OrderNumberColumns = Selectable<OrderNumber>;
export type OrderNumberUpdate = Updateable<OrderNumber>;
export interface Database {
Order_State: OrderState;
Order_Number: OrderNumber;
}

6
lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

54
migrate.ts Normal file
View File

@ -0,0 +1,54 @@
import * as path from 'path'
import { promises as fs } from 'fs'
import {
Kysely,
Migrator,
SqliteDialect,
FileMigrationProvider,
} from 'kysely'
import SQLite from "better-sqlite3";
import { Database } from './lib/types';
export async function migrateToLatest() {
const __dirname = path.resolve();
const db = new Kysely<Database>({
dialect: new SqliteDialect({
database: new SQLite("order.sqlite3"),
}),
})
const { up } = await import('./migration/2024-04-19');
await up(db);
// const migrator = new Migrator({
// db,
// provider: new FileMigrationProvider({
// fs,
// path,
// // This needs to be an absolute path.
// migrationFolder: path.join(__dirname, './migration'),
// }),
// })
// const { error, results } = await migrator.migrateToLatest()
// results?.forEach((it) => {
// if (it.status === 'Success') {
// console.log(`migration "${it.migrationName}" was executed successfully`)
// } else if (it.status === 'Error') {
// console.error(`failed to execute migration "${it.migrationName}"`)
// }
// })
// if (error) {
// console.error('failed to migrate')
// console.error(error)
// process.exit(1)
// }
await db.destroy()
}
migrateToLatest()

22
migration/2024-04-19.ts Normal file
View File

@ -0,0 +1,22 @@
import { Kysely } from 'kysely'
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('Order_State')
.addColumn('id', 'varchar', col => col.primaryKey().notNull())
.addColumn('orders', 'jsonb', col => col.notNull())
.addColumn('completed', 'boolean', col => col.notNull())
.addColumn('payment', 'varchar', col => col.notNull())
.execute()
await db.schema
.createTable('Order_Number')
// current date. (e.g. 2024-04-19)
.addColumn('id', 'varchar', col => col.primaryKey().notNull())
.addColumn('number', 'integer', col => col.notNull())
.execute()
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('Order_State').execute()
await db.schema.dropTable('Order_Number').execute()
}

View File

@ -1,4 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ["@deno/kv"]
}
};
export default nextConfig;

View File

@ -6,21 +6,39 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"shadcn": "shadcn-ui"
},
"type": "module",
"dependencies": {
"@deno/kv": "^0.7.0",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"better-sqlite3": "^9.5.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"kysely": "^0.27.3",
"next": "14.2.1",
"react": "^18",
"react-dom": "^18",
"next": "14.2.1"
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"tsx": "^4.7.2",
"vaul": "^0.9.0"
},
"devDependencies": {
"typescript": "^5",
"@types/better-sqlite3": "^7.6.9",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^8",
"eslint-config-next": "14.2.1"
"eslint-config-next": "14.2.1",
"postcss": "^8",
"shadcn-ui": "^0.8.0",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

File diff suppressed because it is too large Load Diff

26
public/menu.csv Normal file
View File

@ -0,0 +1,26 @@
name,HOT,COLD,category
아메리카노,1.5,2,커피
헤이즐넛 아메리카노,1.5,2,커피
바닐라 아메리카노,1.5,2,커피
꿀단지 커피,2.5,3,커피
곰다방커피,,2,커피
콜드브루,,2,커피
카페모카,2,2.5,커피
카페라떼,3,3.5,카페라떼
헤이즐넛 카페라떼,3,3.5,카페라떼
바닐라 카페라떼,3,3.5,카페라떼
초코라떼,3,3.5,카페라떼
딸기라떼,3,3.5,카페라떼
사과유자차,3,3.5,차
꿀유자차,2,2.5,차
폴라복숭아,,2.5,차
허니자몽 블랙티,2.5,3,차
오미자 차,2,2.5,차
달곰상곰 딸기레몬,3.5,3.5,탄산
청포도 에이드,3,3,탄산
자몽에이드,3,3,탄산
레몬에이드,3,3,탄산
오미자 에이드,3,3,탄산
허니브레드,,3,빵
소금빵,,3,빵
크로크무슈,,3.5,빵
1 name HOT COLD category
2 아메리카노 1.5 2 커피
3 헤이즐넛 아메리카노 1.5 2 커피
4 바닐라 아메리카노 1.5 2 커피
5 꿀단지 커피 2.5 3 커피
6 곰다방커피 2 커피
7 콜드브루 2 커피
8 카페모카 2 2.5 커피
9 카페라떼 3 3.5 카페라떼
10 헤이즐넛 카페라떼 3 3.5 카페라떼
11 바닐라 카페라떼 3 3.5 카페라떼
12 초코라떼 3 3.5 카페라떼
13 딸기라떼 3 3.5 카페라떼
14 사과유자차 3 3.5
15 꿀유자차 2 2.5
16 폴라복숭아 2.5
17 허니자몽 블랙티 2.5 3
18 오미자 차 2 2.5
19 달곰상곰 딸기레몬 3.5 3.5 탄산
20 청포도 에이드 3 3 탄산
21 자몽에이드 3 3 탄산
22 레몬에이드 3 3 탄산
23 오미자 에이드 3 3 탄산
24 허니브레드 3
25 소금빵 3
26 크로크무슈 3.5

View File

@ -1,20 +1,84 @@
import type { Config } from "tailwindcss";
import type { Config } from "tailwindcss"
import defaultTheme from "tailwindcss/defaultTheme"
const config: Config = {
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
fontFamily: {
sans: ["var(--font-sans)", ...defaultTheme.fontFamily.sans],
},
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [],
};
export default config;
plugins: [require("tailwindcss-animate")],
} satisfies Config
export default config

View File

@ -10,6 +10,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"target": "ESNext",
"jsx": "preserve",
"incremental": true,
"plugins": [