From 6e72ae7c37eeeac951b5557e79609b01fde20407 Mon Sep 17 00:00:00 2001 From: Khalidnoori568 Date: Wed, 13 Aug 2025 16:45:10 +0430 Subject: [PATCH 1/2] Add notebook-card --- apps/web/app/page.tsx | 8 +- apps/web/components/gradient-editor.tsx | 2 +- apps/web/components/notebook-editor.tsx | 407 ++++++++++++++++++ .../notebook-preview.style.css | 106 +++++ .../notebookcard-preview/notebook-preview.tsx | 72 ++++ 5 files changed, 592 insertions(+), 3 deletions(-) create mode 100644 apps/web/components/notebook-editor.tsx create mode 100644 packages/ui/src/notebookcard-preview/notebook-preview.style.css create mode 100644 packages/ui/src/notebookcard-preview/notebook-preview.tsx diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 7aca0bf..f7420fa 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -5,7 +5,7 @@ import "./page.module.css"; import QuizMarkdownEditor from "../components/markdown-editor"; import ChatgptEditor from "../components/chatgpt-editor"; import TwitterEditor from "../components/twitter-editor"; - +import NotebookEditor from "../components/notebook-editor"; import GradientEditor from "../components/gradient-editor"; import { CardSizeProvider } from "@repo/ui/context/CardSizeContext"; @@ -26,7 +26,10 @@ const tabs = [ key: "twitter", label: "Twitter Card", }, - + { + key: "notebook", + label: "Notebook Card", + } ]; export default function Home() { @@ -39,6 +42,7 @@ export default function Home() { {tab === "chatgpt" ? : null} {/* {tab === "gradient" ? : null} */} {tab === "twitter" ? : null} + {tab === "notebook" ? : null} diff --git a/apps/web/components/gradient-editor.tsx b/apps/web/components/gradient-editor.tsx index 2e9d96b..6bfe529 100644 --- a/apps/web/components/gradient-editor.tsx +++ b/apps/web/components/gradient-editor.tsx @@ -6,7 +6,7 @@ import { CardSizeContext } from "@repo/ui/context/CardSizeContext"; import SocialMediaController from "@repo/web-ui/SocialMediaController"; const STORAGE_KEY = "gradient-editor-v1"; -const cardBgColor = "bg-white"; +const cardBgColor = "bg-yellow-200"; const textColor = "text-gray-900"; const borderColor = "border-gray-300"; diff --git a/apps/web/components/notebook-editor.tsx b/apps/web/components/notebook-editor.tsx new file mode 100644 index 0000000..ee035c4 --- /dev/null +++ b/apps/web/components/notebook-editor.tsx @@ -0,0 +1,407 @@ +"use client"; + +import { NotebookPreview } from "@repo/ui/notebookcard-preview/notebook-preview"; +import { useContext, useEffect, useState } from "react"; +import { DownloadButton } from "@repo/web-ui/download-button"; +import { + CardSizeContext, + CardSizeProvider, +} from "@repo/ui/context/CardSizeContext"; +import SocialMediaController from "@repo/web-ui/SocialMediaController"; + +const STORAGE_KEY = "notebookCardv1"; + +const cardBgColor = "bg-violet-500"; +const textColor = "text-white"; + + +const componentSocialMapping = { + instagramPost: { + width: 1080 / 2, + height: 1080 / 2, + innerPaddingY: 1080 / 10, + scale: 1, + }, + instagramStory: { + width: 1080 / 2, + height: 1920 / 2, + innerPaddingY: 1920 / 8, + scale: 1, + }, + twitterPost: { + width: 1200 / 2, + height: 675 / 2, + innerPaddingY: 0, + scale: 0.7, + }, + twitterHeader: { + width: (1500 / 2) * 1.2, + height: (500 / 2) * 1.2, + innerPaddingY: 0, + scale: 0.7, + }, + facebookPost: { + width: (1200 / 2) * 1.3, + height: (630 / 2) * 1.3, + innerPaddingY: 630 / 10, + scale: 1, + }, + facebookCover: { + width: 820, + height: 312, + innerPaddingY: 312 / 10, + scale: 0.7, + }, + dribbleShot: { + width: (800 / 2) * 1.5, + height: (600 / 2) * 1.5, + innerPaddingY: 600 / 10, + scale: 1, + }, + linkedinPost: { + width: 1200 / 1.5, + height: 627 / 1.5, + innerPaddingY: 627 / 10, + scale: 1, + }, + linkedinCover: { + width: 1584 / 1.5, + height: 396 / 1.5, + innerPaddingY: 10, + scale: 0.5, + }, +}; + +export default function NotebookEditor() { + const [loaded, setLoaded] = useState(false); + + const [state, setState] = useState({ + previewHeightPixels: 540, + previewWidthPixels: 540, + width: 540, + height: 540, + innerPaddingX: 30, + innerPaddingY: 50, + scale: 1, + exportScale: 1, + pageName: "postmaker.dev", + logoUrl: "/logo.svg", + logoUrlLabel: "Created with Postmaker.dev", + borderRadius: 8, + hasCardBorder: false, + isRtl: false, + title: "Prepare for interviews with ChatGPT", + items: [ + "Generate practice questions", + "Talk through your responses", + "Practice interview scenarios", + "Get ideas to build rapport", + "Do a mock interview" + ], + showCheckboxes: true, + }); + + const { + width: width, + height: height, + innerPaddingX, + innerPaddingY, + pageName, + logoUrl, + logoUrlLabel, + borderRadius, + hasCardBorder, + isRtl, + title, + items, + showCheckboxes, + scale, + exportScale, + } = state; + + // Load from localStorage on mount + // Save + useEffect(() => { + if (!loaded) return; + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + }, [loaded, state]); + + // Save to localStorage whenever any value changes + // Load + useEffect(() => { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved) { + setState(JSON.parse(saved)); + } + setLoaded(true); + }, []); + + const setStateValue = (key: string, value: any) => { + setState((prev) => ({ ...prev, [key]: value })); + }; + + const handleItemChange = (index: number, value: string) => { + const newItems = [...items]; + newItems[index] = value; + setStateValue("items", newItems); + }; + + const addItem = () => { + setStateValue("items", [...items, "New item"]); + }; + + const removeItem = (index: number) => { + const newItems = [...items]; + newItems.splice(index, 1); + setStateValue("items", newItems); + }; + + return ( +
+
+

Notebook Card

+

Page Name:

+ setStateValue(e.target.name, e.target.value)} + placeholder="Enter page name..." + /> +

Logo URL:

+ setStateValue(e.target.name, e.target.value)} + placeholder="Enter logo URL..." + /> +

Logo URL Label:

+ setStateValue(e.target.name, e.target.value)} + placeholder="Enter logo URL label..." + /> +

