This commit is contained in:
2026-05-12 17:35:38 +03:00
parent d9ffcb4b92
commit e3f3e62482
51 changed files with 882 additions and 3413 deletions
+6 -6
View File
@@ -1,11 +1,11 @@
import {
BanknotesIcon,
ClockIcon,
UserGroupIcon,
InboxIcon,
} from '@heroicons/react/24/outline';
import { lusitana } from '@/app/ui/fonts';
import { fetchCardData } from '@/app/lib/data';
UserGroupIcon,
} from "@heroicons/react/24/outline";
import { fetchCardData } from "@/app/lib/data";
import { lusitana } from "@/app/ui/fonts";
const iconMap = {
collected: BanknotesIcon,
@@ -21,7 +21,7 @@ export default async function CardWrapper() {
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData();
return (
<>
<Card title="Collected" value={totalPaidInvoices} type="collected" />
@@ -43,7 +43,7 @@ export function Card({
}: {
title: string;
value: number | string;
type: 'invoices' | 'customers' | 'pending' | 'collected';
type: "invoices" | "customers" | "pending" | "collected";
}) {
const Icon = iconMap[type];
+46 -44
View File
@@ -1,11 +1,11 @@
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';
import { fetchLatestInvoices } from '@/app/lib/data';
import { ArrowPathIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Image from "next/image";
import { fetchLatestInvoices } from "@/app/lib/data";
import { lusitana } from "@/app/ui/fonts";
export default async function LatestInvoices() { // Remove props
export default async function LatestInvoices() {
// Remove props
const latestInvoices = await fetchLatestInvoices();
return (
@@ -16,44 +16,46 @@ export default async function LatestInvoices() { // Remove props
<div className="flex grow flex-col justify-between rounded-xl bg-gray-50 p-4">
{/* NOTE: comment in this code when you get to this point in the course */}
{ <div className="bg-white px-6">
{latestInvoices.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`}
{
<div className="bg-white px-6">
{latestInvoices.map((invoice, i) => {
return (
<div
key={invoice.id}
className={clsx(
"flex flex-row items-center justify-between py-4",
{
"border-t": i !== 0,
},
)}
>
{invoice.amount}
</p>
</div>
);
})}
</div> }
<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>
+15 -15
View File
@@ -1,29 +1,29 @@
'use client';
"use client";
import {
UserGroupIcon,
HomeIcon,
DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import clsx from 'clsx';
HomeIcon,
UserGroupIcon,
} from "@heroicons/react/24/outline";
import clsx from "clsx";
import Link from "next/link";
import { usePathname } from "next/navigation";
// Map of links to display in the side navigation.
// Depending on the size of the application, this would be stored in a database.
const links = [
{ name: 'Home', href: '/dashboard', icon: HomeIcon },
{ name: "Home", href: "/dashboard", icon: HomeIcon },
{
name: 'Invoices',
href: '/dashboard/invoices',
name: "Invoices",
href: "/dashboard/invoices",
icon: DocumentDuplicateIcon,
},
{ name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon },
{ name: "Customers", href: "/dashboard/customers", icon: UserGroupIcon },
];
export default function NavLinks() {
const pathname = usePathname();
return (
const pathname = usePathname();
return (
<>
{links.map((link) => {
const LinkIcon = link.icon;
@@ -32,9 +32,9 @@ return (
key={link.name}
href={link.href}
className={clsx(
'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',
"flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3",
{
'bg-sky-100 text-blue-600': pathname === link.href,
"bg-sky-100 text-blue-600": pathname === link.href,
},
)}
>
+38 -33
View File
@@ -1,8 +1,7 @@
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';
import { fetchRevenue } from '@/app/lib/data';
import { CalendarIcon } from "@heroicons/react/24/outline";
import { fetchRevenue } from "@/app/lib/data";
import { generateYAxis } from "@/app/lib/utils";
import { lusitana } from "@/app/ui/fonts";
// This component is representational only.
// For data visualization UI, check out:
@@ -10,7 +9,8 @@ import { fetchRevenue } from '@/app/lib/data';
// https://www.chartjs.org/
// https://airbnb.io/visx/
export default async function RevenueChart() { // Make component async, remove the props
export default async function RevenueChart() {
// Make component async, remove the props
const revenue = await fetchRevenue(); // Fetch data inside the component
const chartHeight = 350;
@@ -29,36 +29,41 @@ export default async function RevenueChart() { // Make component async, remove t
</h2>
{/* NOTE: comment in this code when you get to this point in the course */}
{ <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 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>
{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 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 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>
);
}
+12 -11
View File
@@ -1,9 +1,8 @@
import Link from 'next/link';
import NavLinks from '@/app/ui/dashboard/nav-links';
import AcmeLogo from '@/app/ui/acme-logo';
import { PowerIcon } from '@heroicons/react/24/outline';
import { signOut } from '@/auth';
import { redirect } from 'next/navigation';
import { PowerIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import AcmeLogo from "@/app/ui/acme-logo";
import NavLinks from "@/app/ui/dashboard/nav-links";
import { signOut } from "@/auth";
export default function SideNav() {
return (
@@ -19,10 +18,12 @@ export default function SideNav() {
<div className="flex grow flex-row justify-between space-x-2 md:flex-col md:space-x-0 md:space-y-2">
<NavLinks />
<div className="hidden h-auto w-full grow rounded-md bg-gray-50 md:block"></div>
<form action={async () => {
'use server';
await signOut();
}}>
<form
action={async () => {
"use server";
await signOut();
}}
>
<button className="flex h-[48px] w-full grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3">
<PowerIcon className="w-6" />
<div className="hidden md:block">Sign Out</div>
@@ -31,4 +32,4 @@ export default function SideNav() {
</div>
</div>
);
}
}