mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-06 01:17:46 +08:00
feat(providers): expand OAuth account management
Add Claude Code manual and cookie authorization, including redacted batch tasks. Harden OAuth imports, duplicate replacement, provider dialogs, and related account-management tests.
This commit is contained in:
@@ -10,52 +10,65 @@
|
||||
>
|
||||
|
||||
<div
|
||||
v-if="!showManualInput"
|
||||
class="rounded-xl border-2 border-dashed transition-colors cursor-pointer"
|
||||
:class="isDragging
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-muted-foreground/40'"
|
||||
@click="fileInputRef?.click()"
|
||||
@dragover.prevent="isDragging = true"
|
||||
@dragleave.prevent="isDragging = false"
|
||||
@drop.prevent="handleFileDrop"
|
||||
class="grid [&>*]:col-start-1 [&>*]:row-start-1"
|
||||
data-testid="json-import-mode-panels"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center py-10 gap-2">
|
||||
<div class="w-9 h-9 rounded-full bg-muted/60 flex items-center justify-center">
|
||||
<Upload class="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-xs font-medium">
|
||||
{{ localizedDropTitle }}
|
||||
</p>
|
||||
<p class="text-[11px] text-muted-foreground mt-0.5">
|
||||
{{ localizedDropHint }}
|
||||
</p>
|
||||
<div
|
||||
class="rounded-xl border-2 border-dashed transition-all duration-150 cursor-pointer"
|
||||
:class="[
|
||||
isDragging
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-border hover:border-muted-foreground/40',
|
||||
showManualInput ? 'opacity-0 pointer-events-none' : 'opacity-100',
|
||||
]"
|
||||
:inert="showManualInput ? '' : undefined"
|
||||
:aria-hidden="showManualInput"
|
||||
data-testid="json-import-file-panel"
|
||||
@click="fileInputRef?.click()"
|
||||
@dragover.prevent="isDragging = true"
|
||||
@dragleave.prevent="isDragging = false"
|
||||
@drop.prevent="handleFileDrop"
|
||||
>
|
||||
<div class="flex h-full flex-col items-center justify-center py-10 gap-2">
|
||||
<div class="w-9 h-9 rounded-full bg-muted/60 flex items-center justify-center">
|
||||
<Upload class="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-xs font-medium">
|
||||
{{ localizedDropTitle }}
|
||||
</p>
|
||||
<p class="text-[11px] text-muted-foreground mt-0.5">
|
||||
{{ localizedDropHint }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="space-y-1.5"
|
||||
>
|
||||
<Label v-if="manualLabel">
|
||||
{{ localizedManualLabel }}
|
||||
</Label>
|
||||
<Textarea
|
||||
:model-value="modelValue"
|
||||
:disabled="disabled"
|
||||
:placeholder="localizedManualPlaceholder"
|
||||
:class="textareaClass"
|
||||
spellcheck="false"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<p
|
||||
v-if="manualDescription"
|
||||
class="text-xs text-muted-foreground"
|
||||
<div
|
||||
class="space-y-1.5 transition-opacity duration-150"
|
||||
:class="showManualInput ? 'opacity-100' : 'opacity-0 pointer-events-none'"
|
||||
:inert="showManualInput ? undefined : ''"
|
||||
:aria-hidden="!showManualInput"
|
||||
data-testid="json-import-manual-panel"
|
||||
>
|
||||
{{ localizedManualDescription }}
|
||||
</p>
|
||||
<Label v-if="manualLabel">
|
||||
{{ localizedManualLabel }}
|
||||
</Label>
|
||||
<Textarea
|
||||
:model-value="modelValue"
|
||||
:disabled="disabled"
|
||||
:placeholder="localizedManualPlaceholder"
|
||||
:class="textareaClass"
|
||||
spellcheck="false"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<p
|
||||
v-if="manualDescription"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{ localizedManualDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center pt-1">
|
||||
@@ -63,6 +76,7 @@
|
||||
v-if="!showManualInput"
|
||||
type="button"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
data-testid="json-import-mode-toggle"
|
||||
@click="showManualInput = true"
|
||||
>
|
||||
{{ localizedPasteToggleText }}
|
||||
@@ -71,6 +85,7 @@
|
||||
v-else
|
||||
type="button"
|
||||
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
data-testid="json-import-mode-toggle"
|
||||
@click="switchToFileMode"
|
||||
>
|
||||
{{ localizedFileToggleText }}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createApp, defineComponent, h, nextTick, ref } from 'vue'
|
||||
|
||||
import JsonImportInput from '@/components/common/JsonImportInput.vue'
|
||||
import { createI18n } from '@/i18n'
|
||||
|
||||
describe('JsonImportInput', () => {
|
||||
it('keeps both mode panels in one layout track while switching modes', async () => {
|
||||
const value = ref('')
|
||||
const Host = defineComponent({
|
||||
setup() {
|
||||
return () => h(JsonImportInput, {
|
||||
modelValue: value.value,
|
||||
'onUpdate:modelValue': (nextValue: string) => {
|
||||
value.value = nextValue
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp(Host)
|
||||
app.use(createI18n())
|
||||
app.mount(root)
|
||||
|
||||
const panels = root.querySelector<HTMLElement>('[data-testid="json-import-mode-panels"]')
|
||||
const filePanel = root.querySelector<HTMLElement>('[data-testid="json-import-file-panel"]')
|
||||
const manualPanel = root.querySelector<HTMLElement>('[data-testid="json-import-manual-panel"]')
|
||||
expect(panels?.classList.contains('grid')).toBe(true)
|
||||
expect(filePanel?.getAttribute('aria-hidden')).toBe('false')
|
||||
expect(filePanel?.hasAttribute('inert')).toBe(false)
|
||||
expect(manualPanel?.getAttribute('aria-hidden')).toBe('true')
|
||||
expect(manualPanel?.getAttribute('inert')).toBe('')
|
||||
|
||||
root.querySelector<HTMLButtonElement>('[data-testid="json-import-mode-toggle"]')?.click()
|
||||
await nextTick()
|
||||
|
||||
expect(root.querySelector('[data-testid="json-import-file-panel"]')).toBe(filePanel)
|
||||
expect(root.querySelector('[data-testid="json-import-manual-panel"]')).toBe(manualPanel)
|
||||
expect(filePanel?.getAttribute('aria-hidden')).toBe('true')
|
||||
expect(filePanel?.getAttribute('inert')).toBe('')
|
||||
expect(manualPanel?.getAttribute('aria-hidden')).toBe('false')
|
||||
expect(manualPanel?.hasAttribute('inert')).toBe(false)
|
||||
|
||||
const textarea = root.querySelector<HTMLTextAreaElement>('textarea')
|
||||
if (!textarea) throw new Error('Expected manual textarea')
|
||||
textarea.value = 'credential-value'
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
await nextTick()
|
||||
expect(value.value).toBe('credential-value')
|
||||
|
||||
root.querySelector<HTMLButtonElement>('[data-testid="json-import-mode-toggle"]')?.click()
|
||||
await nextTick()
|
||||
expect(value.value).toBe('')
|
||||
expect(filePanel?.hasAttribute('inert')).toBe(false)
|
||||
expect(manualPanel?.getAttribute('inert')).toBe('')
|
||||
|
||||
app.unmount()
|
||||
root.remove()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user