+
+
+ {pending && (
+ } onClick={copy}>
+ {translate('resources.player.actions.copyApiKey')}
+
+ )}
+ {isOwner && (
+ : }
+ onClick={() => onChange(generateApiKey())}
+ >
+ {translate(
+ hasKey
+ ? 'resources.player.actions.regenerateApiKey'
+ : 'resources.player.actions.generateApiKey',
+ )}
+
+ )}
+ {saved && isWritable(record?.userId) && (
+ } onClick={() => onChange('')}>
+ {translate('resources.player.actions.revokeApiKey')}
+
+ )}
+
+
+ )
+}
+
+ApiKeyInput.propTypes = {
+ source: PropTypes.string.isRequired,
+ record: PropTypes.object,
+ isCreate: PropTypes.bool,
+ fullWidth: PropTypes.bool,
+ className: PropTypes.string,
+ validate: PropTypes.oneOfType([PropTypes.func, PropTypes.array]),
+}
+
+export default ApiKeyInput
diff --git a/ui/src/player/ApiKeyInput.test.jsx b/ui/src/player/ApiKeyInput.test.jsx
new file mode 100644
index 000000000..dd4269d6e
--- /dev/null
+++ b/ui/src/player/ApiKeyInput.test.jsx
@@ -0,0 +1,172 @@
+import * as React from 'react'
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { Form } from 'react-final-form'
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import ApiKeyInput from './ApiKeyInput'
+
+const hooks = vi.hoisted(() => ({ notify: vi.fn() }))
+const KEY = 'nds_0123456789abcdefghijkl'
+const KEY_FORMAT = /^nds_[0-9A-Za-z]{22}$/
+
+vi.mock('react-admin', async () => {
+ const actual = await vi.importActual('react-admin')
+ return {
+ ...actual,
+ useNotify: () => hooks.notify,
+ useTranslate: () => (key) => key,
+ }
+})
+
+const renderInput = ({
+ record,
+ initialValues = {},
+ isCreate = false,
+ fullWidth,
+}) => {
+ let values
+ const utils = render(
+