diff --git a/frontend/src/components/CommsChannelPicker.svelte b/frontend/src/components/CommsChannelPicker.svelte
new file mode 100644
index 0000000..0bc6f06
--- /dev/null
+++ b/frontend/src/components/CommsChannelPicker.svelte
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+{#if channel === 'email'}
+
+
+ onEmailChange((e.target as HTMLInputElement).value)}
+ placeholder={$_('register.emailPlaceholder')}
+ {disabled}
+ required
+ />
+
+{:else if channel === 'discord'}
+
+
+
onDiscordChange((e.target as HTMLInputElement).value)}
+ onblur={() => onCheckInUse?.('discord', discordUsername)}
+ placeholder={$_('register.discordUsernamePlaceholder')}
+ {disabled}
+ required
+ />
+ {#if discordInUse}
+
{$_('register.discordInUseWarning')}
+ {/if}
+
+{:else if channel === 'telegram'}
+
+
+
onTelegramChange((e.target as HTMLInputElement).value)}
+ onblur={() => onCheckInUse?.('telegram', telegramUsername)}
+ placeholder={$_('register.telegramUsernamePlaceholder')}
+ {disabled}
+ required
+ />
+ {#if telegramInUse}
+
{$_('register.telegramInUseWarning')}
+ {/if}
+
+{:else if channel === 'signal'}
+
+
+
onSignalChange((e.target as HTMLInputElement).value)}
+ onblur={() => onCheckInUse?.('signal', signalUsername)}
+ placeholder={$_('register.signalUsernamePlaceholder')}
+ {disabled}
+ required
+ />
+
{$_('register.signalUsernameHint')}
+ {#if signalInUse}
+
{$_('register.signalInUseWarning')}
+ {/if}
+
+{/if}
diff --git a/frontend/src/components/HandleInput.svelte b/frontend/src/components/HandleInput.svelte
index 98d359a..eb1f077 100644
--- a/frontend/src/components/HandleInput.svelte
+++ b/frontend/src/components/HandleInput.svelte
@@ -7,6 +7,9 @@
placeholder?: string
id?: string
autocomplete?: HTMLInputElement['autocomplete']
+ checkAvailability?: (fullHandle: string) => Promise
+ available?: boolean | null
+ checking?: boolean
onInput: (value: string) => void
onDomainChange: (domain: string) => void
}
@@ -19,11 +22,42 @@
placeholder = 'username',
id = 'handle',
autocomplete = 'off',
+ checkAvailability,
+ available = $bindable(null),
+ checking = $bindable(false),
onInput,
onDomainChange,
}: Props = $props()
const showDomainSelect = $derived(domains.length > 1 && !value.includes('.'))
+
+ let checkTimeout: ReturnType | null = null
+
+ $effect(() => {
+ void value
+ void selectedDomain
+ if (!checkAvailability) return
+ if (checkTimeout) clearTimeout(checkTimeout)
+ available = null
+ if (value.trim().length >= 3 && !value.includes('.')) {
+ checkTimeout = setTimeout(() => runCheck(), 400)
+ }
+ })
+
+ async function runCheck() {
+ if (!checkAvailability) return
+ const fullHandle = value.includes('.')
+ ? value.trim()
+ : `${value.trim()}.${selectedDomain}`
+ checking = true
+ try {
+ available = await checkAvailability(fullHandle)
+ } catch {
+ available = null
+ } finally {
+ checking = false
+ }
+ }