Book Managment Example

parent 1b0609b9
......@@ -8,6 +8,9 @@
"name": "react",
"version": "0.1.0",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"next": "15.2.2",
"react": "^19.0.0",
"react-dom": "^19.0.0"
......@@ -169,6 +172,48 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
"integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
"integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
"integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
"integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": ">=16.3"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
......@@ -3292,8 +3337,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
......@@ -3645,7 +3689,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
......@@ -3818,7 +3861,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
......@@ -4095,7 +4137,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
......@@ -4153,8 +4194,7 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
......
......@@ -9,15 +9,18 @@
"lint": "next lint"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"next": "15.2.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.2.2"
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.2.2",
"@eslint/eslintrc": "^3"
"tailwindcss": "^4"
}
}
import BookList from "@/app/components/booklist";
export default function Home() {
return (
<BookList/>
);
}
\ No newline at end of file
import { useState, useRef, useEffect } from 'react';
import Link from 'next/link';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser } from '@fortawesome/free-solid-svg-icons';
export default function Avatar() {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
// Close dropdown when clicking outside
useEffect(() => {
function handleClickOutside(event) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
// Function to handle logout
const handleLogout = () => {
// Add your logout logic here
console.log('Logging out...');
// For example: router.push('/login');
};
return (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center focus:outline-none"
>
<div className="h-8 w-8 rounded-full overflow-hidden border-2 border-gray-600">
{/* Replace with your user avatar or use a placeholder */}
<FontAwesomeIcon icon={faUser} />
</div>
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-10">
<Link
href="/profile/edit"
className="block px-4 py-2 text-gray-800 hover:bg-gray-100"
onClick={() => setIsOpen(false)}
>
Edit Profile
</Link>
<button
onClick={handleLogout}
className="block w-full text-left px-4 py-2 text-gray-800 hover:bg-gray-100"
>
Logout
</button>
</div>
)}
</div>
);
}
// Individual book card component
export default function Book ({ title, author }) {
return (
<div className="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 flex flex-col">
{/* Author in the top bar */}
<div className="w-full text-gray-700 dark:text-gray-400">
<p className="font-medium truncate text-left">{author}</p>
</div>
{/* Title in the card body */}
<div className="w-full mt-2">
<h3 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-left">{title}</h3>
</div>
{/* Buttons - aligned to the right */}
<div className="w-full flex justify-end mt-5">
<a href="#" className="inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Read more
<svg className="rtl:rotate-180 w-3.5 h-3.5 ms-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M1 5h12m0 0L9 1m4 4L9 9"/>
</svg>
</a>
</div>
</div>
);
};
\ No newline at end of file
// Grid layout component that fetches and displays books
"use client";
import { useState, useEffect } from 'react';
import Book from "./book";
export default function BookList () {
const [books, setBooks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchBooks = async () => {
try {
setLoading(true);
/*
// Replace with your actual API endpoint
const response = await fetch('https://api.example.com/books');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();*/
const data = [
{id: 1, title: "Lord of the rings", author: "JRR Tolkien"},
{id: 2, title: "Artemisa", author: "Andy Weir"},
{id: 3, title: "Dune", author: "Orson Scott Card"}
];
setBooks(data);
} catch (err) {
setError(err.message);
console.error('Error fetching books:', err);
} finally {
setLoading(false);
}
};
fetchBooks();
}, []);
if (loading) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
);
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading books: {error}</p>
<p>Please try again later</p>
</div>
);
}
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-2xl font-bold mb-6">Book Collection</h1>
{books.length === 0 ? (
<p className="text-gray-600">No books found.</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{books.map((book) => (
<Book
key={book.id}
title={book.title}
author={book.author}
/>
))}
</div>
)}
</div>
);
};
\ No newline at end of file
// app/components/Navbar.jsx
"use client"; // This is needed because we use client-side state and event handlers
import Sections from "./sections"
import Avatar from "./avatar"
export default function Navbar() {
return (
<nav className="flex justify-between items-center px-4 py-3 bg-gray-800 text-white">
<Sections/>
<Avatar/>
</nav>
);
}
\ No newline at end of file
import Link from 'next/link';
export default function Sections() {
return (
<div className="flex gap-6">
<Link href="/" className="text-white hover:text-gray-300">
BookCrossing
</Link>
<Link href="/available" className="text-white hover:text-gray-300">
Available
</Link>
<Link href="/loans" className="text-white hover:text-gray-300">
My loans
</Link>
<Link href="/owned" className="text-white hover:text-gray-300">
My books
</Link>
</div>
)
}
\ No newline at end of file
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
import Navbar from './components/navbar';
import './globals.css';
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: 'My App',
description: 'Application with user avatar and dropdown',
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<body>
<Navbar />
<main className="container mx-auto px-4 py-8">
{children}
</main>
</body>
</html>
);
}
}
\ No newline at end of file
export default function Home() {
return (
<>
List of books on loan
</>
);
}
export default function Home() {
return (
<>
Your books
</>
);
}
import Image from "next/image";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
src/app/page.js
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
<>
Welcome to BookCrossing
</>
);
}
"use client";
import { useState } from 'react';
export default function EditProfile() {
const [user, setUser] = useState({
name: 'John Doe',
email: 'john.doe@example.com',
bio: 'Frontend developer passionate about UI/UX'
});
const handleChange = (e) => {
const { name, value } = e.target;
setUser(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
// Add your profile update logic here
console.log('Updated profile:', user);
// Show success message, redirect, etc.
};
return (
<div className="max-w-md mx-auto">
<h1 className="text-2xl font-bold mb-6">Edit Your Profile</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="block mb-1 font-medium">
Name
</label>
<input
type="text"
id="name"
name="name"
value={user.name}
onChange={handleChange}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
<div>
<label htmlFor="email" className="block mb-1 font-medium">
Email
</label>
<input
type="email"
id="email"
name="email"
value={user.email}
onChange={handleChange}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
<div>
<label htmlFor="bio" className="block mb-1 font-medium">
Bio
</label>
<textarea
id="bio"
name="bio"
value={user.bio}
onChange={handleChange}
rows="4"
className="w-full px-3 py-2 border rounded-md"
/>
</div>
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Save Changes
</button>
</form>
</div>
);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment