header & footer
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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>;
|
||||||
@@ -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">
|
||||||
|
<Link href={`/year-month/${formatYearMonth(yearMonth)}/add`} className='grid self-center tooltip' data-tip="Dodaj novi mjesec">
|
||||||
|
<span className='grid self-center'>
|
||||||
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" />
|
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" />
|
||||||
</a>
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>;
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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 ?
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
<div className="pt-4">
|
||||||
<button className="btn btn-primary">Save</button>
|
<button className="btn btn-primary">Save</button>
|
||||||
<button type="button" className="btn btn-neutral ml-3" onClick={handleCancel}>Cancel</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
16
app/ui/Main.tsx
Normal 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>
|
||||||
@@ -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
6
app/ui/PageHeader.tsx
Normal 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>
|
||||||
Reference in New Issue
Block a user