diff --git a/app/(marketing)/[slug]/page.tsx b/app/(marketing)/[slug]/page.tsx index 91ae751..1814a37 100644 --- a/app/(marketing)/[slug]/page.tsx +++ b/app/(marketing)/[slug]/page.tsx @@ -3,7 +3,7 @@ import { allPages } from "contentlayer/generated"; import { Mdx } from "@/components/content/mdx-components"; -import "@/styles/mdx.css"; +import "@/styles/mdx.scss"; import { Metadata } from "next"; diff --git a/app/layout.tsx b/app/layout.tsx index d2a2fcc..6f37427 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,4 @@ -import "@/styles/globals.css"; +import "@/styles/globals.scss"; import { fontHeading, fontSans, fontSatoshi } from "@/assets/fonts"; import { SessionProvider } from "next-auth/react"; diff --git a/components/email/EmailEditor.tsx b/components/email/EmailEditor.tsx new file mode 100644 index 0000000..c3ae846 --- /dev/null +++ b/components/email/EmailEditor.tsx @@ -0,0 +1,142 @@ +import * as React from "react"; +// --- Lib --- +import { Highlight } from "@tiptap/extension-highlight"; +import { Image } from "@tiptap/extension-image"; +import { TaskItem, TaskList } from "@tiptap/extension-list"; +import { Subscript } from "@tiptap/extension-subscript"; +import { Superscript } from "@tiptap/extension-superscript"; +import { TextAlign } from "@tiptap/extension-text-align"; +import { Typography } from "@tiptap/extension-typography"; +import { Selection } from "@tiptap/extensions"; +import { EditorContent, EditorContext, useEditor } from "@tiptap/react"; +// --- Tiptap Core Extensions --- +import { StarterKit } from "@tiptap/starter-kit"; + +import { handleImageUpload, MAX_FILE_SIZE } from "@/lib/tiptap-utils"; +import { useCursorVisibility } from "@/hooks/use-cursor-visibility"; +// --- Hooks --- +import { useIsMobile } from "@/hooks/use-mobile"; +import { useWindowSize } from "@/hooks/use-window-size"; +import { HorizontalRule } from "@/components/shared/tiptap/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension"; +// --- Tiptap Node --- +import { ImageUploadNode } from "@/components/shared/tiptap/tiptap-node/image-upload-node/image-upload-node-extension"; +// import content from "@/components/shared/tiptap/tiptap-templates/simple/data/content.json"; +import { Toolbar } from "@/components/shared/tiptap/tiptap-ui-primitive/toolbar"; + +import { Icons } from "../shared/icons"; +import { + MainToolbarContent, + MobileToolbarContent, +} from "../shared/tiptap/tiptap-templates/simple/simple-editor"; + +export function EmailEditor({ + onGetEditorValue, +}: { + onGetEditorValue: (html: string, text: string) => void; +}) { + const isMobile = useIsMobile(); + const { height } = useWindowSize(); + const [mobileView, setMobileView] = React.useState< + "main" | "highlighter" | "link" + >("main"); + const toolbarRef = React.useRef(null); + + const editor = useEditor({ + immediatelyRender: false, + shouldRerenderOnTransaction: false, + editorProps: { + attributes: { + autocomplete: "off", + autocorrect: "off", + autocapitalize: "off", + "aria-label": "Main content area, start typing to enter text.", + class: "simple-editor", + }, + }, + extensions: [ + StarterKit.configure({ + horizontalRule: false, + link: { + openOnClick: false, + enableClickSelection: true, + }, + }), + HorizontalRule, + TextAlign.configure({ types: ["heading", "paragraph"] }), + TaskList, + TaskItem.configure({ nested: true }), + Highlight.configure({ multicolor: true }), + Image, + Typography, + Superscript, + Subscript, + Selection, + ImageUploadNode.configure({ + accept: "image/*", + maxSize: MAX_FILE_SIZE, + limit: 3, + upload: handleImageUpload, + onError: (error) => console.error("Upload failed:", error), + }), + ], + content: "Hi", + onUpdate: ({ editor }) => { + const html = editor.getHTML(); + const text = editor.getText(); + onGetEditorValue(html, text); + }, + }); + + const rect = useCursorVisibility({ + editor, + overlayHeight: toolbarRef.current?.getBoundingClientRect().height ?? 0, + }); + + React.useEffect(() => { + if (!isMobile && mobileView !== "main") { + setMobileView("main"); + } + }, [isMobile, mobileView]); + editor?.getHTML(); + return ( +
+ + + {mobileView === "main" ? ( + setMobileView("highlighter")} + onLinkClick={() => setMobileView("link")} + isMobile={isMobile} + /> + ) : ( + setMobileView("main")} + /> + )} + + + + + +
+ ); +} diff --git a/components/email/SendEmailModal.tsx b/components/email/SendEmailModal.tsx index ab0fdc4..69cab4c 100644 --- a/components/email/SendEmailModal.tsx +++ b/components/email/SendEmailModal.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useTransition } from "react"; -import dynamic from "next/dynamic"; +import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { Icons } from "../shared/icons"; @@ -10,17 +10,11 @@ import { Drawer, DrawerClose, DrawerContent, - DrawerFooter, DrawerHeader, DrawerTitle, } from "../ui/drawer"; import { Input } from "../ui/input"; - -import "react-quill/dist/quill.snow.css"; - -import { useTranslations } from "next-intl"; - -const ReactQuill = dynamic(() => import("react-quill"), { ssr: false }); +import { EmailEditor } from "./EmailEditor"; interface SendEmailModalProps { className?: string; @@ -36,7 +30,12 @@ export function SendEmailModal({ onSuccess, }: SendEmailModalProps) { const [isOpen, setIsOpen] = useState(false); - const [sendForm, setSendForm] = useState({ to: "", subject: "", html: "" }); + const [sendForm, setSendForm] = useState({ + to: "", + subject: "", + html: "", + text: "", + }); const [isPending, startTransition] = useTransition(); const t = useTranslations("Email"); @@ -46,8 +45,10 @@ export function SendEmailModal({ toast.error("No email address selected"); return; } + console.log("sendForm", sendForm); + // return; if (!sendForm.to || !sendForm.subject || !sendForm.html) { - toast.error("Please fill in all fields"); + toast.error("Please fill in all required fields"); return; } @@ -60,13 +61,14 @@ export function SendEmailModal({ to: sendForm.to, subject: sendForm.subject, html: sendForm.html, + text: sendForm.text, }), }); if (response.ok) { toast.success("Email sent successfully"); setIsOpen(false); - setSendForm({ to: "", subject: "", html: "" }); + setSendForm({ to: "", subject: "", html: "", text: "" }); onSuccess?.(); } else { toast.error("Failed to send email", { @@ -94,28 +96,62 @@ export function SendEmailModal({ )} - - + + {t("Send Email")}{" "} - - - - - + + + + -
-
-