Remove nextjs scaffolding contents
This commit is contained in:
parent
6906b3cb6e
commit
161a27160f
@ -1,23 +1,5 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
/* Copyright (C) 2024 Manuel Bustillo*/
|
||||||
|
|
||||||
// This file contains type definitions for your data.
|
|
||||||
// It describes the shape of the data, and what data type each property should accept.
|
|
||||||
// For simplicity of teaching, we're manually defining these types.
|
|
||||||
// However, these types are generated automatically if you're using an ORM such as Prisma.
|
|
||||||
export type User = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Customer = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
image_url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GuestStatus = 'considered' | 'invited' | 'confirmed' | 'declined' | 'tentative';
|
export type GuestStatus = 'considered' | 'invited' | 'confirmed' | 'declined' | 'tentative';
|
||||||
export type Guest = {
|
export type Guest = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -62,34 +44,6 @@ export type AttendanceSummary = {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Invoice = {
|
|
||||||
id: string;
|
|
||||||
customer_id: string;
|
|
||||||
amount: number;
|
|
||||||
date: string;
|
|
||||||
// In TypeScript, this is called a string union type.
|
|
||||||
// It means that the "status" property can only be one of the two strings: 'pending' or 'paid'.
|
|
||||||
status: 'pending' | 'paid';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Revenue = {
|
|
||||||
month: string;
|
|
||||||
revenue: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LatestInvoice = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
image_url: string;
|
|
||||||
email: string;
|
|
||||||
amount: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The database returns a number for amount, but we later format it to a string with the formatCurrency function
|
|
||||||
export type LatestInvoiceRaw = Omit<LatestInvoice, 'amount'> & {
|
|
||||||
amount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type guestsTable = {
|
export type guestsTable = {
|
||||||
id: string;
|
id: string;
|
||||||
customer_id: string;
|
customer_id: string;
|
||||||
@ -100,35 +54,3 @@ export type guestsTable = {
|
|||||||
amount: number;
|
amount: number;
|
||||||
status: 'pending' | 'paid';
|
status: 'pending' | 'paid';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CustomersTableType = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
image_url: string;
|
|
||||||
total_guests: number;
|
|
||||||
total_pending: number;
|
|
||||||
total_paid: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FormattedCustomersTable = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
image_url: string;
|
|
||||||
total_guests: number;
|
|
||||||
total_pending: string;
|
|
||||||
total_paid: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CustomerField = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InvoiceForm = {
|
|
||||||
id: string;
|
|
||||||
customer_id: string;
|
|
||||||
amount: number;
|
|
||||||
status: 'pending' | 'paid';
|
|
||||||
};
|
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
// This file contains placeholder data that you'll be replacing with real data in the Data Fetching chapter:
|
|
||||||
// https://nextjs.org/learn/dashboard-app/fetching-data
|
|
||||||
const users = [
|
|
||||||
{
|
|
||||||
id: '410544b2-4001-4271-9855-fec4b6a6442a',
|
|
||||||
name: 'User',
|
|
||||||
email: 'user@nextmail.com',
|
|
||||||
password: '123456',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const customers = [
|
|
||||||
{
|
|
||||||
id: 'd6e15727-9fe1-4961-8c5b-ea44a9bd81aa',
|
|
||||||
name: 'Evil Rabbit',
|
|
||||||
email: 'evil@rabbit.com',
|
|
||||||
image_url: '/customers/evil-rabbit.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3958dc9e-712f-4377-85e9-fec4b6a6442a',
|
|
||||||
name: 'Delba de Oliveira',
|
|
||||||
email: 'delba@oliveira.com',
|
|
||||||
image_url: '/customers/delba-de-oliveira.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3958dc9e-742f-4377-85e9-fec4b6a6442a',
|
|
||||||
name: 'Lee Robinson',
|
|
||||||
email: 'lee@robinson.com',
|
|
||||||
image_url: '/customers/lee-robinson.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '76d65c26-f784-44a2-ac19-586678f7c2f2',
|
|
||||||
name: 'Michael Novotny',
|
|
||||||
email: 'michael@novotny.com',
|
|
||||||
image_url: '/customers/michael-novotny.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'CC27C14A-0ACF-4F4A-A6C9-D45682C144B9',
|
|
||||||
name: 'Amy Burns',
|
|
||||||
email: 'amy@burns.com',
|
|
||||||
image_url: '/customers/amy-burns.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '13D07535-C59E-4157-A011-F8D2EF4E0CBB',
|
|
||||||
name: 'Balazs Orban',
|
|
||||||
email: 'balazs@orban.com',
|
|
||||||
image_url: '/customers/balazs-orban.png',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const guests = [
|
|
||||||
{
|
|
||||||
customer_id: customers[0].id,
|
|
||||||
amount: 15795,
|
|
||||||
status: 'pending',
|
|
||||||
date: '2022-12-06',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[1].id,
|
|
||||||
amount: 20348,
|
|
||||||
status: 'pending',
|
|
||||||
date: '2022-11-14',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[4].id,
|
|
||||||
amount: 3040,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2022-10-29',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[3].id,
|
|
||||||
amount: 44800,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2023-09-10',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[5].id,
|
|
||||||
amount: 34577,
|
|
||||||
status: 'pending',
|
|
||||||
date: '2023-08-05',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[2].id,
|
|
||||||
amount: 54246,
|
|
||||||
status: 'pending',
|
|
||||||
date: '2023-07-16',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[0].id,
|
|
||||||
amount: 666,
|
|
||||||
status: 'pending',
|
|
||||||
date: '2023-06-27',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[3].id,
|
|
||||||
amount: 32545,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2023-06-09',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[4].id,
|
|
||||||
amount: 1250,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2023-06-17',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[5].id,
|
|
||||||
amount: 8546,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2023-06-07',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[1].id,
|
|
||||||
amount: 500,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2023-08-19',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[5].id,
|
|
||||||
amount: 8945,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2023-06-03',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
customer_id: customers[2].id,
|
|
||||||
amount: 1000,
|
|
||||||
status: 'paid',
|
|
||||||
date: '2022-06-05',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const revenue = [
|
|
||||||
{ month: 'Jan', revenue: 2000 },
|
|
||||||
{ month: 'Feb', revenue: 1800 },
|
|
||||||
{ month: 'Mar', revenue: 2200 },
|
|
||||||
{ month: 'Apr', revenue: 2500 },
|
|
||||||
{ month: 'May', revenue: 2300 },
|
|
||||||
{ month: 'Jun', revenue: 3200 },
|
|
||||||
{ month: 'Jul', revenue: 3500 },
|
|
||||||
{ month: 'Aug', revenue: 3700 },
|
|
||||||
{ month: 'Sep', revenue: 2500 },
|
|
||||||
{ month: 'Oct', revenue: 2800 },
|
|
||||||
{ month: 'Nov', revenue: 3000 },
|
|
||||||
{ month: 'Dec', revenue: 4800 },
|
|
||||||
];
|
|
||||||
|
|
||||||
export { users, customers, guests, revenue };
|
|
@ -1,124 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
// import bcrypt from 'bcrypt';
|
|
||||||
// import { db } from '@vercel/postgres';
|
|
||||||
// import { guests, customers, revenue, users } from '../lib/placeholder-data';
|
|
||||||
|
|
||||||
// const client = await db.connect();
|
|
||||||
|
|
||||||
// async function seedUsers() {
|
|
||||||
// await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
|
||||||
// await client.sql`
|
|
||||||
// CREATE TABLE IF NOT EXISTS users (
|
|
||||||
// id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
// name VARCHAR(255) NOT NULL,
|
|
||||||
// email TEXT NOT NULL UNIQUE,
|
|
||||||
// password TEXT NOT NULL
|
|
||||||
// );
|
|
||||||
// `;
|
|
||||||
|
|
||||||
// const insertedUsers = await Promise.all(
|
|
||||||
// users.map(async (user) => {
|
|
||||||
// const hashedPassword = await bcrypt.hash(user.password, 10);
|
|
||||||
// return client.sql`
|
|
||||||
// INSERT INTO users (id, name, email, password)
|
|
||||||
// VALUES (${user.id}, ${user.name}, ${user.email}, ${hashedPassword})
|
|
||||||
// ON CONFLICT (id) DO NOTHING;
|
|
||||||
// `;
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return insertedUsers;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async function seedguests() {
|
|
||||||
// await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
|
||||||
|
|
||||||
// await client.sql`
|
|
||||||
// CREATE TABLE IF NOT EXISTS guests (
|
|
||||||
// id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
// customer_id UUID NOT NULL,
|
|
||||||
// amount INT NOT NULL,
|
|
||||||
// status VARCHAR(255) NOT NULL,
|
|
||||||
// date DATE NOT NULL
|
|
||||||
// );
|
|
||||||
// `;
|
|
||||||
|
|
||||||
// const insertedguests = await Promise.all(
|
|
||||||
// guests.map(
|
|
||||||
// (invoice) => client.sql`
|
|
||||||
// INSERT INTO guests (customer_id, amount, status, date)
|
|
||||||
// VALUES (${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date})
|
|
||||||
// ON CONFLICT (id) DO NOTHING;
|
|
||||||
// `,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return insertedguests;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async function seedCustomers() {
|
|
||||||
// await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`;
|
|
||||||
|
|
||||||
// await client.sql`
|
|
||||||
// CREATE TABLE IF NOT EXISTS customers (
|
|
||||||
// id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
// name VARCHAR(255) NOT NULL,
|
|
||||||
// email VARCHAR(255) NOT NULL,
|
|
||||||
// image_url VARCHAR(255) NOT NULL
|
|
||||||
// );
|
|
||||||
// `;
|
|
||||||
|
|
||||||
// const insertedCustomers = await Promise.all(
|
|
||||||
// customers.map(
|
|
||||||
// (customer) => client.sql`
|
|
||||||
// INSERT INTO customers (id, name, email, image_url)
|
|
||||||
// VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url})
|
|
||||||
// ON CONFLICT (id) DO NOTHING;
|
|
||||||
// `,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return insertedCustomers;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async function seedRevenue() {
|
|
||||||
// await client.sql`
|
|
||||||
// CREATE TABLE IF NOT EXISTS revenue (
|
|
||||||
// month VARCHAR(4) NOT NULL UNIQUE,
|
|
||||||
// revenue INT NOT NULL
|
|
||||||
// );
|
|
||||||
// `;
|
|
||||||
|
|
||||||
// const insertedRevenue = await Promise.all(
|
|
||||||
// revenue.map(
|
|
||||||
// (rev) => client.sql`
|
|
||||||
// INSERT INTO revenue (month, revenue)
|
|
||||||
// VALUES (${rev.month}, ${rev.revenue})
|
|
||||||
// ON CONFLICT (month) DO NOTHING;
|
|
||||||
// `,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return insertedRevenue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
return Response.json({
|
|
||||||
message:
|
|
||||||
'Uncomment this file and remove this line. You can delete this file when you are finished.',
|
|
||||||
});
|
|
||||||
// try {
|
|
||||||
// await client.sql`BEGIN`;
|
|
||||||
// await seedUsers();
|
|
||||||
// await seedCustomers();
|
|
||||||
// await seedguests();
|
|
||||||
// await seedRevenue();
|
|
||||||
// await client.sql`COMMIT`;
|
|
||||||
|
|
||||||
// return Response.json({ message: 'Database seeded successfully' });
|
|
||||||
// } catch (error) {
|
|
||||||
// await client.sql`ROLLBACK`;
|
|
||||||
// return Response.json({ error }, { status: 500 });
|
|
||||||
// }
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import Image from 'next/image';
|
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
|
||||||
import Search from '@/app/ui/search';
|
|
||||||
import {
|
|
||||||
CustomersTableType,
|
|
||||||
FormattedCustomersTable,
|
|
||||||
} from '@/app/lib/definitions';
|
|
||||||
|
|
||||||
export default async function CustomersTable({
|
|
||||||
customers,
|
|
||||||
}: {
|
|
||||||
customers: FormattedCustomersTable[];
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<h1 className={`${lusitana.className} mb-8 text-xl md:text-2xl`}>
|
|
||||||
Customers
|
|
||||||
</h1>
|
|
||||||
<Search placeholder="Search customers..." />
|
|
||||||
<div className="mt-6 flow-root">
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<div className="inline-block min-w-full align-middle">
|
|
||||||
<div className="overflow-hidden rounded-md bg-gray-50 p-2 md:pt-0">
|
|
||||||
<div className="md:hidden">
|
|
||||||
{customers?.map((customer) => (
|
|
||||||
<div
|
|
||||||
key={customer.id}
|
|
||||||
className="mb-2 w-full rounded-md bg-white p-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between border-b pb-4">
|
|
||||||
<div>
|
|
||||||
<div className="mb-2 flex items-center">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Image
|
|
||||||
src={customer.image_url}
|
|
||||||
className="rounded-full"
|
|
||||||
alt={`${customer.name}'s profile picture`}
|
|
||||||
width={28}
|
|
||||||
height={28}
|
|
||||||
/>
|
|
||||||
<p>{customer.name}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{customer.email}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full items-center justify-between border-b py-5">
|
|
||||||
<div className="flex w-1/2 flex-col">
|
|
||||||
<p className="text-xs">Pending</p>
|
|
||||||
<p className="font-medium">{customer.total_pending}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-1/2 flex-col">
|
|
||||||
<p className="text-xs">Paid</p>
|
|
||||||
<p className="font-medium">{customer.total_paid}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="pt-4 text-sm">
|
|
||||||
<p>{customer.total_guests} guests</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<table className="hidden min-w-full rounded-md text-gray-900 md:table">
|
|
||||||
<thead className="rounded-md bg-gray-50 text-left text-sm font-normal">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" className="px-4 py-5 font-medium sm:pl-6">
|
|
||||||
Name
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-3 py-5 font-medium">
|
|
||||||
Email
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-3 py-5 font-medium">
|
|
||||||
Total guests
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-3 py-5 font-medium">
|
|
||||||
Total Pending
|
|
||||||
</th>
|
|
||||||
<th scope="col" className="px-4 py-5 font-medium">
|
|
||||||
Total Paid
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody className="divide-y divide-gray-200 text-gray-900">
|
|
||||||
{customers.map((customer) => (
|
|
||||||
<tr key={customer.id} className="group">
|
|
||||||
<td className="whitespace-nowrap bg-white py-5 pl-4 pr-3 text-sm text-black group-first-of-type:rounded-md group-last-of-type:rounded-md sm:pl-6">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Image
|
|
||||||
src={customer.image_url}
|
|
||||||
className="rounded-full"
|
|
||||||
alt={`${customer.name}'s profile picture`}
|
|
||||||
width={28}
|
|
||||||
height={28}
|
|
||||||
/>
|
|
||||||
<p>{customer.name}</p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap bg-white px-4 py-5 text-sm">
|
|
||||||
{customer.email}
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap bg-white px-4 py-5 text-sm">
|
|
||||||
{customer.total_guests}
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap bg-white px-4 py-5 text-sm">
|
|
||||||
{customer.total_pending}
|
|
||||||
</td>
|
|
||||||
<td className="whitespace-nowrap bg-white px-4 py-5 text-sm group-first-of-type:rounded-md group-last-of-type:rounded-md">
|
|
||||||
{customer.total_paid}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
BanknotesIcon,
|
|
||||||
ClockIcon,
|
|
||||||
UserGroupIcon,
|
|
||||||
InboxIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
|
||||||
|
|
||||||
const iconMap = {
|
|
||||||
collected: BanknotesIcon,
|
|
||||||
customers: UserGroupIcon,
|
|
||||||
pending: ClockIcon,
|
|
||||||
guests: InboxIcon,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function CardWrapper() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* NOTE: Uncomment this code in Chapter 9 */}
|
|
||||||
|
|
||||||
{/* <Card title="Collected" value={totalPaidguests} type="collected" />
|
|
||||||
<Card title="Pending" value={totalPendingguests} type="pending" />
|
|
||||||
<Card title="Total guests" value={numberOfguests} type="guests" />
|
|
||||||
<Card
|
|
||||||
title="Total Customers"
|
|
||||||
value={numberOfCustomers}
|
|
||||||
type="customers"
|
|
||||||
/> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Card({
|
|
||||||
title,
|
|
||||||
value,
|
|
||||||
type,
|
|
||||||
}: {
|
|
||||||
title: string;
|
|
||||||
value: number | string;
|
|
||||||
type: 'guests' | 'customers' | 'pending' | 'collected';
|
|
||||||
}) {
|
|
||||||
const Icon = iconMap[type];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rounded-xl bg-gray-50 p-2 shadow-sm">
|
|
||||||
<div className="flex p-4">
|
|
||||||
{Icon ? <Icon className="h-5 w-5 text-gray-700" /> : null}
|
|
||||||
<h3 className="ml-2 text-sm font-medium">{title}</h3>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className={`${lusitana.className}
|
|
||||||
truncate rounded-xl bg-white px-4 py-8 text-center text-2xl`}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import Image from 'next/image';
|
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
|
||||||
import { LatestInvoice } from '@/app/lib/definitions';
|
|
||||||
export default async function Latestguests({
|
|
||||||
latestguests,
|
|
||||||
}: {
|
|
||||||
latestguests: LatestInvoice[];
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="flex w-full flex-col md:col-span-4">
|
|
||||||
<h2 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
|
|
||||||
Latest guests
|
|
||||||
</h2>
|
|
||||||
<div className="flex grow flex-col justify-between rounded-xl bg-gray-50 p-4">
|
|
||||||
{/* NOTE: Uncomment this code in Chapter 7 */}
|
|
||||||
|
|
||||||
{/* <div className="bg-white px-6">
|
|
||||||
{latestguests.map((invoice, i) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={invoice.id}
|
|
||||||
className={clsx(
|
|
||||||
'flex flex-row items-center justify-between py-4',
|
|
||||||
{
|
|
||||||
'border-t': i !== 0,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Image
|
|
||||||
src={invoice.image_url}
|
|
||||||
alt={`${invoice.name}'s profile picture`}
|
|
||||||
className="mr-4 rounded-full"
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
/>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<p className="truncate text-sm font-semibold md:text-base">
|
|
||||||
{invoice.name}
|
|
||||||
</p>
|
|
||||||
<p className="hidden text-sm text-gray-500 sm:block">
|
|
||||||
{invoice.email}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className={`${lusitana.className} truncate text-sm font-medium md:text-base`}
|
|
||||||
>
|
|
||||||
{invoice.amount}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div> */}
|
|
||||||
<div className="flex items-center pb-2 pt-6">
|
|
||||||
<ArrowPathIcon className="h-5 w-5 text-gray-500" />
|
|
||||||
<h3 className="ml-2 text-sm text-gray-500 ">Updated just now</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import { generateYAxis } from '@/app/lib/utils';
|
|
||||||
import { CalendarIcon } from '@heroicons/react/24/outline';
|
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
|
||||||
import { Revenue } from '@/app/lib/definitions';
|
|
||||||
|
|
||||||
// This component is representational only.
|
|
||||||
// For data visualization UI, check out:
|
|
||||||
// https://www.tremor.so/
|
|
||||||
// https://www.chartjs.org/
|
|
||||||
// https://airbnb.io/visx/
|
|
||||||
|
|
||||||
export default async function RevenueChart({
|
|
||||||
revenue,
|
|
||||||
}: {
|
|
||||||
revenue: Revenue[];
|
|
||||||
}) {
|
|
||||||
const chartHeight = 350;
|
|
||||||
// NOTE: Uncomment this code in Chapter 7
|
|
||||||
|
|
||||||
// const { yAxisLabels, topLabel } = generateYAxis(revenue);
|
|
||||||
|
|
||||||
// if (!revenue || revenue.length === 0) {
|
|
||||||
// return <p className="mt-4 text-gray-400">No data available.</p>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full md:col-span-4">
|
|
||||||
<h2 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
|
|
||||||
Recent Revenue
|
|
||||||
</h2>
|
|
||||||
{/* NOTE: Uncomment this code in Chapter 7 */}
|
|
||||||
|
|
||||||
{/* <div className="rounded-xl bg-gray-50 p-4">
|
|
||||||
<div className="sm:grid-cols-13 mt-0 grid grid-cols-12 items-end gap-2 rounded-md bg-white p-4 md:gap-4">
|
|
||||||
<div
|
|
||||||
className="mb-6 hidden flex-col justify-between text-sm text-gray-400 sm:flex"
|
|
||||||
style={{ height: `${chartHeight}px` }}
|
|
||||||
>
|
|
||||||
{yAxisLabels.map((label) => (
|
|
||||||
<p key={label}>{label}</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{revenue.map((month) => (
|
|
||||||
<div key={month.month} className="flex flex-col items-center gap-2">
|
|
||||||
<div
|
|
||||||
className="w-full rounded-md bg-blue-300"
|
|
||||||
style={{
|
|
||||||
height: `${(chartHeight / topLabel) * month.revenue}px`,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<p className="-rotate-90 text-sm text-gray-400 sm:rotate-0">
|
|
||||||
{month.month}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center pb-2 pt-6">
|
|
||||||
<CalendarIcon className="h-5 w-5 text-gray-500" />
|
|
||||||
<h3 className="ml-2 text-sm text-gray-500 ">Last 12 months</h3>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import { clsx } from 'clsx';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
|
||||||
|
|
||||||
interface Breadcrumb {
|
|
||||||
label: string;
|
|
||||||
href: string;
|
|
||||||
active?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Breadcrumbs({
|
|
||||||
breadcrumbs,
|
|
||||||
}: {
|
|
||||||
breadcrumbs: Breadcrumb[];
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<nav aria-label="Breadcrumb" className="mb-6 block">
|
|
||||||
<ol className={clsx(lusitana.className, 'flex text-xl md:text-2xl')}>
|
|
||||||
{breadcrumbs.map((breadcrumb, index) => (
|
|
||||||
<li
|
|
||||||
key={breadcrumb.href}
|
|
||||||
aria-current={breadcrumb.active}
|
|
||||||
className={clsx(
|
|
||||||
breadcrumb.active ? 'text-gray-900' : 'text-gray-500',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Link href={breadcrumb.href}>{breadcrumb.label}</Link>
|
|
||||||
{index < breadcrumbs.length - 1 ? (
|
|
||||||
<span className="mx-3 inline-block">/</span>
|
|
||||||
) : null}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
export function CreateInvoice() {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
href="/dashboard/guests/create"
|
|
||||||
className="flex h-10 items-center rounded-lg bg-blue-600 px-4 text-sm font-medium text-white transition-colors hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
|
||||||
>
|
|
||||||
<span className="hidden md:block">Create Invoice</span>{' '}
|
|
||||||
<PlusIcon className="h-5 md:ml-4" />
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function UpdateInvoice({ id }: { id: string }) {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
href="/dashboard/guests"
|
|
||||||
className="rounded-md border p-2 hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
<PencilIcon className="w-5" />
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DeleteInvoice({ id }: { id: string }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<button className="rounded-md border p-2 hover:bg-gray-100">
|
|
||||||
<span className="sr-only">Delete</span>
|
|
||||||
<TrashIcon className="w-5" />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import { CustomerField } from '@/app/lib/definitions';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import {
|
|
||||||
CheckIcon,
|
|
||||||
ClockIcon,
|
|
||||||
CurrencyDollarIcon,
|
|
||||||
UserCircleIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import { Button } from '@/app/ui/button';
|
|
||||||
|
|
||||||
export default function Form({ customers }: { customers: CustomerField[] }) {
|
|
||||||
return (
|
|
||||||
<form>
|
|
||||||
<div className="rounded-md bg-gray-50 p-4 md:p-6">
|
|
||||||
{/* Customer Name */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<label htmlFor="customer" className="mb-2 block text-sm font-medium">
|
|
||||||
Choose customer
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<select
|
|
||||||
id="customer"
|
|
||||||
name="customerId"
|
|
||||||
className="peer block w-full cursor-pointer rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
|
||||||
defaultValue=""
|
|
||||||
>
|
|
||||||
<option value="" disabled>
|
|
||||||
Select a customer
|
|
||||||
</option>
|
|
||||||
{customers.map((customer) => (
|
|
||||||
<option key={customer.id} value={customer.id}>
|
|
||||||
{customer.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Invoice Amount */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<label htmlFor="amount" className="mb-2 block text-sm font-medium">
|
|
||||||
Choose an amount
|
|
||||||
</label>
|
|
||||||
<div className="relative mt-2 rounded-md">
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
id="amount"
|
|
||||||
name="amount"
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
placeholder="Enter USD amount"
|
|
||||||
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
|
||||||
/>
|
|
||||||
<CurrencyDollarIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Invoice Status */}
|
|
||||||
<fieldset>
|
|
||||||
<legend className="mb-2 block text-sm font-medium">
|
|
||||||
Set the invoice status
|
|
||||||
</legend>
|
|
||||||
<div className="rounded-md border border-gray-200 bg-white px-[14px] py-3">
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<input
|
|
||||||
id="pending"
|
|
||||||
name="status"
|
|
||||||
type="radio"
|
|
||||||
value="pending"
|
|
||||||
className="h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="pending"
|
|
||||||
className="ml-2 flex cursor-pointer items-center gap-1.5 rounded-full bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-600"
|
|
||||||
>
|
|
||||||
Pending <ClockIcon className="h-4 w-4" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<input
|
|
||||||
id="paid"
|
|
||||||
name="status"
|
|
||||||
type="radio"
|
|
||||||
value="paid"
|
|
||||||
className="h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="paid"
|
|
||||||
className="ml-2 flex cursor-pointer items-center gap-1.5 rounded-full bg-green-500 px-3 py-1.5 text-xs font-medium text-white"
|
|
||||||
>
|
|
||||||
Paid <CheckIcon className="h-4 w-4" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 flex justify-end gap-4">
|
|
||||||
<Link
|
|
||||||
href="/dashboard/guests"
|
|
||||||
className="flex h-10 items-center rounded-lg bg-gray-100 px-4 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-200"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Link>
|
|
||||||
<Button type="submit">Create Invoice</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { CustomerField, InvoiceForm } from '@/app/lib/definitions';
|
|
||||||
import {
|
|
||||||
CheckIcon,
|
|
||||||
ClockIcon,
|
|
||||||
CurrencyDollarIcon,
|
|
||||||
UserCircleIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { Button } from '@/app/ui/button';
|
|
||||||
|
|
||||||
export default function EditInvoiceForm({
|
|
||||||
invoice,
|
|
||||||
customers,
|
|
||||||
}: {
|
|
||||||
invoice: InvoiceForm;
|
|
||||||
customers: CustomerField[];
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<form>
|
|
||||||
<div className="rounded-md bg-gray-50 p-4 md:p-6">
|
|
||||||
{/* Customer Name */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<label htmlFor="customer" className="mb-2 block text-sm font-medium">
|
|
||||||
Choose customer
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<select
|
|
||||||
id="customer"
|
|
||||||
name="customerId"
|
|
||||||
className="peer block w-full cursor-pointer rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
|
||||||
defaultValue={invoice.customer_id}
|
|
||||||
>
|
|
||||||
<option value="" disabled>
|
|
||||||
Select a customer
|
|
||||||
</option>
|
|
||||||
{customers.map((customer) => (
|
|
||||||
<option key={customer.id} value={customer.id}>
|
|
||||||
{customer.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<UserCircleIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Invoice Amount */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<label htmlFor="amount" className="mb-2 block text-sm font-medium">
|
|
||||||
Choose an amount
|
|
||||||
</label>
|
|
||||||
<div className="relative mt-2 rounded-md">
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
id="amount"
|
|
||||||
name="amount"
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
defaultValue={invoice.amount}
|
|
||||||
placeholder="Enter USD amount"
|
|
||||||
className="peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
|
|
||||||
/>
|
|
||||||
<CurrencyDollarIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Invoice Status */}
|
|
||||||
<fieldset>
|
|
||||||
<legend className="mb-2 block text-sm font-medium">
|
|
||||||
Set the invoice status
|
|
||||||
</legend>
|
|
||||||
<div className="rounded-md border border-gray-200 bg-white px-[14px] py-3">
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<input
|
|
||||||
id="pending"
|
|
||||||
name="status"
|
|
||||||
type="radio"
|
|
||||||
value="pending"
|
|
||||||
defaultChecked={invoice.status === 'pending'}
|
|
||||||
className="h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="pending"
|
|
||||||
className="ml-2 flex cursor-pointer items-center gap-1.5 rounded-full bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-600"
|
|
||||||
>
|
|
||||||
Pending <ClockIcon className="h-4 w-4" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<input
|
|
||||||
id="paid"
|
|
||||||
name="status"
|
|
||||||
type="radio"
|
|
||||||
value="paid"
|
|
||||||
defaultChecked={invoice.status === 'paid'}
|
|
||||||
className="h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="paid"
|
|
||||||
className="ml-2 flex cursor-pointer items-center gap-1.5 rounded-full bg-green-500 px-3 py-1.5 text-xs font-medium text-white"
|
|
||||||
>
|
|
||||||
Paid <CheckIcon className="h-4 w-4" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 flex justify-end gap-4">
|
|
||||||
<Link
|
|
||||||
href="/dashboard/guests"
|
|
||||||
className="flex h-10 items-center rounded-lg bg-gray-100 px-4 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-200"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Link>
|
|
||||||
<Button type="submit">Edit Invoice</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { generatePagination } from '@/app/lib/utils';
|
|
||||||
|
|
||||||
export default function Pagination({ totalPages }: { totalPages: number }) {
|
|
||||||
// NOTE: Uncomment this code in Chapter 11
|
|
||||||
|
|
||||||
// const allPages = generatePagination(currentPage, totalPages);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* NOTE: Uncomment this code in Chapter 11 */}
|
|
||||||
|
|
||||||
{/* <div className="inline-flex">
|
|
||||||
<PaginationArrow
|
|
||||||
direction="left"
|
|
||||||
href={createPageURL(currentPage - 1)}
|
|
||||||
isDisabled={currentPage <= 1}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex -space-x-px">
|
|
||||||
{allPages.map((page, index) => {
|
|
||||||
let position: 'first' | 'last' | 'single' | 'middle' | undefined;
|
|
||||||
|
|
||||||
if (index === 0) position = 'first';
|
|
||||||
if (index === allPages.length - 1) position = 'last';
|
|
||||||
if (allPages.length === 1) position = 'single';
|
|
||||||
if (page === '...') position = 'middle';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PaginationNumber
|
|
||||||
key={page}
|
|
||||||
href={createPageURL(page)}
|
|
||||||
page={page}
|
|
||||||
position={position}
|
|
||||||
isActive={currentPage === page}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PaginationArrow
|
|
||||||
direction="right"
|
|
||||||
href={createPageURL(currentPage + 1)}
|
|
||||||
isDisabled={currentPage >= totalPages}
|
|
||||||
/>
|
|
||||||
</div> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaginationNumber({
|
|
||||||
page,
|
|
||||||
href,
|
|
||||||
isActive,
|
|
||||||
position,
|
|
||||||
}: {
|
|
||||||
page: number | string;
|
|
||||||
href: string;
|
|
||||||
position?: 'first' | 'last' | 'middle' | 'single';
|
|
||||||
isActive: boolean;
|
|
||||||
}) {
|
|
||||||
const className = clsx(
|
|
||||||
'flex h-10 w-10 items-center justify-center text-sm border',
|
|
||||||
{
|
|
||||||
'rounded-l-md': position === 'first' || position === 'single',
|
|
||||||
'rounded-r-md': position === 'last' || position === 'single',
|
|
||||||
'z-10 bg-blue-600 border-blue-600 text-white': isActive,
|
|
||||||
'hover:bg-gray-100': !isActive && position !== 'middle',
|
|
||||||
'text-gray-300': position === 'middle',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return isActive || position === 'middle' ? (
|
|
||||||
<div className={className}>{page}</div>
|
|
||||||
) : (
|
|
||||||
<Link href={href} className={className}>
|
|
||||||
{page}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaginationArrow({
|
|
||||||
href,
|
|
||||||
direction,
|
|
||||||
isDisabled,
|
|
||||||
}: {
|
|
||||||
href: string;
|
|
||||||
direction: 'left' | 'right';
|
|
||||||
isDisabled?: boolean;
|
|
||||||
}) {
|
|
||||||
const className = clsx(
|
|
||||||
'flex h-10 w-10 items-center justify-center rounded-md border',
|
|
||||||
{
|
|
||||||
'pointer-events-none text-gray-300': isDisabled,
|
|
||||||
'hover:bg-gray-100': !isDisabled,
|
|
||||||
'mr-2 md:mr-4': direction === 'left',
|
|
||||||
'ml-2 md:ml-4': direction === 'right',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const icon =
|
|
||||||
direction === 'left' ? (
|
|
||||||
<ArrowLeftIcon className="w-4" />
|
|
||||||
) : (
|
|
||||||
<ArrowRightIcon className="w-4" />
|
|
||||||
);
|
|
||||||
|
|
||||||
return isDisabled ? (
|
|
||||||
<div className={className}>{icon}</div>
|
|
||||||
) : (
|
|
||||||
<Link className={className} href={href}>
|
|
||||||
{icon}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import { CheckIcon, ClockIcon } from '@heroicons/react/24/outline';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
export default function gueststatus({ status }: { status: string }) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
'inline-flex items-center rounded-full px-2 py-1 text-xs',
|
|
||||||
{
|
|
||||||
'bg-gray-100 text-gray-500': status === 'pending',
|
|
||||||
'bg-green-500 text-white': status === 'paid',
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{status === 'pending' ? (
|
|
||||||
<>
|
|
||||||
Pending
|
|
||||||
<ClockIcon className="ml-1 w-4 text-gray-500" />
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
{status === 'paid' ? (
|
|
||||||
<>
|
|
||||||
Paid
|
|
||||||
<CheckIcon className="ml-1 w-4 text-white" />
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
||||||
|
|
||||||
import { lusitana } from '@/app/ui/fonts';
|
|
||||||
import {
|
|
||||||
AtSymbolIcon,
|
|
||||||
KeyIcon,
|
|
||||||
ExclamationCircleIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import { ArrowRightIcon } from '@heroicons/react/20/solid';
|
|
||||||
import { Button } from './button';
|
|
||||||
|
|
||||||
export default function LoginForm() {
|
|
||||||
return (
|
|
||||||
<form className="space-y-3">
|
|
||||||
<div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8">
|
|
||||||
<h1 className={`${lusitana.className} mb-3 text-2xl`}>
|
|
||||||
Please log in to continue.
|
|
||||||
</h1>
|
|
||||||
<div className="w-full">
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
|
|
||||||
htmlFor="email"
|
|
||||||
>
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="Enter your email address"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<AtSymbolIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
|
||||||
<label
|
|
||||||
className="mb-3 mt-5 block text-xs font-medium text-gray-900"
|
|
||||||
htmlFor="password"
|
|
||||||
>
|
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Enter password"
|
|
||||||
required
|
|
||||||
minLength={6}
|
|
||||||
/>
|
|
||||||
<KeyIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button className="mt-4 w-full">
|
|
||||||
Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
|
|
||||||
</Button>
|
|
||||||
<div className="flex h-8 items-end space-x-1">
|
|
||||||
{/* Add form errors here */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1019 B |
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.7 KiB |
Loading…
x
Reference in New Issue
Block a user