marxhe
This commit is contained in:
parent
c8f916f602
commit
ae44f2e019
|
|
@ -103,13 +103,34 @@ export async function createPost(payload, token) {
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const text = await res.text().catch(() => "");
|
const text = await res.text().catch(() => "");
|
||||||
console.error("createPost failed", res.status, text);
|
let json = null;
|
||||||
throw new Error("Failed to create post");
|
try { json = text ? JSON.parse(text) : null; } catch {}
|
||||||
|
|
||||||
|
const errMsg =
|
||||||
|
(json && (json.message || json.error_description || json.error)) ||
|
||||||
|
(text && text.slice(0, 300)) ||
|
||||||
|
`HTTP ${res.status}`;
|
||||||
|
|
||||||
|
const err = new Error(errMsg);
|
||||||
|
err.status = res.status;
|
||||||
|
err.body = text;
|
||||||
|
|
||||||
|
// Common backend shapes
|
||||||
|
err.code =
|
||||||
|
(json && (json.code || json.error)) ||
|
||||||
|
(res.status === 403 && /email[_\s-]*not[_\s-]*verified/i.test(text || "")
|
||||||
|
? "email_not_verified"
|
||||||
|
: "") ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json().catch(() => ({}));
|
return res.json().catch(() => ({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crée un nouvel utilisateur via le backend
|
* Crée un nouvel utilisateur via le backend
|
||||||
* (backend: POST /api/signup)
|
* (backend: POST /api/signup)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { getEmailVerifiedFromClaims } from "./emailVerified";
|
import {
|
||||||
import { clearAuth, guessUsernameFromToken, jwtExpSeconds, loadAuth, saveAuth } from "./authStorage";
|
clearAuth,
|
||||||
|
guessUsernameFromToken,
|
||||||
|
jwtExpSeconds,
|
||||||
|
loadAuth,
|
||||||
|
saveAuth,
|
||||||
|
decodeJwtPayload,
|
||||||
|
} from "./authStorage";
|
||||||
import { parseAuthError } from "./authErrors";
|
import { parseAuthError } from "./authErrors";
|
||||||
|
|
||||||
const AuthCtx = createContext(null);
|
const AuthCtx = createContext(null);
|
||||||
|
|
@ -9,23 +15,31 @@ function apiBase() {
|
||||||
return (import.meta?.env?.VITE_API_BASE_URL || "").replace(/\/+$/, "");
|
return (import.meta?.env?.VITE_API_BASE_URL || "").replace(/\/+$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function keycloakBase() {
|
|
||||||
return (import.meta?.env?.VITE_KEYCLOAK_BASE_URL || "https://auth.sociowire.com").replace(/\/+$/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function apiFetch(path, opts = {}) {
|
async function apiFetch(path, opts = {}) {
|
||||||
const url = apiBase() + path;
|
const url = apiBase() + path;
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
...opts,
|
...opts,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
|
||||||
...(opts.headers || {}),
|
...(opts.headers || {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tokenEmailVerified(accessToken) {
|
||||||
|
try {
|
||||||
|
const p = decodeJwtPayload(accessToken);
|
||||||
|
if (!p) return null;
|
||||||
|
if (typeof p.email_verified === "boolean") return p.email_verified;
|
||||||
|
if (typeof p.emailVerified === "boolean") return p.emailVerified;
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tolerant whoami: accepts {authenticated:true} OR {username:""} OR {user:""} OR just ok+token
|
||||||
async function whoami(accessToken) {
|
async function whoami(accessToken) {
|
||||||
const res = await apiFetch("/api/whoami", {
|
const res = await apiFetch("/api/whoami", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|
@ -34,7 +48,11 @@ async function whoami(accessToken) {
|
||||||
|
|
||||||
const text = await res.text().catch(() => "");
|
const text = await res.text().catch(() => "");
|
||||||
let json = null;
|
let json = null;
|
||||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
try {
|
||||||
|
json = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
json = null;
|
||||||
|
}
|
||||||
|
|
||||||
const uname =
|
const uname =
|
||||||
(json && (json.username || json.user || json.preferred_username)) ||
|
(json && (json.username || json.user || json.preferred_username)) ||
|
||||||
|
|
@ -43,20 +61,85 @@ async function whoami(accessToken) {
|
||||||
|
|
||||||
const authed =
|
const authed =
|
||||||
!!res.ok &&
|
!!res.ok &&
|
||||||
(json?.authenticated === true || json?.ok === true || !!json?.username || !!json?.user || !!uname);
|
(json?.authenticated === true ||
|
||||||
|
json?.ok === true ||
|
||||||
|
!!json?.username ||
|
||||||
|
!!json?.user ||
|
||||||
|
!!uname);
|
||||||
|
|
||||||
return { ok: !!res.ok, status: res.status, json, text, authenticated: authed, username: uname };
|
return { ok: !!res.ok, status: res.status, json, text, authenticated: authed, username: uname };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NEW: /api/me is the source of truth for email_verified
|
||||||
|
async function me(accessToken) {
|
||||||
|
const res = await apiFetch("/api/me", {
|
||||||
|
method: "GET",
|
||||||
|
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
let json = null;
|
||||||
|
try {
|
||||||
|
json = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
json = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try multiple shapes
|
||||||
|
const u = (json && (json.user || json.profile || json.me)) || json || null;
|
||||||
|
|
||||||
|
const emailVerified =
|
||||||
|
u && typeof u.email_verified === "boolean"
|
||||||
|
? u.email_verified
|
||||||
|
: u && typeof u.emailVerified === "boolean"
|
||||||
|
? u.emailVerified
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: !!res.ok,
|
||||||
|
status: res.status,
|
||||||
|
json,
|
||||||
|
text,
|
||||||
|
emailVerified,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshTokens(refreshToken) {
|
async function refreshTokens(refreshToken) {
|
||||||
const res = await apiFetch("/api/refresh", {
|
const res = await apiFetch("/api/refresh", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = await res.text().catch(() => "");
|
const text = await res.text().catch(() => "");
|
||||||
let json = null;
|
let json = null;
|
||||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
try {
|
||||||
|
json = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
json = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: res.ok, status: res.status, json, text };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resendVerification(accessToken) {
|
||||||
|
const res = await apiFetch("/api/resend-verification", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
||||||
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
let json = null;
|
||||||
|
try {
|
||||||
|
json = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
json = null;
|
||||||
|
}
|
||||||
|
|
||||||
return { ok: res.ok, status: res.status, json, text };
|
return { ok: res.ok, status: res.status, json, text };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,37 +149,57 @@ function nowSec() {
|
||||||
|
|
||||||
export function AuthProvider({ children }) {
|
export function AuthProvider({ children }) {
|
||||||
const [status, setStatus] = useState("loading"); // loading | anon | auth
|
const [status, setStatus] = useState("loading"); // loading | anon | auth
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null); // { username }
|
||||||
const [tokens, setTokens] = useState(null);
|
const [tokens, setTokens] = useState(null); // { access_token, refresh_token, ... }
|
||||||
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [errorCode, setErrorCode] = useState("");
|
const [lastInfo, setLastInfo] = useState("");
|
||||||
const [info, setInfo] = useState("");
|
const [needsEmailVerification, setNeedsEmailVerification] = useState(false);
|
||||||
|
|
||||||
const refreshTimer = useRef(null);
|
const refreshTimer = useRef(null);
|
||||||
|
|
||||||
function clearErrorUI() {
|
function setAnon(msg = "") {
|
||||||
setError("");
|
|
||||||
setErrorCode("");
|
|
||||||
}
|
|
||||||
function clearInfoUI() {
|
|
||||||
setInfo("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAnon(msg = "", code = "") {
|
|
||||||
setStatus("anon");
|
setStatus("anon");
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setTokens(null);
|
setTokens(null);
|
||||||
setError(msg || "");
|
setError(msg || "");
|
||||||
setErrorCode(code || "");
|
setLastInfo("");
|
||||||
|
setNeedsEmailVerification(false);
|
||||||
clearAuth();
|
clearAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAuthFromTokens(t, usernameGuess = "") {
|
function setAuthFromTokens(t, usernameGuess = "") {
|
||||||
setTokens(t);
|
setTokens(t);
|
||||||
saveAuth({ ...t, obtained_at: Date.now() });
|
saveAuth({ ...t, obtained_at: Date.now() });
|
||||||
|
|
||||||
const u = usernameGuess || guessUsernameFromToken(t?.access_token) || "";
|
const u = usernameGuess || guessUsernameFromToken(t?.access_token) || "";
|
||||||
setUser(u ? { username: u } : null);
|
setUser(u ? { username: u } : null);
|
||||||
|
|
||||||
setStatus("auth");
|
setStatus("auth");
|
||||||
clearErrorUI();
|
setError("");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshProfile(accessTokenOverride) {
|
||||||
|
const at = accessTokenOverride || tokens?.access_token;
|
||||||
|
if (!at) return { ok: False };
|
||||||
|
|
||||||
|
// /api/me is the truth; token claim is fallback only
|
||||||
|
const mr = await me(at);
|
||||||
|
let verified = mr.emailVerified
|
||||||
|
|
||||||
|
if (typeof verified !== "boolean") {
|
||||||
|
const tv = tokenEmailVerified(at);
|
||||||
|
if (typeof tv === "boolean") verified = tv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof verified === "boolean") {
|
||||||
|
setNeedsEmailVerification(!verified);
|
||||||
|
return { ok: true, verified };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cannot determine, do not block UI aggressively
|
||||||
|
setNeedsEmailVerification(false);
|
||||||
|
return { ok: mr.ok, verified: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureFreshSession(existing) {
|
async function ensureFreshSession(existing) {
|
||||||
|
|
@ -127,7 +230,11 @@ export function AuthProvider({ children }) {
|
||||||
if (w.ok && w.authenticated) {
|
if (w.ok && w.authenticated) {
|
||||||
setUser(w.username ? { username: w.username } : null);
|
setUser(w.username ? { username: w.username } : null);
|
||||||
setStatus("auth");
|
setStatus("auth");
|
||||||
clearErrorUI();
|
setError("");
|
||||||
|
|
||||||
|
// ✅ Update email verification state from backend
|
||||||
|
await refreshProfile(existing.access_token);
|
||||||
|
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,21 +259,33 @@ export function AuthProvider({ children }) {
|
||||||
setAnon("Session expirée (pas de refresh_token).");
|
setAnon("Session expirée (pas de refresh_token).");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rr = await refreshTokens(cur.refresh_token);
|
const rr = await refreshTokens(cur.refresh_token);
|
||||||
if (!rr.ok || !rr.json?.access_token) {
|
if (!rr.ok || !rr.json?.access_token) {
|
||||||
setAnon("Session expirée. Please login again.");
|
setAnon("Session expirée. Please login again.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const merged = { ...cur, ...rr.json };
|
const merged = { ...cur, ...rr.json };
|
||||||
if (!merged.refresh_token) merged.refresh_token = cur.refresh_token;
|
if (!merged.refresh_token) merged.refresh_token = cur.refresh_token;
|
||||||
|
|
||||||
setAuthFromTokens(merged);
|
setAuthFromTokens(merged);
|
||||||
|
|
||||||
|
// keep verification fresh too
|
||||||
|
await refreshProfile(merged.access_token);
|
||||||
|
} else {
|
||||||
|
// lightweight: refresh verification occasionally without spamming
|
||||||
|
// (every 30s interval already runs; but this call is only parsing token unless needed)
|
||||||
|
const tv = tokenEmailVerified(cur.access_token);
|
||||||
|
if (typeof tv === "boolean") setNeedsEmailVerification(!tv);
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restore() {
|
async function restore() {
|
||||||
setStatus("loading");
|
setStatus("loading");
|
||||||
clearErrorUI();
|
setError("");
|
||||||
|
setLastInfo("");
|
||||||
const saved = loadAuth();
|
const saved = loadAuth();
|
||||||
if (!saved?.access_token) {
|
if (!saved?.access_token) {
|
||||||
setAnon("");
|
setAnon("");
|
||||||
|
|
@ -183,22 +302,28 @@ export function AuthProvider({ children }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(username, password) {
|
async function login(username, password) {
|
||||||
clearInfoUI();
|
setError("");
|
||||||
clearErrorUI();
|
setLastInfo("");
|
||||||
setStatus("loading");
|
setStatus("loading");
|
||||||
|
|
||||||
const res = await apiFetch("/api/login", {
|
const res = await apiFetch("/api/login", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username, password }),
|
body: JSON.stringify({ username, password }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = await res.text().catch(() => "");
|
const text = await res.text().catch(() => "");
|
||||||
let j = null;
|
let j = null;
|
||||||
try { j = text ? JSON.parse(text) : null; } catch {}
|
try {
|
||||||
|
j = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
j = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.ok || !j?.access_token) {
|
if (!res.ok || !j?.access_token) {
|
||||||
const parsed = parseAuthError(text || j?.error_description || j?.error || "", res.status);
|
const parsed = parseAuthError(text || "", res.status);
|
||||||
setAnon(parsed.message, parsed.code);
|
setNeedsEmailVerification(parsed.code === "email_verification_required");
|
||||||
|
setAnon(parsed.message || j?.error_description || j?.error || text || "Login failed");
|
||||||
return { ok: false, status: res.status, json: j, text };
|
return { ok: false, status: res.status, json: j, text };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,7 +339,10 @@ export function AuthProvider({ children }) {
|
||||||
|
|
||||||
setUser(w.username ? { username: w.username } : null);
|
setUser(w.username ? { username: w.username } : null);
|
||||||
setStatus("auth");
|
setStatus("auth");
|
||||||
clearErrorUI();
|
|
||||||
|
// ✅ update verified state from backend
|
||||||
|
await refreshProfile(t.access_token);
|
||||||
|
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,18 +352,25 @@ export function AuthProvider({ children }) {
|
||||||
setAnon("");
|
setAnon("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ IMPORTANT: arriving on /auth/verified should CLEAR old errors (stale banner)
|
async function resendVerificationEmail() {
|
||||||
useEffect(() => {
|
const at = tokens?.access_token || "";
|
||||||
try {
|
if (!at) {
|
||||||
const p = window.location.pathname || "";
|
setError("Not logged in.");
|
||||||
if (p.startsWith("/auth/verified") || p.startsWith("/auth/verif")) {
|
return { ok: false };
|
||||||
clearErrorUI();
|
}
|
||||||
setInfo("✅ Email confirmé. Connecte-toi pour continuer.");
|
|
||||||
// optional: clean URL so refresh doesn't re-trigger forever
|
setError("");
|
||||||
try { window.history.replaceState({}, "", "/"); } catch {}
|
setLastInfo("");
|
||||||
|
|
||||||
|
const r = await resendVerification(at);
|
||||||
|
if (!r.ok) {
|
||||||
|
setError(r.json?.error || r.json?.message || r.text || `Resend failed (${r.status})`);
|
||||||
|
return { ok: false, status: r.status };
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastInfo("Verification email sent. Check inbox/spam, then refresh session (logout/login).");
|
||||||
|
return { ok: true };
|
||||||
}
|
}
|
||||||
} catch {}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
restore();
|
restore();
|
||||||
|
|
@ -247,44 +382,40 @@ export function AuthProvider({ children }) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Optional helper: open Keycloak account
|
// Backward-compatible fields expected by TopBar/MapView
|
||||||
function openKeycloak() {
|
|
||||||
try { window.open(keycloakBase(), "_blank", "noopener,noreferrer"); } catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = useMemo(() => {
|
const value = useMemo(() => {
|
||||||
const loading = status === "loading";
|
const loading = status === "loading";
|
||||||
const authenticated = status === "auth";
|
const authenticated = status === "auth";
|
||||||
const username = user?.username || "";
|
const username = user?.username || "";
|
||||||
const token = tokens?.access_token || "";
|
const token = tokens?.access_token || "";
|
||||||
|
|
||||||
const needsEmailVerification = errorCode === "email_verification_required";
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status, user, tokens,
|
// internal
|
||||||
|
status,
|
||||||
|
user,
|
||||||
|
tokens,
|
||||||
|
error,
|
||||||
|
|
||||||
|
// expected by UI
|
||||||
loading,
|
loading,
|
||||||
authenticated,
|
authenticated,
|
||||||
username,
|
username,
|
||||||
token,
|
token,
|
||||||
|
|
||||||
lastError: error || "",
|
lastError: error || "",
|
||||||
lastErrorCode: errorCode || "",
|
lastInfo: lastInfo || "",
|
||||||
lastInfo: info || "",
|
needsEmailVerification: !!needsEmailVerification,
|
||||||
|
resendVerificationEmail,
|
||||||
clearError: clearErrorUI,
|
|
||||||
clearInfo: clearInfoUI,
|
|
||||||
setInfo,
|
|
||||||
setError, // keep for compatibility
|
|
||||||
|
|
||||||
needsEmailVerification,
|
|
||||||
openKeycloak,
|
|
||||||
|
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
restore,
|
restore,
|
||||||
|
refreshProfile,
|
||||||
|
setError,
|
||||||
|
setLastInfo,
|
||||||
|
setNeedsEmailVerification,
|
||||||
};
|
};
|
||||||
}, [status, user, tokens, error, errorCode, info]);
|
}, [status, user, tokens, error, lastInfo, needsEmailVerification]);
|
||||||
|
|
||||||
return <AuthCtx.Provider value={value}>{children}</AuthCtx.Provider>;
|
return <AuthCtx.Provider value={value}>{children}</AuthCtx.Provider>;
|
||||||
}
|
}
|
||||||
|
|
@ -292,13 +423,3 @@ export function AuthProvider({ children }) {
|
||||||
export function useAuth() {
|
export function useAuth() {
|
||||||
return useContext(AuthCtx);
|
return useContext(AuthCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SW_AUTH_DEBUG_EMAIL */
|
|
||||||
try {
|
|
||||||
// Helps debug: look for email_verified in token
|
|
||||||
// Toggle by setting: localStorage.SW_AUTH_DEBUG_EMAIL="1"
|
|
||||||
if (typeof window !== "undefined" && localStorage.getItem("SW_AUTH_DEBUG_EMAIL") === "1") {
|
|
||||||
console.log("[SW AUTH] tokenParsed:", tokenParsed);
|
|
||||||
console.log("[SW AUTH] email_verified:", tokenParsed?.email_verified, "emailVerified:", tokenParsed?.emailVerified);
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export default function MapView({
|
||||||
selectedPost,
|
selectedPost,
|
||||||
onSelectPost,
|
onSelectPost,
|
||||||
}) {
|
}) {
|
||||||
const { authenticated, username, token } = useAuth();
|
const { authenticated, username, token, needsEmailVerification } = useAuth();
|
||||||
|
|
||||||
const { containerRef, mapRef, markersRef, expandedElRef, viewParams, hasLastView } =
|
const { containerRef, mapRef, markersRef, expandedElRef, viewParams, hasLastView } =
|
||||||
useMapCore(theme);
|
useMapCore(theme);
|
||||||
|
|
@ -207,6 +207,11 @@ export default function MapView({
|
||||||
alert("You must be logged in to place a wire.");
|
alert("You must be logged in to place a wire.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needsEmailVerification) {
|
||||||
|
alert("You must verify your email before posting. Use 'Resend email' in the top bar, then login again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
setDraftTitle("");
|
setDraftTitle("");
|
||||||
setDraftBody("");
|
setDraftBody("");
|
||||||
|
|
@ -233,6 +238,11 @@ export default function MapView({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (needsEmailVerification) {
|
||||||
|
setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const authorName = (username || "").trim() || "anon";
|
const authorName = (username || "").trim() || "anon";
|
||||||
|
|
||||||
let baseLng;
|
let baseLng;
|
||||||
|
|
@ -283,7 +293,16 @@ export default function MapView({
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
setSaveError("Error creating post.");
|
|
||||||
|
const code = e && (e.code || "");
|
||||||
|
const msg = String((e && e.message) || "").toLowerCase();
|
||||||
|
const isNotVerified = code === "email_not_verified" || (e && e.status === 403 && (msg.includes("not verified") || msg.includes("verify")));
|
||||||
|
|
||||||
|
if (isNotVerified) {
|
||||||
|
setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again.");
|
||||||
|
} else {
|
||||||
|
setSaveError((e && e.message) || "Error creating post.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue