Initial Next.js clone of Pars Elevator (plift.ir).

RTL Meshkee storefront with home/about/contact/products/blog, API auth/contact/blog wiring, and SEO sitemap-config.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-05 14:55:42 +03:30
co-authored by Cursor
commit 5909899b1e
83 changed files with 10035 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
"use client";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { getMe, getTenant, refreshAuth } from "@/lib/meshkee";
import { BUSINESS_URL, CUSTOMER_URL, TOKEN_KEYS } from "@/lib/config";
import { displayName, isWebsiteAdmin } from "@/lib/format";
import type { MeshkeeUser } from "@/lib/types";
type AuthContextValue = {
user: MeshkeeUser | null;
loading: boolean;
isAdmin: boolean;
label: string;
customerUrl: string;
businessUrl: string;
profileUrl: string;
loginUrl: string;
logout: () => void;
refresh: () => Promise<void>;
};
const AuthContext = createContext<AuthContextValue | null>(null);
function readToken(key: string) {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
function writeTokens(access?: string | null, refresh?: string | null) {
try {
if (access) localStorage.setItem(TOKEN_KEYS.access, access);
else localStorage.removeItem(TOKEN_KEYS.access);
if (refresh) localStorage.setItem(TOKEN_KEYS.refresh, refresh);
else localStorage.removeItem(TOKEN_KEYS.refresh);
} catch {
/* ignore */
}
}
function captureTokensFromUrl() {
if (typeof window === "undefined") return;
const url = new URL(window.location.href);
const hash = new URLSearchParams(url.hash.replace(/^#/, ""));
const access =
url.searchParams.get("accessToken") ||
url.searchParams.get("token") ||
hash.get("accessToken") ||
hash.get("token");
const refresh =
url.searchParams.get("refreshToken") || hash.get("refreshToken");
if (!access && !refresh) return;
writeTokens(access, refresh);
url.searchParams.delete("accessToken");
url.searchParams.delete("refreshToken");
url.searchParams.delete("token");
url.hash = "";
window.history.replaceState({}, "", `${url.pathname}${url.search}`);
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<MeshkeeUser | null>(null);
const [loading, setLoading] = useState(true);
const [businessId, setBusinessId] = useState<string | number | null>(null);
const loginUrl = useMemo(() => {
const returnTo =
typeof window !== "undefined"
? window.location.origin
: "https://www.plift.ir";
return `${CUSTOMER_URL}?redirect=${encodeURIComponent(returnTo)}`;
}, []);
const refresh = useCallback(async () => {
captureTokensFromUrl();
const access = readToken(TOKEN_KEYS.access);
const refreshToken = readToken(TOKEN_KEYS.refresh);
if (!access && !refreshToken) {
setUser(null);
setLoading(false);
return;
}
try {
let token = access;
if (!token && refreshToken) {
const refreshed = await refreshAuth(refreshToken);
writeTokens(refreshed.accessToken, refreshed.refreshToken);
token = refreshed.accessToken;
if (refreshed.user) {
setUser(refreshed.user);
setLoading(false);
return;
}
}
if (!token) {
setUser(null);
return;
}
const me = await getMe(token);
setUser(me);
} catch {
if (refreshToken) {
try {
const refreshed = await refreshAuth(refreshToken);
writeTokens(refreshed.accessToken, refreshed.refreshToken);
const me = refreshed.user || (await getMe(refreshed.accessToken));
setUser(me);
return;
} catch {
writeTokens(null, null);
setUser(null);
}
} else {
writeTokens(null, null);
setUser(null);
}
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
let cancelled = false;
(async () => {
try {
const tenant = await getTenant();
if (!cancelled) setBusinessId(tenant.id);
} catch {
/* ignore */
}
if (!cancelled) await refresh();
})();
return () => {
cancelled = true;
};
}, [refresh]);
const logout = useCallback(() => {
writeTokens(null, null);
setUser(null);
}, []);
const value: AuthContextValue = {
user,
loading,
isAdmin: user ? isWebsiteAdmin(user, businessId) : false,
label: user ? `${displayName(user)} عزیز` : "",
customerUrl: CUSTOMER_URL,
businessUrl: BUSINESS_URL,
profileUrl: `${CUSTOMER_URL}/profile`,
loginUrl,
logout,
refresh,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}