diff --git a/bun.lockb b/bun.lockb index de7bb1b..2815c07 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 31c1d87..6aae44e 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "sonner": "^1.5.0", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", + "vaul": "^0.9.1", "zod": "^3.23.8", "zustand": "^4.5.4" }, diff --git a/src/app/(main)/i/[subdomain]/page.tsx b/src/app/(main)/i/[subdomain]/page.tsx index 60fac29..e52aed3 100644 --- a/src/app/(main)/i/[subdomain]/page.tsx +++ b/src/app/(main)/i/[subdomain]/page.tsx @@ -1,6 +1,7 @@ import type { Metadata, ResolvingMetadata } from "next"; import { notFound } from "next/navigation"; import Recursive from "~/components/editor/elements/recursive"; +import FloatingActionButton from "~/components/editor/fab"; import EditorProvider from "~/components/editor/provider"; import { getInvitationByEventUrl } from "~/lib/db/schema/invitations.query"; @@ -45,10 +46,13 @@ export default async function Page({ params }: Props) { editorData={invitation.customFields} editorState={{ isPreviewMode: true }} > -
+
{invitation.customFields.elements.map((childElement) => ( ))} + {invitation.customFields?.fab?.type === "invitation_response" && ( + + )}
); diff --git a/src/components/editor/constant.ts b/src/components/editor/constant.ts index f4cccf8..899b769 100644 --- a/src/components/editor/constant.ts +++ b/src/components/editor/constant.ts @@ -37,6 +37,7 @@ export const kakaoMapDefaultStyles: React.CSSProperties = { export const editorTabValue = { ELEMENTS: "Elements", SETTINGS: "Settings", + INVITATION_RESPONSE: "Invitation Response", ELEMENT_SETTINGS: "Element Settings", } as const; @@ -61,6 +62,9 @@ export const bodyElement = { export const initialEditorData: EditorData = { elements: [bodyElement], + fab: { + type: "invitation_response", + }, }; export const initialEditorState: EditorState = { diff --git a/src/components/editor/fab/index.tsx b/src/components/editor/fab/index.tsx new file mode 100644 index 0000000..ba27a03 --- /dev/null +++ b/src/components/editor/fab/index.tsx @@ -0,0 +1,15 @@ +"use client"; + +import BottomSheet from "~/app/(playground)/pg/bottom-sheet/_components/bottom-sheet"; +import { useEditor } from "~/components/editor/provider"; + +export default function FloatingActionButton() { + const { editor } = useEditor(); + + switch (editor.data.fab.type) { + case "invitation_response": + return ; + default: + return null; + } +} diff --git a/src/components/editor/fab/invitation-response-fab.tsx b/src/components/editor/fab/invitation-response-fab.tsx new file mode 100644 index 0000000..232616b --- /dev/null +++ b/src/components/editor/fab/invitation-response-fab.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { useForm } from "@tanstack/react-form"; +import { toast } from "sonner"; +import { Drawer } from "vaul"; +import { SheetHeader, SheetTitle } from "~/components/ui/sheet"; + +export default function InvitationResponseFab() { + return ( + +
+ + 세션 참여 조사하기 + +
+ + + + + + + +
+ ); +} + +function InvitationResponseForm() { + const form = useForm({ + defaultValues: { + name: "", + attendance: "", + }, + onSubmit: async ({ value }) => { + const { name, attendance } = value; + console.log(name, attendance); + // await createInvitationResponses(name, attendance as unknown as boolean); + toast("참여가 완료되었습니다.", { duration: 2000 }); + }, + }); + + return ( +
+ + 세션 참여 조사 + +
+ ); +} diff --git a/src/components/editor/main.tsx b/src/components/editor/main.tsx index 0a029c3..c0fb560 100644 --- a/src/components/editor/main.tsx +++ b/src/components/editor/main.tsx @@ -3,6 +3,7 @@ import { EyeOff } from "lucide-react"; import ElementHelper from "~/components/editor/elements/element-helper"; import Recursive from "~/components/editor/elements/recursive"; +import FloatingActionButton from "~/components/editor/fab"; import { useEditor } from "~/components/editor/provider"; import { Button } from "~/components/ui/button"; import { ScrollArea } from "~/components/ui/scroll-area"; @@ -24,7 +25,7 @@ export default function EditorMain() { return (
( ))} +
diff --git a/src/components/editor/provider.tsx b/src/components/editor/provider.tsx index 95c1cdb..8bf4185 100644 --- a/src/components/editor/provider.tsx +++ b/src/components/editor/provider.tsx @@ -21,7 +21,7 @@ export const EditorContext = createContext<{ }); export type EditorProps = { - editorData?: EditorData; + editorData?: Partial; editorConfig?: Partial; editorState?: Partial; }; @@ -38,7 +38,10 @@ export default function EditorProvider({ }: EditorProviderProps) { const [editor, dispatch] = useReducer(editorReducer, { ...initialEditor, - data: editorData ?? initialEditor.data, + data: { + ...initialEditor.data, + ...editorData, + }, config: { ...initialEditorConfig, ...editorConfig, diff --git a/src/components/editor/sidebar/index.tsx b/src/components/editor/sidebar/index.tsx index 7f6908a..b4c786b 100644 --- a/src/components/editor/sidebar/index.tsx +++ b/src/components/editor/sidebar/index.tsx @@ -1,11 +1,12 @@ "use client"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@radix-ui/react-tabs"; -import { PlusIcon, SettingsIcon, WrenchIcon } from "lucide-react"; +import { MailIcon, PlusIcon, SettingsIcon, WrenchIcon } from "lucide-react"; import { editorTabValue } from "~/components/editor/constant"; import { useEditor } from "~/components/editor/provider"; import SidebarElementSettingsTab from "~/components/editor/sidebar/sidebar-element-settings-tab"; import SidebarElementsTab from "~/components/editor/sidebar/sidebar-elements-tab"; +import SidebarInvitationResponseTab from "~/components/editor/sidebar/sidebar-invitation-response-tab"; import SidebarSettingsTab from "~/components/editor/sidebar/sidebar-settings-tab"; import type { EditorTabTypeValue } from "~/components/editor/type"; import { isValidSelectEditorElement } from "~/components/editor/util"; @@ -29,6 +30,11 @@ export default function EditorSidebar() { icon: , content: , }, + { + value: editorTabValue.INVITATION_RESPONSE, + icon: , + content: , + }, { value: editorTabValue.ELEMENT_SETTINGS, icon: , diff --git a/src/components/editor/sidebar/sidebar-invitation-response-tab.tsx b/src/components/editor/sidebar/sidebar-invitation-response-tab.tsx new file mode 100644 index 0000000..23231fe --- /dev/null +++ b/src/components/editor/sidebar/sidebar-invitation-response-tab.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useEditor } from "~/components/editor/provider"; +import { + SheetDescription, + SheetHeader, + SheetTitle, +} from "~/components/ui/sheet"; +import { Switch } from "~/components/ui/switch"; + +export default function SidebarInvitationResponseTab() { + const { editor, dispatch } = useEditor(); + + return ( +
+ +
+ 초대 응답 설정 + +
+ + 이벤트에 대한 초대 응답 설정을 관리합니다. + +
+ {editor.config && } +
+ ); +} + +function InvitationResponseContent() { + return ( +
+
+
+ ); +} diff --git a/src/components/editor/type.ts b/src/components/editor/type.ts index db5cd11..6573121 100644 --- a/src/components/editor/type.ts +++ b/src/components/editor/type.ts @@ -52,6 +52,9 @@ export type EditorState = { export type EditorData = { elements: EditorElement[]; + fab: { + type: "" | "invitation_response"; + }; }; export type EditorHistory = { diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx new file mode 100644 index 0000000..efdb086 --- /dev/null +++ b/src/components/ui/drawer.tsx @@ -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) => ( + +); +Drawer.displayName = "Drawer"; + +const DrawerTrigger = DrawerPrimitive.Trigger; + +const DrawerPortal = DrawerPrimitive.Portal; + +const DrawerClose = DrawerPrimitive.Close; + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)); +DrawerContent.displayName = "DrawerContent"; + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DrawerHeader.displayName = "DrawerHeader"; + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DrawerFooter.displayName = "DrawerFooter"; + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; + +export { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerOverlay, + DrawerPortal, + DrawerTitle, + DrawerTrigger, +};