mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Show invite link with copy functionality and QR-Code, add tests
This commit is contained in:
parent
3ce71da9ff
commit
505ec10152
20
lib/package-lock.json
generated
20
lib/package-lock.json
generated
@ -37,6 +37,7 @@
|
||||
"react-leaflet-cluster": "^2.1.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-photo-album": "^3.0.2",
|
||||
"react-qr-code": "^2.0.16",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
@ -11103,6 +11104,12 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qr.js": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
|
||||
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz",
|
||||
@ -11319,6 +11326,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-qr-code": {
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.16.tgz",
|
||||
"integrity": "sha512-8f54aTOo7DxYr1LB47pMeclV5SL/zSbJxkXHIS2a+QnAIa4XDVIdmzYRC+CBCJeDLSCeFHn8gHtltwvwZGJD/w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.8.1",
|
||||
"qr.js": "0.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
|
||||
@ -125,6 +125,7 @@
|
||||
"react-leaflet-cluster": "^2.1.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-photo-album": "^3.0.2",
|
||||
"react-qr-code": "^2.0.16",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
|
||||
@ -47,6 +47,7 @@ export default [
|
||||
/node_modules\/tiptap-markdown/,
|
||||
/node_modules\/markdown-it-task-lists/,
|
||||
/node_modules\/classnames/,
|
||||
/node_modules\/react-qr-code/,
|
||||
],
|
||||
requireReturnsDefault: 'auto',
|
||||
}),
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
|
||||
import { InviteLinkView } from './InviteLinkView'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
const itemWithSecret: Item = {
|
||||
secrets: [
|
||||
{
|
||||
secret: 'secret1',
|
||||
},
|
||||
],
|
||||
id: '1',
|
||||
name: 'Test Item',
|
||||
}
|
||||
|
||||
const itemWithoutSecret: Item = {
|
||||
secrets: [],
|
||||
id: '2',
|
||||
name: 'Test Item Without Secret',
|
||||
}
|
||||
|
||||
const itemWithUndefinedSecrets: Item = {
|
||||
id: '3',
|
||||
name: 'Test Item With Undefined Secrets',
|
||||
}
|
||||
|
||||
describe('<InviteLinkView />', () => {
|
||||
let wrapper: ReturnType<typeof render>
|
||||
|
||||
const Wrapper = ({ item }: { item: Item }) => {
|
||||
return render(<InviteLinkView item={item} />)
|
||||
}
|
||||
|
||||
describe('when item does not have secrets', () => {
|
||||
it('does not render anything', () => {
|
||||
wrapper = Wrapper({ item: itemWithoutSecret })
|
||||
expect(wrapper.container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when item has secrets undefined', () => {
|
||||
it('does not render anything', () => {
|
||||
wrapper = Wrapper({ item: itemWithUndefinedSecrets })
|
||||
expect(wrapper.container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when item has secrets', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper({ item: itemWithSecret })
|
||||
})
|
||||
|
||||
it('renders the secret', () => {
|
||||
expect(wrapper.getByDisplayValue('secret1', { exact: false })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('matches the snapshot', () => {
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('copies the secret to clipboard when button is clicked', () => {
|
||||
const copyButton = wrapper.getByRole('button')
|
||||
expect(copyButton).toBeInTheDocument()
|
||||
|
||||
const clipboardSpy = vi.spyOn(navigator.clipboard, 'writeText')
|
||||
|
||||
fireEvent.click(copyButton)
|
||||
|
||||
// TODO Implement in a way that the URL stays consistent on CI
|
||||
expect(clipboardSpy).toHaveBeenCalledWith('http://localhost:3000/invite/secret1')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,28 +1,37 @@
|
||||
import { TextView } from '#components/Map/Subcomponents/ItemPopupComponents'
|
||||
import { ClipboardIcon } from '@heroicons/react/24/outline'
|
||||
import QRCode from 'react-qr-code'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const InviteLinkView = ({ item }: { item: Item }) => {
|
||||
// TODO Only show if user has permission to view secrets.
|
||||
// usePermission() seems to be useful.
|
||||
// Only show if user has permission to view secrets.
|
||||
if (!item.secrets || item.secrets.length === 0) return
|
||||
|
||||
if (!item.secrets || item.secrets.length === 0) {
|
||||
console.log('No secrets found for item', item.id)
|
||||
// Generate a new secret if none exists?
|
||||
return
|
||||
}
|
||||
const link = `${window.location.origin}/invite/${item.secrets[0].secret}`
|
||||
|
||||
const copyToClipboard = () => {
|
||||
void navigator.clipboard
|
||||
.writeText(link)
|
||||
.then(() => toast.success('Invite link copied to clipboard!'))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='tw:my-10 tw:mt-2 tw:px-6'>
|
||||
<h2 className='tw:text-lg tw:font-semibold'>Invite</h2>
|
||||
<div className='tw:mt-2 tw:text-sm'>
|
||||
<div className='tw:mt-2 tw:text-sm tw:flex tw:gap-2 tw:mb-2'>
|
||||
<input
|
||||
type='text'
|
||||
value={link}
|
||||
readOnly
|
||||
className='tw:w-full tw:mb-2 tw:p-2 tw:border tw:rounded'
|
||||
className='tw:w-full tw:p-2 tw:border tw:rounded'
|
||||
/>
|
||||
<button onClick={copyToClipboard} className='btn btn-circle btn-primary'>
|
||||
<ClipboardIcon className='w-6 h-6' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='tw:bg-white tw:p-2 tw:w-fit'>
|
||||
<QRCode value={link} size={128} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user