header & footer

This commit is contained in:
2024-01-16 10:24:21 +01:00
parent 92eacc2764
commit 226beb974f
13 changed files with 101 additions and 41 deletions

View File

@@ -1,10 +1,11 @@
import { BillEditForm } from '@/app/ui/BillEditForm'; import { BillEditForm } from '@/app/ui/BillEditForm';
import { Main } from '@/app/ui/Main';
export default async function Page({ params:{ id:locationID } }: { params: { id:string } }) { export default async function Page({ params:{ id:locationID } }: { params: { id:string } }) {
return ( return (
<main> <Main>
<BillEditForm locationID={locationID} /> <BillEditForm locationID={locationID} />
</main> </Main>
); );
} }

View File

@@ -1,5 +1,6 @@
import { fetchBillById } from '@/app/lib/actions/billActions'; import { fetchBillById } from '@/app/lib/actions/billActions';
import { BillEditForm } from '@/app/ui/BillEditForm'; import { BillEditForm } from '@/app/ui/BillEditForm';
import { Main } from '@/app/ui/Main';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
export default async function Page({ params:{ id } }: { params: { id:string } }) { export default async function Page({ params:{ id } }: { params: { id:string } }) {
@@ -12,8 +13,8 @@ export default async function Page({ params:{ id } }: { params: { id:string } })
return(notFound()); return(notFound());
} }
return ( return (
<main> <Main>
<BillEditForm locationID={locationID} bill={bill} /> <BillEditForm locationID={locationID} bill={bill} />
</main> </Main>
); );
} }

View File

