From 63a68296a56d5e4b61da9cf2277daa7dc357d7c9 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Fri, 29 May 2026 08:12:36 +0200 Subject: [PATCH] feat(05-07): 3-tab SettingsView, SettingsCloudTab, CloudCredentialModal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Convert SettingsView to 3-tab layout (Preferences/AI/Cloud) matching AdminView pattern - Extract SettingsPreferencesTab.vue and SettingsAiTab.vue from original SettingsView - Create SettingsCloudTab.vue with all 4 providers, status badges, action buttons - Create CloudCredentialModal.vue for WebDAV/Nextcloud credential input - Handle OAuth callback query params (cloud_connected/cloud_error) in SettingsView.onMounted - Add success toast (auto-dismiss 5s) and persistent error banner for OAuth results - Fix pre-existing build failure: add build.target=esnext to vite.config.js for top-level await support - 2 SettingsCloudTab mount tests passing (W4 — CLAUDE.md) --- .../components/cloud/CloudCredentialModal.vue | 195 +++++++++++++ .../src/components/settings/SettingsAiTab.vue | 9 + .../components/settings/SettingsCloudTab.vue | 260 ++++++++++++++++++ .../settings/SettingsPreferencesTab.vue | 65 +++++ .../__tests__/SettingsCloudTab.test.js | 59 ++++ frontend/src/views/SettingsView.vue | 175 ++++++++---- frontend/vite.config.js | 4 + 7 files changed, 706 insertions(+), 61 deletions(-) create mode 100644 frontend/src/components/cloud/CloudCredentialModal.vue create mode 100644 frontend/src/components/settings/SettingsAiTab.vue create mode 100644 frontend/src/components/settings/SettingsCloudTab.vue create mode 100644 frontend/src/components/settings/SettingsPreferencesTab.vue create mode 100644 frontend/src/components/settings/__tests__/SettingsCloudTab.test.js diff --git a/frontend/src/components/cloud/CloudCredentialModal.vue b/frontend/src/components/cloud/CloudCredentialModal.vue new file mode 100644 index 0000000..81f85b7 --- /dev/null +++ b/frontend/src/components/cloud/CloudCredentialModal.vue @@ -0,0 +1,195 @@ + + + diff --git a/frontend/src/components/settings/SettingsAiTab.vue b/frontend/src/components/settings/SettingsAiTab.vue new file mode 100644 index 0000000..dcf04f6 --- /dev/null +++ b/frontend/src/components/settings/SettingsAiTab.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/components/settings/SettingsCloudTab.vue b/frontend/src/components/settings/SettingsCloudTab.vue new file mode 100644 index 0000000..a2d0cde --- /dev/null +++ b/frontend/src/components/settings/SettingsCloudTab.vue @@ -0,0 +1,260 @@ + + + diff --git a/frontend/src/components/settings/SettingsPreferencesTab.vue b/frontend/src/components/settings/SettingsPreferencesTab.vue new file mode 100644 index 0000000..f7b5f33 --- /dev/null +++ b/frontend/src/components/settings/SettingsPreferencesTab.vue @@ -0,0 +1,65 @@ + + + diff --git a/frontend/src/components/settings/__tests__/SettingsCloudTab.test.js b/frontend/src/components/settings/__tests__/SettingsCloudTab.test.js new file mode 100644 index 0000000..562cac1 --- /dev/null +++ b/frontend/src/components/settings/__tests__/SettingsCloudTab.test.js @@ -0,0 +1,59 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' + +// Mock store module before importing component (W4 — CLAUDE.md unit test requirement) +vi.mock('../../../stores/cloudConnections.js', () => ({ + useCloudConnectionsStore: () => ({ + connections: [], + loading: false, + error: null, + fetchConnections: vi.fn(), + disconnect: vi.fn(), + disconnectAll: vi.fn(), + }), +})) + +// Mock api/client.js to avoid HTTP calls +vi.mock('../../../api/client.js', () => ({ + connectWebDav: vi.fn(), + listCloudConnections: vi.fn(), + disconnectCloud: vi.fn(), +})) + +import SettingsCloudTab from '../SettingsCloudTab.vue' + +const globalPlugins = { + plugins: [createPinia()], + stubs: { + // Stub CloudCredentialModal to avoid portal/teleport complexity in tests + CloudCredentialModal: { + template: '
', + props: ['show', 'provider'], + }, + }, +} + +beforeEach(() => { + setActivePinia(createPinia()) + vi.clearAllMocks() +}) + +describe('SettingsCloudTab', () => { + it('renders all 4 provider rows', () => { + const wrapper = mount(SettingsCloudTab, { global: globalPlugins }) + expect(wrapper.text()).toContain('Google Drive') + expect(wrapper.text()).toContain('OneDrive') + expect(wrapper.text()).toContain('Nextcloud') + expect(wrapper.text()).toContain('WebDAV') + }) + + it('shows Connect buttons when no connections active', () => { + const wrapper = mount(SettingsCloudTab, { global: globalPlugins }) + const buttons = wrapper.findAll('button') + expect(buttons.length).toBeGreaterThan(0) + // At least some "Connect" buttons should be visible when no connections + const buttonTexts = buttons.map(b => b.text()).join(' ') + expect(buttonTexts).toContain('Connect') + }) +}) diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 4c54837..708082c 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -1,79 +1,132 @@ diff --git a/frontend/vite.config.js b/frontend/vite.config.js index ebd6662..7d0bf9e 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -3,6 +3,10 @@ import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], + build: { + // top-level await in main.js requires esnext target + target: 'esnext', + }, server: { host: '0.0.0.0', port: 5173,