Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
auto-install-peers=true
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@buildo/bento-design-system": "^0.20.4",
"@buildo/formo": "^2.0.2",
"@tanstack/react-query": "^4.36.1",
"i18next": "^23.5.1",
"react": "^18.2.0",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

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

10 changes: 8 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import { BentoProvider, Headline, Inset } from "@buildo/bento-design-system";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { useTranslation } from "react-i18next";
import Home from "./pages/Home";
import RestaurantDetail from "./pages/RestaurantDetail";
import React from "react";

function App() {
const { t } = useTranslation();
const [restId, setRestId] = React.useState("0");

return (
<BentoProvider defaultMessages={defaultMessages}>
Expand All @@ -19,8 +22,11 @@ function App() {
</Headline>
</Inset>
<Routes>
<Route path="/" element={<Home />} />
{/* <Route path="/" element={<Restaurant />} /> */}
<Route path="/home" element={<Home setId={setRestId} />} />
<Route
path="/restaurat-detail"
element={<RestaurantDetail id={restId} />}
/>
</Routes>
</BrowserRouter>
</BentoProvider>
Expand Down
40 changes: 38 additions & 2 deletions src/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,44 @@ import { apiSecret } from "./models";

const apiKey = import.meta.env.VITE_API_KEY;

export const getRestaurantList = async (range: number) => {
const uri = `/api/search?sort_by=best_match&limit=${range}&location=Milano`;
export const getRestaurantList = async ({
prices,
location,
radius,
}: {
prices: boolean[];
location: string;
radius: number;
}) => {
const priceParamsString: string = prices
.map((price, index) => {
if (price) {
return `price=${index + 1}`;
}
})
.filter((price) => price !== "")
.join("&");
const uri = `/api/search?sort_by=best_match&location=${location}&radius=${radius}000&${priceParamsString}`;
const apik = apiSecret.safeParse(apiKey);

if (apik.success) {
return (
await fetch(uri, {
method: "get",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + apiKey,
},
})
).json();
} else {
throw apik.error;
}
};

export const getRestaurantDetails = async (id: string) => {
const uri = `/api/${id}`;
const apik = apiSecret.safeParse(apiKey);

if (apik.success) {
Expand Down
23 changes: 23 additions & 0 deletions src/components/LocationFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TextField } from "@buildo/bento-design-system";
import { useTranslation } from "react-i18next";

type LocationProps = {
location: string;
setLocation: (arg: string) => void;
};

function LocationFilter({ location, setLocation }: LocationProps) {
const { t } = useTranslation();

return (
<TextField
name="name"
label={t("Location.Label")}
placeholder={t("Location.Placeholder")}
value={location}
onChange={setLocation}
/>
);
}

export default LocationFilter;
33 changes: 33 additions & 0 deletions src/components/PriceFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CheckboxField, Stack, Title } from "@buildo/bento-design-system";
import { useTranslation } from "react-i18next";

type PriceFilterProps = {
price: boolean[];
setPrice: (arg: boolean[]) => void;
};

function PriceFilter({ price, setPrice }: PriceFilterProps) {
const { t } = useTranslation();

return (
<Stack space={8} align="left">
<Title size="small">{t("priceRangefilter")}</Title>
{[...Array(4).keys()].map((_, position) => {
return (
<CheckboxField
key={"checkbox-" + position}
label={"$".repeat(position + 1)}
value={price[position]}
onChange={() => {
setPrice(
price.map((item, index) => (index === position ? !item : item))
);
}}
/>
);
})}
</Stack>
);
}

export default PriceFilter;
22 changes: 22 additions & 0 deletions src/components/RangeDistanceFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { SliderField } from "@buildo/bento-design-system";
import { useTranslation } from "react-i18next";
type RangeFilterProps = {
distance: number;
setDistance: (arg: number) => void;
};

function RangeDistanceFilter({ distance, setDistance }: RangeFilterProps) {
const { t } = useTranslation();
return (
<SliderField
type="single"
label={t("RangeDistance.Label")}
value={distance}
onChange={setDistance}
minValue={0}
maxValue={10}
step={1}
/>
);
}
export default RangeDistanceFilter;
40 changes: 26 additions & 14 deletions src/components/RestaurantPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,45 @@ import {
Title,
Body,
Label,
Box,
Button,
} from "@buildo/bento-design-system";
import { useTranslation } from "react-i18next";
import { PreviewProp } from "../models";
import { useNavigate } from "react-router-dom";
import { PreviewPropComponent } from "../models";

function RestaurantPreview(props: PreviewProp) {
function RestaurantPreview(props: PreviewPropComponent) {
const { t } = useTranslation();
const rating = props.rating;
const rating = props.vars.rating;
const imagePrev =
props.imageUrl === ""
props.vars.imageUrl === ""
? "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Barbieri_-_ViaSophia25668.jpg/1200px-Barbieri_-_ViaSophia25668.jpg"
: props.imageUrl;
: props.vars.imageUrl;

const navigate = useNavigate();
return (
<Card elevation="small" borderRadius={8} paddingX={24} paddingY={40}>
<Stack space={8}>
<Box height="full" justifyContent="center" alignItems="center">
<img src={imagePrev} width="70%" height="100px" />
</Box>
<Card elevation="small" borderRadius={8} padding={16} paddingTop={24}>
<Stack space={8} align={"center"}>
<img
src={imagePrev}
style={{ height: "200px", width: "100%", objectFit: "scale-down" }}
/>
<Title size="medium">{props.vars.name}</Title>
<Body size="medium">
{props.vars.address}

<Title size="small">{props.name}</Title>
<Body size="small">
{props.address}
<Label size="small" color="default">
{t("Card.Rating", { rating })}
</Label>
</Body>
<Button
onPress={() => {
props.setId(props.vars.id);
return navigate("/restaurat-detail");
}}
kind="transparent"
label={t("Card.ButtonLabel")}
hierarchy="primary"
></Button>
</Stack>
</Card>
);
Expand Down
39 changes: 29 additions & 10 deletions src/hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
import { useQuery } from "@tanstack/react-query";
import { getRestaurantList } from "./api";
import { fromJsonToProp } from "./utils";
import { getRestaurantList, getRestaurantDetails } from "./api";
import { fromJsonToPropPreview, fromJsonToPropDetails } from "./utils";
import { PreviewList, DetailsPropApi } from "./models";

function useGetRestaurantList(range: number) {
return useQuery({
queryKey: ["retrieve-list", range],
queryFn: async () => {
const prom: JSON = await getRestaurantList(range);
return fromJsonToProp(prom);
export function useGetRestaurantList(filtersParams: {
prices: boolean[];
location: string;
radius: number;
}) {
return useQuery(
[
filtersParams.location.toString,
filtersParams.radius.toString,
filtersParams.prices.toString,
],
async (): Promise<PreviewList> => {
const prom: JSON = await getRestaurantList(filtersParams);
return fromJsonToPropPreview(prom);
},
});
{ enabled: false }
);
}

export default useGetRestaurantList;
export function useGetRestaurantDetails(id: string) {
return useQuery(
["restaurantDetails", id],
async (): Promise<DetailsPropApi> => {
const prom: JSON = await getRestaurantDetails(id);
return fromJsonToPropDetails(prom);
},
{ enabled: false }
);
}
15 changes: 13 additions & 2 deletions src/locales/en.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
{
"title": "YelpLike",
"Card": {
"Rating": "Rating: {{rating}} ★"
}
"Rating": "Rating: {{rating}} ⭐",
"ButtonLabel":"See details"
},
"priceRangefilter":"Filter by average price : ",
"Location":{
"Placeholder":"Type here your location ... ",
"Label": "Location"
},
"RangeDistance":{
"Label":"Distance :"
},
"SearchButton": "Search"

}
34 changes: 30 additions & 4 deletions src/models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ export const apiSecret = z
.string()
.length(128, { message: "Must be exactly 128 characters long" });

export const previewProp = z
export const previewPropApi = z
.object({
id: z.string(),
alias: z.string(),
name: z.string(),
rating: z.number(),
Expand All @@ -20,15 +21,40 @@ export const previewProp = z
...rest,
}));

export type PreviewProp = z.infer<typeof previewProp>;
export type PreviewPropApi = z.infer<typeof previewPropApi>;

export const previewList = z.object({
businesses: previewProp.array(),
businesses: previewPropApi.array(),
});

export type PreviewList = z.infer<typeof previewList>;

export const filterParams = z.object({
range: z.number(),
prices: z.array(z.boolean()),
location: z.string(),
radius: z.number(),
});
export type FiltersParams = z.infer<typeof filterParams>;

export const previewPropComponent = z.object({
vars: previewPropApi,
setId: z.function().args(z.string()).returns(z.void()),
});
export type PreviewPropComponent = z.infer<typeof previewPropComponent>;

export const detailsPropApi = z
.object({
name: z.string(),
rating: z.number(),
photos: z.array(z.string()),
location: z.object({
display_address: z.array(z.string()),
}),
price: z.string(),
})
.transform(({ location, ...rest }) => ({
address: location.display_address,
...rest,
}));

export type DetailsPropApi = z.infer<typeof detailsPropApi>;
Loading