@@ -10,6 +10,8 @@ import { YearMonth } from './lib/db-types';
import { formatYearMonth } from './lib/format'; import { formatYearMonth } from './lib/format';
import { FC } from 'react'; import { FC } from 'react';
import Pagination from './ui/Pagination'; import Pagination from './ui/Pagination';
import { PageHeader } from './ui/PageHeader';
import { Main } from './ui/Main';
const getNextYearMonth = (yearMonth:YearMonth) => { const getNextYearMonth = (yearMonth:YearMonth) => {
const {year, month} = yearMonth; const {year, month} = yearMonth;
@@ -62,7 +64,7 @@ const Page:FC<PageProps> = async ({ searchParams }) => {
let monthlyExpense = 0; let monthlyExpense = 0;
return ( return (
<main className="flex min-h-screen flex-col p-6 bg-base-300"> <Main>
{ {
// if this is the latest year, show the add month button // if this is the latest year, show the add month button
currentYear === latestYear && currentYear === latestYear &&
@@ -116,8 +118,7 @@ const Page:FC<PageProps> = async ({ searchParams }) => {
<div className="mt-5 flex w-full justify-center"> <div className="mt-5 flex w-full justify-center">
<Pagination availableYears={availableYears} /> <Pagination availableYears={availableYears} />
</div> </div>
<PageFooter /> </Main>
</main>
); );
} }

View File

@@ -1,7 +1,10 @@
import { Main } from "../ui/Main";
import { PageFooter } from "../ui/PageFooter";
import { PageHeader } from "../ui/PageHeader";
const ConsentPage = () => const ConsentPage = () =>
<main> <Main>
<article className="prose container mx-auto my-[4em]"> <article className="prose container mx-auto px-6">
<h1>Application Privacy Policy for Home Utility Bills Tracking Web App</h1> <h1>Application Privacy Policy for Home Utility Bills Tracking Web App</h1>
<h2>1. Introduction</h2> <h2>1. Introduction</h2>
<p>Welcome to our Home Utility Bills Tracking Web Application (App). This Privacy Policy is intended to inform you about how we collect, use, and disclose your personal information through the operation of the App.</p> <p>Welcome to our Home Utility Bills Tracking Web Application (App). This Privacy Policy is intended to inform you about how we collect, use, and disclose your personal information through the operation of the App.</p>
@@ -32,6 +35,6 @@ const ConsentPage = () =>
<h2>11. Consent</h2> <h2>11. Consent</h2>
<p>By using our App, you consent to our privacy policy.</p> <p>By using our App, you consent to our privacy policy.</p>
</article> </article>
</main>; </Main>;
export default ConsentPage; export default ConsentPage;

View File

@@ -1,7 +1,10 @@
import { Main } from "../ui/Main";
import { PageFooter } from "../ui/PageFooter";
import { PageHeader } from "../ui/PageHeader";
const TermsPage = () => const TermsPage = () =>
<main> <Main>
<article className="prose container mx-auto my-[4em]"> <article className="prose container">
<h1>Terms of Service for Home Utility Bills Tracking Web Application</h1> <h1>Terms of Service for Home Utility Bills Tracking Web Application</h1>
<h2>1. Introduction</h2> <h2>1. Introduction</h2>
<p>Welcome to our Home Utility Bills Tracking Web Application (App). These Terms of Service (Terms) govern your access to and use of our App. By accessing or using the App, you agree to be bound by these Terms.</p> <p>Welcome to our Home Utility Bills Tracking Web Application (App). These Terms of Service (Terms) govern your access to and use of our App. By accessing or using the App, you agree to be bound by these Terms.</p>
@@ -42,6 +45,6 @@ const TermsPage = () =>
<h2>11. Contact Us</h2> <h2>11. Contact Us</h2>
<p>If you have any questions about these Terms, please contact us at [Contact Information].</p> <p>If you have any questions about these Terms, please contact us at [Contact Information].</p>
</article> </article>
</main>; </Main>;
export default TermsPage; export default TermsPage;

View File

@@ -1,6 +1,7 @@
import { PlusCircleIcon } from "@heroicons/react/24/outline"; import { PlusCircleIcon } from "@heroicons/react/24/outline";
import { YearMonth } from "../lib/db-types"; import { YearMonth } from "../lib/db-types";
import { formatYearMonth } from "../lib/format"; import { formatYearMonth } from "../lib/format";
import Link from "next/link";
export interface AddLocationButtonProps { export interface AddLocationButtonProps {
@@ -10,9 +11,9 @@ export interface AddLocationButtonProps {
export const AddLocationButton:React.FC<AddLocationButtonProps> = ({yearMonth}) => export const AddLocationButton:React.FC<AddLocationButtonProps> = ({yearMonth}) =>
<div className="card card-compact card-bordered max-w-[36em] bg-base-100 shadow-s my-1"> <div className="card card-compact card-bordered max-w-[36em] bg-base-100 shadow-s my-1">
<a href={`/location/${ formatYearMonth(yearMonth) }/add`} className="card-body tooltip self-center" data-tip="Add a new billing location"> <Link href={`/location/${ formatYearMonth(yearMonth) }/add`} className="card-body tooltip self-center" data-tip="Add a new billing location">
<span className='grid self-center' data-tip="Add a new billing location"> <span className='grid self-center' data-tip="Add a new billing location">
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" /> <PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" />
</span> </span>
</a> </Link>
</div>; </div>;

View File

@@ -2,12 +2,17 @@ import { PlusCircleIcon } from "@heroicons/react/24/outline";
import React from "react"; import React from "react";
import { formatYearMonth } from "../lib/format"; import { formatYearMonth } from "../lib/format";
import { YearMonth } from "../lib/db-types"; import { YearMonth } from "../lib/db-types";
import Link from "next/link";
export interface AddMonthButtonProps { export interface AddMonthButtonProps {
yearMonth: YearMonth; yearMonth: YearMonth;
} }
export const AddMonthButton:React.FC<AddMonthButtonProps> = ({ yearMonth }) => export const AddMonthButton:React.FC<AddMonthButtonProps> = ({ yearMonth }) =>
<a href={`/year-month/${formatYearMonth(yearMonth)}/add`} className='grid self-center tooltip' data-tip="Dodaj novi mjesec"> <div className="card card-compact card-bordered max-w-[36em] shadow-s my-1">
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" /> <Link href={`/year-month/${formatYearMonth(yearMonth)}/add`} className='grid self-center tooltip' data-tip="Dodaj novi mjesec">
</a> <span className='grid self-center'>
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" />
</span>
</Link>
</div>;

View File

@@ -5,6 +5,7 @@ import { Bill } from "../lib/db-types";
import React, { FC } from "react"; import React, { FC } from "react";
import { useFormState } from "react-dom"; import { useFormState } from "react-dom";
import { gotoHome, updateOrAddBill } from "../lib/actions/billActions"; import { gotoHome, updateOrAddBill } from "../lib/actions/billActions";
import Link from "next/link";
// Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment // Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment
// This is a workaround for that // This is a workaround for that
@@ -41,15 +42,15 @@ export const BillEditForm:FC<BillEditFormProps> = ({ locationID, bill }) => {
} }
return( return(
<div className="card card-compact card-bordered max-w-sm bg-base-100 shadow-s my-1"> <div className="card card-compact card-bordered min-w-96 bg-base-100 shadow-s">
<div className="card-body"> <div className="card-body">
<form action={ dispatch }> <form action={ dispatch }>
{ {
// don't show the delete button if we are adding a new bill // don't show the delete button if we are adding a new bill
bill ? bill ?
<a href={`/bill/${locationID}-${billID}/delete/`}> <Link href={`/bill/${locationID}-${billID}/delete/`}>
<TrashIcon className="h-[1em] w-[1em] absolute cursor-pointer text-error bottom-5 right-4 text-2xl" /> <TrashIcon className="h-[1em] w-[1em] absolute cursor-pointer text-error bottom-5 right-4 text-2xl" />
</a> : null </Link> : null
} }
<input id="billName" name="billName" type="text" placeholder="Bill name" className="input input-bordered w-full" defaultValue={name} required /> <input id="billName" name="billName" type="text" placeholder="Bill name" className="input input-bordered w-full" defaultValue={name} required />
@@ -65,10 +66,10 @@ export const BillEditForm:FC<BillEditFormProps> = ({ locationID, bill }) => {
// <textarea className="textarea textarea-bordered my-1 w-full max-w-sm block" placeholder="Opis" value="Pričuva, Voda, Smeće"></textarea> // <textarea className="textarea textarea-bordered my-1 w-full max-w-sm block" placeholder="Opis" value="Pričuva, Voda, Smeće"></textarea>
attachment ? attachment ?
<a href={`/attachment/${locationID}-${billID}/`} target="_blank" className='text-center block max-w-[24em] text-nowrap truncate inline-block mt-4'> <Link href={`/attachment/${locationID}-${billID}/`} target="_blank" className='text-center block max-w-[24em] text-nowrap truncate inline-block mt-4'>
<DocumentIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" /> <DocumentIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" />
{decodeURIComponent(attachment.fileName)} {decodeURIComponent(attachment.fileName)}
</a> </Link>
: null : null
} }
<input id="billAttachment" name="billAttachment" type="file" className="file-input file-input-bordered w-full max-w-sm file-input-xs my-2" /> <input id="billAttachment" name="billAttachment" type="file" className="file-input file-input-bordered w-full max-w-sm file-input-xs my-2" />

View File

@@ -6,6 +6,7 @@ import { BillBadge } from "./BillBadge";
import { BillingLocation } from "../lib/db-types"; import { BillingLocation } from "../lib/db-types";
import { formatYearMonth } from "../lib/format"; import { formatYearMonth } from "../lib/format";
import { formatCurrency } from "../lib/formatStrings"; import { formatCurrency } from "../lib/formatStrings";
import Link from "next/link";
export interface LocationCardProps { export interface LocationCardProps {
location: BillingLocation location: BillingLocation
@@ -17,19 +18,19 @@ export const LocationCard:FC<LocationCardProps> = ({location: { _id, name, yearM
const monthlyExpense = bills.reduce((acc, bill) => acc + (bill.payedAmount ?? 0), 0); const monthlyExpense = bills.reduce((acc, bill) => acc + (bill.payedAmount ?? 0), 0);
return( return(
<div className="card card-compact card-bordered max-w-[36em] bg-base-100 shadow-s my-1"> <div className="card card-compact card-bordered min-w-96 max-w-[36em] bg-base-100 shadow-s my-1">
<div className="card-body"> <div className="card-body">
<a href={`/location/${_id}/edit`} className="card-subtitle tooltip" data-tip="Edit Location"> <Link href={`/location/${_id}/edit`} className="card-subtitle tooltip" data-tip="Edit Location">
<Cog8ToothIcon className="h-[1em] w-[1em] absolute cursor-pointer top-3 right-3 text-2xl" /> <Cog8ToothIcon className="h-[1em] w-[1em] absolute cursor-pointer top-3 right-3 text-2xl" />
</a> </Link>
<h2 className="card-title mr-[2em]">{formatYearMonth(yearMonth)} {name}</h2> <h2 className="card-title mr-[2em]">{formatYearMonth(yearMonth)} {name}</h2>
<div className="card-actions"> <div className="card-actions">
{ {
bills.map(bill => <BillBadge key={`${bill._id}`} locationId={_id} bill={bill} />) bills.map(bill => <BillBadge key={`${bill._id}`} locationId={_id} bill={bill} />)
} }
<a href={`/bill/${_id}/add`} className="tooltip" data-tip="Add a new bill"> <Link href={`/bill/${_id}/add`} className="tooltip" data-tip="Add a new bill">
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-2xl" /> <PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-2xl" />
</a> </Link>
</div> </div>
{ {
monthlyExpense > 0 ? monthlyExpense > 0 ?

View File

@@ -6,6 +6,8 @@ import { BillingLocation, YearMonth } from "../lib/db-types";
import { updateOrAddLocation } from "../lib/actions/locationActions"; import { updateOrAddLocation } from "../lib/actions/locationActions";
import { useFormState } from "react-dom"; import { useFormState } from "react-dom";
import { gotoHome } from "../lib/actions/billActions"; import { gotoHome } from "../lib/actions/billActions";
import { Main } from "./Main";
import Link from "next/link";
export interface LocationEditFormProps { export interface LocationEditFormProps {
/** location which should be edited */ /** location which should be edited */
@@ -27,15 +29,15 @@ export const LocationEditForm:FC<LocationEditFormProps> = ({ location, yearMonth
}; };
return( return(
<main> <Main>
<div className="card card-compact card-bordered max-w-sm bg-base-100 shadow-s my-1"> <div className="card card-compact card-bordered min-w-96 bg-base-100 shadow-s my-1">
<div className="card-body"> <div className="card-body">
<form action={dispatch}> <form action={dispatch}>
{ {
location && location &&
<a href={`/location/${location._id}/delete`} className="absolute bottom-5 right-4 tooltip" data-tip="Delete Location"> <Link href={`/location/${location._id}/delete`} className="absolute bottom-5 right-4 tooltip" data-tip="Delete Location">
<TrashIcon className="h-[1em] w-[1em] text-error text-2xl" /> <TrashIcon className="h-[1em] w-[1em] text-error text-2xl" />
</a> </Link>
} }
<input id="locationName" name="locationName" type="text" placeholder="Naziv lokacije" className="input input-bordered w-full" defaultValue={location?.name ?? ""} /> <input id="locationName" name="locationName" type="text" placeholder="Naziv lokacije" className="input input-bordered w-full" defaultValue={location?.name ?? ""} />
<div id="status-error" aria-live="polite" aria-atomic="true"> <div id="status-error" aria-live="polite" aria-atomic="true">
@@ -66,11 +68,13 @@ export const LocationEditForm:FC<LocationEditFormProps> = ({ location, yearMonth
} }
</div> </div>
<button className="btn btn-primary">Save</button> <div className="pt-4">
<button type="button" className="btn btn-neutral ml-3" onClick={handleCancel}>Cancel</button> <button className="btn btn-primary">Save</button>
<button type="button" className="btn btn-neutral ml-3" onClick={handleCancel}>Cancel</button>
</div>
</form> </form>
</div> </div>
</div> </div>
</main> </Main>
) )
} }

16
app/ui/Main.tsx Normal file
View File

@@ -0,0 +1,16 @@
import { FC } from "react";
import { PageHeader } from "./PageHeader";
import { PageFooter } from "./PageFooter";
export interface MainProps {
}
export const Main:FC<MainProps> = ({ children }) =>
<main className="flex min-h-screen flex-col bg-base-300">
<PageHeader />
<div className="mx-auto px-4">
{children}
</div>
<PageFooter />
</main>

View File

@@ -1,8 +1,25 @@
import Link from "next/link";
import React from "react"; import React from "react";
export const PageFooter:React.FC = () => <> export const PageFooter: React.FC = () =>
<h2 className='text-xl text-sky-400/75 font-semibold mt-4'>Docs</h2> <div className="bg-base-100 text-base-content mt-10">
<p><a href="https://tailwindcss.com/docs/" target="_blank">tailwindcss.com</a></p> <footer className="footer mx-auto max-w-2xl px-4 py-10">
<p><a href="https://heroicons.com/" target="_blank">heroicons.com</a></p> <div>
<p><a href="https://daisyui.com/components/" target="_blank">daisyui.com</a></p> <div className="flex items-center gap-2">
</> <img src="/icon4.png"></img>
<div className="font-title inline-flex text-3xl font-black">Bills Tracker</div>
</div>
<p className="text-base-content/70 mb-5">Web app for tracking home utility bills</p>
<Link href="/" className="link link-hover">Home</Link>
<Link href="/policy/" className="link link-hover">Privacy Policy</Link>
<Link href="/terms/" className="link link-hover">Terms of Service</Link>
</div>
<div>
<span className="footer-title opacity-70">documents</span>
<a href="https://tailwindcss.com/docs/" target="_blank" className="link link-hover">tailwindcss.com</a>
<a href="https://heroicons.com/" target="_blank" className="link link-hover">heroicons.com</a>
<a href="https://daisyui.com/components/" target="_blank" className="link link-hover">daisyui.com</a>
</div>
</footer>
</div>;

6
app/ui/PageHeader.tsx Normal file
View File

@@ -0,0 +1,6 @@
import Link from "next/link";
export const PageHeader = () =>
<div className="navbar bg-base-100 mb-6">
<Link className="btn btn-ghost text-xl" href="/"><img src="/icon3.png"></img> Utility Bills Tracker</Link>
</div>