popsite-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/App.jsx +95 -0
- package/README.md +92 -0
- package/components/layout/PortalHeader.jsx +18 -0
- package/components/layout/SystemSidebar.jsx +33 -0
- package/components/modules/AnalyticsDashboardModule.jsx +17 -0
- package/components/modules/ChatMessagingModule.jsx +17 -0
- package/components/modules/EcommerceStoreModule.jsx +17 -0
- package/components/modules/EventTicketBookingModule.jsx +17 -0
- package/components/modules/FlightBookingModule.jsx +17 -0
- package/components/modules/FoodOrderingModule.jsx +17 -0
- package/components/modules/HospitalAppointmentModule.jsx +17 -0
- package/components/modules/HotelBookingModule.jsx +17 -0
- package/components/modules/InvoiceBillingModule.jsx +17 -0
- package/components/modules/LibraryManagementModule.jsx +17 -0
- package/components/modules/ModuleContentDeck.jsx +44 -0
- package/components/modules/MovieBookingModule.jsx +17 -0
- package/components/modules/QuizExamModule.jsx +17 -0
- package/components/modules/StudentRegistrationModule.jsx +17 -0
- package/components/modules/SystemModuleRenderer.jsx +19 -0
- package/components/modules/SystemModuleTemplate.jsx +62 -0
- package/components/modules/SystemVisualWidget.jsx +123 -0
- package/components/modules/moduleContentMap.js +238 -0
- package/components/modules/moduleEnhancementsMap.js +439 -0
- package/components/modules/systemModuleMap.js +31 -0
- package/components/system/DynamicSystemForm.jsx +154 -0
- package/components/system/SystemHero.jsx +21 -0
- package/components/system/SystemSummaryCard.jsx +53 -0
- package/data/systems/analyticsDashboard.js +48 -0
- package/data/systems/chatMessaging.js +43 -0
- package/data/systems/ecommerceStore.js +50 -0
- package/data/systems/eventTicketBooking.js +50 -0
- package/data/systems/flightBooking.js +38 -0
- package/data/systems/foodOrdering.js +48 -0
- package/data/systems/hospitalAppointment.js +50 -0
- package/data/systems/hotelBooking.js +38 -0
- package/data/systems/index.js +31 -0
- package/data/systems/invoiceBilling.js +50 -0
- package/data/systems/libraryManagement.js +43 -0
- package/data/systems/movieBooking.js +48 -0
- package/data/systems/quizExam.js +38 -0
- package/data/systems/studentRegistration.js +43 -0
- package/dist/popsite-ui.es.js +4368 -0
- package/dist/popsite-ui.umd.js +60 -0
- package/dist/style.css +1 -0
- package/index.html +13 -0
- package/library/index.js +20 -0
- package/main.jsx +15 -0
- package/package.json +40 -0
- package/src/App.jsx +12 -0
- package/src/components/modules/AnalyticsDashboardModule.jsx +224 -0
- package/src/components/modules/ChatMessagingModule.jsx +294 -0
- package/src/components/modules/EcommerceStoreModule.jsx +405 -0
- package/src/components/modules/EventTicketBookingModule.jsx +253 -0
- package/src/components/modules/FlightBookingModule.jsx +399 -0
- package/src/components/modules/FoodOrderingModule.jsx +316 -0
- package/src/components/modules/HospitalAppointmentModule.jsx +267 -0
- package/src/components/modules/HotelBookingModule.jsx +317 -0
- package/src/components/modules/InvoiceBillingModule.jsx +302 -0
- package/src/components/modules/LandingPageModule.jsx +185 -0
- package/src/components/modules/LibraryManagementModule.jsx +189 -0
- package/src/components/modules/MovieBookingModule.jsx +337 -0
- package/src/components/modules/QuizExamModule.jsx +255 -0
- package/src/components/modules/StudentRegistrationModule.jsx +292 -0
- package/src/components/system/SystemHero.jsx +44 -0
- package/src/components/system/SystemSummaryCard.jsx +29 -0
- package/src/components/system/Toast.jsx +69 -0
- package/src/data/systems/analyticsDashboard.js +32 -0
- package/src/data/systems/chatMessaging.js +59 -0
- package/src/data/systems/ecommerceStore.js +84 -0
- package/src/data/systems/eventBooking.js +33 -0
- package/src/data/systems/flightBooking.js +59 -0
- package/src/data/systems/foodOrdering.js +48 -0
- package/src/data/systems/hospitalAppointment.js +48 -0
- package/src/data/systems/hotelBooking.js +59 -0
- package/src/data/systems/invoiceBilling.js +19 -0
- package/src/data/systems/landingPage.js +29 -0
- package/src/data/systems/libraryManagement.js +17 -0
- package/src/data/systems/movieBooking.js +49 -0
- package/src/data/systems/quizExam.js +31 -0
- package/src/data/systems/studentRegistration.js +9 -0
- package/src/index.js +22 -0
- package/src/main.jsx +10 -0
- package/src/styles.css +296 -0
- package/styles.css +820 -0
- package/utils/systemEngine.js +128 -0
- package/vite.config.js +8 -0
- package/vite.lib.config.js +27 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { landingPageMockData } from '../../data/systems/landingPage';
|
|
3
|
+
|
|
4
|
+
export function LandingPageModule() {
|
|
5
|
+
const [currentView, setCurrentView] = useState('hero'); // hero, features, testimonials, pricing
|
|
6
|
+
|
|
7
|
+
// Animated counters
|
|
8
|
+
const [userCount, setUserCount] = useState(0);
|
|
9
|
+
const [deployCount, setDeployCount] = useState(0);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (currentView !== 'hero') return;
|
|
13
|
+
|
|
14
|
+
// Animate counters on mount
|
|
15
|
+
const duration = 2000;
|
|
16
|
+
const steps = 60;
|
|
17
|
+
const userStep = landingPageMockData.stats.users / steps;
|
|
18
|
+
const deployStep = landingPageMockData.stats.deployments / steps;
|
|
19
|
+
|
|
20
|
+
let currentStep = 0;
|
|
21
|
+
|
|
22
|
+
const timer = setInterval(() => {
|
|
23
|
+
currentStep++;
|
|
24
|
+
if (currentStep <= steps) {
|
|
25
|
+
setUserCount(Math.floor(userStep * currentStep));
|
|
26
|
+
setDeployCount(Math.floor(deployStep * currentStep));
|
|
27
|
+
} else {
|
|
28
|
+
setUserCount(landingPageMockData.stats.users);
|
|
29
|
+
setDeployCount(landingPageMockData.stats.deployments);
|
|
30
|
+
clearInterval(timer);
|
|
31
|
+
}
|
|
32
|
+
}, duration / steps);
|
|
33
|
+
|
|
34
|
+
return () => clearInterval(timer);
|
|
35
|
+
}, [currentView]);
|
|
36
|
+
|
|
37
|
+
const renderNav = () => (
|
|
38
|
+
<nav className="pk-nav pk-glass">
|
|
39
|
+
<div className="pk-heading-md pk-gradient-text" style={{ cursor: 'pointer' }} onClick={() => setCurrentView('hero')}>
|
|
40
|
+
PopSite
|
|
41
|
+
</div>
|
|
42
|
+
<div style={{ display: 'flex', gap: '1.5rem' }} className="pk-hidden-mobile">
|
|
43
|
+
<button style={{ background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: currentView === 'hero' ? 'var(--pk-primary)' : 'var(--pk-text-main)' }} onClick={() => setCurrentView('hero')}>Home</button>
|
|
44
|
+
<button style={{ background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: currentView === 'features' ? 'var(--pk-primary)' : 'var(--pk-text-main)' }} onClick={() => setCurrentView('features')}>Features</button>
|
|
45
|
+
<button style={{ background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: currentView === 'testimonials' ? 'var(--pk-primary)' : 'var(--pk-text-main)' }} onClick={() => setCurrentView('testimonials')}>Testimonials</button>
|
|
46
|
+
<button style={{ background: 'none', border: 'none', cursor: 'pointer', fontWeight: 600, color: currentView === 'pricing' ? 'var(--pk-primary)' : 'var(--pk-text-main)' }} onClick={() => setCurrentView('pricing')}>Pricing</button>
|
|
47
|
+
</div>
|
|
48
|
+
<button className="pk-btn pk-btn-primary" onClick={() => setCurrentView('pricing')}>Get Started</button>
|
|
49
|
+
</nav>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const renderHero = () => (
|
|
53
|
+
<div className="pk-container animate-fade-in" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '80vh', textAlign: 'center' }}>
|
|
54
|
+
{/* Floating CSS elements for showcase */}
|
|
55
|
+
<div style={{ position: 'absolute', top: '20%', left: '10%', width: '150px', height: '150px', background: 'radial-gradient(circle, var(--pk-primary), transparent 70%)', opacity: 0.2, filter: 'blur(20px)', animation: 'floatA 6s infinite ease-in-out' }}></div>
|
|
56
|
+
<div style={{ position: 'absolute', bottom: '20%', right: '10%', width: '250px', height: '250px', background: 'radial-gradient(circle, var(--pk-secondary), transparent 70%)', opacity: 0.15, filter: 'blur(30px)', animation: 'floatB 8s infinite ease-in-out reverse' }}></div>
|
|
57
|
+
|
|
58
|
+
<div className="pk-badge" style={{ marginBottom: '2rem', padding: '0.5rem 1rem', fontSize: '0.875rem' }}>✨ v1.0 is now live</div>
|
|
59
|
+
|
|
60
|
+
<h1 className="pk-heading-xl" style={{ fontSize: '4.5rem', marginBottom: '1.5rem', lineHeight: 1.1 }}>
|
|
61
|
+
<span className="pk-gradient-text">{landingPageMockData.hero.title}</span>
|
|
62
|
+
</h1>
|
|
63
|
+
|
|
64
|
+
<p className="pk-text-body" style={{ fontSize: '1.5rem', maxWidth: '800px', marginBottom: '3rem' }}>
|
|
65
|
+
{landingPageMockData.hero.subtitle}
|
|
66
|
+
</p>
|
|
67
|
+
|
|
68
|
+
<div style={{ display: 'flex', gap: '1rem', marginBottom: '4rem' }}>
|
|
69
|
+
<button className="pk-btn pk-btn-primary" style={{ fontSize: '1.125rem', padding: '1rem 2rem' }} onClick={() => setCurrentView('pricing')}>{landingPageMockData.hero.cta1}</button>
|
|
70
|
+
<button className="pk-btn pk-btn-outline" style={{ fontSize: '1.125rem', padding: '1rem 2rem' }} onClick={() => setCurrentView('features')}>{landingPageMockData.hero.cta2}</button>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Animated Counters */}
|
|
74
|
+
<div className="pk-grid pk-glass" style={{ gridTemplateColumns: 'repeat(3, 1fr)', gap: '4rem', padding: '2rem 4rem', borderRadius: 'var(--pk-radius-lg)', background: 'var(--pk-bg-glass)', display: 'inline-grid' }}>
|
|
75
|
+
<div>
|
|
76
|
+
<h2 className="pk-heading-lg" style={{ color: 'var(--pk-primary)' }}>{userCount.toLocaleString()}+</h2>
|
|
77
|
+
<p className="pk-text-muted pk-label">Developers</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div>
|
|
80
|
+
<h2 className="pk-heading-lg" style={{ color: 'var(--pk-secondary)' }}>{deployCount.toLocaleString()}+</h2>
|
|
81
|
+
<p className="pk-text-muted pk-label">Deployments</p>
|
|
82
|
+
</div>
|
|
83
|
+
<div>
|
|
84
|
+
<h2 className="pk-heading-lg" style={{ color: 'var(--pk-success)' }}>{landingPageMockData.stats.uptime}%</h2>
|
|
85
|
+
<p className="pk-text-muted pk-label">Uptime SLA</p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<style>{`
|
|
90
|
+
@keyframes floatA { 0%, 100% { transform: translateY(0px) scale(1); } 50% { transform: translateY(-30px) scale(1.1); } }
|
|
91
|
+
@keyframes floatB { 0%, 100% { transform: translateY(0px) scale(1); } 50% { transform: translateY(40px) scale(0.9); } }
|
|
92
|
+
`}</style>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const renderFeatures = () => (
|
|
97
|
+
<div className="pk-container animate-fade-in" style={{ padding: '4rem 2rem' }}>
|
|
98
|
+
<div style={{ textAlign: 'center', marginBottom: '4rem' }}>
|
|
99
|
+
<h2 className="pk-heading-xl" style={{ marginBottom: '1rem' }}>Architected for Speed</h2>
|
|
100
|
+
<p className="pk-text-body" style={{ fontSize: '1.25rem' }}>Everything you need to build scalable interfaces without the bloat.</p>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="pk-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))', gap: '3rem' }}>
|
|
104
|
+
{landingPageMockData.features.map(f => (
|
|
105
|
+
<div key={f.id} className="pk-card pk-card-interactive" style={{ padding: '3rem 2rem', textAlign: 'center', background: 'var(--pk-bg-glass)', border: '1px solid rgba(255,255,255,0.4)', backdropFilter: 'blur(10px)' }}>
|
|
106
|
+
<div style={{ fontSize: '3rem', marginBottom: '1.5rem', filter: 'drop-shadow(0 10px 10px rgba(0,0,0,0.1))' }}>{f.icon}</div>
|
|
107
|
+
<h3 className="pk-heading-md" style={{ marginBottom: '1rem' }}>{f.title}</h3>
|
|
108
|
+
<p className="pk-text-body">{f.description}</p>
|
|
109
|
+
</div>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const renderTestimonials = () => (
|
|
116
|
+
<div className="pk-container animate-fade-in" style={{ padding: '4rem 2rem' }}>
|
|
117
|
+
<div style={{ textAlign: 'center', marginBottom: '4rem' }}>
|
|
118
|
+
<h2 className="pk-heading-xl" style={{ marginBottom: '1rem' }}>Loved by Engineers</h2>
|
|
119
|
+
<p className="pk-text-body" style={{ fontSize: '1.25rem' }}>Here's what the community is saying.</p>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div className="pk-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '2rem' }}>
|
|
123
|
+
{landingPageMockData.testimonials.map(t => (
|
|
124
|
+
<div key={t.id} className="pk-card pk-card-interactive" style={{ padding: '2rem', display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
|
125
|
+
<div style={{ color: '#f59e0b', fontSize: '1.5rem', letterSpacing: '4px' }}>★★★★★</div>
|
|
126
|
+
<p className="pk-text-body" style={{ fontSize: '1.125rem', fontStyle: 'italic', flex: 1 }}>"{t.text}"</p>
|
|
127
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
|
128
|
+
<img src={t.avatar} alt={t.name} style={{ width: '50px', height: '50px', borderRadius: '50%', objectFit: 'cover' }} />
|
|
129
|
+
<div>
|
|
130
|
+
<h4 className="pk-label">{t.name}</h4>
|
|
131
|
+
<p className="pk-text-sm" style={{ color: 'var(--pk-primary)' }}>{t.role}</p>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const renderPricing = () => (
|
|
141
|
+
<div className="pk-container animate-fade-in" style={{ padding: '4rem 2rem' }}>
|
|
142
|
+
<div style={{ textAlign: 'center', marginBottom: '4rem' }}>
|
|
143
|
+
<h2 className="pk-heading-xl" style={{ marginBottom: '1rem' }}>Simple, transparent pricing</h2>
|
|
144
|
+
<p className="pk-text-body" style={{ fontSize: '1.25rem' }}>No hidden fees. Absolute freedom.</p>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div className="pk-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '2rem', alignItems: 'center' }}>
|
|
148
|
+
{landingPageMockData.pricing.map(p => (
|
|
149
|
+
<div key={p.id} className="pk-card" style={{ padding: '3rem 2rem', position: 'relative', border: p.featured ? '2px solid var(--pk-primary)' : '1px solid var(--pk-border)', transform: p.featured ? 'scale(1.05)' : 'scale(1)', zIndex: p.featured ? 10 : 1 }}>
|
|
150
|
+
{p.featured && (
|
|
151
|
+
<div style={{ position: 'absolute', top: 0, left: '50%', transform: 'translate(-50%, -50%)', background: 'var(--pk-primary)', color: 'white', padding: '0.25rem 1rem', borderRadius: 'var(--pk-radius-full)', fontWeight: 600, fontSize: '0.875rem' }}>
|
|
152
|
+
Most Popular
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
<h3 className="pk-heading-md" style={{ marginBottom: '0.5rem', textAlign: 'center' }}>{p.tier}</h3>
|
|
156
|
+
<div className="pk-heading-xl pk-gradient-text" style={{ textAlign: 'center', marginBottom: '2rem' }}>{p.price}</div>
|
|
157
|
+
|
|
158
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', marginBottom: '3rem' }}>
|
|
159
|
+
{p.features.map(f => (
|
|
160
|
+
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
|
161
|
+
<span style={{ color: 'var(--pk-success)' }}>✓</span>
|
|
162
|
+
<span className="pk-text-body">{f}</span>
|
|
163
|
+
</div>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<button className={`pk-btn pk-w-full ${p.featured ? 'pk-btn-primary' : 'pk-btn-outline'}`} style={{ padding: '1rem' }}>
|
|
168
|
+
{p.cta}
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
))}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div style={{ position: 'relative', minHeight: '100vh', backgroundColor: 'var(--pk-bg-main)', overflow: 'hidden' }}>
|
|
178
|
+
{renderNav()}
|
|
179
|
+
{currentView === 'hero' && renderHero()}
|
|
180
|
+
{currentView === 'features' && renderFeatures()}
|
|
181
|
+
{currentView === 'testimonials' && renderTestimonials()}
|
|
182
|
+
{currentView === 'pricing' && renderPricing()}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { libraryManagementMockData } from '../../data/systems/libraryManagement';
|
|
3
|
+
import { useToast } from '../system/Toast';
|
|
4
|
+
|
|
5
|
+
export function LibraryManagementModule() {
|
|
6
|
+
const [currentView, setCurrentView] = useState('catalog'); // catalog, dashboard
|
|
7
|
+
const [catalog, setCatalog] = useState(libraryManagementMockData.catalog);
|
|
8
|
+
const [myBooks, setMyBooks] = useState(libraryManagementMockData.myBooks);
|
|
9
|
+
|
|
10
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
|
|
13
|
+
const { showToast, ToastContainer } = useToast();
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const savedCat = localStorage.getItem('popsite_library_cat');
|
|
17
|
+
const savedMy = localStorage.getItem('popsite_library_my');
|
|
18
|
+
if (savedCat) try { setCatalog(JSON.parse(savedCat)); } catch(e){}
|
|
19
|
+
if (savedMy) try { setMyBooks(JSON.parse(savedMy)); } catch(e){}
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
localStorage.setItem('popsite_library_cat', JSON.stringify(catalog));
|
|
24
|
+
localStorage.setItem('popsite_library_my', JSON.stringify(myBooks));
|
|
25
|
+
}, [catalog, myBooks]);
|
|
26
|
+
|
|
27
|
+
const withDelay = (callback, delay = 800) => {
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
setTimeout(() => {
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
callback();
|
|
32
|
+
}, delay);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleBorrow = (book) => {
|
|
36
|
+
withDelay(() => {
|
|
37
|
+
// Calculate due date (14 days from now)
|
|
38
|
+
const due = new Date();
|
|
39
|
+
due.setDate(due.getDate() + 14);
|
|
40
|
+
|
|
41
|
+
const newRecord = {
|
|
42
|
+
book,
|
|
43
|
+
borrowDate: new Date().toISOString().split('T')[0],
|
|
44
|
+
dueDate: due.toISOString().split('T')[0]
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
setMyBooks([...myBooks, newRecord]);
|
|
48
|
+
setCatalog(catalog.map(b => b.id === book.id ? { ...b, available: false } : b));
|
|
49
|
+
showToast(`You have borrowed "${book.title}"`, 'success');
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleReturn = (record) => {
|
|
54
|
+
withDelay(() => {
|
|
55
|
+
setMyBooks(myBooks.filter(m => m.book.id !== record.book.id));
|
|
56
|
+
|
|
57
|
+
// If the book originally existed in our catalog state array, flip it back to available
|
|
58
|
+
const existsInCatalog = catalog.find(b => b.id === record.book.id);
|
|
59
|
+
if (existsInCatalog) {
|
|
60
|
+
setCatalog(catalog.map(b => b.id === record.book.id ? { ...b, available: true } : b));
|
|
61
|
+
} else {
|
|
62
|
+
// If it was a mock injected book, add it to catalog
|
|
63
|
+
setCatalog([...catalog, { ...record.book, available: true }]);
|
|
64
|
+
}
|
|
65
|
+
showToast(`Returned "${record.book.title}" successfully`, 'info');
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const isOverdue = (dueDateStr) => {
|
|
70
|
+
const due = new Date(dueDateStr);
|
|
71
|
+
const now = new Date();
|
|
72
|
+
return now > due;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const renderNav = () => (
|
|
76
|
+
<div className="pk-card" style={{ marginBottom: '2rem', padding: '1rem', display: 'flex', gap: '1rem' }}>
|
|
77
|
+
<button className={currentView === 'catalog' ? 'pk-btn pk-btn-primary' : 'pk-btn pk-btn-outline'} onClick={() => setCurrentView('catalog')}>Public Catalog</button>
|
|
78
|
+
<button className={currentView === 'dashboard' ? 'pk-btn pk-btn-primary' : 'pk-btn pk-btn-outline'} onClick={() => setCurrentView('dashboard')}>My Books ({myBooks.length})</button>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const renderCatalog = () => {
|
|
83
|
+
const filtered = catalog.filter(b =>
|
|
84
|
+
b.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
85
|
+
b.author.toLowerCase().includes(searchQuery.toLowerCase())
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="animate-fade-in">
|
|
90
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
|
|
91
|
+
<h2 className="pk-heading-lg">Browse Collection</h2>
|
|
92
|
+
<input type="text" className="pk-input" placeholder="Search title or author..." value={searchQuery} onChange={e => setSearchQuery(e.target.value)} />
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div className="pk-grid" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))' }}>
|
|
96
|
+
{filtered.map(book => (
|
|
97
|
+
<div key={book.id} className="pk-card pk-card-interactive" style={{ display: 'flex', flexDirection: 'column' }}>
|
|
98
|
+
<div style={{ height: '300px', width: '100%' }}>
|
|
99
|
+
<img src={book.cover} alt={book.title} style={{ width: '100%', height: '100%', objectFit: 'cover' }} onError={(e)=>{e.target.style.display='none'}} />
|
|
100
|
+
</div>
|
|
101
|
+
<div className="pk-card-body" style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
102
|
+
<span className="pk-badge" style={{ alignSelf: 'flex-start', marginBottom: '0.5rem', background: 'var(--pk-bg-main)', color: 'var(--pk-text-muted)' }}>{book.genre}</span>
|
|
103
|
+
<h3 className="pk-heading-md" style={{ marginBottom: '0.25rem' }}>{book.title}</h3>
|
|
104
|
+
<p className="pk-text-sm" style={{ marginBottom: '1rem', flex: 1 }}>{book.author}</p>
|
|
105
|
+
|
|
106
|
+
{book.available ? (
|
|
107
|
+
<button className="pk-btn pk-btn-primary pk-w-full" onClick={() => handleBorrow(book)}>Borrow Book</button>
|
|
108
|
+
) : (
|
|
109
|
+
<button className="pk-btn pk-btn-outline pk-w-full" disabled>Currently Rented</button>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const renderDashboard = () => (
|
|
120
|
+
<div className="animate-fade-in">
|
|
121
|
+
<h2 className="pk-heading-lg" style={{ marginBottom: '2rem' }}>My Bookshelf</h2>
|
|
122
|
+
|
|
123
|
+
{myBooks.length === 0 ? (
|
|
124
|
+
<div className="pk-empty-state">
|
|
125
|
+
<div className="pk-empty-icon">📚</div>
|
|
126
|
+
<h3 className="pk-heading-md">Your shelf is empty</h3>
|
|
127
|
+
<p className="pk-text-body">Go to the catalog to borrow some books.</p>
|
|
128
|
+
</div>
|
|
129
|
+
) : (
|
|
130
|
+
<div className="pk-grid pk-flex-col pk-gap-4">
|
|
131
|
+
{myBooks.map((record, i) => {
|
|
132
|
+
const overdue = isOverdue(record.dueDate);
|
|
133
|
+
return (
|
|
134
|
+
<div key={i} className="pk-card" style={{ display: 'flex', alignItems: 'center', gap: '1.5rem', padding: '1rem', borderLeft: overdue ? '4px solid var(--pk-danger)' : '1px solid var(--pk-border)' }}>
|
|
135
|
+
<div style={{ width: '80px', height: '110px', borderRadius: '4px', overflow: 'hidden' }}>
|
|
136
|
+
<img src={record.book.cover} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
137
|
+
</div>
|
|
138
|
+
<div style={{ flex: 1 }}>
|
|
139
|
+
<h3 className="pk-heading-md" style={{ marginBottom: '0.25rem' }}>{record.book.title}</h3>
|
|
140
|
+
<p className="pk-text-sm" style={{ marginBottom: '1rem' }}>{record.book.author}</p>
|
|
141
|
+
|
|
142
|
+
<div style={{ display: 'flex', gap: '2rem' }}>
|
|
143
|
+
<div>
|
|
144
|
+
<p className="pk-label pk-text-muted" style={{ fontSize: '0.75rem' }}>Borrowed</p>
|
|
145
|
+
<p className="pk-text-body" style={{ fontWeight: 600 }}>{record.borrowDate}</p>
|
|
146
|
+
</div>
|
|
147
|
+
<div>
|
|
148
|
+
<p className="pk-label pk-text-muted" style={{ fontSize: '0.75rem' }}>Due Date</p>
|
|
149
|
+
<p className="pk-text-body" style={{ fontWeight: 600, color: overdue ? 'var(--pk-danger)' : 'var(--pk-text-main)' }}>{record.dueDate}</p>
|
|
150
|
+
</div>
|
|
151
|
+
{overdue && (
|
|
152
|
+
<div style={{ alignSelf: 'flex-end', background: 'var(--pk-danger)', color: 'white', padding: '0.1rem 0.5rem', borderRadius: 'var(--pk-radius-sm)', fontSize: '0.75rem', fontWeight: 'bold' }}>OVERDUE</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
<button className="pk-btn pk-btn-outline" onClick={() => handleReturn(record)}>Return Book</button>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
})}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className="pk-container" style={{ minHeight: '100vh' }}>
|
|
167
|
+
{isLoading && (
|
|
168
|
+
<div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(255,255,255,0.7)', zIndex: 100, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backdropFilter: 'blur(4px)' }}>
|
|
169
|
+
<div className="pk-skeleton" style={{ width: '80px', height: '80px', borderRadius: '50%', marginBottom: '1rem' }}></div>
|
|
170
|
+
<h3 className="pk-heading-md">Syncing Library Database...</h3>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
<div style={{ marginBottom: '2rem' }}>
|
|
175
|
+
<h1 className="pk-heading-xl">{libraryManagementMockData.libraryName}</h1>
|
|
176
|
+
<p className="pk-text-body">Your portal to infinite knowledge.</p>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
{renderNav()}
|
|
180
|
+
|
|
181
|
+
<div style={{ position: 'relative' }}>
|
|
182
|
+
{currentView === 'catalog' && renderCatalog()}
|
|
183
|
+
{currentView === 'dashboard' && renderDashboard()}
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<ToastContainer />
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|