Remove nextjs scaffolding contents #113
| @ -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