Initial Commit Fix
This commit is contained in:
parent
b3acaef775
commit
5b69ae10e8
@ -3,7 +3,7 @@ import { log } from "../../../../utils/logger";
|
||||
export async function fetchAdminUserFullData({ id, accessToken }) {
|
||||
log("fetchAdminUserFullData called", { id, accessToken });
|
||||
const res = await fetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/admin/users/${id}/full`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/admin/users/${id}/full`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -26,7 +26,7 @@ export async function fetchAdminUserFullData({ id, accessToken }) {
|
||||
export async function fetchAdminUserDocuments({ id, accessToken }) {
|
||||
log("fetchAdminUserDocuments called", { id, accessToken });
|
||||
const res = await fetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/users/${id}/documents`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/users/${id}/documents`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
||||
@ -52,7 +52,7 @@ export async function fetchUserFull(accessToken, id) {
|
||||
log("[fetchUserFull] accessToken:", accessToken);
|
||||
log("[fetchUserFull] Request headers:", headers);
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/users/${id}/full`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/users/${id}/full`,
|
||||
{
|
||||
method: "GET",
|
||||
headers,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { showToast } from "../../../toast/toastUtils"; // adjust import if needed
|
||||
import useAuthStore from "../../../../store/authStore"; // <-- use zustand auth store
|
||||
|
||||
export default function AdminUserActionsSection({
|
||||
userId, // <-- Ensure userId is passed as prop
|
||||
@ -18,8 +19,12 @@ export default function AdminUserActionsSection({
|
||||
showToast({ type: "error", tKey: "toast:passwordResetGenericError" });
|
||||
return;
|
||||
}
|
||||
const token = sessionStorage.getItem("accessToken"); // <-- use sessionStorage
|
||||
console.log("JWT accessToken for request:", token);
|
||||
|
||||
// Prefer in-memory zustand token, fallback to sessionStorage only if absent
|
||||
const storeToken = useAuthStore.getState().accessToken;
|
||||
const token = storeToken || sessionStorage.getItem("accessToken");
|
||||
console.log("JWT accessToken for request (store -> session fallback):", token);
|
||||
|
||||
if (!token) {
|
||||
showToast({ type: "error", tKey: "toast:unauthorized" });
|
||||
return;
|
||||
|
||||
@ -2,7 +2,19 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function AdminUserPermissionsSection({ permissions }) {
|
||||
const { t } = useTranslation('user_management');
|
||||
const { t, i18n } = useTranslation('user_management');
|
||||
|
||||
// resolve permission display: try namespace key "permissions.names.<permName>" else use perm.name/raw
|
||||
const resolvePermissionLabel = (perm) => {
|
||||
const name = perm?.name || perm;
|
||||
const key = `permissions.names.${name}`;
|
||||
if (i18n?.exists(`user_management:${key}`)) {
|
||||
return t(key);
|
||||
}
|
||||
// fallback to description if present, else raw name
|
||||
return perm?.description || name;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-10">
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
||||
@ -19,7 +31,7 @@ export default function AdminUserPermissionsSection({ permissions }) {
|
||||
key={perm.id || perm.name || perm}
|
||||
className="inline-block bg-green-100 text-green-700 px-3 py-1 rounded-full text-xs font-medium border border-green-200"
|
||||
>
|
||||
{perm.name || perm}
|
||||
{resolvePermissionLabel(perm)}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
|
||||
@ -27,7 +27,7 @@ const PermissionModal = ({ open, userId, onClose, onSuccess }) => {
|
||||
setLoading(true);
|
||||
setMessage("");
|
||||
const baseUrl = import.meta.env.VITE_API_BASE_URL;
|
||||
const userPermUrl = `${baseUrl}/api/auth/users/${userId}/permissions`;
|
||||
const userPermUrl = `${baseUrl}/api/users/${userId}/permissions`;
|
||||
const allPermUrl = `${baseUrl}/api/permissions`;
|
||||
|
||||
console.log("PermissionModal: Requesting user permissions from", userPermUrl);
|
||||
|
||||
@ -35,7 +35,13 @@ function AdminUserProfilePage() {
|
||||
}, [user, profile]);
|
||||
|
||||
const [permissionModalOpen, setPermissionModalOpen] = React.useState(false);
|
||||
const [permissionsState, setPermissionsState] = React.useState(permissions);
|
||||
// initialize empty and sync below so async-loaded permissions update the UI
|
||||
const [permissionsState, setPermissionsState] = React.useState(() => permissions || []);
|
||||
|
||||
// Keep local permissionsState in sync with fetched permissions prop
|
||||
React.useEffect(() => {
|
||||
setPermissionsState(permissions || []);
|
||||
}, [permissions]);
|
||||
|
||||
// Refresh permissions after modal update
|
||||
const refreshPermissions = async () => {
|
||||
|
||||
@ -26,7 +26,7 @@ export async function fetchVerificationPendingUsers(accessToken) {
|
||||
export async function fetchVerifyUserFull(accessToken, id) {
|
||||
log("fetchVerifyUserFull called", { accessToken, id });
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/users/${id}/full`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/users/${id}/full`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
||||
@ -4,7 +4,7 @@ import { log } from "../../../utils/logger";
|
||||
export async function fetchUserStatusApi() {
|
||||
log("fetchUserStatusApi called");
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/user/status`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/user/status`,
|
||||
{ method: "GET", credentials: "include" }
|
||||
);
|
||||
if (!res.ok) {
|
||||
@ -19,7 +19,7 @@ export async function fetchUserStatusApi() {
|
||||
export async function fetchUserApi() {
|
||||
log("fetchUserApi called");
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/me`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/me`,
|
||||
{ method: "GET", credentials: "include" }
|
||||
);
|
||||
if (!res.ok) {
|
||||
@ -34,7 +34,7 @@ export async function fetchUserApi() {
|
||||
export async function refreshTokenApi() {
|
||||
log("refreshTokenApi called");
|
||||
const res = await fetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/refresh`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/refresh`,
|
||||
{ method: "POST", credentials: "include" }
|
||||
);
|
||||
if (!res.ok) {
|
||||
|
||||
@ -37,8 +37,8 @@ export async function loginApi(email, password) {
|
||||
const normalizedBase = rawBase.replace(/\/+$/, '');
|
||||
log('loginApi normalized base:', normalizedBase);
|
||||
const endpoint = /\/api$/i.test(normalizedBase)
|
||||
? normalizedBase + '/auth/login'
|
||||
: normalizedBase + '/api/auth/login';
|
||||
? normalizedBase + '/login'
|
||||
: normalizedBase + '/api/login';
|
||||
log('loginApi endpoint:', endpoint);
|
||||
|
||||
function maskEmail(e) {
|
||||
|
||||
@ -49,7 +49,7 @@ function Header() {
|
||||
const handleLogout = async () => {
|
||||
log("🚪 Header: User logout initiated");
|
||||
try {
|
||||
log("🌐 Header: Calling Zustand logout (will call /api/auth/logout)");
|
||||
log("🌐 Header: Calling Zustand logout (will call /api/logout)");
|
||||
await logout();
|
||||
log("✅ Header: Zustand logout completed");
|
||||
showToast({ type: "success", tKey: "toast:logoutSuccess" });
|
||||
|
||||
@ -2,7 +2,7 @@ import { log } from "../../../utils/logger";
|
||||
|
||||
export async function requestPasswordResetApi(email) {
|
||||
log("requestPasswordResetApi called", { email });
|
||||
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/auth/request-password-reset`, {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/request-password-reset`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email })
|
||||
@ -28,12 +28,12 @@ export async function requestPasswordResetApi(email) {
|
||||
|
||||
export async function verifyPasswordResetTokenApi(token) {
|
||||
log("verifyPasswordResetTokenApi called", { token });
|
||||
return fetch(`${import.meta.env.VITE_API_BASE_URL}/api/auth/verify-password-reset?token=${encodeURIComponent(token)}`);
|
||||
return fetch(`${import.meta.env.VITE_API_BASE_URL}/api/verify-password-reset?token=${encodeURIComponent(token)}`);
|
||||
}
|
||||
|
||||
export async function resetPasswordApi(token, newPassword) {
|
||||
log("resetPasswordApi called", { token, newPassword: !!newPassword });
|
||||
return fetch(`${import.meta.env.VITE_API_BASE_URL}/api/auth/reset-password`, {
|
||||
return fetch(`${import.meta.env.VITE_API_BASE_URL}/api/reset-password`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ token, newPassword })
|
||||
|
||||
@ -4,7 +4,7 @@ import { log } from "../../../utils/logger";
|
||||
export async function fetchUserProfile(accessToken) {
|
||||
log("fetchUserProfile called", { accessToken });
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/me`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/me`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -26,7 +26,7 @@ export async function fetchUserProfile(accessToken) {
|
||||
export async function fetchUserPermissions(userId, accessToken) {
|
||||
log("fetchUserPermissions called", { userId, accessToken });
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/auth/users/${userId}/permissions`,
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/users/${userId}/permissions`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
||||
@ -186,7 +186,7 @@ function PersonalCompleteProfileForm({
|
||||
return (
|
||||
<form
|
||||
className="w-full px-4 py-8 space-y-8 bg-white
|
||||
sm:max-w-2xl sm:mx-auto sm:mt-12 sm:border sm:border-gray-200 sm:rounded-2xl sm:shadow-2xl sm:px-16 sm:py-12"
|
||||
md:max-w-[65%] md:mx-auto md:mt-12 md:border md:border-gray-200 md:rounded-2xl md:shadow-2xl md:px-16 md:py-12"
|
||||
onSubmit={handleValidatedSubmit}
|
||||
>
|
||||
<h2 className="text-xl sm:text-2xl font-extrabold text-blue-900 mb-2 text-center">
|
||||
|
||||
@ -6,7 +6,7 @@ export async function sendVerificationEmailApi() {
|
||||
const lang = getCurrentLanguage();
|
||||
log("sendVerificationEmailApi called, lang:", lang);
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL || "https://profit-planet.partners"}/api/auth/send-verification-email`,
|
||||
`${import.meta.env.VITE_API_BASE_URL || "https://profit-planet.partners"}/api/send-verification-email`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
@ -34,7 +34,7 @@ export async function verifyEmailCodeApi(code) {
|
||||
const lang = getCurrentLanguage();
|
||||
log("verifyEmailCodeApi called, code:", code, "lang:", lang);
|
||||
const res = await authFetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL || "https://profit-planet.partners"}/api/auth/verify-email-code`,
|
||||
`${import.meta.env.VITE_API_BASE_URL || "https://profit-planet.partners"}/api/verify-email-code`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { authFetch } from "../../../utils/authFetch";
|
||||
import { log } from "../../../utils/logger";
|
||||
import useAuthStore from "../../../store/authStore";
|
||||
import { showToast } from "../../toast/toastUtils"; // show user feedback
|
||||
|
||||
export async function createReferralLinkApi({ expiresInDays, maxUses }) {
|
||||
log("[referralApi] Creating referral link (authFetch)", { expiresInDays, maxUses });
|
||||
@ -7,11 +9,22 @@ export async function createReferralLinkApi({ expiresInDays, maxUses }) {
|
||||
const url = `${import.meta.env.VITE_API_BASE_URL}/api/referral/create`;
|
||||
log("[referralApi] POST", url, "payload:", payload);
|
||||
|
||||
// Always use in-memory token from zustand
|
||||
const token = useAuthStore.getState().accessToken;
|
||||
if (!token) {
|
||||
log("[referralApi] No accessToken in auth store - aborting request");
|
||||
showToast({ type: "error", message: "Nicht autorisiert. Bitte melden Sie sich erneut an." });
|
||||
throw new Error("No access token");
|
||||
}
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
};
|
||||
|
||||
const res = await authFetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers,
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
@ -22,12 +35,35 @@ export async function createReferralLinkApi({ expiresInDays, maxUses }) {
|
||||
if (!res.ok) {
|
||||
let errorMsg = "Failed to generate referral link";
|
||||
try {
|
||||
const data = await res.json();
|
||||
errorMsg = data.message || errorMsg;
|
||||
} catch {}
|
||||
const data = await res.json().catch(() => null);
|
||||
errorMsg = data?.message || errorMsg;
|
||||
// If server says max links reached, show explicit toast with server message
|
||||
if (res.status === 403 && data?.message) {
|
||||
// Try to extract count and role from message like:
|
||||
// "Maximum active referral links reached (15) for your role (admin)."
|
||||
const msg = data.message;
|
||||
const countMatch = msg.match(/\((\d+)\)/);
|
||||
const roleMatch = msg.match(/for your role\s*\(([^)]+)\)/i);
|
||||
const count = countMatch ? countMatch[1] : undefined;
|
||||
const role = roleMatch ? roleMatch[1] : undefined;
|
||||
// Prefer a translated toast with values; fallback to server message if translation not present
|
||||
showToast({
|
||||
type: "error",
|
||||
tKey: "referral:toast.maxLinksReached",
|
||||
values: { count, role, defaultValue: msg },
|
||||
});
|
||||
} else {
|
||||
showToast({ type: "error", message: "Empfehlungslink konnte nicht erstellt werden. Bitte erneut versuchen." });
|
||||
}
|
||||
} catch (parseErr) {
|
||||
showToast({ type: "error", message: "Empfehlungslink konnte nicht erstellt werden. Bitte erneut versuchen." });
|
||||
}
|
||||
log("[referralApi] Error creating referral link:", errorMsg);
|
||||
throw new Error(errorMsg);
|
||||
const err = new Error(errorMsg);
|
||||
err.handled = true; // signal caller that toast was already shown
|
||||
throw err;
|
||||
}
|
||||
|
||||
const result = await res.json();
|
||||
log("[referralApi] Referral link created:", result);
|
||||
return result;
|
||||
@ -51,22 +87,58 @@ export async function listReferralLinksApi() {
|
||||
export async function deactivateReferralLinkApi(tokenId) {
|
||||
log("[referralApi] Deactivating referral link (authFetch)...", { tokenId });
|
||||
const url = `${import.meta.env.VITE_API_BASE_URL}/api/referral/deactivate`;
|
||||
|
||||
// Always use in-memory token from zustand
|
||||
const token = useAuthStore.getState().accessToken;
|
||||
if (!token) {
|
||||
log("[referralApi] No accessToken in auth store - aborting deactivate request");
|
||||
showToast({ type: "error", message: "Nicht autorisiert. Bitte melden Sie sich erneut an." });
|
||||
throw new Error("No access token");
|
||||
}
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`,
|
||||
};
|
||||
|
||||
const res = await authFetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers,
|
||||
body: JSON.stringify({ tokenId }),
|
||||
});
|
||||
|
||||
log("[referralApi] deactivate response status:", res.status);
|
||||
const rawText = await res.clone().text().catch(() => null);
|
||||
log("[referralApi] deactivate raw response body:", rawText);
|
||||
|
||||
if (!res.ok) {
|
||||
let errorMsg = "Failed to deactivate referral link";
|
||||
try {
|
||||
const data = await res.json();
|
||||
errorMsg = data.message || errorMsg;
|
||||
} catch {}
|
||||
const data = await res.json().catch(() => null);
|
||||
errorMsg = data?.message || errorMsg;
|
||||
|
||||
// Show specific toast messages based on status
|
||||
if (res.status === 401) {
|
||||
showToast({ type: "error", message: "Nicht autorisiert. Bitte melden Sie sich erneut an." });
|
||||
} else if (res.status === 403) {
|
||||
showToast({ type: "error", message: data?.message || "Sie haben keine Berechtigung." });
|
||||
} else if (res.status === 404) {
|
||||
showToast({ type: "error", message: "Empfehlungslink nicht gefunden." });
|
||||
} else if (res.status === 429) {
|
||||
showToast({ type: "error", message: "Zu viele Anfragen. Bitte später erneut versuchen." });
|
||||
} else {
|
||||
showToast({ type: "error", message: data?.message || "Deaktivierung des Empfehlungslinks fehlgeschlagen" });
|
||||
}
|
||||
} catch (parseErr) {
|
||||
showToast({ type: "error", message: "Deaktivierung des Empfehlungslinks fehlgeschlagen" });
|
||||
}
|
||||
|
||||
log("[referralApi] Error deactivating referral link:", errorMsg);
|
||||
throw new Error(errorMsg);
|
||||
const err = new Error(errorMsg);
|
||||
err.handled = true; // signal caller that toast was already shown
|
||||
throw err;
|
||||
}
|
||||
|
||||
const result = await res.json();
|
||||
log("[referralApi] Referral link deactivated:", result);
|
||||
return result;
|
||||
|
||||
@ -3,6 +3,7 @@ import useAuthStore from "../../../store/authStore";
|
||||
import { showToast } from "../../toast/toastUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { log } from "../../../utils/logger";
|
||||
import { createReferralLinkApi } from "../api/referralApi";
|
||||
|
||||
function GenerateReferralForm({ onReferralGenerated }) {
|
||||
const [expiresInDays, setExpiresInDays] = useState(3); // can become number | 'unlimited'
|
||||
@ -29,41 +30,21 @@ function GenerateReferralForm({ onReferralGenerated }) {
|
||||
: { expiresInDays, maxUses };
|
||||
|
||||
log("[GenerateReferralForm] Payload to /api/referral/create:", body);
|
||||
const res = await fetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/referral/create`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
log("[GenerateReferralForm] Response status:", res.status, "headers:", Object.fromEntries(res.headers.entries()));
|
||||
const rawText = await res.clone().text();
|
||||
log("[GenerateReferralForm] Raw response body:", rawText);
|
||||
|
||||
if (!res.ok) {
|
||||
log("[GenerateReferralForm] Failed to generate referral link");
|
||||
showToast({ type: "error", message: t('toast.generateFail') });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const data = await createReferralLinkApi(body);
|
||||
log("[GenerateReferralForm] Referral link response:", data);
|
||||
|
||||
if (data.link) {
|
||||
if (data && data.link) {
|
||||
showToast({ type: "success", message: t('toast.generateSuccess') });
|
||||
} else {
|
||||
showToast({ type: "error", message: t('toast.generateNoLink') });
|
||||
}
|
||||
|
||||
await onReferralGenerated();
|
||||
} catch (error) {
|
||||
log("[GenerateReferralForm] Error generating referral link:", error);
|
||||
showToast({ type: "error", message: t('toast.generateError') });
|
||||
// createReferralLinkApi will have shown server message toast for known cases,
|
||||
// show a generic error only if no user-friendly message was already shown
|
||||
if (!error?.handled) {
|
||||
showToast({ type: "error", message: t('toast.generateError') });
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { showToast } from "../../../features/toast/toastUtils.js";
|
||||
import { showToast } from "../../toast/toastUtils"; // correct relative path
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { log } from "../../../utils/logger";
|
||||
import { deactivateReferralLinkApi } from "../api/referralApi"; // use API helper
|
||||
|
||||
function ReferralLinksTable({ links, onLinkUpdate }) {
|
||||
const [deactivatingLinks, setDeactivatingLinks] = useState(new Set());
|
||||
@ -13,43 +14,23 @@ function ReferralLinksTable({ links, onLinkUpdate }) {
|
||||
setDeactivatingLinks((prev) => new Set(prev).add(tokenId));
|
||||
log("[ReferralLinksTable] Deactivating link...", { tokenId });
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${import.meta.env.VITE_API_BASE_URL}/api/referral/deactivate`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${sessionStorage.getItem("accessToken")}`,
|
||||
},
|
||||
body: JSON.stringify({ tokenId }),
|
||||
}
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
log("[ReferralLinksTable] Deactivate response:", data);
|
||||
if (data.success) {
|
||||
showToast({
|
||||
type: "success",
|
||||
message: t('toast.deactivateSuccess'),
|
||||
});
|
||||
if (onLinkUpdate) onLinkUpdate();
|
||||
} else {
|
||||
showToast({
|
||||
type: "error",
|
||||
message: data.message || t('toast.deactivateFail'),
|
||||
});
|
||||
}
|
||||
// Use API helper which reads token from zustand and handles errors/toasts centrally
|
||||
const data = await deactivateReferralLinkApi(tokenId);
|
||||
log("[ReferralLinksTable] Deactivate response:", data);
|
||||
if (data && data.success) {
|
||||
showToast({ type: "success", message: t('toast.deactivateSuccess') });
|
||||
if (onLinkUpdate) onLinkUpdate();
|
||||
} else {
|
||||
log("[ReferralLinksTable] Failed to deactivate link");
|
||||
showToast({
|
||||
type: "error",
|
||||
message: t('toast.deactivateFail'),
|
||||
});
|
||||
showToast({ type: "error", message: data?.message || t('toast.deactivateFail') });
|
||||
}
|
||||
} catch (error) {
|
||||
log("[ReferralLinksTable] Error deactivating link:", error);
|
||||
console.error("Error deactivating referral link:", error);
|
||||
showToast({ type: "error", message: t('toast.deactivateFail') });
|
||||
// deactivateReferralLinkApi already shows specific toasts for many statuses;
|
||||
// show fallback if needed
|
||||
if (!error?.handled) {
|
||||
showToast({ type: "error", message: t('toast.deactivateFail') });
|
||||
}
|
||||
} finally {
|
||||
setDeactivatingLinks((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
|
||||
@ -34,7 +34,9 @@
|
||||
"deactivated": "Deaktiviert",
|
||||
"unknown": "Unbekannt",
|
||||
"copy": "Kopieren",
|
||||
"copySuccess": "Link in die Zwischenablage kopiert!"
|
||||
"copySuccess": "Link in die Zwischenablage kopiert!",
|
||||
"never": "Nie",
|
||||
"unlimited": "Unbegrenzt"
|
||||
},
|
||||
"form": {
|
||||
"generateHeading": "Neuen Empfehlungslink erstellen",
|
||||
@ -42,6 +44,8 @@
|
||||
"linkExpiration": "Link Ablauf",
|
||||
"maximumUses": "Maximale Verwendungen",
|
||||
"option": {
|
||||
"expirationUnlimited": "Unbegrenzt",
|
||||
"unlimited": "Unbegrenzt",
|
||||
"1Day": "24 Stunden (1 Tag)",
|
||||
"3Days": "72 Stunden (3 Tage)",
|
||||
"1Week": "168 Stunden (1 Woche)",
|
||||
@ -63,6 +67,8 @@
|
||||
"generateFail": "Empfehlungslink konnte nicht erstellt werden. Bitte erneut versuchen.",
|
||||
"generateNoLink": "Erstellung fehlgeschlagen – kein Link zurückgegeben.",
|
||||
"generateError": "Beim Erstellen des Empfehlungslinks ist ein Fehler aufgetreten.",
|
||||
"noPermission": "Du hast keine Berechtigung für die Empfehlungsverwaltung."
|
||||
"noPermission": "Du hast keine Berechtigung für die Empfehlungsverwaltung.",
|
||||
"unauthorized": "Nicht autorisiert. Bitte melden Sie sich erneut an.",
|
||||
"maxLinksReached": "Maximale Anzahl aktiver Empfehlungslinks erreicht ({{count}}) für Ihre Rolle ({{role}})."
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,10 +24,11 @@
|
||||
"enlarge": "Vergrößern",
|
||||
"download": "Herunterladen",
|
||||
"sendPasswordReset": "Passwort-Reset senden",
|
||||
"sendingPasswordReset": "Passwort-Reset wird gesendet...",
|
||||
"editPermissions": "Berechtigungen bearbeiten",
|
||||
"statistic": "Statistik",
|
||||
"statistic": "Statistiken",
|
||||
"exportUserData": "Benutzerdaten exportieren",
|
||||
"logs": "Logs",
|
||||
"logs": "Protokolle",
|
||||
"delete": "Löschen"
|
||||
},
|
||||
"filters": {
|
||||
@ -117,7 +118,10 @@
|
||||
"download": "Download"
|
||||
},
|
||||
"permissions": {
|
||||
"none": "Keine Berechtigungen"
|
||||
"none": "Keine speziellen Berechtigungen zugewiesen.",
|
||||
"names": {
|
||||
"can_create_referrals": "Darf Empfehlungslinks erstellen"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"loading": "Lädt...",
|
||||
@ -135,5 +139,10 @@
|
||||
"confirmText": "Sind Sie sicher, dass Sie diesen Benutzer und alle zugehörigen Daten dauerhaft löschen möchten?",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen"
|
||||
},
|
||||
"toast": {
|
||||
"passwordResetSuccess": "Passwort-Reset-Link erfolgreich gesendet!",
|
||||
"passwordResetGenericError": "Passwort-Reset konnte nicht angefordert werden. Bitte erneut versuchen.",
|
||||
"unauthorized": "Nicht autorisiert."
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,9 @@
|
||||
"deactivated": "Deactivated",
|
||||
"unknown": "Unknown",
|
||||
"copy": "Copy",
|
||||
"copySuccess": "Link copied to clipboard!"
|
||||
"copySuccess": "Link copied to clipboard!",
|
||||
"never": "Never",
|
||||
"unlimited": "Unlimited"
|
||||
},
|
||||
"form": {
|
||||
"generateHeading": "Generate New Referral Link",
|
||||
@ -42,6 +44,8 @@
|
||||
"linkExpiration": "Link Expiration",
|
||||
"maximumUses": "Maximum Uses",
|
||||
"option": {
|
||||
"expirationUnlimited": "Unlimited",
|
||||
"unlimited": "Unlimited",
|
||||
"1Day": "24 Hours (1 Day)",
|
||||
"3Days": "72 Hours (3 Days)",
|
||||
"1Week": "168 Hours (1 Week)",
|
||||
@ -63,6 +67,8 @@
|
||||
"generateFail": "Failed to generate referral link. Please try again.",
|
||||
"generateNoLink": "Referral link generation failed - no link provided.",
|
||||
"generateError": "An error occurred while generating the referral link.",
|
||||
"noPermission": "You do not have permission to access Referral Management."
|
||||
"noPermission": "You do not have permission to access Referral Management.",
|
||||
"unauthorized": "Not authorized. Please sign in again.",
|
||||
"maxLinksReached": "Maximum active referral links reached ({{count}}) for your role ({{role}})."
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,10 +23,11 @@
|
||||
"edit": "Edit",
|
||||
"enlarge": "Enlarge",
|
||||
"download": "Download",
|
||||
"sendPasswordReset": "Send Password Reset Link",
|
||||
"editPermissions": "Edit Permissions",
|
||||
"statistic": "Statistic",
|
||||
"exportUserData": "Export User Data",
|
||||
"sendPasswordReset": "Send password reset",
|
||||
"sendingPasswordReset": "Sending password reset...",
|
||||
"editPermissions": "Edit permissions",
|
||||
"statistic": "Statistics",
|
||||
"exportUserData": "Export user data",
|
||||
"logs": "Logs",
|
||||
"delete": "Delete"
|
||||
},
|
||||
@ -117,7 +118,10 @@
|
||||
"download": "Download"
|
||||
},
|
||||
"permissions": {
|
||||
"none": "No permissions"
|
||||
"none": "No special permissions assigned.",
|
||||
"names": {
|
||||
"can_create_referrals": "Can create referral links"
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"loading": "Loading...",
|
||||
@ -135,5 +139,10 @@
|
||||
"confirmText": "Are you sure you want to permanently delete this user and all related data?",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete"
|
||||
},
|
||||
"toast": {
|
||||
"passwordResetSuccess": "Password reset link sent successfully!",
|
||||
"passwordResetGenericError": "Failed to request password reset. Please try again.",
|
||||
"unauthorized": "Not authorized."
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ const useAuthStore = create((set, get) => ({
|
||||
logout: async () => {
|
||||
log("🚪 Zustand: Logging out — revoking refresh token on server");
|
||||
try {
|
||||
const logoutUrl = `${import.meta.env.VITE_API_BASE_URL}/api/auth/logout`;
|
||||
const logoutUrl = `${import.meta.env.VITE_API_BASE_URL}/api/logout`;
|
||||
log("🌐 Zustand: Calling logout endpoint:", logoutUrl);
|
||||
const res = await fetch(logoutUrl, {
|
||||
method: "POST",
|
||||
@ -127,12 +127,23 @@ const useAuthStore = create((set, get) => ({
|
||||
return get().refreshPromise;
|
||||
}
|
||||
|
||||
// SHORT-CIRCUIT: if we already have a valid accessToken that's not about to expire, skip refresh
|
||||
const currentToken = get().accessToken;
|
||||
if (currentToken) {
|
||||
const expiry = getTokenExpiry(currentToken);
|
||||
if (expiry && expiry.getTime() - Date.now() > 60 * 1000) { // more than 60s left
|
||||
log("⏸️ Zustand: accessToken present and valid, skipping refresh");
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
log("🔄 Zustand: refreshAuthToken - starting new refresh");
|
||||
// create promise so concurrent callers can await it
|
||||
const p = (async () => {
|
||||
set({ isRefreshing: true });
|
||||
try {
|
||||
const refreshUrl = `${import.meta.env.VITE_API_BASE_URL}/api/auth/refresh`;
|
||||
// NOTE: backend expects /api/refresh (align with other clients)
|
||||
const refreshUrl = `${import.meta.env.VITE_API_BASE_URL}/api/refresh`;
|
||||
log("🌐 Zustand: Calling refresh endpoint:", refreshUrl);
|
||||
|
||||
const res = await fetch(refreshUrl, {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user