larxius commited on
Commit
13d6398
·
verified ·
1 Parent(s): 3f6ba6e

Update frontend/src/pages/SuperAdminPanel.jsx

Browse files
frontend/src/pages/SuperAdminPanel.jsx CHANGED
@@ -504,6 +504,7 @@ const SuperAdminPanel = () => {
504
  const [confirmModal, setConfirmModal] = useState({ isOpen: false, title: '', desc: '', onConfirm: null, type: 'primary' });
505
  const [promptModal, setPromptModal] = useState({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null });
506
  const [promptValues, setPromptValues] = useState({});
 
507
  const [viewInvoice, setViewInvoice] = useState(null);
508
  const [rescheduleModal, setRescheduleModal] = useState({
509
  isOpen: false,
@@ -517,7 +518,7 @@ const SuperAdminPanel = () => {
517
  });
518
 
519
  const closeConfirm = () => setConfirmModal({ isOpen: false, title: '', desc: '', onConfirm: null, type: 'primary' });
520
- const closePrompt = () => { setPromptModal({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null }); setPromptValues({}); };
521
 
522
  const handlePromptChange = (key, value) => setPromptValues(prev => ({ ...prev, [key]: value }));
523
 
@@ -845,7 +846,7 @@ const SuperAdminPanel = () => {
845
  desc: 'Invite or add a new global platform member.',
846
  inputs: [
847
  { key: 'email', label: 'User Email', placeholder: 'user@example.com' },
848
- { key: 'password', label: 'Password (Optional - Auto-generated if left blank)', placeholder: 'Set initial password...', type: 'password' },
849
  { key: 'role', label: 'Role', type: 'select', options: MEMBER_ROLE_OPTIONS },
850
  { key: 'org_id', label: 'Organization', type: 'select', options: orgOptions }
851
  ],
@@ -854,6 +855,11 @@ const SuperAdminPanel = () => {
854
  toast.error('Please enter a valid user email');
855
  return;
856
  }
 
 
 
 
 
857
  try {
858
  const res = await authFetch('/api/auth/users', {
859
  method: 'POST',
@@ -862,7 +868,7 @@ const SuperAdminPanel = () => {
862
  email: values.email.trim(),
863
  role: values.role,
864
  org_id: values.org_id || null,
865
- password: values.password && values.password.trim() ? values.password.trim() : null
866
  })
867
  });
868
  const data = await res.json().catch(() => ({}));
@@ -905,11 +911,16 @@ const SuperAdminPanel = () => {
905
  desc: `Update details & credentials for ${u.email}`,
906
  inputs: [
907
  { key: 'email', label: 'User Email', disabled: true },
908
- { key: 'password', label: 'New Password (Leave blank to keep current password)', placeholder: 'Type new password to update...', type: 'password' },
909
  { key: 'role', label: 'Role', type: 'select', options: MEMBER_ROLE_OPTIONS },
910
  { key: 'org_id', label: 'Organization', type: 'select', options: orgOptions }
911
  ],
912
  onConfirm: async (vals) => {
 
 
 
 
 
913
  try {
914
  const res = await authFetch(`/api/auth/users/${u.id}`, {
915
  method: 'PUT',
@@ -917,7 +928,7 @@ const SuperAdminPanel = () => {
917
  body: JSON.stringify({
918
  role: vals.role,
919
  org_id: vals.org_id || null,
920
- password: vals.password && vals.password.trim() ? vals.password.trim() : null
921
  })
922
  });
923
  const data = await res.json().catch(() => ({}));
@@ -1893,6 +1904,57 @@ const SuperAdminPanel = () => {
1893
  return <option key={val} value={val}>{lbl}</option>;
1894
  })}
1895
  </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1896
  ) : (
1897
  <input
1898
  type={input.type || "text"}
 
504
  const [confirmModal, setConfirmModal] = useState({ isOpen: false, title: '', desc: '', onConfirm: null, type: 'primary' });
505
  const [promptModal, setPromptModal] = useState({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null });
506
  const [promptValues, setPromptValues] = useState({});
507
+ const [showPasswordPrompt, setShowPasswordPrompt] = useState({});
508
  const [viewInvoice, setViewInvoice] = useState(null);
509
  const [rescheduleModal, setRescheduleModal] = useState({
510
  isOpen: false,
 
518
  });
519
 
520
  const closeConfirm = () => setConfirmModal({ isOpen: false, title: '', desc: '', onConfirm: null, type: 'primary' });
521
+ const closePrompt = () => { setPromptModal({ isOpen: false, title: '', desc: '', inputs: [], onConfirm: null }); setPromptValues({}); setShowPasswordPrompt({}); };
522
 
523
  const handlePromptChange = (key, value) => setPromptValues(prev => ({ ...prev, [key]: value }));
524
 
 
846
  desc: 'Invite or add a new global platform member.',
847
  inputs: [
848
  { key: 'email', label: 'User Email', placeholder: 'user@example.com' },
849
+ { key: 'password', label: 'Password (Optional - Auto-generated if left blank)', placeholder: 'Set initial password...', type: 'password', showRules: true },
850
  { key: 'role', label: 'Role', type: 'select', options: MEMBER_ROLE_OPTIONS },
851
  { key: 'org_id', label: 'Organization', type: 'select', options: orgOptions }
852
  ],
 
855
  toast.error('Please enter a valid user email');
856
  return;
857
  }
858
+ const pwd = values.password ? values.password.trim() : '';
859
+ if (pwd && (pwd.length < 8 || !/[A-Z]/.test(pwd) || !/[a-z]/.test(pwd) || !/[^A-Za-z0-9]/.test(pwd))) {
860
+ toast.error('Password must meet all 4 security requirements (8+ chars, 1 uppercase, 1 lowercase, 1 special character).');
861
+ return;
862
+ }
863
  try {
864
  const res = await authFetch('/api/auth/users', {
865
  method: 'POST',
 
868
  email: values.email.trim(),
869
  role: values.role,
870
  org_id: values.org_id || null,
871
+ password: pwd || null
872
  })
873
  });
874
  const data = await res.json().catch(() => ({}));
 
911
  desc: `Update details & credentials for ${u.email}`,
912
  inputs: [
913
  { key: 'email', label: 'User Email', disabled: true },
914
+ { key: 'password', label: 'New Password (Leave blank to keep current password)', placeholder: 'Type new password to update...', type: 'password', showRules: true },
915
  { key: 'role', label: 'Role', type: 'select', options: MEMBER_ROLE_OPTIONS },
916
  { key: 'org_id', label: 'Organization', type: 'select', options: orgOptions }
917
  ],
918
  onConfirm: async (vals) => {
919
+ const pwd = vals.password ? vals.password.trim() : '';
920
+ if (pwd && (pwd.length < 8 || !/[A-Z]/.test(pwd) || !/[a-z]/.test(pwd) || !/[^A-Za-z0-9]/.test(pwd))) {
921
+ toast.error('Password must meet all 4 security requirements (8+ chars, 1 uppercase, 1 lowercase, 1 special character).');
922
+ return;
923
+ }
924
  try {
925
  const res = await authFetch(`/api/auth/users/${u.id}`, {
926
  method: 'PUT',
 
928
  body: JSON.stringify({
929
  role: vals.role,
930
  org_id: vals.org_id || null,
931
+ password: pwd || null
932
  })
933
  });
934
  const data = await res.json().catch(() => ({}));
 
1904
  return <option key={val} value={val}>{lbl}</option>;
1905
  })}
1906
  </select>
1907
+ ) : input.type === 'password' ? (
1908
+ <div>
1909
+ <div className="relative">
1910
+ <input
1911
+ type={showPasswordPrompt[input.key] ? "text" : "password"}
1912
+ value={promptValues[input.key] || ''}
1913
+ onChange={(e) => handlePromptChange(input.key, e.target.value)}
1914
+ placeholder={input.placeholder}
1915
+ disabled={input.disabled}
1916
+ className="w-full bg-surface-container border border-outline-variant rounded-lg pl-3 pr-10 py-2 focus:border-primary outline-none text-on-surface text-sm"
1917
+ />
1918
+ <button
1919
+ type="button"
1920
+ onClick={() => setShowPasswordPrompt(prev => ({ ...prev, [input.key]: !prev[input.key] }))}
1921
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-on-surface-variant hover:text-primary bg-transparent border-0 cursor-pointer p-1 flex items-center justify-center"
1922
+ title={showPasswordPrompt[input.key] ? "Hide password" : "Show password"}
1923
+ >
1924
+ <span className="material-symbols-outlined text-[18px]">
1925
+ {showPasswordPrompt[input.key] ? 'visibility_off' : 'visibility'}
1926
+ </span>
1927
+ </button>
1928
+ </div>
1929
+ {input.showRules && (promptValues[input.key] || '').length > 0 && (
1930
+ <div className="flex flex-col gap-1.5 p-3 bg-surface-container-high/60 border border-outline-variant/60 rounded-xl text-xs mt-2 animate-fade-in">
1931
+ <div className={`flex items-center gap-2 font-medium transition-colors ${(promptValues[input.key] || '').length >= 8 ? 'text-green-600 dark:text-green-500 font-bold' : 'text-on-surface-variant'}`}>
1932
+ <span className="material-symbols-outlined text-[16px]">
1933
+ {(promptValues[input.key] || '').length >= 8 ? 'check_circle' : 'radio_button_unchecked'}
1934
+ </span>
1935
+ <span>At least 8 characters</span>
1936
+ </div>
1937
+ <div className={`flex items-center gap-2 font-medium transition-colors ${/[A-Z]/.test(promptValues[input.key] || '') ? 'text-green-600 dark:text-green-500 font-bold' : 'text-on-surface-variant'}`}>
1938
+ <span className="material-symbols-outlined text-[16px]">
1939
+ {/[A-Z]/.test(promptValues[input.key] || '') ? 'check_circle' : 'radio_button_unchecked'}
1940
+ </span>
1941
+ <span>One uppercase letter</span>
1942
+ </div>
1943
+ <div className={`flex items-center gap-2 font-medium transition-colors ${/[a-z]/.test(promptValues[input.key] || '') ? 'text-green-600 dark:text-green-500 font-bold' : 'text-on-surface-variant'}`}>
1944
+ <span className="material-symbols-outlined text-[16px]">
1945
+ {/[a-z]/.test(promptValues[input.key] || '') ? 'check_circle' : 'radio_button_unchecked'}
1946
+ </span>
1947
+ <span>One lowercase letter</span>
1948
+ </div>
1949
+ <div className={`flex items-center gap-2 font-medium transition-colors ${/[^A-Za-z0-9]/.test(promptValues[input.key] || '') ? 'text-green-600 dark:text-green-500 font-bold' : 'text-on-surface-variant'}`}>
1950
+ <span className="material-symbols-outlined text-[16px]">
1951
+ {/[^A-Za-z0-9]/.test(promptValues[input.key] || '') ? 'check_circle' : 'radio_button_unchecked'}
1952
+ </span>
1953
+ <span>One special character</span>
1954
+ </div>
1955
+ </div>
1956
+ )}
1957
+ </div>
1958
  ) : (
1959
  <input
1960
  type={input.type || "text"}