Skip to content

Commit 8c0ef93

Browse files
committed
feat: add option to link sessions
1 parent 17ba950 commit 8c0ef93

File tree

6 files changed

+133
-22
lines changed

6 files changed

+133
-22
lines changed

src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const App = () => {
3535
1700: 'Closing & Retro',
3636
1730: 'Dinner Break',
3737
1900: 'Evening Activities',
38+
1946: `You can add links, too!|${import.meta.env.PUBLIC_HOMEPAGE}`,
3839
},
3940
hidePastSessions: false,
4041
}
@@ -118,7 +119,9 @@ export const App = () => {
118119
onAdd={(add) => {
119120
updateSessions((sessions) => ({
120121
...sessions,
121-
[parseInt(`${add.hour}${add.minute}`, 10)]: add.name,
122+
[parseInt(`${add.hour}${add.minute}`, 10)]: `${add.name}${
123+
add.url === '' ? '' : `|${add.url.toString()}`
124+
}`,
122125
}))
123126
}}
124127
onDelete={(time) => {

src/Editor.tsx

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AddIcon, DeleteIcon } from 'app/FeatherIcons'
22
import formStyles from 'app/Form.module.css'
3+
import { SessionName } from 'app/SessionName'
34
import tableStyles from 'app/Table.module.css'
45
import { formatEventTime, toEventTime } from 'app/time'
56
import { useRef, useState } from 'react'
@@ -8,6 +9,7 @@ type AddSession = {
89
name: string
910
hour: string
1011
minute: string
12+
url: string
1113
}
1214

1315
const toNumber = (n: string) => {
@@ -18,6 +20,9 @@ const toNumber = (n: string) => {
1820
}
1921
}
2022

23+
const urlRegex =
24+
/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/
25+
2126
export const Editor = ({
2227
conferenceDate,
2328
sessions,
@@ -34,19 +39,22 @@ export const Editor = ({
3439
name: '',
3540
hour: '19',
3641
minute: '45',
42+
url: '',
3743
})
3844
const inputRef = useRef<HTMLInputElement>(null)
3945
const isInputValid = () => {
4046
const hour = toNumber(add.hour)
4147
const minute = toNumber(add.minute)
48+
const urlIsValid = add.url !== '' ? urlRegex.test(add.url) : true
4249
return (
4350
add.name.length > 0 &&
4451
hour !== null &&
4552
hour >= 0 &&
4653
hour <= 23 &&
4754
minute !== null &&
4855
minute >= 0 &&
49-
minute <= 59
56+
minute <= 59 &&
57+
urlIsValid
5058
)
5159
}
5260

@@ -58,6 +66,7 @@ export const Editor = ({
5866
updateAdd({
5967
...add,
6068
name: '',
69+
url: '',
6170
})
6271
inputRef.current?.focus()
6372
}
@@ -114,25 +123,58 @@ export const Editor = ({
114123
/>
115124
</td>
116125
<td>
117-
<input
118-
className={formStyles.Input}
119-
type="text"
120-
value={add.name}
121-
onKeyUp={({ key }) => {
122-
if (key === 'Enter') {
123-
if (isInputValid()) {
124-
addAction(add)
125-
onAdd(add)
126+
<form className={formStyles.Form}>
127+
<fieldset>
128+
<label htmlFor="name">Session name</label>
129+
<input
130+
className={formStyles.TextInput}
131+
type="text"
132+
value={add.name}
133+
id={'name'}
134+
onKeyUp={({ key }) => {
135+
if (key === 'Enter') {
136+
if (isInputValid()) {
137+
addAction(add)
138+
onAdd(add)
139+
}
140+
}
141+
}}
142+
onChange={({ target: { value } }) =>
143+
updateAdd({
144+
...add,
145+
name: value,
146+
})
126147
}
127-
}
128-
}}
129-
onChange={({ target: { value } }) =>
130-
updateAdd({
131-
...add,
132-
name: value,
133-
})
134-
}
135-
/>
148+
placeholder='e.g. "Intro Session"'
149+
/>
150+
</fieldset>
151+
<fieldset>
152+
<label htmlFor="url">
153+
Optional: URL to use as a hyperlink.
154+
</label>
155+
<input
156+
className={formStyles.TextInput}
157+
type="url"
158+
id="url"
159+
value={add.url ?? ''}
160+
onChange={({ target: { value } }) =>
161+
updateAdd({
162+
...add,
163+
url: value,
164+
})
165+
}
166+
onKeyUp={({ key }) => {
167+
if (key === 'Enter') {
168+
if (isInputValid()) {
169+
addAction(add)
170+
onAdd(add)
171+
}
172+
}
173+
}}
174+
placeholder='e.g. "https://example.com/"'
175+
/>
176+
</fieldset>
177+
</form>
136178
</td>
137179
</tr>
138180
{Object.entries(sessions).map(([time, name]) => (
@@ -150,7 +192,9 @@ export const Editor = ({
150192
<td className={'time'}>
151193
{formatEventTime(eventTime(time as unknown as number))}
152194
</td>
153-
<td>{name}</td>
195+
<td>
196+
<SessionName name={name} />
197+
</td>
154198
</tr>
155199
))}
156200
</tbody>

src/Form.module.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
}
4141
}
4242

43+
.TextInput {
44+
composes: Input;
45+
width: calc(100% - 1rem);
46+
}
47+
4348
.DateEditor {
4449
display: flex;
4550
flex-direction: column;
@@ -51,3 +56,17 @@
5156
flex-direction: row;
5257
}
5358
}
59+
60+
.Form label {
61+
width: 100%;
62+
display: block;
63+
margin-bottom: 0.5rem;
64+
}
65+
.Form fieldset {
66+
border: 0;
67+
margin: 0;
68+
padding: 0;
69+
}
70+
.Form fieldset + fieldset {
71+
margin-top: 1rem;
72+
}

src/Schedule.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SessionName } from 'app/SessionName'
12
import tableStyles from 'app/Table.module.css'
23
import {
34
formatEventTime,
@@ -116,7 +117,9 @@ export const Schedule = ({
116117
conferenceDate={userTime(0)}
117118
startTime={userTime(time as unknown as number)}
118119
/>
119-
<td>{name}</td>
120+
<td>
121+
<SessionName name={name} />
122+
</td>
120123
</tr>
121124
))}
122125
</tbody>

src/SessionName.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const sessionLinkRegEx =
2+
/^(.+)\|(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*))$/
3+
4+
export const toURL = (url: string): URL | undefined => {
5+
try {
6+
return new URL(url)
7+
} catch {
8+
console.error(`Invalid URL: ${url}`)
9+
return undefined
10+
}
11+
}
12+
13+
export const formatSessionName = (
14+
name: string,
15+
): { sessionName: string; url?: URL } => {
16+
let sessionName = name
17+
let url: URL | undefined = undefined
18+
const matches = sessionLinkRegEx.exec(name)
19+
if (matches !== null) {
20+
sessionName = matches[1]
21+
url = toURL(matches[2])
22+
}
23+
return {
24+
sessionName,
25+
url,
26+
}
27+
}
28+
29+
export const SessionName = ({ name }: { name: string }) => {
30+
const { sessionName, url } = formatSessionName(name)
31+
if (url !== undefined)
32+
return (
33+
<a href={url.toString()} target="_blank" rel="noreferrer">
34+
{sessionName}
35+
</a>
36+
)
37+
return <>{sessionName}</>
38+
}

src/Table.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,7 @@
5353
.Table th small {
5454
opacity: 0.5;
5555
}
56+
57+
.Table a {
58+
color: var(--color-text);
59+
}

0 commit comments

Comments
 (0)