-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from Ayon95/booking_page
Add booking details page
- Loading branch information
Showing
21 changed files
with
675 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import styled from 'styled-components'; | ||
import { HiArrowLeft, HiOutlineHomeModern } from 'react-icons/hi2'; | ||
|
||
import Heading from '@/ui/Heading'; | ||
import { LinkButtonIconText } from '@/ui/button/ButtonIconText'; | ||
import Badge from '@/ui/Badge'; | ||
import { bookingStatusToBadgeColorMap } from '@/utils/constants'; | ||
import { Booking, BookingStatus } from '@/types/bookings'; | ||
import { formatDate } from '@/utils/helpers'; | ||
import BookingGuestDetailsTable from './BookingGuestDetailsTable'; | ||
import BookingPaymentSummary from './BookingPaymentSummary'; | ||
|
||
interface BookingProps { | ||
booking: Booking; | ||
} | ||
|
||
const dateFormatOptions: Intl.DateTimeFormatOptions = { | ||
weekday: 'short', | ||
month: 'short', | ||
day: '2-digit', | ||
year: 'numeric', | ||
}; | ||
|
||
function BookingDetails({ booking }: BookingProps) { | ||
return ( | ||
<Container> | ||
<Header> | ||
<HeadingContainer> | ||
<Heading as="h1">Booking #{booking.id}</Heading> | ||
<StyledBadge type={bookingStatusToBadgeColorMap[booking.status as BookingStatus]}> | ||
{booking.status} | ||
</StyledBadge> | ||
</HeadingContainer> | ||
<LinkButtonIconText to="/bookings"> | ||
<HiArrowLeft /> | ||
<span>Bookings</span> | ||
</LinkButtonIconText> | ||
</Header> | ||
<BookingDate className="text-sm"> | ||
Booked on{' '} | ||
<time dateTime={booking.created_at}> | ||
{formatDate(booking.created_at, dateFormatOptions)} | ||
</time> | ||
</BookingDate> | ||
<Grid> | ||
<div> | ||
<BookingDurationDetails> | ||
<div> | ||
<HiOutlineHomeModern /> | ||
<p> | ||
<span className="fw-semi-bold">{booking.num_nights}</span>{' '} | ||
{booking.num_nights > 1 ? 'nights' : 'night'} in{' '} | ||
<span className="fw-semi-bold">Cabin {booking.cabin?.name}</span> | ||
</p> | ||
</div> | ||
<div> | ||
<p>Check-in</p> | ||
<time dateTime={booking.start_date} className="fw-semi-bold"> | ||
{formatDate(booking.start_date, dateFormatOptions)} | ||
</time> | ||
</div> | ||
<div> | ||
<p>Check-out</p> | ||
<time dateTime={booking.end_date} className="fw-semi-bold"> | ||
{formatDate(booking.end_date, dateFormatOptions)} | ||
</time> | ||
</div> | ||
</BookingDurationDetails> | ||
{booking.guest && ( | ||
<Section> | ||
<Heading as="h2">Guest Details</Heading> | ||
<BookingGuestDetailsTable | ||
guestDetails={{ num_guests: booking.num_guests, guest: booking.guest }} | ||
/> | ||
</Section> | ||
)} | ||
{booking.observations && ( | ||
<Observations> | ||
<Heading as="h2">Observations & Requests</Heading> | ||
<p>{booking.observations}</p> | ||
</Observations> | ||
)} | ||
</div> | ||
|
||
<BookingPaymentSummary | ||
paymentInfo={{ | ||
cabin_price: booking.cabin_price, | ||
extra_price: booking.extra_price, | ||
total_price: booking.total_price, | ||
has_breakfast: booking.has_breakfast, | ||
is_paid: booking.is_paid, | ||
}} | ||
/> | ||
</Grid> | ||
</Container> | ||
); | ||
} | ||
|
||
export default BookingDetails; | ||
|
||
const Container = styled.div` | ||
--section-spacing: 4rem; | ||
`; | ||
|
||
const Header = styled.div` | ||
display: flex; | ||
flex-wrap: wrap; | ||
justify-content: space-between; | ||
align-items: center; | ||
gap: 1rem; | ||
margin-bottom: 0.6rem; | ||
`; | ||
|
||
const HeadingContainer = styled.div` | ||
display: flex; | ||
align-items: center; | ||
gap: 1.6rem; | ||
`; | ||
|
||
const BookingDate = styled.p` | ||
margin-bottom: 4rem; | ||
`; | ||
|
||
const StyledBadge = styled(Badge)` | ||
transform: translateY(3px); | ||
`; | ||
|
||
const Grid = styled.div` | ||
display: grid; | ||
row-gap: var(--section-spacing); | ||
column-gap: 3rem; | ||
@media only screen and (min-width: 75em) { | ||
grid-template-columns: 2.4fr 1fr; | ||
} | ||
`; | ||
|
||
const Section = styled.section` | ||
margin-top: var(--section-spacing); | ||
h2 { | ||
margin-bottom: 4px; | ||
} | ||
`; | ||
|
||
const BookingDurationDetails = styled.div` | ||
display: grid; | ||
grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr)); | ||
align-items: center; | ||
gap: 2rem; | ||
padding: 2rem; | ||
border-radius: var(--border-radius-sm); | ||
text-align: center; | ||
background-color: var(--color-brand-100); | ||
svg { | ||
width: 2.2rem; | ||
height: 2.2rem; | ||
} | ||
`; | ||
|
||
const Observations = styled(Section)` | ||
p { | ||
max-width: 75ch; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import styled from 'styled-components'; | ||
|
||
import { Booking } from '@/types/bookings'; | ||
import Table from '@/ui/Table'; | ||
|
||
interface BookingGuestDetailsTableProps { | ||
guestDetails: Pick<Booking, 'num_guests' | 'guest'>; | ||
} | ||
|
||
function BookingGuestDetailsTable({ guestDetails }: BookingGuestDetailsTableProps) { | ||
const { num_guests, guest } = guestDetails; | ||
|
||
if (!guest) return null; | ||
|
||
return ( | ||
<StyledTable id="guestDetailsTable" caption="Guest Details"> | ||
<Table.Head> | ||
<Table.Row> | ||
<Table.HeaderCell>Name</Table.HeaderCell> | ||
<Table.HeaderCell>Email</Table.HeaderCell> | ||
<Table.HeaderCell>Total Guests</Table.HeaderCell> | ||
<Table.HeaderCell>Country</Table.HeaderCell> | ||
<Table.HeaderCell>National ID</Table.HeaderCell> | ||
</Table.Row> | ||
</Table.Head> | ||
<Table.Body> | ||
<Table.Row> | ||
<Table.Cell label="name" className="fw-semi-bold"> | ||
{guest.full_name} | ||
</Table.Cell> | ||
<Table.Cell label="email">{guest.email}</Table.Cell> | ||
<NumericTextCell label="total guests">{num_guests}</NumericTextCell> | ||
<Table.Cell label="country"> | ||
<CountryDetails> | ||
{guest.country_flag && <Flag src={guest.country_flag} />} | ||
{guest.nationality && <span>{guest.nationality}</span>} | ||
</CountryDetails> | ||
</Table.Cell> | ||
<NumericTextCell label="national ID">{guest.national_id ?? '-'}</NumericTextCell> | ||
</Table.Row> | ||
</Table.Body> | ||
</StyledTable> | ||
); | ||
} | ||
|
||
export default BookingGuestDetailsTable; | ||
|
||
const StyledTable = styled(Table)` | ||
th, | ||
td { | ||
padding: 0.5rem; | ||
} | ||
td img { | ||
border-radius: var(--border-radius-tiny); | ||
} | ||
`; | ||
|
||
const Flag = styled.img` | ||
width: 3rem; | ||
border-radius: var(--border-radius-tiny); | ||
`; | ||
|
||
const CountryDetails = styled.div` | ||
display: flex; | ||
align-items: center; | ||
gap: 1rem; | ||
`; | ||
|
||
const NumericTextCell = styled(Table.Cell)` | ||
font-family: var(--fontFamily-numeric); | ||
font-size: 1.5rem; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { Booking } from '@/types/bookings'; | ||
import Badge from '@/ui/Badge'; | ||
import Heading from '@/ui/Heading'; | ||
import { formatPrice } from '@/utils/helpers'; | ||
import styled from 'styled-components'; | ||
|
||
interface BookingPaymentSummaryProps { | ||
paymentInfo: Pick< | ||
Booking, | ||
'cabin_price' | 'extra_price' | 'total_price' | 'has_breakfast' | 'is_paid' | ||
>; | ||
} | ||
|
||
function BookingPaymentSummary({ paymentInfo }: BookingPaymentSummaryProps) { | ||
const { cabin_price, extra_price, total_price, has_breakfast, is_paid } = paymentInfo; | ||
return ( | ||
<section> | ||
<HeadingContainer> | ||
<Heading as="h2">Payment Summary</Heading> | ||
<StyledBadge type={is_paid ? 'brand' : 'red'}>{is_paid ? 'paid' : 'not paid'}</StyledBadge> | ||
</HeadingContainer> | ||
<div> | ||
<PriceItemContainer> | ||
<PriceItem> | ||
Cabin: <data value={cabin_price}>{formatPrice(cabin_price)}</data> | ||
</PriceItem> | ||
</PriceItemContainer> | ||
{extra_price && ( | ||
<PriceItemContainer> | ||
<PriceItem> | ||
Extra: <data value={extra_price}>{formatPrice(extra_price)}</data> | ||
</PriceItem> | ||
{has_breakfast && <PriceItemExtraInfo>Includes breakfast</PriceItemExtraInfo>} | ||
</PriceItemContainer> | ||
)} | ||
<PriceItemContainer> | ||
<PriceItem> | ||
<strong>Total:</strong>{' '} | ||
<strong> | ||
<data value={total_price}>{formatPrice(total_price)}</data> | ||
</strong> | ||
</PriceItem> | ||
</PriceItemContainer> | ||
</div> | ||
</section> | ||
); | ||
} | ||
|
||
export default BookingPaymentSummary; | ||
|
||
const HeadingContainer = styled.div` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
gap: 1rem; | ||
margin-bottom: 1.6rem; | ||
`; | ||
|
||
const StyledBadge = styled(Badge)` | ||
flex-shrink: 0; | ||
transform: translateY(3px); | ||
`; | ||
|
||
const PriceItemContainer = styled.div` | ||
font-size: calc(var(--fontSize-base) + 0.2rem); | ||
&:not(:last-child) { | ||
margin-bottom: 1.2rem; | ||
} | ||
`; | ||
|
||
const PriceItem = styled.p` | ||
display: flex; | ||
justify-content: space-between; | ||
gap: 1rem; | ||
data { | ||
font-family: var(--fontFamily-numeric); | ||
} | ||
`; | ||
|
||
const PriceItemExtraInfo = styled.p` | ||
font-size: 1.3rem; | ||
font-weight: 600; | ||
color: var(--color-brand-600); | ||
`; |
Oops, something went wrong.