diff --git a/src/app/components/RestaurantCard.tsx b/src/app/components/RestaurantCard.tsx new file mode 100644 index 0000000..05444e2 --- /dev/null +++ b/src/app/components/RestaurantCard.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { PublicRestaurant } from "@/app/types/Types"; +import { glassSurfaceCard } from "@/lib/styles/glassSurface"; +import { ArrowLeft2, Location } from "iconsax-react"; +import Image from "next/image"; +import Link from "next/link"; +import { useState } from "react"; + +const FALLBACK_LOGO = "/assets/images/avatar.svg"; + +function isValidLogoUrl(url: string | null | undefined): url is string { + if (!url?.trim()) return false; + const lower = url.toLowerCase(); + if (lower.endsWith(".pdf")) return false; + return lower.startsWith("http") || lower.startsWith("/"); +} + +function getInitials(name: string): string { + const trimmed = name.trim(); + if (!trimmed) return "?"; + return trimmed.charAt(0); +} + +type Props = { + restaurant: PublicRestaurant; +}; + +export default function RestaurantCard({ restaurant }: Props) { + const validLogo = isValidLogoUrl(restaurant.logo); + const [logoSrc, setLogoSrc] = useState(validLogo && restaurant.logo ? restaurant.logo : FALLBACK_LOGO); + const showInitials = !validLogo || logoSrc === FALLBACK_LOGO; + + return ( + +
+ {showInitials ? ( +
{getInitials(restaurant.name)}
+ ) : ( + {restaurant.name} setLogoSrc(FALLBACK_LOGO)} /> + )} +
+ +
+
+

{restaurant.name}

+
+ + {restaurant.address && ( +
+ +

{restaurant.address?.trim()}

+
+ )} + + {restaurant.establishedYear != null && ( +

+ سال تاسیس {restaurant.establishedYear} +

+ )} +
+ + + + ); +} diff --git a/src/app/components/RestaurantsSkeleton.tsx b/src/app/components/RestaurantsSkeleton.tsx new file mode 100644 index 0000000..babf676 --- /dev/null +++ b/src/app/components/RestaurantsSkeleton.tsx @@ -0,0 +1,24 @@ +import { glassSurfaceCard } from "@/lib/styles/glassSurface"; + +function SkeletonCard() { + return ( +
+
+
+
+
+
+
+
+ ); +} + +export default function RestaurantsSkeleton() { + return ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ ); +} diff --git a/src/app/home.css b/src/app/home.css new file mode 100644 index 0000000..745621a --- /dev/null +++ b/src/app/home.css @@ -0,0 +1,6 @@ +.danak-home { + --danak-orange: #ff5e2b; + --danak-orange-dark: #e84e1c; + --danak-orange-light: rgb(255 94 43 / 12%); + --danak-orange-muted: rgb(255 94 43 / 6%); +} diff --git a/src/app/hooks/useAppData.ts b/src/app/hooks/useAppData.ts new file mode 100644 index 0000000..ca8ce03 --- /dev/null +++ b/src/app/hooks/useAppData.ts @@ -0,0 +1,9 @@ +import { useQuery } from "@tanstack/react-query"; +import * as api from "../service/Service"; + +export const useGetAllRestaurants = () => { + return useQuery({ + queryKey: ["restaurants"], + queryFn: api.getAllRestaurants, + }); +}; diff --git a/src/app/page.tsx b/src/app/page.tsx index 2418fd6..b9cf9f5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,14 +1,75 @@ -import Link from 'next/link' +"use client"; -export default function Home () { - return ( -
- Auth -
- Menu -
- Index -
-
- ) +import RestaurantCard from "@/app/components/RestaurantCard"; +import RestaurantsSkeleton from "@/app/components/RestaurantsSkeleton"; +import { useGetAllRestaurants } from "@/app/hooks/useAppData"; +import { PublicRestaurant } from "@/app/types/Types"; +import SearchBox from "@/components/input/SearchBox"; +import Image from "next/image"; +import { useMemo, useState } from "react"; +import "./home.css"; + +function filterRestaurants(restaurants: PublicRestaurant[], query: string): PublicRestaurant[] { + const normalized = query.trim().toLowerCase(); + if (!normalized) return restaurants; + + return restaurants.filter((r) => r.name.toLowerCase().includes(normalized) || r.slug.toLowerCase().includes(normalized) || r.address?.toLowerCase().includes(normalized)); +} + +export default function Home() { + const { data, isLoading, error } = useGetAllRestaurants(); + const [search, setSearch] = useState(""); + + const restaurants = data?.data ?? []; + const filtered = useMemo(() => filterRestaurants(restaurants, search), [restaurants, search]); + + return ( +
+
+
+ BY DANAK +

دی‌منو پلاس

+

BY DANAK

+
+ +
+ setSearch(e.target.value)} placeholder="جستجوی رستوران یا کافه..." /> +
+
+ +
+ {!isLoading && !error && ( +

+ {filtered.length.toLocaleString("fa-IR")} مورد + {search.trim() ? " یافت شد" : ""} +

+ )} + + {isLoading && } + + {error && ( +
+

خطا در دریافت لیست رستوران‌ها

+

لطفاً اتصال اینترنت خود را بررسی کنید و دوباره تلاش کنید.

+
+ )} + + {!isLoading && !error && filtered.length === 0 && ( +
+ +

{search.trim() ? "نتیجه‌ای یافت نشد" : "رستورانی یافت نشد"}

+

{search.trim() ? "عبارت جستجو را تغییر دهید" : "در حال حاضر رستورانی در لیست وجود ندارد"}

+
+ )} + + {!isLoading && !error && filtered.length > 0 && ( +
+ {filtered.map((restaurant) => ( + + ))} +
+ )} +
+
+ ); } diff --git a/src/app/service/Service.ts b/src/app/service/Service.ts new file mode 100644 index 0000000..eab1d48 --- /dev/null +++ b/src/app/service/Service.ts @@ -0,0 +1,7 @@ +import { RestaurantsListResponse } from "@/app/types/Types"; +import { api } from "@/config/axios"; + +export const getAllRestaurants = async (): Promise => { + const { data } = await api.get("/public/restaurants"); + return data; +}; diff --git a/src/app/types/Types.ts b/src/app/types/Types.ts new file mode 100644 index 0000000..1687651 --- /dev/null +++ b/src/app/types/Types.ts @@ -0,0 +1,11 @@ +import { BaseResponse } from "@/app/[name]/(Main)/types/Types"; + +export interface PublicRestaurant { + slug: string; + name: string; + address: string | null; + logo: string | null; + establishedYear: number | null; +} + +export type RestaurantsListResponse = BaseResponse;