feat: ability to change the order of signers in create screen #61
80
package-lock.json
generated
80
package-lock.json
generated
@ -19,12 +19,15 @@
|
|||||||
"axios": "1.6.7",
|
"axios": "1.6.7",
|
||||||
"crypto-hash": "3.0.0",
|
"crypto-hash": "3.0.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"dnd-core": "16.0.1",
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mui-file-input": "4.0.4",
|
"mui-file-input": "4.0.4",
|
||||||
"nostr-tools": "2.3.1",
|
"nostr-tools": "2.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-dnd": "16.0.1",
|
||||||
|
"react-dnd-html5-backend": "16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "9.1.0",
|
"react-redux": "9.1.0",
|
||||||
"react-router-dom": "6.22.1",
|
"react-router-dom": "6.22.1",
|
||||||
@ -1641,6 +1644,21 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-dnd/asap": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
|
||||||
|
},
|
||||||
|
"node_modules/@react-dnd/invariant": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
|
||||||
|
},
|
||||||
|
"node_modules/@react-dnd/shallowequal": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
|
||||||
|
},
|
||||||
"node_modules/@reduxjs/toolkit": {
|
"node_modules/@reduxjs/toolkit": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz",
|
||||||
@ -2020,7 +2038,7 @@
|
|||||||
"version": "20.11.20",
|
"version": "20.11.20",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
|
||||||
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
|
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
@ -2762,6 +2780,24 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dnd-core": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-dnd/asap": "^5.0.1",
|
||||||
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
|
"redux": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dnd-core/node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
@ -3212,8 +3248,7 @@
|
|||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
@ -4392,6 +4427,43 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dnd": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
|
"@react-dnd/shallowequal": "^4.0.1",
|
||||||
|
"dnd-core": "^16.0.1",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"hoist-non-react-statics": "^3.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/hoist-non-react-statics": ">= 3.3.1",
|
||||||
|
"@types/node": ">= 12",
|
||||||
|
"@types/react": ">= 16",
|
||||||
|
"react": ">= 16.14"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/hoist-non-react-statics": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dnd-html5-backend": {
|
||||||
|
"version": "16.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
|
||||||
|
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
|
||||||
|
"dependencies": {
|
||||||
|
"dnd-core": "^16.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
@ -4989,7 +5061,7 @@
|
|||||||
"version": "5.26.5",
|
"version": "5.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
|
@ -25,12 +25,15 @@
|
|||||||
"axios": "1.6.7",
|
"axios": "1.6.7",
|
||||||
"crypto-hash": "3.0.0",
|
"crypto-hash": "3.0.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"dnd-core": "16.0.1",
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mui-file-input": "4.0.4",
|
"mui-file-input": "4.0.4",
|
||||||
"nostr-tools": "2.3.1",
|
"nostr-tools": "2.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-dnd": "16.0.1",
|
||||||
|
"react-dnd-html5-backend": "16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "9.1.0",
|
"react-redux": "9.1.0",
|
||||||
"react-router-dom": "6.22.1",
|
"react-router-dom": "6.22.1",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Clear } from '@mui/icons-material'
|
import { Clear, DragHandle } from '@mui/icons-material'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -20,7 +20,7 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import { MuiFileInput } from 'mui-file-input'
|
import { MuiFileInput } from 'mui-file-input'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
@ -43,6 +43,10 @@ import {
|
|||||||
uploadToFileStorage
|
uploadToFileStorage
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
|
import { DndProvider } from 'react-dnd'
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||||
|
import type { Identifier, XYCoord } from 'dnd-core'
|
||||||
|
import { useDrag, useDrop } from 'react-dnd'
|
||||||
|
|
||||||
export const CreatePage = () => {
|
export const CreatePage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -85,13 +89,21 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
const addUser = (pubkey: string) => {
|
const addUser = (pubkey: string) => {
|
||||||
setUsers((prev) => {
|
setUsers((prev) => {
|
||||||
|
const signers = prev.filter((user) => user.role === UserRole.signer)
|
||||||
|
const viewers = prev.filter((user) => user.role === UserRole.viewer)
|
||||||
|
|
||||||
const existingUserIndex = prev.findIndex(
|
const existingUserIndex = prev.findIndex(
|
||||||
(user) => user.pubkey === pubkey
|
(user) => user.pubkey === pubkey
|
||||||
)
|
)
|
||||||
|
|
||||||
// add new
|
// add new
|
||||||
if (existingUserIndex === -1)
|
if (existingUserIndex === -1) {
|
||||||
return [...prev, { pubkey, role: userRole }]
|
if (userRole === UserRole.signer) {
|
||||||
|
return [...signers, { pubkey, role: userRole }, ...viewers]
|
||||||
|
} else {
|
||||||
|
return [...signers, ...viewers, { pubkey, role: userRole }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const existingUser = prev[existingUserIndex]
|
const existingUser = prev[existingUserIndex]
|
||||||
|
|
||||||
@ -104,7 +116,11 @@ export const CreatePage = () => {
|
|||||||
updatedUser.role = userRole
|
updatedUser.role = userRole
|
||||||
updatedUsers[existingUserIndex] = updatedUser
|
updatedUsers[existingUserIndex] = updatedUser
|
||||||
|
|
||||||
return updatedUsers
|
// signers should be placed at the start of the array
|
||||||
|
return [
|
||||||
|
...updatedUsers.filter((user) => user.role === UserRole.signer),
|
||||||
|
...updatedUsers.filter((user) => user.role === UserRole.viewer)
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +148,7 @@ export const CreatePage = () => {
|
|||||||
setLoadingSpinnerDesc('')
|
setLoadingSpinnerDesc('')
|
||||||
})
|
})
|
||||||
|
|
||||||
if (nip05Profile) {
|
if (nip05Profile && nip05Profile.pubkey) {
|
||||||
const pubkey = nip05Profile.pubkey
|
const pubkey = nip05Profile.pubkey
|
||||||
addUser(pubkey)
|
addUser(pubkey)
|
||||||
setUserInput('')
|
setUserInput('')
|
||||||
@ -145,25 +161,40 @@ export const CreatePage = () => {
|
|||||||
setError('Invalid input! Make sure to provide correct npub or nip05.')
|
setError('Invalid input! Make sure to provide correct npub or nip05.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUserRoleChange = (role: UserRole, index: number) => {
|
const handleUserRoleChange = (role: UserRole, pubkey: string) => {
|
||||||
setUsers((prevUsers) => {
|
setUsers((prevUsers) =>
|
||||||
// Create a shallow copy of the previous state
|
prevUsers.map((user) => {
|
||||||
const updatedUsers = [...prevUsers]
|
if (user.pubkey === pubkey) {
|
||||||
// Create a shallow copy of the user object at the specified index
|
return {
|
||||||
const updatedUser = { ...updatedUsers[index] }
|
...user,
|
||||||
// Update the role property of the copied user object
|
role
|
||||||
updatedUser.role = role
|
}
|
||||||
s marked this conversation as resolved
Outdated
|
|||||||
// Update the user object at the specified index in the copied array
|
}
|
||||||
updatedUsers[index] = updatedUser
|
|
||||||
// Return the updated array
|
return user
|
||||||
return updatedUsers
|
})
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRemoveUser = (pubkey: string) => {
|
const handleRemoveUser = (pubkey: string) => {
|
||||||
setUsers((prev) => prev.filter((user) => user.pubkey !== pubkey))
|
setUsers((prev) => prev.filter((user) => user.pubkey !== pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* changes the position of signer in the signers list
|
||||||
|
*
|
||||||
|
* @param dragIndex represents the current position of user
|
||||||
|
* @param hoverIndex represents the target position of user
|
||||||
|
*/
|
||||||
|
const moveSigner = (dragIndex: number, hoverIndex: number) => {
|
||||||
|
setUsers((prevUsers) => {
|
||||||
|
const updatedUsers = [...prevUsers]
|
||||||
|
const [draggedUser] = updatedUsers.splice(dragIndex, 1)
|
||||||
|
updatedUsers.splice(hoverIndex, 0, draggedUser)
|
||||||
|
return updatedUsers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleSelectFiles = (files: File[]) => {
|
const handleSelectFiles = (files: File[]) => {
|
||||||
setDisplayUserInput(true)
|
setDisplayUserInput(true)
|
||||||
setSelectedFiles((prev) => {
|
setSelectedFiles((prev) => {
|
||||||
@ -435,6 +466,7 @@ export const CreatePage = () => {
|
|||||||
users={users}
|
users={users}
|
||||||
handleUserRoleChange={handleUserRoleChange}
|
handleUserRoleChange={handleUserRoleChange}
|
||||||
handleRemoveUser={handleRemoveUser}
|
handleRemoveUser={handleRemoveUser}
|
||||||
|
moveSigner={moveSigner}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
<Button onClick={handleCreate} variant="contained">
|
<Button onClick={handleCreate} variant="contained">
|
||||||
@ -450,14 +482,16 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
type DisplayUsersProps = {
|
type DisplayUsersProps = {
|
||||||
users: User[]
|
users: User[]
|
||||||
handleUserRoleChange: (role: UserRole, index: number) => void
|
handleUserRoleChange: (role: UserRole, pubkey: string) => void
|
||||||
handleRemoveUser: (pubkey: string) => void
|
handleRemoveUser: (pubkey: string) => void
|
||||||
|
moveSigner: (dragIndex: number, hoverIndex: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const DisplayUser = ({
|
const DisplayUser = ({
|
||||||
users,
|
users,
|
||||||
handleUserRoleChange,
|
handleUserRoleChange,
|
||||||
handleRemoveUser
|
handleRemoveUser,
|
||||||
|
moveSigner
|
||||||
}: DisplayUsersProps) => {
|
}: DisplayUsersProps) => {
|
||||||
const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
|
const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>(
|
||||||
{}
|
{}
|
||||||
@ -494,55 +528,218 @@ const DisplayUser = ({
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className={styles.tableCell}>User</TableCell>
|
<TableCell className={styles.tableHeaderCell}>User</TableCell>
|
||||||
<TableCell className={styles.tableCell}>Role</TableCell>
|
<TableCell className={styles.tableHeaderCell}>Role</TableCell>
|
||||||
<TableCell>Action</TableCell>
|
<TableCell>Action</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.map((user, index) => {
|
<DndProvider backend={HTML5Backend}>
|
||||||
const userMeta = metadata[user.pubkey]
|
{users
|
||||||
return (
|
.filter((user) => user.role === UserRole.signer)
|
||||||
<TableRow key={index}>
|
.map((user, index) => (
|
||||||
<TableCell className={styles.tableCell}>
|
<SignerRow
|
||||||
<UserComponent
|
key={`signer-${index}`}
|
||||||
pubkey={user.pubkey}
|
userMeta={metadata[user.pubkey]}
|
||||||
name={
|
user={user}
|
||||||
userMeta?.display_name ||
|
index={index}
|
||||||
userMeta?.name ||
|
moveSigner={moveSigner}
|
||||||
shorten(hexToNpub(user.pubkey))
|
handleUserRoleChange={handleUserRoleChange}
|
||||||
}
|
handleRemoveUser={handleRemoveUser}
|
||||||
image={userMeta?.picture}
|
/>
|
||||||
/>
|
))}
|
||||||
</TableCell>
|
</DndProvider>
|
||||||
<TableCell className={styles.tableCell}>
|
{users
|
||||||
<Select
|
.filter((user) => user.role === UserRole.viewer)
|
||||||
fullWidth
|
.map((user, index) => {
|
||||||
value={user.role}
|
const userMeta = metadata[user.pubkey]
|
||||||
onChange={(e) =>
|
return (
|
||||||
handleUserRoleChange(e.target.value as UserRole, index)
|
<TableRow key={index}>
|
||||||
}
|
<TableCell className={styles.tableCell}>
|
||||||
>
|
<UserComponent
|
||||||
<MenuItem value={UserRole.signer}>
|
pubkey={user.pubkey}
|
||||||
{UserRole.signer}
|
name={
|
||||||
</MenuItem>
|
userMeta?.display_name ||
|
||||||
<MenuItem value={UserRole.viewer}>
|
userMeta?.name ||
|
||||||
{UserRole.viewer}
|
shorten(hexToNpub(user.pubkey))
|
||||||
</MenuItem>
|
}
|
||||||
</Select>
|
image={userMeta?.picture}
|
||||||
</TableCell>
|
/>
|
||||||
<TableCell>
|
</TableCell>
|
||||||
<Tooltip title="Remove User" arrow>
|
<TableCell className={styles.tableCell}>
|
||||||
<IconButton onClick={() => handleRemoveUser(user.pubkey)}>
|
<Select
|
||||||
<Clear style={{ color: 'red' }} />
|
fullWidth
|
||||||
</IconButton>
|
value={user.role}
|
||||||
</Tooltip>
|
onChange={(e) =>
|
||||||
</TableCell>
|
handleUserRoleChange(
|
||||||
</TableRow>
|
e.target.value as UserRole,
|
||||||
)
|
user.pubkey
|
||||||
})}
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuItem value={UserRole.signer}>
|
||||||
|
{UserRole.signer}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem value={UserRole.viewer}>
|
||||||
|
{UserRole.viewer}
|
||||||
|
</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Tooltip title="Remove User" arrow>
|
||||||
|
<IconButton onClick={() => handleRemoveUser(user.pubkey)}>
|
||||||
|
<Clear style={{ color: 'red' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DragItem {
|
||||||
|
index: number
|
||||||
|
id: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignerRowProps = {
|
||||||
|
userMeta: ProfileMetadata
|
||||||
|
user: User
|
||||||
|
index: number
|
||||||
|
moveSigner: (dragIndex: number, hoverIndex: number) => void
|
||||||
|
handleUserRoleChange: (role: UserRole, pubkey: string) => void
|
||||||
|
handleRemoveUser: (pubkey: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignerRow = ({
|
||||||
|
userMeta,
|
||||||
|
user,
|
||||||
|
index,
|
||||||
|
moveSigner,
|
||||||
|
handleUserRoleChange,
|
||||||
|
handleRemoveUser
|
||||||
|
}: SignerRowProps) => {
|
||||||
|
const ref = useRef<HTMLTableRowElement>(null)
|
||||||
|
|
||||||
|
const [{ handlerId }, drop] = useDrop<
|
||||||
|
DragItem,
|
||||||
|
void,
|
||||||
|
{ handlerId: Identifier | null }
|
||||||
|
>({
|
||||||
|
accept: 'row',
|
||||||
|
collect(monitor) {
|
||||||
|
return {
|
||||||
|
handlerId: monitor.getHandlerId()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hover(item: DragItem, monitor) {
|
||||||
|
if (!ref.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const dragIndex = item.index
|
||||||
|
const hoverIndex = index
|
||||||
|
|
||||||
|
// Don't replace items with themselves
|
||||||
|
if (dragIndex === hoverIndex) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine rectangle on screen
|
||||||
|
const hoverBoundingRect = ref.current?.getBoundingClientRect()
|
||||||
|
|
||||||
|
// Get vertical middle
|
||||||
|
const hoverMiddleY =
|
||||||
|
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||||
|
|
||||||
|
// Determine mouse position
|
||||||
|
const clientOffset = monitor.getClientOffset()
|
||||||
|
|
||||||
|
// Get pixels to the top
|
||||||
|
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
|
||||||
|
|
||||||
|
// Only perform the move when the mouse has crossed half of the items height
|
||||||
|
// When dragging downwards, only move when the cursor is below 50%
|
||||||
|
// When dragging upwards, only move when the cursor is above 50%
|
||||||
|
|
||||||
|
// Dragging downwards
|
||||||
|
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dragging upwards
|
||||||
|
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time to actually perform the action
|
||||||
|
moveSigner(dragIndex, hoverIndex)
|
||||||
|
|
||||||
|
// Note: we're mutating the monitor item here!
|
||||||
|
// Generally it's better to avoid mutations,
|
||||||
|
// but it's good here for the sake of performance
|
||||||
|
// to avoid expensive index searches.
|
||||||
|
item.index = hoverIndex
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [{ isDragging }, drag] = useDrag({
|
||||||
|
type: 'row',
|
||||||
|
item: () => {
|
||||||
|
return { id: user.pubkey, index }
|
||||||
|
},
|
||||||
|
collect: (monitor: any) => ({
|
||||||
|
isDragging: monitor.isDragging()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const opacity = isDragging ? 0 : 1
|
||||||
|
drag(drop(ref))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
sx={{ cursor: 'move', opacity }}
|
||||||
|
data-handler-id={handlerId}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<TableCell
|
||||||
|
className={styles.tableCell}
|
||||||
|
sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}
|
||||||
|
>
|
||||||
|
<DragHandle />
|
||||||
|
<UserComponent
|
||||||
|
pubkey={user.pubkey}
|
||||||
|
name={
|
||||||
|
userMeta?.display_name ||
|
||||||
|
userMeta?.name ||
|
||||||
|
shorten(hexToNpub(user.pubkey))
|
||||||
|
}
|
||||||
|
image={userMeta?.picture}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={styles.tableCell}>
|
||||||
|
<Select
|
||||||
|
fullWidth
|
||||||
|
value={user.role}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleUserRoleChange(e.target.value as UserRole, user.pubkey)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuItem value={UserRole.signer}>{UserRole.signer}</MenuItem>
|
||||||
|
<MenuItem value={UserRole.viewer}>{UserRole.viewer}</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Tooltip title="Remove User" arrow>
|
||||||
|
<IconButton onClick={() => handleRemoveUser(user.pubkey)}>
|
||||||
|
<Clear style={{ color: 'red' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -17,8 +17,13 @@
|
|||||||
border-bottom: 0.5px solid;
|
border-bottom: 0.5px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tableHeaderCell {
|
||||||
|
border-right: 1px solid rgba(224, 224, 224, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.tableCell {
|
.tableCell {
|
||||||
border-right: 1px solid rgba(224, 224, 224, 1);
|
border-right: 1px solid rgba(224, 224, 224, 1);
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
Loading…
Reference in New Issue
Block a user
is singer a typo?