Skip to content

Commit

Permalink
feat: actual booking
Browse files Browse the repository at this point in the history
  • Loading branch information
NickNaskida committed Oct 3, 2023
1 parent dbf9eec commit 96935de
Show file tree
Hide file tree
Showing 22 changed files with 214 additions and 92 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

### Description

### Tech Stack & Libraries

##### Client
- React - [Documentation](https://react.dev)
- Vite - [Documentation](https://vitejs.dev/guide/)
- TailwindCSS - [Documentation](https://tailwindcss.com/docs)

##### Server
- Python
- FastAPI - Async web framework [Documentation](https://fastapi.tiangolo.com/)
- Aiogram - Async Telegram Bot API framework [Documentation](https://docs.aiogram.dev/en/latest/)
- Pydantic - Validation Library [Documentation](https://docs.pydantic.dev/latest/)
- SQLAlchemy - ORM [Documentation](https://docs.sqlalchemy.org/en/20/)
- SQLite - Database

### Project Structure
```
...
Expand Down
16 changes: 16 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"match-sorter": "^6.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.47.0",
"react-router-dom": "^6.16.0",
"sort-by": "^0.0.2"
},
Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
35 changes: 3 additions & 32 deletions frontend/src/components/VenueCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,19 @@ import { useNavigate } from "react-router-dom";

import { Card, CardHeader, CardFooter, Image, Button } from "@nextui-org/react";

import Image1 from "@/assets/images/1.jpg";
import Image2 from "@/assets/images/2.jpg";
import Image3 from "@/assets/images/3.jpg";
import Image4 from "@/assets/images/4.jpg";
import Image5 from "@/assets/images/5.jpg";
import Image6 from "@/assets/images/6.jpg";
import Image7 from "@/assets/images/7.jpg";
import Image8 from "@/assets/images/8.jpg";
import Image9 from "@/assets/images/9.jpg";
import Image10 from "@/assets/images/10.jpg";

const VenueCard = ({ venue }) => {
const navigate = useNavigate();

const [selectedImageUrl, setSelectedImageUrl] = useState(null);
const imageUrls = [
Image1,
Image2,
Image3,
Image4,
Image5,
Image6,
Image7,
Image8,
Image9,
Image10,
];

useEffect(() => {
const randomIndex = Math.floor(Math.random() * imageUrls.length);
setSelectedImageUrl(imageUrls[randomIndex]);
}, []);

return (
<Card radius="lg" className="border-none max-w-xl h-full">
<CardHeader className="absolute z-10 top-0 flex-col !items-start">
<span className="font-bold text-lg drop-shadow-2xl">{venue.name}</span>
</CardHeader>
<Image
src={selectedImageUrl}
src={`/images/${venue.id}.jpg`}
removeWrapper
className="z-0 object-cover"
loading="lazy"
/>
<CardFooter className="flex flex-col items-start overflow-hidden pb-1 absolute bottom-0 shadow-small z-10">
<Button
Expand All @@ -53,7 +24,7 @@ const VenueCard = ({ venue }) => {
color="default"
radius="md"
size="sm"
onClick={() => {navigate(`/book/${venue.id}`, { state: { imageUrl : selectedImageUrl }})}}
onClick={() => {navigate(`/book/${venue.id}`)}}
>
Book Now
</Button>
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import React from "react";
import ReactDOM from "react-dom/client";

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { NextUIProvider } from "@nextui-org/react";

import './index.css'
import "./index.css";

// Route components
import BaseLayout from '@/pages/BaseLayout.jsx';
import Index from '@/pages/Index.jsx';
import BookingIndex from '@/pages/BookingIndex.jsx';
import BaseLayout from "@/pages/BaseLayout.jsx";
import Index from "@/pages/Index.jsx";
import BookingIndex from "@/pages/BookingIndex.jsx";

// Initialize react router
const router = createBrowserRouter([
Expand All @@ -18,8 +18,8 @@ const router = createBrowserRouter([
element: <BaseLayout />,
children: [
{ path: "/", element: <Index /> },
{ path: "/book/:venueId", element: <BookingIndex />}
]
{ path: "/book/:venueId", element: <BookingIndex /> },
],
},
]);

Expand Down
49 changes: 49 additions & 0 deletions frontend/src/pages/BookingConfirm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState, useEffect, useCallback } from "react";
import { useParams, useLocation, useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";

import { Image, Input } from "@nextui-org/react";

import axiosInstance from "@/services/api";
import { useTelegram } from "@/hooks/useTelegram";

const BookingConfirm = () => {
const { tg } = useTelegram();
const navigate = useNavigate();
const location = useLocation();

const { venueId } = useParams();
const venueAddress = location.state.venueAddress;
const venueName = location.state.venueName;

const { register, handleSubmit } = useForm();
const onSubmit = useCallback((data) => {
console.log(data);

// axiosInstance.post(`/bookings/${venueId}`, {
// signal: abortController.signal,
// _auth: tg.initData,
// queryId: queryId,
// });
}, []);

// handle back button click
tg.onEvent("backButtonClicked", () => {
navigate(`/book/${venueId}`);
});

useEffect(() => {
tg.MainButton.text = "Confirm Booking";
}, [tg]);



return (
<section className="w-full">


</section>
);
};

export default BookingConfirm;
93 changes: 62 additions & 31 deletions frontend/src/pages/BookingIndex.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState, useEffect } from "react";
import { useParams, useLocation, useNavigate } from "react-router-dom";
import { useState, useEffect, useCallback } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";

import { Image, Spinner } from "@nextui-org/react";
import { Image, Spinner, Input } from "@nextui-org/react";

import axiosInstance from "@/services/api";
import { useTelegram } from "@/hooks/useTelegram";
Expand All @@ -14,14 +15,42 @@ const STATUS = {
};

const BookingIndex = () => {
const { tg, queryId } = useTelegram();
const { tg } = useTelegram();
const navigate = useNavigate();
const location = useLocation();

const { venueId } = useParams();
const [venue, setVenue] = useState(null);
const [status, setStatus] = useState(STATUS.IDLE);

const { register, handleSubmit } = useForm();
const onSubmit = useCallback(
(data) => {
const abortController = new AbortController();

// Send required fields
axiosInstance.post(`/bookings/${venueId}`, {
signal: abortController.signal,
_auth: tg.initData,
queryId: tg.initData.queryId,
under_name: data.under_name,
date: data.date,
comment: data.comment,
});
},
[venueId, tg]
);

useEffect(() => {
const abortController = new AbortController();
tg.onEvent("mainButtonClicked", () => {
handleSubmit(onSubmit)();
});

return () => {
abortController.abort();
};
}, [tg, handleSubmit, onSubmit]);

useEffect(() => {
setStatus(STATUS.LOADING);

Expand All @@ -44,23 +73,6 @@ const BookingIndex = () => {
tg.BackButton.show();
}, [venueId]);

// handle main button click
useEffect(() => {
const abortController = new AbortController();

tg.onEvent("mainButtonClicked", () => {
axiosInstance.post(`/bookings/${venueId}`, {
signal: abortController.signal,
_auth: tg.initData,
queryId: queryId,
});
});

return () => {
abortController.abort();
};
}, [tg, venueId]);

// handle back button click
tg.onEvent("backButtonClicked", () => {
navigate("/");
Expand All @@ -70,22 +82,41 @@ const BookingIndex = () => {
<section>
{status === STATUS.SUCCESS ? (
<>
{location.state && (
<Image src={location.state.imageUrl} className="w-full h-full" />
)}
<h1 className="text-2xl pt-2 font-black">{venue.name}</h1>
<h2 className="text-sm font-medium hint pb-2">
{venue.address}, {venue.city}
</h2>
<span className="text-sm">{venue.description}</span>
<div className="flex flex-row gap-3 mb-4">
<Image
src={`/images/${venueId}.jpg`}
className="w-20 h-20"
loading="lazy"
/>
<div className="flex flex-col justify-center">
<span className="text-xl font-bold">{venue.name}</span>
<span className="text-xs hint mb-1">
{venue.address}, {venue.city}
</span>
<span className="text-xs">{venue.description}</span>
</div>
</div>
<form className="flex flex-col gap-2">
<Input
variant="bordered"
placeholder="Under name"
{...register("under_name")}
/>
<Input type="date" variant="bordered" {...register("date")} />
<Input
variant="bordered"
placeholder="comment"
{...register("comment")}
/>
</form>
</>
) : status === STATUS.LOADING ? (
<div className="flex justify-center items-center h-48">
<Spinner color="primary" size="lg" />
</div>
) : (
<div className="flex justify-center items-center h-48">
<span className="text-2xl font-bold">Error.</span>
<span className="text-2xl font-bold">Error</span>
</div>
)}
</section>
Expand Down
38 changes: 38 additions & 0 deletions server/migrations/versions/5371171eb23e_edit_booking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""edit booking
Revision ID: 5371171eb23e
Revises: 75668988960f
Create Date: 2023-10-03 11:07:05.857324
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '5371171eb23e'
down_revision: Union[str, None] = '75668988960f'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('bookings', sa.Column('under_name', sa.String(), nullable=True))
op.add_column('bookings', sa.Column('date', sa.Date(), nullable=True))
op.add_column('bookings', sa.Column('comment', sa.String(), nullable=True))
op.drop_column('bookings', 'first_name')
op.drop_column('bookings', 'last_name')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('bookings', sa.Column('last_name', sa.VARCHAR(), nullable=True))
op.add_column('bookings', sa.Column('first_name', sa.VARCHAR(), nullable=True))
op.drop_column('bookings', 'comment')
op.drop_column('bookings', 'date')
op.drop_column('bookings', 'under_name')
# ### end Alembic commands ###
5 changes: 3 additions & 2 deletions server/src/api/endpoints/booking.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ async def book_venue(venue_id: int, request: Request):
booking = BookingCreate(
venue_id=venue_id,
user_id=user.id,
first_name=user.first_name,
last_name=user.last_name
under_name=json_data.get("under_name"),
date=json_data.get("date"),
comment=json_data.get("comment")
)
db_obj = Booking(**booking.model_dump())

Expand Down
Loading

0 comments on commit 96935de

Please sign in to comment.