feat(stage-tamagotchi): connection now has visible and copy btn

This commit is contained in:
Neko Ayaka
2026-05-15 17:54:30 +08:00
parent 3984677b01
commit 5c71f46f99
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { isStageTamagotchi } from '@proj-airi/stage-shared'
import { ConnectionSettings } from '@proj-airi/stage-ui/components'
import { Callout, FieldCheckbox, FieldInput, SelectTab } from '@proj-airi/ui'
import { Button, Callout, FieldCheckbox, FieldInput, Input, SelectTab } from '@proj-airi/ui'
import { refDebounced, useClipboard } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { computed, shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import ServerChannelQrCard from './server-channel-qr-card.vue'
@@ -34,6 +35,12 @@ const exposureMode = computed({
const showAdvancedHostname = computed(() => exposureMode.value === 'advanced')
const showDesktopServerControls = computed(() => isStageTamagotchi())
const authTokenInput = shallowRef(authToken.value)
const authTokenInputDebounced = refDebounced(authTokenInput, 500)
const authTokenVisible = shallowRef(false)
const { copied: authTokenCopied, copy: copyAuthToken, isSupported: isClipboardSupported } = useClipboard({ source: authTokenInput, legacy: true })
const authTokenInputType = computed(() => authTokenVisible.value ? 'text' : 'password')
const canCopyAuthToken = computed(() => isClipboardSupported.value && authTokenInput.value.length > 0)
const exposureModeOptions = computed(() => [
{
@@ -49,6 +56,16 @@ const exposureModeOptions = computed(() => [
value: 'advanced',
},
])
watch(authToken, (value) => {
if (value !== authTokenInput.value)
authTokenInput.value = value
})
watch(authTokenInputDebounced, (value) => {
if (value !== authToken.value)
authToken.value = value
})
</script>
<template>
@@ -94,14 +111,51 @@ const exposureModeOptions = computed(() => [
placeholder="192.168.1.25"
/>
<FieldInput
<div
v-if="showDesktopServerControls"
v-model="authToken"
type="password"
:label="t('settings.pages.connection.server-auth-token.label')"
:description="t('settings.pages.connection.server-auth-token.description')"
:placeholder="t('settings.pages.connection.server-auth-token.placeholder')"
/>
:class="['max-w-full']"
>
<label :class="['flex', 'flex-col', 'gap-4']">
<div>
<div :class="['flex', 'items-center', 'gap-1', 'text-sm', 'font-medium']">
{{ t('settings.pages.connection.server-auth-token.label') }}
</div>
<div :class="['text-xs', 'text-neutral-500', 'dark:text-neutral-400']" text-wrap>
{{ t('settings.pages.connection.server-auth-token.description') }}
</div>
</div>
<div :class="['flex', 'items-center', 'gap-2']">
<Input
v-model="authTokenInput"
:type="authTokenInputType"
:placeholder="t('settings.pages.connection.server-auth-token.placeholder')"
/>
<Button
type="button"
variant="secondary-muted"
size="sm"
shape="square"
:icon="authTokenVisible ? 'i-solar:eye-closed-bold-duotone' : 'i-solar:eye-bold-duotone'"
:aria-label="authTokenVisible ? 'Hide auth token' : 'Show auth token'"
:title="authTokenVisible ? 'Hide auth token' : 'Show auth token'"
data-testid="server-auth-token-visibility-toggle"
@click="authTokenVisible = !authTokenVisible"
/>
<Button
type="button"
variant="secondary-muted"
size="sm"
shape="square"
:icon="authTokenCopied ? 'i-solar:check-circle-bold-duotone' : 'i-solar:copy-line-duotone'"
:disabled="!canCopyAuthToken"
:aria-label="authTokenCopied ? 'Copied auth token' : 'Copy auth token'"
:title="authTokenCopied ? 'Copied auth token' : 'Copy auth token'"
data-testid="server-auth-token-copy"
@click="copyAuthToken()"
/>
</div>
</label>
</div>
<ServerChannelQrCard />
</template>