Title:

+ setStateValue(e.target.name, e.target.value)} + placeholder="Enter title..." + /> + +

Checklist Items:

+ {items.map((item, index) => ( +
+ handleItemChange(index, e.target.value)} + placeholder="Enter item text..." + /> + +
+ ))} + + +
+ +
+ +
+ + + setStateValue(e.target.name, Number(e.target.value)) + } + className="w-full" + /> +
+
+ + + setStateValue(e.target.name, Number(e.target.value)) + } + className="w-full" + /> +
+
+ + + setStateValue(e.target.name, Number(e.target.value) / 100) + } + className="w-full" + /> +
+
+ + + setStateValue(e.target.name, Number(e.target.value)) + } + className="w-full" + /> +
+ +
+ +
+
+ +
+ +
+ + + setStateValue(e.target.name, Number(e.target.value)) + } + className="w-full" + /> +
+
+ + + setStateValue(e.target.name, Number(e.target.value)) + } + className="w-full" + /> +
+
+ {/* Resizable Preview Panel */} +
+ +
+ +
+
+ +
+ ); +} \ No newline at end of file diff --git a/packages/ui/src/notebookcard-preview/notebook-preview.style.css b/packages/ui/src/notebookcard-preview/notebook-preview.style.css new file mode 100644 index 0000000..cd958b1 --- /dev/null +++ b/packages/ui/src/notebookcard-preview/notebook-preview.style.css @@ -0,0 +1,106 @@ +/* Main container with purple background */ +.notebook-preview-container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + /* background-color: #8b5cf6; bg-purple-500 */ + padding: 20px; + box-sizing: border-box; +} + +/* Notebook page with enhanced shadow */ +.notebook-page { + position: relative; + width: 100%; + height: 100%; + background-color: #f8f1e5; /* Keeping notebook color */ + background-image: linear-gradient(#e5dfd3 1px, transparent 1px); + background-size: 100% 20px; + padding: 30px 25px 25px 45px; + box-shadow: + 0 6px 12px rgba(0, 0, 0, 0.25), /* Stronger shadow for contrast */ + 0 10px 24px rgba(0, 0, 0, 0.15); + border-radius: 2px; + font-family: 'Reenie Beanie', cursive, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +} + +/* Spiral binding */ +.notebook-page::before { + content: ""; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 25px; + background: repeating-linear-gradient( + to bottom, + #d9c7a7, + #d9c7a7 18px, + #f8f1e5 18px, + #f8f1e5 36px + ); + border-right: 1px solid #d9c7a7; + box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1); +} + +/* Title styling */ +.notebook-title { + font-size: 22px; + font-weight: bold; + margin-bottom: 20px; + color: #333; + position: relative; + display: inline-block; +} + +.notebook-title::after { + content: ""; + position: absolute; + bottom: -5px; + left: 0; + width: 100%; + height: 2px; + background-color: #333; + transform: rotate(-1deg); +} + +/* Checklist items */ +.notebook-items { + list-style-type: none; + padding: 0; + margin: 0; +} + +.notebook-item { + font-size: 18px; + margin-bottom: 10px; + color: #333; + display: flex; + align-items: center; + position: relative; + left: -5px; + line-height: 1.3; +} + +.checkbox { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid #333; + margin-right: 12px; + position: relative; + border-radius: 3px; +} + +.checkmark { + position: absolute; + top: -2px; + left: -2px; + width: 16px; + height: 16px; + fill: #333; +} + + diff --git a/packages/ui/src/notebookcard-preview/notebook-preview.tsx b/packages/ui/src/notebookcard-preview/notebook-preview.tsx new file mode 100644 index 0000000..3fe4ef1 --- /dev/null +++ b/packages/ui/src/notebookcard-preview/notebook-preview.tsx @@ -0,0 +1,72 @@ +"use client"; + +import React from "react"; +import { CardContainer } from "../layouts/card-container"; +import "./notebook-preview.style.css"; + +interface NotebookPreviewProps { + scale?: number; + className?: string; + styles?: React.CSSProperties; + pageName?: string; + logoUrl?: string; + logoUrlLabel?: string; + + // component props + title: string; + items: string[]; + showCheckboxes?: boolean; +} + +export const NotebookPreview: React.FC = ({ + scale = 1, + className = "", + styles = {}, + pageName, + logoUrl = "", + logoUrlLabel = "", + + // component props: + title = "Prepare for interviews with ChatGPT", + items = [ + "Generate practice questions", + "Talk through your responses", + "Practice interview scenarios", + "Get ideas to build rapport", + "Do a mock interview" + ], + showCheckboxes = true, +}) => { + return ( + +
+
+

{title}

+
    + {items.map((item, index) => ( +
  • + {showCheckboxes && ( + + + + + + )} + {item} +
  • + ))} +
+ +
+
+
+ ); +}; \ No newline at end of file From 4cefeb5b82d295133ad60c2f4222a291aec7c487 Mon Sep 17 00:00:00 2001 From: Khalidnoori568 Date: Tue, 19 Aug 2025 15:00:10 +0430 Subject: [PATCH 2/2] new update --- apps/web/app/page.tsx | 54 +- apps/web/components/common/index.tsx | 136 +++++ apps/web/components/gradient-editor.tsx | 509 ++++++++-------- apps/web/components/notebook-editor.tsx | 379 ++++++------ apps/web/components/twitter-editor.tsx | 443 -------------- apps/web/components/ui/aspect-ratio.tsx | 7 + apps/web/components/ui/input.tsx | 22 + apps/web/components/ui/slider.tsx | 28 + apps/web/components/ui/switch.tsx | 29 + apps/web/components/ui/tabs.tsx | 57 ++ apps/web/components/ui/textarea.tsx | 22 + apps/web/components/ui/toggle.tsx | 45 ++ apps/web/components/ui/use-mobile.tsx | 19 + apps/web/components/x-editor.tsx | 544 ++++++++++++++++++ apps/web/lib/cn.ts | 36 ++ apps/web/package.json | 12 +- .../gradient-preview/PastelGradientCanvas.tsx | 1 + .../src/gradient-preview/gradient-preview.tsx | 5 +- .../notebook-preview.style.css | 4 +- .../notebookcard-preview/notebook-preview.tsx | 1 - .../x-preview.style.css} | 74 ++- .../x-preview.tsx} | 144 +++-- pnpm-lock.yaml | 458 +++++++++++++++ 23 files changed, 2037 insertions(+), 992 deletions(-) create mode 100644 apps/web/components/common/index.tsx delete mode 100644 apps/web/components/twitter-editor.tsx create mode 100644 apps/web/components/ui/aspect-ratio.tsx create mode 100644 apps/web/components/ui/input.tsx create mode 100644 apps/web/components/ui/slider.tsx create mode 100644 apps/web/components/ui/switch.tsx create mode 100644 apps/web/components/ui/tabs.tsx create mode 100644 apps/web/components/ui/textarea.tsx create mode 100644 apps/web/components/ui/toggle.tsx create mode 100644 apps/web/components/ui/use-mobile.tsx create mode 100644 apps/web/components/x-editor.tsx create mode 100644 apps/web/lib/cn.ts rename packages/ui/src/{twitter-preview/twitter-preview.style.css => x-preview/x-preview.style.css} (63%) rename packages/ui/src/{twitter-preview/twitter-preview.tsx => x-preview/x-preview.tsx} (50%) diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index f7420fa..cbff035 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,10 +1,11 @@ "use client"; import { MainHomeLayout } from "@repo/web-ui/layout"; -import React, { useState } from "react"; +import React, { useCallback, Suspense } from "react"; +import { useSearchParams, useRouter } from "next/navigation"; import "./page.module.css"; import QuizMarkdownEditor from "../components/markdown-editor"; import ChatgptEditor from "../components/chatgpt-editor"; -import TwitterEditor from "../components/twitter-editor"; +import XEditor from "../components/x-editor"; import NotebookEditor from "../components/notebook-editor"; import GradientEditor from "../components/gradient-editor"; import { CardSizeProvider } from "@repo/ui/context/CardSizeContext"; @@ -18,33 +19,50 @@ const tabs = [ key: "chatgpt", label: "ChatGPT Card", }, - // { - // key: "gradient", - // label: "Gradient Card", - // }, { - key: "twitter", - label: "Twitter Card", + key: "gradient", + label: "Gradient Card", + }, + { + key: "x", + label: "X Card", }, { key: "notebook", label: "Notebook Card", - } + }, + ]; -export default function Home() { - const [tab, setTab] = useState("markdown"); +function HomeContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + + const currentTab = searchParams.get("t") || "markdown"; + + const setTab = useCallback((newTab: string) => { + const params = new URLSearchParams(searchParams.toString()); + params.set("t", newTab); + router.push(`?${params.toString()}`); + }, [searchParams, router]); return ( - + - {tab === "markdown" ? : null} - {tab === "chatgpt" ? : null} - {/* {tab === "gradient" ? : null} */} - {tab === "twitter" ? : null} - {tab === "notebook" ? : null} - + {currentTab === "markdown" ? : null} + {currentTab === "chatgpt" ? : null} + {currentTab === "gradient" ? : null} + {currentTab === "x" ? : null} + {currentTab === "notebook" ? : null} ); +} + +export default function Home() { + return ( + Loading...}> + + + ); } \ No newline at end of file diff --git a/apps/web/components/common/index.tsx b/apps/web/components/common/index.tsx new file mode 100644 index 0000000..6452dd0 --- /dev/null +++ b/apps/web/components/common/index.tsx @@ -0,0 +1,136 @@ +"use client"; +import { Slider } from "../ui/slider"; +import { Input } from "../ui/input"; +import { Textarea } from "../ui/textarea"; +import { cn } from "../../lib/cn"; + +function SliderBox({ + keyName, + value, + label, + min, + max, + unit, + widthWrapper = false, + formatValue = false, + setStateValue, +}: { + keyName: string; + value: number; + label: string; + min: number; + max: number; + unit?: string; + formatValue?: boolean; + widthWrapper?: boolean; + setStateValue: (name: string, value: any) => void; +}) { + const comp = ( + <> + + setStateValue(keyName, val)} + className="w-full" + /> + + ); + + if (widthWrapper) { + return {comp}; + } + return comp; +} + +function DrawInput({ + keyName, + value, + placeholder, + label, + onChange: setStateValue, + isTextArea = false, + type = "text", + className = "", +}: { + keyName: string; + value: string; + placeholder?: string; + label?: React.ReactNode; + onChange: (name: string, value: any) => void; + isTextArea?: boolean; + type?: string; + className?: string; +}) { + return ( +
+ + {isTextArea ? ( +