Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions apps/console/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,6 @@ const nextConfig: NextConfig = {
"@spaceorder/auth",
"@spaceorder/db",
],
async redirects() {
return [
{
source: "/",
destination: "/stores",
permanent: false,
},
];
},
};

export default nextConfig;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import { COOKIE_TABLE } from "@spaceorder/db/constants";
import { useEffect } from "react";

type PersistLastStoreProps = {
storeId: string;
};

/**
* 현재 보고 있는 매장을 마지막 접속 매장 쿠키에 기록한다.
* 공유 URL로 직접 진입하는 경우에도 쿠키가 갱신되어, 다음 진입 시 해당 매장으로 리다이렉트된다.
* httpOnly가 아닌 쿠키이므로 클라이언트에서 직접 기록한다.
*/
export default function PersistLastStore({ storeId }: PersistLastStoreProps) {
useEffect(() => {
document.cookie = `${COOKIE_TABLE.LAST_ACCESSED_STORE_ID}=${storeId}; path=/; max-age=${60 * 60 * 24 * 365}`;
}, [storeId]);

return null;
}
25 changes: 25 additions & 0 deletions apps/console/src/app/(navigator)/(sidebar)/[storeId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { PublicStore } from "@spaceorder/db";
import ServerPrefetch from "@/components/ServerPrefetch";
import PersistLastStore from "./components/PersistLastStore";

/**
* 매장 스코프 레이아웃.
* `/stores/v1/{storeId}` 조회로 매장 소유·유효성을 검증하고(백엔드 StoreAccessGuard),
* 실패 시 상위 error boundary로 전달한다. 유효한 매장은 마지막 접속 매장 쿠키에 기록한다.
*/
export default async function StoreScopeLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: Promise<{ storeId: string }>;
}>) {
const { storeId } = await params;

return (
<ServerPrefetch<PublicStore> url={`/stores/v1/${storeId}`} shouldSuccess>
<PersistLastStore storeId={storeId} />
{children}
</ServerPrefetch>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import MainLayout from "../../components/MainLayout";

export default function MenusSettingLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return <MainLayout>{children}</MainLayout>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { sideMenuItem } from "@/shared/config/sidebarGroup";
import PageTitle from "../../components/PageTitle";
import HeaderLinkButton from "../../components/HeaderLinkButton";
import { Plus } from "lucide-react";

export default function MenusSettingPage() {
return (
<PageTitle title={sideMenuItem.title} Icon={sideMenuItem.icon}>
<HeaderLinkButton linkTo="menus/add" icon={<Plus strokeWidth={2.5} />}>
<span>메뉴 추가</span>
</HeaderLinkButton>
</PageTitle>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default function GridLayout({
children: React.ReactNode;
}) {
return (
<section className="antialiased h-full flex gap-2 px-6 pb-4">
<section className="antialiased h-full flex gap-2 pr-6 pb-4">
{children}
</section>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Card } from "@spaceorder/ui/components/layouts/card";
import ConditionalLink from "@/components/ConditionalLink";
import { useParams } from "next/navigation";
import { useParams, usePathname } from "next/navigation";
import { BoardTableWithSession } from "@spaceorder/db/types";

interface TableOrderCardProps {
Expand All @@ -14,10 +14,9 @@ export function TableOrderCard({
children,
sanitizedTable,
}: TableOrderCardProps) {
const { storeId, tableId } = useParams<{
storeId: string;
tableId: string;
}>();
const { tableId } = useParams<{ tableId: string }>();
const [pathname] = usePathname().split("orders");

const isActivatedTable = sanitizedTable.isActive === true;
const isSelected = tableId === sanitizedTable.publicId;

Expand All @@ -31,7 +30,7 @@ export function TableOrderCard({
return (
<ConditionalLink
condition={isActivatedTable && !isSelected}
href={`/stores/${storeId}/orders/${sanitizedTable.publicId}`}
href={`${pathname}orders/${sanitizedTable.publicId}`}
className="rounded-2xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<Card
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LAST_ACCESSED_STORE_ID, OrderBoardByStore } from "@spaceorder/db";
import { OrderBoardByStore } from "@spaceorder/db";
import { useQueryClient } from "@tanstack/react-query";
import { useParams } from "next/navigation";
import { setTablesCache } from "../utils/setTablesCache";
Expand All @@ -13,9 +13,5 @@ export const useSetCacheByStoreBoard = () => {
setTablesCache(tableBoard, queryClient, resolvedStoreId);
};

const setStoreInLocalStorage = (storeId: string) => {
localStorage.setItem(LAST_ACCESSED_STORE_ID, storeId);
};

return { setCache, setStoreInLocalStorage };
return { setCache };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import MainLayout from "../../components/MainLayout";

export default function TablesSettingLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return <MainLayout>{children}</MainLayout>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { sideTableItem } from "@/shared/config/sidebarGroup";
import PageTitle from "../../components/PageTitle";
import HeaderLinkButton from "../../components/HeaderLinkButton";
import { Plus } from "lucide-react";

export default function TablesSettingPage() {
return (
<PageTitle title={sideTableItem.title} Icon={sideTableItem.icon}>
<HeaderLinkButton linkTo="tables/add" icon={<Plus strokeWidth={2.5} />}>
<span>테이블 추가</span>
</HeaderLinkButton>
</PageTitle>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import TouchEventButton from "@spaceorder/ui/components/buttons/TouchEventButton";
import Link from "next/link";
import { ComponentProps } from "react";

type HeaderLinkButtonProps = {
linkTo: string;
icon?: React.ReactNode;
} & ComponentProps<typeof TouchEventButton>;

export default function HeaderLinkButton({
children,
icon,
linkTo,
...props
}: HeaderLinkButtonProps) {
const iconElement = (
<>
{icon}
<span>{children}</span>
</>
);
return (
<Link href={linkTo} className="w-fit">
<TouchEventButton swallowEvent={false} {...props}>
{iconElement}
</TouchEventButton>
</Link>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function MainLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return <div className="pt-4 pl-4 pr-6">{children}</div>;
}
Original file line number Diff line number Diff line change
@@ -1,67 +1,24 @@
"use client";

import { sidebarList } from "@/shared/config/sidebarList";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarTrigger,
} from "@spaceorder/ui/components/layouts/sidebar";
import UserNameDropDown from "../../components/UserNameDropDown";
import Link from "next/link";
import { usePathname } from "next/navigation";
import RealtimeStatusDot from "@/components/realtime/RealtimeStatusDot";
import SidebarGroups from "./SidebarItemGroups";
import SidebarFooterLayout from "./SidebarFooterLayout";
import StoreSwitcher from "./StoreSwitcher";

export default function NavSidebar() {
const pathname = usePathname();

return (
<Sidebar collapsible="icon" variant="inset">
<SidebarHeader>
<SidebarTrigger />
<StoreSwitcher />
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel className="whitespace-nowrap">
주문 관리
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{sidebarList.map((sidebarElement) => (
<SidebarMenuItem key={sidebarElement.title}>
<SidebarMenuButton
asChild
isActive={pathname.includes(sidebarElement.url)}
>
<Link href={sidebarElement.url}>
<sidebarElement.icon />
<span>{sidebarElement.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroups />
</SidebarContent>
<SidebarFooter>
<SidebarGroup>
<SidebarMenu>
<SidebarMenuItem key={"realtimeStatus"}>
<RealtimeStatusDot />
</SidebarMenuItem>
<SidebarMenuItem key={"userName"}>
<UserNameDropDown />
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
<SidebarFooterLayout />
</SidebarFooter>
</Sidebar>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type PageTitleProps = {
title: string;
Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
children?: React.ReactNode;
};

export default function PageTitle({ title, Icon, children }: PageTitleProps) {
return (
<header className="flex justify-between items-center">
<div className="flex items-center gap-x-2">
<Icon />
<h1 className="text-2xl font-bold">{title}</h1>
</div>
{children}
</header>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import RealtimeStatusDot from "@/components/realtime/RealtimeStatusDot";
import {
SidebarGroup,
SidebarMenu,
SidebarMenuItem,
SidebarTrigger,
} from "@spaceorder/ui/components/layouts/sidebar";
import UserNameDropDown from "../../components/UserNameDropDown";
import { Button } from "@spaceorder/ui/components/buttons/button";

export default function SidebarFooterLayout() {
return (
<SidebarGroup>
<SidebarMenu>
<SidebarMenuItem key={"realtimeStatus"}>
<RealtimeStatusDot />
</SidebarMenuItem>
<SidebarMenuItem className="flex gap-x-1.5" key={"userName"}>
<Button
asChild
variant={"outline"}
size={"icon-sm"}
className="shadow-none"
>
<SidebarTrigger />
</Button>
<UserNameDropDown />
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import { SIDEBAR_GROUP } from "@/shared/config/sidebarGroup";
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@spaceorder/ui/components/layouts/sidebar";
import Link from "next/link";
import { useParams, usePathname } from "next/navigation";

export default function SidebarGroups() {
const pathname = usePathname();
const { storeId } = useParams<{ storeId?: string }>();

return (
<>
{SIDEBAR_GROUP.map((group) => (
<SidebarGroup key={group.groupTitle}>
<SidebarGroupLabel className="whitespace-nowrap">
{group.groupTitle}
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{group.groupItems.map((sidebarItem) => (
<SidebarMenuItem key={sidebarItem.title}>
<SidebarMenuButton
asChild
isActive={pathname.includes(`/${sidebarItem.segment}`)}
aria-disabled={!storeId}
>
<Link
href={storeId ? `/${storeId}/${sidebarItem.segment}` : "#"}
>
<sidebarItem.icon />
<span>{sidebarItem.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</>
);
}
Loading