diff --git a/public/analyticsIcon.svg b/public/analyticsIcon.svg new file mode 100644 index 0000000..f717d2a --- /dev/null +++ b/public/analyticsIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/authorImg.png b/public/authorImg.png new file mode 100644 index 0000000..965599e Binary files /dev/null and b/public/authorImg.png differ diff --git a/public/authorImg2.png b/public/authorImg2.png new file mode 100644 index 0000000..b5c2e74 Binary files /dev/null and b/public/authorImg2.png differ diff --git a/public/authorImg3.png b/public/authorImg3.png new file mode 100644 index 0000000..df9e1e8 Binary files /dev/null and b/public/authorImg3.png differ diff --git a/public/authorImg4.png b/public/authorImg4.png new file mode 100644 index 0000000..7cf263b Binary files /dev/null and b/public/authorImg4.png differ diff --git a/public/backArrow.svg b/public/backArrow.svg new file mode 100644 index 0000000..0686f6c --- /dev/null +++ b/public/backArrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/bookCover.png b/public/bookCover.png new file mode 100644 index 0000000..a028b82 Binary files /dev/null and b/public/bookCover.png differ diff --git a/public/dashboardIcon.svg b/public/dashboardIcon.svg new file mode 100644 index 0000000..957ed57 --- /dev/null +++ b/public/dashboardIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/earningsIcon.svg b/public/earningsIcon.svg new file mode 100644 index 0000000..ad5b0eb --- /dev/null +++ b/public/earningsIcon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/headerImg.png b/public/headerImg.png new file mode 100644 index 0000000..69f462e Binary files /dev/null and b/public/headerImg.png differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..a9c8e60 Binary files /dev/null and b/public/logo.png differ diff --git a/public/manageIcon.svg b/public/manageIcon.svg new file mode 100644 index 0000000..43895a6 --- /dev/null +++ b/public/manageIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/notificationIcon.svg b/public/notificationIcon.svg new file mode 100644 index 0000000..d431897 --- /dev/null +++ b/public/notificationIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/profile.png b/public/profile.png new file mode 100644 index 0000000..fa42a33 Binary files /dev/null and b/public/profile.png differ diff --git a/public/profileIcon.svg b/public/profileIcon.svg new file mode 100644 index 0000000..035a658 --- /dev/null +++ b/public/profileIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/readersIcon.svg b/public/readersIcon.svg new file mode 100644 index 0000000..4144506 --- /dev/null +++ b/public/readersIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/signoutIcon.svg b/public/signoutIcon.svg new file mode 100644 index 0000000..71ecaa3 --- /dev/null +++ b/public/signoutIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/upload.png b/public/upload.png new file mode 100644 index 0000000..8ee7c29 Binary files /dev/null and b/public/upload.png differ diff --git a/public/verified.svg b/public/verified.svg new file mode 100644 index 0000000..6a70f63 --- /dev/null +++ b/public/verified.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/author/layout.tsx b/src/app/author/layout.tsx index 888fbef..fec8c4d 100644 --- a/src/app/author/layout.tsx +++ b/src/app/author/layout.tsx @@ -1,5 +1,7 @@ +"use client"; import React from 'react'; +import SideNavBar from '@/components/layout/Sidenavbar'; interface LayoutProps { children: React.ReactNode; @@ -7,14 +9,14 @@ interface LayoutProps { const Layout: React.FC = ({ children }) => { return ( -
-
-

Author Page

-
-
{children}
-
-

© {new Date().getFullYear()} ChainLib

-
+
+ {/* Sidebar */} + + + {/* Main Content */} +
+
{children}
+
); }; diff --git a/src/app/author/page.tsx b/src/app/author/page.tsx index 058e2b2..8eb93ff 100644 --- a/src/app/author/page.tsx +++ b/src/app/author/page.tsx @@ -1,13 +1,326 @@ +"use client"; +import { useState } from 'react'; +import Image from 'next/image'; +import Link from 'next/link'; +import { MoveLeft, Bell, BadgeCheck } from 'lucide-react'; +import { FiBook, FiDollarSign, FiUsers, FiEye } from 'react-icons/fi'; +import ProfileCompletionModal from '@/components/common/Modal'; +import SearchBar from '@/components/common/SearchBar'; +function AuthorDashboard() { + const [showProfileModal, setShowProfileModal] = useState(true); + const [showProfileAlert, setShowProfileAlert] = useState(true); + + // Mock data - would be fetched from API + const stats = { + booksPublished: 12, + totalEarning: 817.00, + totalReads: 12, + followers: 12 + }; + + const recentBooks = [ + { id: 1, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: true }, + { id: 2, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: true }, + { id: 3, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: false }, + { id: 4, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: true } + ]; + + const trendingBooks = [ + { id: 1, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: true }, + { id: 2, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: true }, + { id: 3, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: false }, + { id: 4, title: 'Native Invisibility', author: 'Darrin Collins', price: 10, rating: 4.5, imageUrl: '/bookCover.png', verified: true } + ]; + + const topAuthors = [ + { id: 1, name: 'Elizabeth Joe', imageUrl: '/authorImg.png', verified: true }, + { id: 2, name: 'Alex Paul', imageUrl: '/authorImg2.png', verified: true }, + { id: 3, name: 'Samson Tersoor', imageUrl: '/authorImg3.png', verified: true }, + { id: 4, name: 'Vamika Maya', imageUrl: '/authorImg4.png', verified: true } + ]; + return ( +
+
+ +
+ +
-function Page(){ - return( -

Page

- ) -} +
+ {/* Notification bell */} +
+ +
+ + {/* Profile */} +
+
+ Profile +
+
+

Joseph Yanum

+

@joeyanum

+
+ +
+
+
+ + + + {/* Header with Title */} +
+ {/* Background Image */} +
+ Header background +
+
+

Your Words

+

Deserve the World

+
+
+ + + +
+
+ + {/* Stats Cards */} +
+
+
+
+

Books Published

+

{stats.booksPublished}

+
+
+ +
+
+
+ +
+
+
+

Total Earning

+

${stats.totalEarning.toFixed(2)}

+
+
+ +
+
+
+
+
+
+

Total Reads

+

{stats.totalReads}

+
+
+ +
+
+
+ +
+
+
+

Followers

+

{stats.followers}

+
+
+ +
+
+
+
+ + {/* Profile Completion Alert */} + {showProfileAlert && ( +
+
+ + ! + +

+ Just a few more details and your profile will be complete! +
+ Let's get this done so you can enjoy all the features. +

+
+ +
+ )} + + {/* Recent Published Books */} +
+
+

Recent Published Books

+ + View All → + +
+
+ {recentBooks.map(book => ( +
+
+ {book.title} +
+
+

{book.title}

+
+

By {book.author}

+ {book.verified && ( +
+ Verified Author +
+ )} +
+
+

${book.price}

+
+ + {book.rating} +
+
+
+
+ ))} +
+
+ + {/* Trending Books */} +
+
+

Trending Books

+ + View All → + +
+
+ {trendingBooks.map(book => ( +
+
+ {book.title} +
+
+

{book.title}

+
+

By {book.author}

+ {book.verified && ( +
+ Verified Author +
+ )} +
+
+

${book.price}

+
+ + {book.rating} +
+
+
+
+ ))} +
+
+ + {/* Top Authors of the Week */} +
+
+

Top Authors for the Week

+ + View All → + +
+
+ {topAuthors.map(author => ( +
+ {/* Full size author image with overlay */} +
+ {author.name} + {/* Dark gradient overlay */} +
+
+ + {/* Author name and verified tag positioned at the bottom */} +
+
+

{author.name}

+
+ Verified Author +
+
+
+
+ ))} +
+
+ + {/* Profile Completion Modal */} + setShowProfileModal(false)} + /> +
+ ); +} -export default Page; \ No newline at end of file +export default AuthorDashboard; \ No newline at end of file diff --git a/src/app/author/publish/page.tsx b/src/app/author/publish/page.tsx index 159f2c1..b9d1117 100644 --- a/src/app/author/publish/page.tsx +++ b/src/app/author/publish/page.tsx @@ -1,15 +1,247 @@ +"use client" +import { useState } from "react" +import { FileUpload } from "@/components/common/Upload" +import Image from "next/image" +import { MoveLeft, Bell, BadgeCheck } from "lucide-react" +export default function page() { + const [seriesType, setSeriesType] = useState("new") + return ( +
+
+ {/* Header with profile and notification */} +
+
+ +

Manage Content

+
+
+ {/* Notification bell */} +
+ +
+ {/* Profile */} +
+
+ Profile +
+
+

Joseph Yanum

+

@joeyanum

+
+ +
+
+
+ {/* Step indicators - updated to match the image */} +
+
+ {/* Connecting lines */} +
-function Page(){ - return( -

Page

- ) -} + {/* Step 1 */} +
+
+ 1 +
+
Book Content
+
+ + {/* Step 2 */} +
+
+ 2 +
+
Book Details
+
+ + {/* Step 3 */} +
+
+ 3 +
+
Pricing
+
+
+
+ +
+

Series

+ +
+
+ {/* Cover Image Upload */} + +
+ +
+
+

Series Content

+ +
+
+ +
+ +
+ + + +
+
+
+ + {seriesType === "existing" && ( + <> + +
+
+ {/* This is a placeholder for the sunset image in the original */} +
+
+

Best of the Best

+

Current Series 4

+
+
+ + )} +
+
-export default Page; \ No newline at end of file +
+
+ + +
+ +
+ + +
+ +
+ +
+ +
+ + + +
+
+
+ +
+ + +
+
+ +
+ + +
+ + +
+ + +
+
+
+
+
+
+ ) +} diff --git a/src/components/common/Modal.tsx b/src/components/common/Modal.tsx index 86e0c4c..4ed36e3 100644 --- a/src/components/common/Modal.tsx +++ b/src/components/common/Modal.tsx @@ -1,10 +1,91 @@ +"use client"; +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { FiUser, FiX } from 'react-icons/fi'; -function Modal(){ - return( -

Modal

- ) +interface ProfileCompletionModalProps { + isOpen: boolean; + onClose: () => void; + redirectTo?: string; } +export default function ProfileCompletionModal({ + isOpen, + onClose, + redirectTo = '/author-profile/edit', +}: ProfileCompletionModalProps) { + const router = useRouter(); + const [isVisible, setIsVisible] = useState(isOpen); -export default Modal; \ No newline at end of file + useEffect(() => { + setIsVisible(isOpen); + }, [isOpen]); + + const handleClose = () => { + onClose(); + setIsVisible(false); + }; + + const handleComplete = () => { + handleClose(); + router.push(redirectTo); + }; + + // When modal is open, prevent body scrolling + useEffect(() => { + if (isVisible) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'unset'; + } + return () => { + document.body.style.overflow = 'unset'; + }; + }, [isVisible]); + + if (!isVisible) return null; + + return ( +
+
e.stopPropagation()} + > + + +
+
+ +
+

Unlock Your Full Potential

+

+ You're almost there! Complete your profile so we can tailor your + experience and connect you with the right opportunities. +

+
+ +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/common/SearchBar.tsx b/src/components/common/SearchBar.tsx index 6b19580..6f2586f 100644 --- a/src/components/common/SearchBar.tsx +++ b/src/components/common/SearchBar.tsx @@ -1,11 +1,37 @@ +"use client"; +import { useState } from 'react'; +import { FiSearch } from 'react-icons/fi'; - -function SearchBar(){ - return( -

Search bar

- ) +interface SearchBarProps { + placeholder?: string; + onSearch?: (query: string) => void; } +function SearchBar({ placeholder = "Search...", onSearch }: SearchBarProps) { + const [query, setQuery] = useState(''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (onSearch) { + onSearch(query); + } + }; + + return ( +
+
+ + setQuery(e.target.value)} + placeholder={placeholder} + className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ ); +} export default SearchBar; \ No newline at end of file diff --git a/src/components/common/Upload.tsx b/src/components/common/Upload.tsx new file mode 100644 index 0000000..5ed877c --- /dev/null +++ b/src/components/common/Upload.tsx @@ -0,0 +1,219 @@ +"use client" + +import { useState, useRef, type DragEvent, type ChangeEvent } from "react" +import Image from "next/image" + +interface FileUploadProps { + supportedFormats: string + icon: "file" | "image" + acceptedFileTypes: string[] + containerHeight?: string +} + +export function FileUpload({ + supportedFormats, + icon, + acceptedFileTypes, + containerHeight = "h-44" +}: FileUploadProps) { + const [isDragging, setIsDragging] = useState(false) + const [file, setFile] = useState(null) + const [preview, setPreview] = useState(null) + const fileInputRef = useRef(null) + + const handleDragOver = (e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragging(true) + } + + const handleDragLeave = (e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragging(false) + } + + const handleDrop = (e: DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragging(false) + + const droppedFiles = Array.from(e.dataTransfer.files) + + if (droppedFiles.length === 0) return + + const droppedFile = droppedFiles[0] + + // Check if file type is accepted + if (!acceptedFileTypes.includes(droppedFile.type)) { + alert( + `File type not supported. Please upload ${supportedFormats.toLowerCase().replace("supported format: ", "")}`, + ) + return + } + + setFile(droppedFile) + + // Create preview for images + if (droppedFile.type.startsWith("image/")) { + const reader = new FileReader() + reader.onload = () => { + setPreview(reader.result as string) + } + reader.readAsDataURL(droppedFile) + } else { + setPreview(null) + } + } + + const handleFileChange = (e: ChangeEvent) => { + const selectedFiles = e.target.files + + if (!selectedFiles || selectedFiles.length === 0) return + + const selectedFile = selectedFiles[0] + + // Check if file type is accepted + if (!acceptedFileTypes.includes(selectedFile.type)) { + alert( + `File type not supported. Please upload ${supportedFormats.toLowerCase().replace("supported format: ", "")}`, + ) + return + } + + setFile(selectedFile) + + // Create preview for images + if (selectedFile.type.startsWith("image/")) { + const reader = new FileReader() + reader.onload = () => { + setPreview(reader.result as string) + } + reader.readAsDataURL(selectedFile) + } else { + setPreview(null) + } + } + + const handleClick = () => { + fileInputRef.current?.click() + } + + const removeFile = () => { + setFile(null) + setPreview(null) + if (fileInputRef.current) { + fileInputRef.current.value = "" + } + } + + return ( +
+ {!file ? ( +
+
+ {icon === "image" ? ( +
+ Upload image +
+ ) : ( +
+ Upload file +
+ )} +
+

Drag and drop or Click to choose file from device

+

{supportedFormats}

+ +
+ ) : ( +
+
+ {preview ? ( +
+ Preview +
+ ) : ( +
+ + + + + + + +
+ )} +
+

{file.name}

+

{(file.size / 1024).toFixed(2)} KB

+
+ +
+
+ )} +

{supportedFormats}

+
+ ) +} diff --git a/src/components/layout/Sidenavbar.tsx b/src/components/layout/Sidenavbar.tsx index a6cf30e..f48fdc5 100644 --- a/src/components/layout/Sidenavbar.tsx +++ b/src/components/layout/Sidenavbar.tsx @@ -1,15 +1,167 @@ +"use client" +import type * as React from "react" +import { useState, useEffect } from "react" +import Link from "next/link" +import Image from "next/image" +import { usePathname } from "next/navigation" +import { Menu, X } from "lucide-react" +import { cn } from "../../lib/utils" -function SideNavBar (){ - return( -

Side bar

- ) +type SidebarItem = { + icon: string + label: string + href: string + badge?: number } +export function Sidebar() { + const pathname = usePathname() + // Start with sidebar closed on mobile by default + const [isOpen, setIsOpen] = useState(false) + const [isMobile, setIsMobile] = useState(true) + // Check if we're on mobile screen size + useEffect(() => { + const checkScreenSize = () => { + const mobile = window.innerWidth < 768 + setIsMobile(mobile) + setIsOpen(!mobile) // Open on desktop, closed on mobile + } + + // Initial check + checkScreenSize() + + // Add event listener + window.addEventListener('resize', checkScreenSize) + + // Cleanup + return () => window.removeEventListener('resize', checkScreenSize) + }, []) -export default SideNavBar; + const sidebarItems: SidebarItem[] = [ + { + icon: "/dashboardIcon.svg", + label: "Dashboard", + href: "/author", + }, + { + icon: "/manageIcon.svg", + label: "Manage Content", + href: "/author/publish", + }, + { + icon: "/earningsIcon.svg", + label: "Earnings", + href: "/author/earnings", + }, + { + icon: "/analyticsIcon.svg", + label: "Analytics panel", + href: "/author/analytics", + }, + { + icon: "/readersIcon.svg", + label: "Readers Feedback", + href: "/author/feedback", + }, + { + icon: "/notificationIcon.svg", + label: "Notification", + href: "/notifications", + badge: 1, + }, + { + icon: "/profileIcon.svg", + label: "Profile", + href: "/profile", + }, + { + icon: "/signoutIcon.svg", + label: "Sign Out", + href: "/signout", + }, + ] + return ( + <> + {/* Toggle button for mobile */} + + {/* Sidebar */} + + + {/* Overlay for mobile when sidebar is open */} + {isOpen && isMobile && ( +
setIsOpen(false)} + /> + )} + + ) +} +export default Sidebar; \ No newline at end of file