Skip to content

Commit 09d05b5

Browse files
committed
Add ability to create read-only URLs from the settings page [#8]
1 parent 2069d02 commit 09d05b5

8 files changed

Lines changed: 200 additions & 9 deletions

File tree

frontend/src/components/retro-settings/APIKeyManager.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
margin: 16px 0 8px;
66

77
&.options {
8-
text-align: center;
8+
display: flex;
9+
flex-flow: row wrap;
10+
gap: 8px 16px;
11+
justify-content: center;
912
}
1013
}
1114

frontend/src/components/retro-settings/APIKeyManager.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { Retro, RetroApiKey, RetroAuth } from '../../shared/api-entities';
55
import { retroApiKeyService } from '../../api/api';
66
import { formatDateTime } from '../../time/formatters';
77
import { CreateAPIKeyPopup } from './CreateAPIKeyPopup';
8+
import { CreateReadonlyUrlPopup } from './CreateReadonlyUrlPopup';
89
import { DeleteAPIKeyPopup } from './DeleteAPIKeyPopup';
910
import './APIKeyManager.css';
1011

@@ -15,7 +16,7 @@ interface PropsT {
1516

1617
export const APIKeyManager = memo(({ retro, retroAuth }: PropsT) => {
1718
const [deleting, setDeleting] = useState<RetroApiKey | null>(null);
18-
const [adding, setAdding] = useState(false);
19+
const [adding, setAdding] = useState(0);
1920
const apiKeys = useAwaited(
2021
(signal) =>
2122
retroApiKeyService.getList(retro.id, retroAuth.retroToken, signal),
@@ -71,17 +72,31 @@ export const APIKeyManager = memo(({ retro, retroAuth }: PropsT) => {
7172
<button
7273
type="button"
7374
className="global-button"
74-
onClick={() => setAdding(true)}
75+
onClick={() => setAdding(1)}
7576
>
7677
+ Create new API Key
7778
</button>
79+
<button
80+
type="button"
81+
className="global-button"
82+
onClick={() => setAdding(2)}
83+
>
84+
+ Create new read-only URL
85+
</button>
7886
</li>
7987
<CreateAPIKeyPopup
8088
retro={retro}
8189
retroToken={retroAuth.retroToken}
82-
isOpen={adding}
90+
isOpen={adding === 1}
91+
onSave={() => apiKeys.forceRefresh()}
92+
onClose={() => setAdding(0)}
93+
/>
94+
<CreateReadonlyUrlPopup
95+
retro={retro}
96+
retroToken={retroAuth.retroToken}
97+
isOpen={adding === 2}
8398
onSave={() => apiKeys.forceRefresh()}
84-
onClose={() => setAdding(false)}
99+
onClose={() => setAdding(0)}
85100
/>
86101
<DeleteAPIKeyPopup
87102
retro={retro}

frontend/src/components/retro-settings/CreateAPIKeyPopup.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,22 @@
44
}
55

66
.popup-add-api-key {
7+
max-width: calc(100vw - 64px);
8+
79
& p {
810
margin: 16px 0;
911
}
1012

13+
.key-output {
14+
text-align: center;
15+
overflow: auto;
16+
padding: 20px;
17+
background: var(--beige-back);
18+
border-radius: 5px;
19+
white-space: nowrap;
20+
user-select: all;
21+
}
22+
1123
.scope-id {
1224
display: inline-block;
1325
min-width: 7em;

frontend/src/components/retro-settings/CreateAPIKeyPopup.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const CreateAPIKeyPopup = memo(
5858
API Key created. Make a note of this value; it will not be
5959
possible to read it again.
6060
</p>
61-
<p>
61+
<p className="key-output">
6262
<code>{key}</code>
6363
</p>
6464
<section className="dialog-options">
@@ -76,8 +76,17 @@ export const CreateAPIKeyPopup = memo(
7676
className="global-form popup-add-api-key"
7777
onSubmit={performCreate}
7878
>
79+
<p>
80+
API keys can be used to link external services, for example
81+
displaying action items in a chat. To build your own integration,
82+
see the{' '}
83+
<a href="/api-docs" target="_blank" rel="noopener noreferrer">
84+
API Documentation
85+
</a>
86+
.
87+
</p>
7988
<label>
80-
API Key Name
89+
Reference Name (for identifying this key later)
8190
<input
8291
ref={realAutoFocus}
8392
name="api-key-name"
@@ -114,7 +123,7 @@ export const CreateAPIKeyPopup = memo(
114123
className="global-button primary"
115124
disabled={sending}
116125
>
117-
Add
126+
Create
118127
</button>
119128
<Alert message={error} />
120129
</section>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.popup-add-readonly-url.global-form {
2+
width: auto;
3+
max-width: 600px;
4+
}
5+
6+
.popup-add-readonly-url {
7+
max-width: min(calc(100vw - 64px), 800px);
8+
9+
& p {
10+
margin: 16px 0;
11+
}
12+
13+
.url-output {
14+
text-align: center;
15+
overflow: auto;
16+
padding: 20px;
17+
background: var(--beige-back);
18+
border-radius: 5px;
19+
white-space: nowrap;
20+
user-select: all;
21+
}
22+
23+
.dialog-options {
24+
margin: 32px 0 16px;
25+
text-align: center;
26+
27+
& button {
28+
margin: 0 16px;
29+
}
30+
}
31+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { memo, useEffect, useState } from 'react';
2+
import type { Retro } from '../../shared/api-entities';
3+
import { useSubmissionCallback } from '../../hooks/useSubmissionCallback';
4+
import { realAutoFocus } from '../../helpers/realAutoFocus';
5+
import { retroApiKeyService } from '../../api/api';
6+
import { Popup } from '../common/Popup';
7+
import { Alert } from '../common/Alert';
8+
import './CreateReadonlyUrlPopup.css';
9+
10+
interface PropsT {
11+
retro: Retro;
12+
retroToken: string;
13+
isOpen: boolean;
14+
onSave: () => void;
15+
onClose: () => void;
16+
}
17+
18+
const DEFAULT_NAME = 'Read-Only Link';
19+
20+
export const CreateReadonlyUrlPopup = memo(
21+
({ retro, retroToken, isOpen, onSave, onClose }: PropsT) => {
22+
const { protocol, host } = document.location;
23+
const [url, setURL] = useState('');
24+
const [name, setName] = useState(DEFAULT_NAME);
25+
const [performCreate, sending, error, resetError] = useSubmissionCallback(
26+
async () => {
27+
const created = await retroApiKeyService.create({
28+
retro,
29+
retroToken,
30+
name,
31+
scopes: ['read'],
32+
});
33+
setURL(`${protocol}//${host}/watch/${retro.id}#${created.key}`);
34+
onSave();
35+
},
36+
);
37+
38+
useEffect(() => {
39+
if (isOpen) {
40+
setURL('');
41+
setName(DEFAULT_NAME);
42+
resetError();
43+
}
44+
}, [isOpen]);
45+
46+
return (
47+
<Popup
48+
title="New Read-Only URL"
49+
isOpen={isOpen}
50+
keys={{ Enter: url ? onClose : performCreate, Escape: onClose }}
51+
onClose={onClose}
52+
>
53+
{url ? (
54+
<div className="popup-add-readonly-url">
55+
<p>
56+
URL created. Make a note of this; it will not be possible to read
57+
it again.
58+
</p>
59+
<p className="url-output">
60+
<a href={url} target="_blank" rel="noopener noreferrer">
61+
{url}
62+
</a>
63+
</p>
64+
<section className="dialog-options">
65+
<button
66+
type="button"
67+
className="global-button primary"
68+
onClick={onClose}
69+
>
70+
Close
71+
</button>
72+
</section>
73+
</div>
74+
) : (
75+
<form
76+
className="global-form popup-add-readonly-url"
77+
onSubmit={performCreate}
78+
>
79+
<p>
80+
Read-only URLs can be used to display live retros on shared
81+
devices, such as a smart TV or a meeting room computer. You can
82+
create multiple read-only URLs at once, and revoke any URL at any
83+
time.
84+
</p>
85+
<label>
86+
Reference Name (this does not appear in the URL)
87+
<input
88+
ref={realAutoFocus}
89+
name="api-key-name"
90+
type="text"
91+
placeholder="URL name"
92+
value={name}
93+
onChange={(e) => setName(e.currentTarget.value)}
94+
autoComplete="off"
95+
required
96+
/>
97+
</label>
98+
<section className="dialog-options">
99+
<button type="button" className="global-button" onClick={onClose}>
100+
Cancel
101+
</button>
102+
<button
103+
type="submit"
104+
className="global-button primary"
105+
disabled={sending}
106+
>
107+
Create
108+
</button>
109+
<Alert message={error} />
110+
</section>
111+
</form>
112+
)}
113+
</Popup>
114+
);
115+
},
116+
);

frontend/src/components/retro-settings/SettingsForm.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ form.retro-settings {
3434
}
3535
}
3636

37+
.retro-id-display {
38+
user-select: all;
39+
}
40+
3741
.form-actions {
3842
margin: 0 -16px;
3943
padding: 16px;

frontend/src/components/retro-settings/SettingsForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ export const SettingsForm = memo(
146146
.
147147
</p>
148148
<p>
149-
This retro&rsquo;s ID is: <code>{retro.id}</code>
149+
This retro&rsquo;s ID is:{' '}
150+
<code className="retro-id-display">{retro.id}</code>
150151
</p>
151152
<APIKeyManager retro={retro} retroAuth={retroAuth} />
152153
</div>

0 commit comments

Comments
 (0)