feat: added book list preview for home page
This commit is contained in:
parent
cad467d8f4
commit
d02a07541b
@ -1,5 +1,4 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import './styles.css'
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: 'A blank template using Payload in a Next.js app.',
|
description: 'A blank template using Payload in a Next.js app.',
|
||||||
@ -12,7 +11,7 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<main>{children}</main>
|
<main className="bg-white dark:bg-gray-800">{children}</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import React from 'react'
|
|||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
import config from '@/payload.config'
|
import config from '@/payload.config'
|
||||||
import './styles.css'
|
import BookList from '@/components/BookList'
|
||||||
|
import { Book } from '@/payload-types'
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
const headers = await getHeaders()
|
const headers = await getHeaders()
|
||||||
@ -15,6 +16,21 @@ export default async function HomePage() {
|
|||||||
|
|
||||||
const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}`
|
const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}`
|
||||||
|
|
||||||
|
const books = await payload.find({
|
||||||
|
collection: 'books',
|
||||||
|
depth: 2,
|
||||||
|
limit: 12,
|
||||||
|
overrideAccess: false,
|
||||||
|
select: {
|
||||||
|
title: true,
|
||||||
|
authors: true,
|
||||||
|
publication: true,
|
||||||
|
lcc: true,
|
||||||
|
genre: true,
|
||||||
|
isbn: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="home">
|
<div className="home">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@ -48,6 +64,7 @@ export default async function HomePage() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BookList books={(books.docs || []) as Book[]} />
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
<p>Update this page by editing</p>
|
<p>Update this page by editing</p>
|
||||||
<a className="codeLink" href={fileURL}>
|
<a className="codeLink" href={fileURL}>
|
||||||
|
|||||||
@ -1,164 +0,0 @@
|
|||||||
:root {
|
|
||||||
--font-mono: 'Roboto Mono', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 32px;
|
|
||||||
|
|
||||||
background: rgb(0, 0, 0);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#app {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: system-ui;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 32px;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(1000, 1000, 1000);
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 40px 0;
|
|
||||||
font-size: 64px;
|
|
||||||
line-height: 70px;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
margin: 24px 0;
|
|
||||||
font-size: 42px;
|
|
||||||
line-height: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
font-size: 38px;
|
|
||||||
line-height: 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 400px) {
|
|
||||||
font-size: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 24px 0;
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
margin: calc(var(--base) * 0.75) 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: currentColor;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
opacity: 0.8;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.7;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
padding: 45px;
|
|
||||||
max-width: 1024px;
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
@media (max-width: 400px) {
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.links {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin {
|
|
||||||
color: rgb(0, 0, 0);
|
|
||||||
background: rgb(1000, 1000, 1000);
|
|
||||||
border: 1px solid rgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.docs {
|
|
||||||
color: rgb(1000, 1000, 1000);
|
|
||||||
background: rgb(0, 0, 0);
|
|
||||||
border: 1px solid rgb(1000, 1000, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.codeLink {
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
background: rgb(60, 60, 60);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5
src/app/globals.css
Normal file
5
src/app/globals.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
17
src/app/layout.tsx
Normal file
17
src/app/layout.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
type LayoutProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
import './globals.css'
|
||||||
|
|
||||||
|
const Layout = ({ children }: LayoutProps) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import { access } from 'node:fs/promises'
|
|
||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
export const Authors: CollectionConfig = {
|
export const Authors: CollectionConfig = {
|
||||||
|
|||||||
@ -40,6 +40,14 @@ export const Books: CollectionConfig = {
|
|||||||
name: 'lcc',
|
name: 'lcc',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'isbn',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'asin',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'publication',
|
name: 'publication',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -54,6 +62,7 @@ export const Books: CollectionConfig = {
|
|||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
relationTo: 'genre',
|
relationTo: 'genre',
|
||||||
hasMany: true,
|
hasMany: true,
|
||||||
|
maxDepth: 3,
|
||||||
admin: {
|
admin: {
|
||||||
allowEdit: true,
|
allowEdit: true,
|
||||||
allowCreate: true
|
allowCreate: true
|
||||||
|
|||||||
84
src/components/BookList/index.tsx
Normal file
84
src/components/BookList/index.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Badge } from '@/components/badge'
|
||||||
|
import { Author, Book, Genre } from '@/payload-types'
|
||||||
|
import { Avatar } from '../avatar'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
books: Book[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeAuthorsLabel = (book: Book) => {
|
||||||
|
const authors = book.authors as Author[] // TODO: endure this type safety
|
||||||
|
|
||||||
|
const translators = authors?.filter((a) => a.role === 'Translator').map((t) => t.lf)
|
||||||
|
const editors = authors?.filter((a) => a.role === 'Editor').map((e) => e.lf)
|
||||||
|
const actualAuthors = authors
|
||||||
|
?.filter((a) => !editors.includes(a.lf) && !translators.includes(a.lf))
|
||||||
|
.map((a) => a.lf)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
<li>{actualAuthors.join(', ')}</li>
|
||||||
|
{!!translators?.length && <li>Translators: {translators.join(', ')}</li>}
|
||||||
|
{!!editors?.length && <li>Editors: {editors.join(', ')}</li>}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeGenreBadges = (book: Book) => {
|
||||||
|
return (book.genre as Genre[])?.map((g) => (
|
||||||
|
<Badge key={g.name + book.title + book.id} className="ml-0.5">
|
||||||
|
{g.name}
|
||||||
|
</Badge>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BookList(props: Props) {
|
||||||
|
const { books } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="user-repositories">
|
||||||
|
<ul role="list" className="divide-y divide-gray-800">
|
||||||
|
{books.map((b) => (
|
||||||
|
<li key={b.lcc + (b.title || '')} className="flex justify-between gap-x-6 py-5">
|
||||||
|
<div className="flex max-w-9/12 min-w-0 gap-x-4">
|
||||||
|
<Avatar
|
||||||
|
square
|
||||||
|
className="size-12 flex-none bg-gray-800"
|
||||||
|
src={
|
||||||
|
b.isbn
|
||||||
|
? `https://covers.openlibrary.org/b/isbn/${b.isbn}-S.jpg`
|
||||||
|
: '/images/book-48.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="min-w-0 flex-auto">
|
||||||
|
<p className="text-sm/6 font-semibold text-white">
|
||||||
|
<span>{b.title}</span>
|
||||||
|
<small className="ml-1 italic font-thin">{b.lcc}</small>
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 truncate text-xs/5 text-gray-400">{makeGenreBadges(b)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-end">
|
||||||
|
<p className="text-sm/6 text-white text-right">{makeAuthorsLabel(b)}</p>
|
||||||
|
{
|
||||||
|
/*b.lastBorrowed*/ false ? (
|
||||||
|
<p className="mt-1 text-xs/5 text-gray-400">
|
||||||
|
Last Borrowed{' '}
|
||||||
|
<time dateTime={new Date().toUTCString()}>{new Date().toUTCString()}</time>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="mt-1 flex items-center gap-x-1.5">
|
||||||
|
<div className="flex-none rounded-full bg-emerald-500/20 p-1">
|
||||||
|
<div className="size-1.5 rounded-full bg-emerald-500" />
|
||||||
|
</div>
|
||||||
|
<p className="text-xs/5 text-gray-400">Available</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -5,6 +5,11 @@ export const Header: GlobalConfig = {
|
|||||||
slug: 'header',
|
slug: 'header',
|
||||||
label: 'Header Nav',
|
label: 'Header Nav',
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'logo',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'media',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'headerLinks',
|
name: 'headerLinks',
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|||||||
@ -181,6 +181,8 @@ export interface Book {
|
|||||||
authors?: (number | Author)[] | null;
|
authors?: (number | Author)[] | null;
|
||||||
pages?: number | null;
|
pages?: number | null;
|
||||||
lcc?: string | null;
|
lcc?: string | null;
|
||||||
|
isbn?: string | null;
|
||||||
|
asin?: string | null;
|
||||||
publication?: string | null;
|
publication?: string | null;
|
||||||
date?: string | null;
|
date?: string | null;
|
||||||
genre?: (number | Genre)[] | null;
|
genre?: (number | Genre)[] | null;
|
||||||
@ -420,6 +422,8 @@ export interface BooksSelect<T extends boolean = true> {
|
|||||||
authors?: T;
|
authors?: T;
|
||||||
pages?: T;
|
pages?: T;
|
||||||
lcc?: T;
|
lcc?: T;
|
||||||
|
isbn?: T;
|
||||||
|
asin?: T;
|
||||||
publication?: T;
|
publication?: T;
|
||||||
date?: T;
|
date?: T;
|
||||||
genre?: T;
|
genre?: T;
|
||||||
@ -535,6 +539,7 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
|||||||
*/
|
*/
|
||||||
export interface Header {
|
export interface Header {
|
||||||
id: number;
|
id: number;
|
||||||
|
logo?: (number | null) | Media;
|
||||||
headerLinks?:
|
headerLinks?:
|
||||||
| {
|
| {
|
||||||
label?: string | null;
|
label?: string | null;
|
||||||
@ -551,6 +556,7 @@ export interface Header {
|
|||||||
* via the `definition` "header_select".
|
* via the `definition` "header_select".
|
||||||
*/
|
*/
|
||||||
export interface HeaderSelect<T extends boolean = true> {
|
export interface HeaderSelect<T extends boolean = true> {
|
||||||
|
logo?: T;
|
||||||
headerLinks?:
|
headerLinks?:
|
||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user