feat: add users question
This commit is contained in:
+27
-2
@@ -1,7 +1,11 @@
|
||||
// Thin fetch wrapper for the meditation API. The app is a static SPA, so every
|
||||
// call runs in the browser and carries the bearer token from localStorage.
|
||||
|
||||
import { MEDITATION_BASE_URL, TOKEN_STORAGE_KEY } from "./config";
|
||||
import {
|
||||
APPRO_TOKEN_STORAGE_KEY,
|
||||
MEDITATION_BASE_URL,
|
||||
TOKEN_STORAGE_KEY,
|
||||
} from "./config";
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
@@ -29,6 +33,21 @@ export function clearToken() {
|
||||
window.localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||
}
|
||||
|
||||
export function getApproToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
return window.localStorage.getItem(APPRO_TOKEN_STORAGE_KEY);
|
||||
}
|
||||
|
||||
export function setApproToken(token: string) {
|
||||
if (typeof window === "undefined") return;
|
||||
window.localStorage.setItem(APPRO_TOKEN_STORAGE_KEY, token);
|
||||
}
|
||||
|
||||
export function clearApproToken() {
|
||||
if (typeof window === "undefined") return;
|
||||
window.localStorage.removeItem(APPRO_TOKEN_STORAGE_KEY);
|
||||
}
|
||||
|
||||
type Query = Record<string, string | number | boolean | undefined | null>;
|
||||
|
||||
interface RequestOptions {
|
||||
@@ -40,6 +59,8 @@ interface RequestOptions {
|
||||
baseUrl?: string;
|
||||
// Skip attaching the bearer token (used by the login calls).
|
||||
auth?: boolean;
|
||||
// Extra headers to merge into the request.
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
function buildUrl(path: string, query?: Query, baseUrl = MEDITATION_BASE_URL) {
|
||||
@@ -60,7 +81,7 @@ export async function apiFetch<T = unknown>(
|
||||
path: string,
|
||||
options: RequestOptions = {},
|
||||
): Promise<T> {
|
||||
const { method = "GET", body, query, baseUrl, auth = true } = options;
|
||||
const { method = "GET", body, query, baseUrl, auth = true, headers: extraHeaders } = options;
|
||||
|
||||
const headers: Record<string, string> = { Accept: "application/json" };
|
||||
|
||||
@@ -69,6 +90,10 @@ export async function apiFetch<T = unknown>(
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (extraHeaders) {
|
||||
Object.assign(headers, extraHeaders);
|
||||
}
|
||||
|
||||
let payload: BodyInit | undefined;
|
||||
if (body instanceof FormData) {
|
||||
payload = body; // browser sets multipart Content-Type + boundary
|
||||
|
||||
+6
-8
@@ -13,7 +13,6 @@ import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { APPRO_BASE_URL, MEDITATION_BASE_URL, PACKAGE_NAME } from "./config";
|
||||
@@ -21,8 +20,10 @@ import {
|
||||
ApiError,
|
||||
apiFetch,
|
||||
clearToken,
|
||||
clearApproToken,
|
||||
getToken,
|
||||
setToken,
|
||||
setApproToken,
|
||||
toFormData,
|
||||
} from "./api";
|
||||
|
||||
@@ -73,16 +74,12 @@ async function meditationLogin(approoToken: string): Promise<string> {
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [token, setTokenState] = useState<string | null>(null);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTokenState(getToken());
|
||||
setReady(true);
|
||||
}, []);
|
||||
const [token, setTokenState] = useState<string | null>(() => getToken());
|
||||
const [ready] = useState(true);
|
||||
|
||||
const login = useCallback(async (identifier: string, password: string) => {
|
||||
const approoToken = await approAgencyLogin(identifier, password);
|
||||
setApproToken(approoToken);
|
||||
const medToken = await meditationLogin(approoToken);
|
||||
setToken(medToken);
|
||||
setTokenState(medToken);
|
||||
@@ -90,6 +87,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const logout = useCallback(() => {
|
||||
clearToken();
|
||||
clearApproToken();
|
||||
setTokenState(null);
|
||||
}, []);
|
||||
|
||||
|
||||
+2
-1
@@ -19,5 +19,6 @@ export const ASSET_BASE_URL =
|
||||
process.env.NEXT_PUBLIC_ASSET_BASE_URL ??
|
||||
MEDITATION_BASE_URL.replace(/\/api\/?$/, "");
|
||||
|
||||
// localStorage key for the meditation bearer token.
|
||||
// localStorage keys for the meditation bearer token and approagency token.
|
||||
export const TOKEN_STORAGE_KEY = "meditation_admin_token";
|
||||
export const APPRO_TOKEN_STORAGE_KEY = "approagency_admin_token";
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TagIcon,
|
||||
TimerIcon,
|
||||
TrophyIcon,
|
||||
UserIcon,
|
||||
WorryIcon,
|
||||
} from "@/components/icons";
|
||||
|
||||
@@ -113,6 +114,11 @@ export const NAV: NavSection[] = [
|
||||
icon: SurveyIcon,
|
||||
items: [{ href: "/dashboard/surveys", label: "پرسشهای نظرسنجی" }],
|
||||
},
|
||||
{
|
||||
label: "کاربران",
|
||||
icon: UserIcon,
|
||||
items: [{ href: "/dashboard/users", label: "کاربران نظرسنجی" }],
|
||||
},
|
||||
{
|
||||
label: "گفتگو با مشاور",
|
||||
icon: ChatIcon,
|
||||
|
||||
Reference in New Issue
Block a user