Implemented comprehensive user editing in admin dashboard with PATCH endpoint for updating email, display name, role, password, and disabled status. Added validation to prevent admins from demoting or disabling themselves, and duplicate email detection. Refactored AdminPanel to modal-based editor with separate create/edit modes, form state management, and save/cancel actions. Enhanced UI with field labels, checkbox for account status, action
154 lines
9.5 KiB
JavaScript
154 lines
9.5 KiB
JavaScript
import { useEffect } from "react";
|
|
import { stylesTokens } from "../theme";
|
|
import { applyTheme } from "../themes";
|
|
|
|
|
|
|
|
export function useHpGlobalStyles() {
|
|
// Google Fonts
|
|
useEffect(() => {
|
|
if (document.getElementById("hp-fonts")) return;
|
|
|
|
const pre1 = document.createElement("link");
|
|
pre1.id = "hp-fonts-pre1";
|
|
pre1.rel = "preconnect";
|
|
pre1.href = "https://fonts.googleapis.com";
|
|
|
|
const pre2 = document.createElement("link");
|
|
pre2.id = "hp-fonts-pre2";
|
|
pre2.rel = "preconnect";
|
|
pre2.href = "https://fonts.gstatic.com";
|
|
pre2.crossOrigin = "anonymous";
|
|
|
|
const link = document.createElement("link");
|
|
link.id = "hp-fonts";
|
|
link.rel = "stylesheet";
|
|
link.href =
|
|
"https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@700;900&family=IM+Fell+English:ital,wght@0,400;0,700;1,400&display=swap";
|
|
|
|
document.head.appendChild(pre1);
|
|
document.head.appendChild(pre2);
|
|
document.head.appendChild(link);
|
|
}, []);
|
|
|
|
// Keyframes
|
|
useEffect(() => {
|
|
if (document.getElementById("hp-anim-style")) return;
|
|
const style = document.createElement("style");
|
|
style.id = "hp-anim-style";
|
|
style.innerHTML = `
|
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
@keyframes popIn { from { opacity: 0; transform: translateY(8px) scale(0.985); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
@keyframes rowPulse { 0%{ transform: scale(1); } 50%{ transform: scale(1.01); } 100%{ transform: scale(1); } }
|
|
@keyframes candleGlow {
|
|
0% { opacity: .55; transform: translateY(0px) scale(1); filter: blur(16px); }
|
|
35% { opacity: .85; transform: translateY(-2px) scale(1.02); filter: blur(18px); }
|
|
70% { opacity: .62; transform: translateY(1px) scale(1.01); filter: blur(17px); }
|
|
100% { opacity: .55; transform: translateY(0px) scale(1); filter: blur(16px); }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}, []);
|
|
|
|
// html/body reset
|
|
useEffect(() => {
|
|
document.documentElement.style.height = "100%";
|
|
document.body.style.height = "100%";
|
|
document.documentElement.style.margin = "0";
|
|
document.body.style.margin = "0";
|
|
document.documentElement.style.padding = "0";
|
|
document.body.style.padding = "0";
|
|
}, []);
|
|
|
|
// Global CSS
|
|
useEffect(() => {
|
|
if (document.getElementById("hp-global-style")) return;
|
|
const style = document.createElement("style");
|
|
style.id = "hp-global-style";
|
|
style.innerHTML = `
|
|
html, body {
|
|
overscroll-behavior-y: none;
|
|
-webkit-text-size-adjust: 100%;
|
|
text-rendering: optimizeLegibility;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
background: ${stylesTokens.pageBg};
|
|
color: ${stylesTokens.textMain};
|
|
}
|
|
body {
|
|
overflow-x: hidden;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
#root { background: transparent; }
|
|
* { -webkit-tap-highlight-color: transparent; }
|
|
button, input, select { font-family: inherit; }
|
|
button { transition: transform 140ms ease, filter 140ms ease, border-color 140ms ease, background 140ms ease; }
|
|
button:not(:disabled):hover { filter: brightness(1.12); transform: translateY(-1px); }
|
|
button:not(:disabled):active { transform: translateY(0); filter: brightness(0.98); }
|
|
button:disabled { cursor: not-allowed; opacity: 0.55; }
|
|
input:focus, select:focus { border-color: var(--hp-textGold) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--hp-textGold) 16%, transparent); }
|
|
.hp-row { transition: background 140ms ease, transform 140ms ease, box-shadow 140ms ease; }
|
|
.hp-row:hover { box-shadow: inset 0 0 0 1px rgba(233,216,166,0.12); }
|
|
.hp-start-overlay { position: fixed; inset: 0; z-index: 2147483646; display: grid; place-items: center; padding: 20px; background: radial-gradient(circle at center, rgba(233,216,166,0.12), rgba(4,4,7,0.88) 48%, rgba(0,0,0,0.96)); animation: hpStartFade 260ms ease-out; }
|
|
.hp-start-card { position: relative; width: min(420px, 100%); padding: 30px 22px 24px; border: 1px solid rgba(233,216,166,0.34); border-radius: 24px; text-align: center; background: linear-gradient(145deg, rgba(34,31,38,0.96), rgba(10,10,14,0.96)); box-shadow: 0 24px 90px rgba(0,0,0,0.72), 0 0 60px rgba(233,216,166,0.12); animation: hpStartCard 520ms cubic-bezier(.2,.8,.2,1); overflow: hidden; }
|
|
.hp-start-card::before, .hp-start-card::after { content: ""; position: absolute; left: 12%; right: 12%; height: 1px; background: linear-gradient(90deg, transparent, rgba(233,216,166,0.72), transparent); animation: hpStartLine 1.8s ease-in-out infinite; }
|
|
.hp-start-card::before { top: 12px; }
|
|
.hp-start-card::after { bottom: 12px; animation-delay: .45s; }
|
|
.hp-start-rune { color: var(--hp-textGold); font-size: 32px; line-height: 1; animation: hpStartRune 1.4s ease-in-out infinite; }
|
|
.hp-start-kicker { margin-top: 14px; color: var(--hp-textDim); font-size: 12px; letter-spacing: .16em; text-transform: uppercase; }
|
|
.hp-start-title { margin-top: 8px; color: var(--hp-textGold); font-family: "Cinzel Decorative", "IM Fell English", system-ui; font-size: 25px; font-weight: 900; }
|
|
.hp-start-subtitle { margin-top: 8px; color: var(--hp-textMain); font-size: 15px; }
|
|
.hp-start-card button { border: 1px solid rgba(233,216,166,0.26); border-radius: 12px; background: rgba(233,216,166,0.10); padding: 10px 16px; font-weight: 900; cursor: pointer; }
|
|
@keyframes hpStartFade { from { opacity: 0; } to { opacity: 1; } }
|
|
@keyframes hpStartCard { from { opacity: 0; transform: translateY(18px) scale(.92) rotateX(8deg); } to { opacity: 1; transform: translateY(0) scale(1) rotateX(0); } }
|
|
@keyframes hpStartRune { 0%, 100% { transform: rotate(0) scale(1); opacity: .7; } 50% { transform: rotate(180deg) scale(1.22); opacity: 1; } }
|
|
@keyframes hpStartLine { 0%, 100% { opacity: .3; transform: scaleX(.7); } 50% { opacity: 1; transform: scaleX(1); } }
|
|
.hp-topbar-actions { margin-left: auto; }
|
|
.hp-user-menu-wrap { min-width: 0; }
|
|
.hp-admin-user-row { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.5fr) 70px 76px 150px; }
|
|
.hp-admin-action { min-width: 0; }
|
|
.hp-admin-actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; min-width: 0; }
|
|
.hp-admin-check { display: flex; align-items: center; gap: 8px; color: var(--hp-textDim); font-size: 13px; }
|
|
.hp-admin-check input { width: 18px; height: 18px; accent-color: var(--hp-textGold); }
|
|
.hp-admin-role { padding: 4px 8px; border-radius: 999px; background: rgba(233,216,166,0.10); border: 1px solid rgba(233,216,166,0.16); font-size: 12px; }
|
|
.hp-admin-status { color: #baf3c9 !important; font-size: 13px; }
|
|
.hp-admin-status--disabled { color: #ffb3b3 !important; }
|
|
@media (max-width: 600px) {
|
|
.hp-shell { padding: calc(12px + env(safe-area-inset-top)) 10px calc(28px + env(safe-area-inset-bottom)) !important; }
|
|
.hp-topbar { align-items: center !important; padding: 10px !important; gap: 6px !important; flex-wrap: nowrap !important; }
|
|
.hp-topbar > div:first-child { min-width: 0; flex: 1; }
|
|
.hp-topbar > div:first-child > div:last-child { max-width: 105px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px !important; }
|
|
.hp-topbar-actions { width: auto; flex: 0 0 auto; margin-left: 0; gap: 5px !important; }
|
|
.hp-user-menu-wrap { flex: 0 0 auto; min-width: 0; }
|
|
.hp-topbar-user, .hp-topbar-new { flex: 0 0 auto; min-height: 40px; width: auto; padding: 8px 9px !important; font-size: 13px; }
|
|
.hp-topbar-user { justify-content: center; }
|
|
.hp-user-dropdown { left: 50% !important; right: auto !important; transform: translateX(-50%); width: max-content; max-width: calc(100vw - 20px); min-width: min(220px, calc(100vw - 20px)) !important; }
|
|
.hp-user-dropdown > div:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.hp-admin-user-row { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 10px; padding: 11px !important; border-radius: 15px !important; }
|
|
.hp-admin-name { grid-column: 1; grid-row: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 16px; }
|
|
.hp-admin-email { grid-column: 1; grid-row: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px !important; }
|
|
.hp-admin-role { grid-column: 2; grid-row: 1; }
|
|
.hp-admin-status { grid-column: 2; grid-row: 2; }
|
|
.hp-admin-actions { grid-column: 1 / -1; grid-row: 3; width: 100%; justify-content: flex-end; }
|
|
.hp-admin-actions .hp-admin-action { width: auto; min-width: 0; min-height: 38px; padding: 7px 10px !important; }
|
|
.hp-row { grid-template-columns: minmax(0, 1fr) 42px 62px !important; gap: 7px !important; padding: 12px 11px !important; }
|
|
.hp-row button { min-height: 40px; }
|
|
button { min-height: 42px; }
|
|
input, select { min-height: 44px; box-sizing: border-box; }
|
|
.hp-topbar + * { margin-top: 12px !important; }
|
|
}
|
|
@media (max-width: 360px) {
|
|
.hp-row { grid-template-columns: minmax(0, 1fr) 38px 56px !important; font-size: 14px; }
|
|
.hp-topbar { gap: 5px !important; }
|
|
.hp-topbar-user, .hp-topbar-new { padding-left: 7px !important; padding-right: 7px !important; font-size: 12px; }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}, []);
|
|
|
|
// Ensure a theme is applied once (fallback)
|
|
useEffect(() => {
|
|
applyTheme("default");
|
|
}, []);
|
|
}
|