|
1 | 1 | import React, { useState, useEffect } from 'react'; |
2 | 2 | import { Button, Input, Table, Space, Modal, Form, Select, message, Popconfirm } from 'antd'; |
3 | | -import { PlusOutlined, UploadOutlined, DeleteOutlined, DownloadOutlined } from '@ant-design/icons'; |
| 3 | +import { PlusOutlined, UploadOutlined, DeleteOutlined, DownloadOutlined, EditOutlined } from '@ant-design/icons'; |
4 | 4 | import { useAllowedUsersNpubs, useAllowedUsersValidation } from '@app/hooks/useAllowedUsers'; |
5 | 5 | import { AllowedUsersSettings, AllowedUsersMode } from '@app/types/allowedUsers.types'; |
6 | 6 | import * as S from './NPubManagement.styles'; |
@@ -30,10 +30,13 @@ export const NPubManagement: React.FC<NPubManagementProps> = ({ |
30 | 30 | mode |
31 | 31 | }) => { |
32 | 32 | const [isAddModalVisible, setIsAddModalVisible] = useState(false); |
| 33 | + const [isEditModalVisible, setIsEditModalVisible] = useState(false); |
33 | 34 | const [isBulkModalVisible, setIsBulkModalVisible] = useState(false); |
34 | 35 | const [bulkText, setBulkText] = useState(''); |
35 | 36 | const [unifiedUsers, setUnifiedUsers] = useState<UnifiedUser[]>([]); |
| 37 | + const [editingUser, setEditingUser] = useState<UnifiedUser | null>(null); |
36 | 38 | const [addForm] = Form.useForm<AddNpubFormData>(); |
| 39 | + const [editForm] = Form.useForm<AddNpubFormData>(); |
37 | 40 |
|
38 | 41 | const readNpubs = useAllowedUsersNpubs('read'); |
39 | 42 | const writeNpubs = useAllowedUsersNpubs('write'); |
@@ -132,6 +135,67 @@ export const NPubManagement: React.FC<NPubManagementProps> = ({ |
132 | 135 | } |
133 | 136 | }; |
134 | 137 |
|
| 138 | + const handleEditUser = (user: UnifiedUser) => { |
| 139 | + setEditingUser(user); |
| 140 | + setIsEditModalVisible(true); |
| 141 | + // Set form values after modal is visible |
| 142 | + setTimeout(() => { |
| 143 | + editForm.setFieldsValue({ |
| 144 | + npub: user.npub, |
| 145 | + tier: user.tier, |
| 146 | + readAccess: user.readAccess, |
| 147 | + writeAccess: user.writeAccess |
| 148 | + }); |
| 149 | + }, 0); |
| 150 | + }; |
| 151 | + |
| 152 | + const handleSaveEdit = async () => { |
| 153 | + try { |
| 154 | + const values = await editForm.validateFields(); |
| 155 | + const originalUser = editingUser!; |
| 156 | + |
| 157 | + // Check what actually changed |
| 158 | + const readChanged = originalUser.readAccess !== values.readAccess; |
| 159 | + const writeChanged = originalUser.writeAccess !== values.writeAccess; |
| 160 | + const tierChanged = originalUser.tier !== values.tier; |
| 161 | + |
| 162 | + // Handle read access changes |
| 163 | + if (readChanged || (tierChanged && values.readAccess)) { |
| 164 | + if (values.readAccess) { |
| 165 | + // Remove old entry if exists and re-add with new tier |
| 166 | + if (originalUser.readAccess) { |
| 167 | + await readNpubs.removeNpub(values.npub); |
| 168 | + } |
| 169 | + await readNpubs.addNpub(values.npub, values.tier); |
| 170 | + } else { |
| 171 | + // Remove read access |
| 172 | + await readNpubs.removeNpub(values.npub); |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + // Handle write access changes |
| 177 | + if (writeChanged || (tierChanged && values.writeAccess)) { |
| 178 | + if (values.writeAccess) { |
| 179 | + // Remove old entry if exists and re-add with new tier |
| 180 | + if (originalUser.writeAccess) { |
| 181 | + await writeNpubs.removeNpub(values.npub); |
| 182 | + } |
| 183 | + await writeNpubs.addNpub(values.npub, values.tier); |
| 184 | + } else { |
| 185 | + // Remove write access |
| 186 | + await writeNpubs.removeNpub(values.npub); |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + setIsEditModalVisible(false); |
| 191 | + setEditingUser(null); |
| 192 | + editForm.resetFields(); |
| 193 | + } catch (error) { |
| 194 | + console.error('Edit user error:', error); |
| 195 | + message.error('Failed to update user'); |
| 196 | + } |
| 197 | + }; |
| 198 | + |
135 | 199 | const handleRemoveUser = async (npub: string) => { |
136 | 200 | try { |
137 | 201 | // Remove from both lists |
@@ -262,17 +326,27 @@ export const NPubManagement: React.FC<NPubManagementProps> = ({ |
262 | 326 | title: 'Actions', |
263 | 327 | key: 'actions', |
264 | 328 | render: (_: any, record: UnifiedUser) => ( |
265 | | - <Popconfirm |
266 | | - title="Are you sure you want to remove this user completely?" |
267 | | - onConfirm={() => handleRemoveUser(record.npub)} |
268 | | - > |
| 329 | + <Space size="small"> |
269 | 330 | <Button |
270 | 331 | type="text" |
271 | | - danger |
272 | | - icon={<DeleteOutlined />} |
| 332 | + icon={<EditOutlined />} |
273 | 333 | size="small" |
| 334 | + onClick={() => handleEditUser(record)} |
| 335 | + title="Edit user" |
274 | 336 | /> |
275 | | - </Popconfirm> |
| 337 | + <Popconfirm |
| 338 | + title="Are you sure you want to remove this user completely?" |
| 339 | + onConfirm={() => handleRemoveUser(record.npub)} |
| 340 | + > |
| 341 | + <Button |
| 342 | + type="text" |
| 343 | + danger |
| 344 | + icon={<DeleteOutlined />} |
| 345 | + size="small" |
| 346 | + title="Remove user" |
| 347 | + /> |
| 348 | + </Popconfirm> |
| 349 | + </Space> |
276 | 350 | ) |
277 | 351 | } |
278 | 352 | ]; |
@@ -372,6 +446,65 @@ export const NPubManagement: React.FC<NPubManagementProps> = ({ |
372 | 446 | </Form> |
373 | 447 | </Modal> |
374 | 448 |
|
| 449 | + {/* Edit User Modal */} |
| 450 | + <Modal |
| 451 | + title="Edit User" |
| 452 | + open={isEditModalVisible} |
| 453 | + onOk={handleSaveEdit} |
| 454 | + onCancel={() => { |
| 455 | + setIsEditModalVisible(false); |
| 456 | + setEditingUser(null); |
| 457 | + editForm.resetFields(); |
| 458 | + }} |
| 459 | + destroyOnClose |
| 460 | + > |
| 461 | + <Form |
| 462 | + form={editForm} |
| 463 | + layout="vertical" |
| 464 | + initialValues={{ |
| 465 | + npub: editingUser?.npub || '', |
| 466 | + tier: editingUser?.tier || '', |
| 467 | + readAccess: editingUser?.readAccess || false, |
| 468 | + writeAccess: editingUser?.writeAccess || false |
| 469 | + }} |
| 470 | + > |
| 471 | + <Form.Item |
| 472 | + name="npub" |
| 473 | + label="NPUB" |
| 474 | + > |
| 475 | + <Input disabled /> |
| 476 | + </Form.Item> |
| 477 | + |
| 478 | + <Form.Item |
| 479 | + name="tier" |
| 480 | + label="Tier" |
| 481 | + rules={[{ required: true, message: 'Please select a tier' }]} |
| 482 | + > |
| 483 | + <Select placeholder="Select tier" options={tierOptions} /> |
| 484 | + </Form.Item> |
| 485 | + |
| 486 | + <Form.Item |
| 487 | + label="Permissions" |
| 488 | + style={{ marginBottom: 0 }} |
| 489 | + > |
| 490 | + <Space direction="vertical"> |
| 491 | + <div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: 'var(--text-main-color)' }}> |
| 492 | + <Form.Item name="readAccess" valuePropName="checked" style={{ marginBottom: 0 }}> |
| 493 | + <S.StyledSwitch size="small" /> |
| 494 | + </Form.Item> |
| 495 | + <span>Read Access</span> |
| 496 | + </div> |
| 497 | + <div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: 'var(--text-main-color)' }}> |
| 498 | + <Form.Item name="writeAccess" valuePropName="checked" style={{ marginBottom: 0 }}> |
| 499 | + <S.StyledSwitch size="small" /> |
| 500 | + </Form.Item> |
| 501 | + <span>Write Access</span> |
| 502 | + </div> |
| 503 | + </Space> |
| 504 | + </Form.Item> |
| 505 | + </Form> |
| 506 | + </Modal> |
| 507 | + |
375 | 508 | {/* Bulk Import Modal */} |
376 | 509 | <Modal |
377 | 510 | title="Bulk Import Users" |
|
0 commit comments