Compare commits
21 Commits
97b049376a
...
f98c6d235e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f98c6d235e | ||
| 56050a6d59 | |||
|
|
ecac45ca9c | ||
| 23ab85109f | |||
|
|
2dc427259c | ||
|
|
1c2a2642aa | ||
| 5717a1058a | |||
|
|
82856a6779 | ||
|
|
7d7412bbc8 | ||
|
|
e89568fece | ||
|
|
ba1df8a261 | ||
|
|
e61e5e7ce5 | ||
|
|
4ac05d5079 | ||
|
|
24d95e4ac4 | ||
|
|
039c34ffdb | ||
|
|
3de81bd07a | ||
|
|
b74bb4e0eb | ||
|
|
7a63c50eff | ||
|
|
3225ed2394 | ||
|
|
b7200cc1fd | ||
|
|
a5364ba345 |
24
app/creator/layout.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import SideNav from "@/components/custom/Side_Nav";
|
||||
import Navbar from "@/components/custom/Nav_bar";
|
||||
|
||||
export default function CreatorLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<div className="flex min-h-screen">
|
||||
<div className="fixed left-0 top-0 h-screen">
|
||||
<SideNav />
|
||||
</div>
|
||||
<div className="flex flex-col w-full ml-[380px]">
|
||||
<div className="fixed top-0 right-0 left-[380px] z-10">
|
||||
<Navbar />
|
||||
</div>
|
||||
<main className="flex-1 p-6 bg-[#F3F3F3] mt-[75px]">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
7
app/creator/page.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
export default function CreatorPage() {
|
||||
return (
|
||||
<div className="text-black">
|
||||
<h1>Creator</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
7
app/creator/studio/page.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import PlayGround from "@/components/custom/Play_Ground";
|
||||
|
||||
export default function PlayGroundPage() {
|
||||
return (
|
||||
<PlayGround />
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
|
||||
import TrashCards, { Trash } from "../components/cards/TrashCards";
|
||||
import EmptyRecords from "../components/common/EmptyRecords";
|
||||
import TrashCards, { Trash } from "@/app/components/cards/TrashCards";
|
||||
import EmptyRecords from "@/app/components/common/EmptyRecords";
|
||||
|
||||
const TrashPage = () => {
|
||||
const trashData: Trash[] = [
|
||||
@ -8,6 +8,7 @@
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--color-sideNavColor:#010313;
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
|
||||
94
components/custom/Frame.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image';
|
||||
import { CopyPlus, Trash2, Plus, X } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
interface Card {
|
||||
id: number;
|
||||
isEditing: boolean;
|
||||
}
|
||||
|
||||
export default function Frame() {
|
||||
const [cards, setCards] = useState<Card[]>([]);
|
||||
const [nextId, setNextId] = useState(1);
|
||||
|
||||
const handleAddCard = () => {
|
||||
setCards([...cards, { id: nextId, isEditing: true }]);
|
||||
setNextId(nextId + 1);
|
||||
};
|
||||
|
||||
const handleDeleteCard = (id: number) => {
|
||||
setCards(cards.filter(card => card.id !== id));
|
||||
};
|
||||
|
||||
const totalPages = cards.length + 1;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center">
|
||||
|
||||
<div className="w-[450px] flex items-center justify-between px-4 py-2 mb-2" style={{ minHeight: 40 }}>
|
||||
<span className="text-[#2633B9] text-[14px] font-[#1A237E400]">Page 1/{totalPages}</span>
|
||||
<div className="flex gap-2">
|
||||
<CopyPlus
|
||||
size={18}
|
||||
className="cursor-pointer text-[#1A237E] hover:text-blue-600"
|
||||
onClick={handleAddCard}
|
||||
/>
|
||||
<Trash2
|
||||
size={18}
|
||||
className="cursor-pointer text-[#1A237E] hover:text-red-600"
|
||||
onClick={() => handleDeleteCard(0)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex flex-col items-center gap-8">
|
||||
|
||||
<div className="w-[450px] flex flex-col items-center">
|
||||
<div className="w-[410px] h-[510px] flex items-center justify-center">
|
||||
<Image src="/pdf-image.png" alt="Excerpt Card" width={410} height={510} style={{ objectFit: 'contain' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{cards.map((card, index) => (
|
||||
<div key={card.id} className="w-[450px] flex flex-col items-center">
|
||||
<div className="w-full flex items-center justify-between px-4 py-2 mb-2" style={{ minHeight: 40 }}>
|
||||
<span className="text-[#2633B9] text-[14px] font-[#1A237E400]">Page {index + 2}/{totalPages}</span>
|
||||
<div className="flex gap-2">
|
||||
<CopyPlus
|
||||
size={18}
|
||||
className="cursor-pointer text-[#1A237E] hover:text-blue-600"
|
||||
onClick={handleAddCard}
|
||||
/>
|
||||
<Trash2
|
||||
size={18}
|
||||
className="cursor-pointer text-[#1A237E] hover:text-red-600"
|
||||
onClick={() => handleDeleteCard(card.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[410px] h-[510px] bg-white rounded-lg shadow-md">
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
<div className="w-[450px] flex justify-center">
|
||||
<Button
|
||||
onClick={handleAddCard}
|
||||
variant="outline"
|
||||
className="flex justify-center items-center gap-2 py-2 text-[#1A237E] font-medium bg-white hover:bg-blue-50 transition-colors shadow-sm"
|
||||
style={{ width: 410 }}
|
||||
>
|
||||
<Plus size={20} className="text-[#1A237E]" />
|
||||
<span className="text-center text-[16px] font-[400]">Add new page</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
components/custom/Nav_bar.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { Bell } from 'lucide-react';
|
||||
import { Search } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
export default function Navbar() {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', padding: '24px 40px', background: 'white', width: '100%', height: '75px' }}>
|
||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'flex-end', marginRight: 120 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', background: '#F2F4F8', borderRadius: '10px', padding: '8px 16px', minWidth: 350 }}>
|
||||
<Search size={20} style={{ color: '#6B7280', marginRight: 8 }} />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search ebooks, folders, and uploads"
|
||||
className="bg-transparent text-[#6B7280] text-base w-[250px] focus-visible:ring-0 focus-visible:ring-offset-0 border-0 focus:border-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 24 }}>
|
||||
<div style={{ position: 'relative', cursor: 'pointer' }}>
|
||||
<Bell size={22} style={{ color: '#222' }} />
|
||||
<span style={{ position: 'absolute', top: -2, right: 0, width: 9, height: 9, background: '#FF3B30', borderRadius: '50%', border: '1.5px solid white' }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
|
||||
<div style={{ width: 32, height: 32, borderRadius: '50%', background: '#FFD600', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 600, color: '#222', fontSize: 14, position: 'relative', zIndex: 1 }}>
|
||||
D
|
||||
</div>
|
||||
<div style={{ width: 32, height: 32, borderRadius: '50%', background: 'white', border: '1px solid #E5E7EB', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20, color: '#222', cursor: 'pointer', marginLeft: -8, position: 'relative', zIndex: 0 }}>
|
||||
+
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
components/custom/Play_Ground.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import Frame from "./Frame";
|
||||
|
||||
export default function PlayGround() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Frame />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
102
components/custom/Recent_Design.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import Image from "next/image";
|
||||
import { ExternalLink, Ellipsis, Trash2 } from 'lucide-react';
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
// Sample data for recent designs
|
||||
const recentDesigns = [
|
||||
{
|
||||
icon: "/recent-image-1.png",
|
||||
title: "Good morning Gabe ...",
|
||||
},
|
||||
{
|
||||
icon: "/recent-image-2.png",
|
||||
title: "Daphne's first eBook...",
|
||||
},
|
||||
{
|
||||
icon: "/recent-image-3.png",
|
||||
title: "Story of my life (Story...",
|
||||
},
|
||||
{
|
||||
icon: "/recent-image-4.png",
|
||||
title: "Good morning Gabe ...",
|
||||
},
|
||||
{
|
||||
icon: "/recent-image-5.png",
|
||||
title: "A fantastic saga, the...",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* RecentDesign Component
|
||||
* Displays a sidebar with recent designs and a trash section
|
||||
*/
|
||||
export default function RecentDesign() {
|
||||
return (
|
||||
<div className="bg-white border-t md:border-t-0 md:border-r border-[#D9D7D7] w-full md:w-[300px] flex flex-col h-auto md:h-screen justify-between p-4 md:px-4 md:pt-6 md:pb-4">
|
||||
{/* Logo and Title Section */}
|
||||
<div>
|
||||
<div className="flex flex-col items-center mb-6 md:mb-12">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Wodey logo"
|
||||
width={157}
|
||||
height={53}
|
||||
className="w-[120px] md:w-[157px] h-auto md:h-[53px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Recent Designs Header */}
|
||||
<div className="text-[#27275A] text-[12px] font-[400] mb-2 pl-1">Recent designs</div>
|
||||
|
||||
|
||||
<div className="grid grid-cols-2 md:flex md:flex-col gap-2">
|
||||
{recentDesigns.map((design, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center bg-white rounded-lg py-2 px-2 hover:bg-[#F5F6FA] group transition cursor-pointer"
|
||||
>
|
||||
|
||||
<div className="w-6 h-6 md:w-8 md:h-8 flex items-center justify-center mr-2">
|
||||
<Image
|
||||
src={design.icon}
|
||||
alt="icon"
|
||||
width={28}
|
||||
height={28}
|
||||
className="w-5 h-5 md:w-7 md:h-7"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Design Title */}
|
||||
<span className="flex-1 text-[10px] text-[#27275A] truncate max-w-[100px] md:max-w-[120px] font-[400]">
|
||||
{design.title}
|
||||
</span>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="ml-auto">
|
||||
<Button variant="ghost" size="icon" className="h-10 w-10 p-1">
|
||||
<div className="flex items-center gap-0.5 md:gap-1 pr-5">
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 md:h-8 md:w-8 p-0 hover:bg-[#e6e6e8]">
|
||||
<ExternalLink className="text-[#27275A] bg-[#f2f2f3] border border-gray-200 rounded-[4px] p-1 w-7 h-7 md:w-8 md:h-8" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 md:h-8 md:w-8 p-0 hover:bg-[#e6e6e8]">
|
||||
<Ellipsis className="text-[#27275A] bg-[#f2f2f3] border border-gray-200 rounded-[4px] p-1 w-7 h-7 md:w-8 md:h-8" />
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Trash Section */}
|
||||
<Button variant="ghost" className="flex items-center gap-2 p-2 hover:bg-[#F5F6FA] rounded-lg mt-4 md:mt-0 w-full justify-start">
|
||||
<Link href="/creator/trash" className="flex items-center gap-2">
|
||||
<Trash2 className="text-[#27275A] w-[16px] md:w-[20px]" />
|
||||
<span className="text-[#27275A] text-[12px] md:text-[14px] font-[400]">Trash</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
components/custom/Side_Nav.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import Sidebar from "./Side_bar";
|
||||
import RecentDesign from "./Recent_Design";
|
||||
|
||||
export default function SideNav() {
|
||||
return (
|
||||
<div className="flex h-screen">
|
||||
<div className="h-full">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div className="h-full">
|
||||
<RecentDesign />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
77
components/custom/Side_bar.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Menu } from "lucide-react";
|
||||
|
||||
|
||||
const links = [
|
||||
{
|
||||
icon: "/home-icon.png",
|
||||
label: "Home",
|
||||
path: "/"
|
||||
},
|
||||
{
|
||||
icon: "/design-icon.png",
|
||||
label: "Design St.",
|
||||
path: "/design"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export default function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={`bg-[#050616] w-full md:w-[80px] h-auto md:h-screen transition-all duration-300 ease-in-out ${isMenuOpen ? 'md:w-[200px]' : ''}`}>
|
||||
<div className="flex md:flex-col items-center justify-center gap-4 p-4 md:pt-10">
|
||||
{/* Menu Icon */}
|
||||
<div className="flex md:flex-col items-center gap-4 md:gap-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="mb-5 hover:bg-[#1a1c2b]/50"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
>
|
||||
<Menu className="h-6 w-6 text-white" />
|
||||
</Button>
|
||||
|
||||
{/* Navigation Links */}
|
||||
{links.map((link) => (
|
||||
<div
|
||||
key={link.path}
|
||||
className={`flex flex-col items-center cursor-pointer group`}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={`p-2 rounded-lg transition-colors duration-200 ${
|
||||
pathname === link.path ? 'bg-[#1a1c2b]' : 'group-hover:bg-[#1a1c2b]/50'
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={link.icon}
|
||||
alt={link.label}
|
||||
width={24}
|
||||
height={24}
|
||||
className="group-hover:opacity-80 transition-opacity duration-200"
|
||||
priority={link.path === "/"} // Prioritize loading home icon
|
||||
/>
|
||||
</Button>
|
||||
|
||||
{/* Link Label */}
|
||||
<p className={`text-white text-xs mt-1 transition-all duration-300 ease-in-out ${
|
||||
isMenuOpen ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-4'
|
||||
} group-hover:opacity-80`}>
|
||||
{link.label}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
5865
package-lock.json
generated
Normal file
@ -9,14 +9,14 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.503.0",
|
||||
"next": "15.3.1",
|
||||
"@radix-ui/react-avatar": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.0",
|
||||
"@radix-ui/react-tabs": "^1.1.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"iconsax-react": "^0.0.8",
|
||||
"lucide-react": "^0.503.0",
|
||||
"next": "15.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwind-merge": "^3.2.0"
|
||||
@ -38,5 +38,6 @@
|
||||
"sharp",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.9.0+sha512.0486e394640d3c1fb3c9d43d49cf92879ff74f8516959c235308f5a8f62e2e19528a65cdc2a3058f587cde71eba3d5b56327c8c33a97e4c4051ca48a10ca2d5f"
|
||||
}
|
||||
|
||||
BIN
public/design-icon.png
Normal file
|
After Width: | Height: | Size: 312 B |
BIN
public/home-icon.png
Normal file
|
After Width: | Height: | Size: 366 B |
BIN
public/logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
public/menu-icon.png
Normal file
|
After Width: | Height: | Size: 208 B |
BIN
public/pdf-image.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
public/recent-image-1.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/recent-image-2.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/recent-image-3.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/recent-image-4.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/recent-image-5.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |