Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 087563bce7 | |||
| e839db7331 |
@@ -366,6 +366,26 @@ export function SettingsShopPanel({ cafeId }: SettingsShopPanelProps) {
|
|||||||
>
|
>
|
||||||
ذخیره موقعیت
|
ذخیره موقعیت
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
||||||
|
notify.error("مرورگر شما موقعیتیابی را پشتیبانی نمیکند");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(pos) => {
|
||||||
|
setLatInput(pos.coords.latitude.toFixed(5));
|
||||||
|
setLngInput(pos.coords.longitude.toFixed(5));
|
||||||
|
setLocationError(null);
|
||||||
|
},
|
||||||
|
() => notify.error("دسترسی به موقعیت امکانپذیر نبود. لطفاً اجازه دسترسی بدهید."),
|
||||||
|
{ enableHighAccuracy: true, timeout: 10000 }
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
موقعیت فعلی من
|
||||||
|
</Button>
|
||||||
{(latInput || lngInput) && (
|
{(latInput || lngInput) && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useState } from "react";
|
|||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
import { apiGet, apiPost } from "@/lib/api/client";
|
import { apiGet, apiPost } from "@/lib/api/client";
|
||||||
|
import { useApiError } from "@/lib/use-api-error";
|
||||||
import { useAuthStore } from "@/lib/stores/auth.store";
|
import { useAuthStore } from "@/lib/stores/auth.store";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -52,6 +53,7 @@ function formatDate(iso: string) {
|
|||||||
|
|
||||||
export function SupportScreen() {
|
export function SupportScreen() {
|
||||||
const t = useTranslations("support");
|
const t = useTranslations("support");
|
||||||
|
const apiError = useApiError();
|
||||||
const cafeId = useAuthStore((s) => s.user?.cafeId);
|
const cafeId = useAuthStore((s) => s.user?.cafeId);
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
const [body, setBody] = useState("");
|
const [body, setBody] = useState("");
|
||||||
@@ -61,6 +63,7 @@ export function SupportScreen() {
|
|||||||
data: tickets = [],
|
data: tickets = [],
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["support", cafeId],
|
queryKey: ["support", cafeId],
|
||||||
@@ -135,7 +138,7 @@ export function SupportScreen() {
|
|||||||
</p>
|
</p>
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<Card className="rounded-xl border border-destructive/30 p-4 text-sm text-destructive">
|
<Card className="rounded-xl border border-destructive/30 p-4 text-sm text-destructive">
|
||||||
<p>{t("loadFailed")}</p>
|
<p>{apiError(error, t("loadFailed"))}</p>
|
||||||
<Button variant="outline" size="sm" className="mt-2" onClick={() => void refetch()}>
|
<Button variant="outline" size="sm" className="mt-2" onClick={() => void refetch()}>
|
||||||
{t("retry")}
|
{t("retry")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -70,16 +70,29 @@ export default async function CafePage({
|
|||||||
const t = await getTranslations({ locale, namespace: "cafe" });
|
const t = await getTranslations({ locale, namespace: "cafe" });
|
||||||
const isFa = locale === "fa";
|
const isFa = locale === "fa";
|
||||||
|
|
||||||
const [cafe, menu, reviews] = await Promise.all([
|
// Resolve the café first so an unknown slug 404s cleanly instead of doing
|
||||||
getCafe(slug),
|
// (and potentially erroring on) the menu/review fetches.
|
||||||
|
const cafe = await getCafe(slug);
|
||||||
|
if (!cafe) notFound();
|
||||||
|
|
||||||
|
const [menu, reviews] = await Promise.all([
|
||||||
getCafeMenu(slug),
|
getCafeMenu(slug),
|
||||||
getCafeReviews(slug),
|
getCafeReviews(slug),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!cafe) notFound();
|
|
||||||
|
|
||||||
const name = isFa ? cafe.name : (cafe.nameEn ?? cafe.name);
|
const name = isFa ? cafe.name : (cafe.nameEn ?? cafe.name);
|
||||||
const profile = cafe.discoverProfile;
|
// discoverProfile may be absent for cafés that never filled it in — fall back
|
||||||
|
// to an empty profile so the page renders instead of throwing a 500.
|
||||||
|
const profile = cafe.discoverProfile ?? {
|
||||||
|
themes: [],
|
||||||
|
size: null,
|
||||||
|
floors: null,
|
||||||
|
vibes: [],
|
||||||
|
occasions: [],
|
||||||
|
spaceFeatures: [],
|
||||||
|
noiseLevel: null,
|
||||||
|
priceTier: null,
|
||||||
|
};
|
||||||
const priceTier = profile.priceTier;
|
const priceTier = profile.priceTier;
|
||||||
|
|
||||||
// Similar cafes
|
// Similar cafes
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ interface Props {
|
|||||||
export function CafeCard({ cafe, locale, href }: Props) {
|
export function CafeCard({ cafe, locale, href }: Props) {
|
||||||
const isFa = locale === "fa";
|
const isFa = locale === "fa";
|
||||||
const name = isFa ? cafe.name : (cafe.name);
|
const name = isFa ? cafe.name : (cafe.name);
|
||||||
const priceTier = cafe.discoverProfile.priceTier;
|
const priceTier = cafe.discoverProfile?.priceTier ?? null;
|
||||||
|
const themes = cafe.discoverProfile?.themes ?? [];
|
||||||
const priceLabel = priceTier ? (PRICE_TIER_LABELS[priceTier]?.[isFa ? "fa" : "en"] ?? priceTier) : null;
|
const priceLabel = priceTier ? (PRICE_TIER_LABELS[priceTier]?.[isFa ? "fa" : "en"] ?? priceTier) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -72,9 +73,9 @@ export function CafeCard({ cafe, locale, href }: Props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
{cafe.discoverProfile.themes.length > 0 && (
|
{themes.length > 0 && (
|
||||||
<div className="mt-2 flex flex-wrap gap-1">
|
<div className="mt-2 flex flex-wrap gap-1">
|
||||||
{cafe.discoverProfile.themes.slice(0, 3).map((tag) => (
|
{themes.slice(0, 3).map((tag) => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="rounded-full bg-brand-50 px-2 py-0.5 text-[10px] font-medium text-brand-700"
|
className="rounded-full bg-brand-50 px-2 py-0.5 text-[10px] font-medium text-brand-700"
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ export function CafeJsonLd({ cafe, locale, baseUrl }: Props) {
|
|||||||
worstRating: "1",
|
worstRating: "1",
|
||||||
},
|
},
|
||||||
} : {}),
|
} : {}),
|
||||||
...(cafe.discoverProfile.themes.length ? {
|
...(cafe.discoverProfile?.themes?.length ? {
|
||||||
servesCuisine: cafe.discoverProfile.themes,
|
servesCuisine: cafe.discoverProfile.themes,
|
||||||
} : {}),
|
} : {}),
|
||||||
priceRange: (() => {
|
priceRange: (() => {
|
||||||
const tier = cafe.discoverProfile.priceTier;
|
const tier = cafe.discoverProfile?.priceTier;
|
||||||
if (tier === "budget") return "﷼";
|
if (tier === "budget") return "﷼";
|
||||||
if (tier === "moderate") return "﷼﷼";
|
if (tier === "moderate") return "﷼﷼";
|
||||||
if (tier === "upscale") return "﷼﷼﷼";
|
if (tier === "upscale") return "﷼﷼﷼";
|
||||||
|
|||||||
@@ -3,4 +3,7 @@ import { defineRouting } from "next-intl/routing";
|
|||||||
export const routing = defineRouting({
|
export const routing = defineRouting({
|
||||||
locales: ["fa", "en"],
|
locales: ["fa", "en"],
|
||||||
defaultLocale: "fa",
|
defaultLocale: "fa",
|
||||||
|
// Iran-first: don't pick the locale from the browser's Accept-Language
|
||||||
|
// (Persian users often have an en-US browser). Locale-less URLs default to fa.
|
||||||
|
localeDetection: false,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user