Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions scaffold/spec-site/src/components/MemoSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useMemo } from '@/composables/useMemo'
import { useUser } from '@/composables/useUser'
import { useConfirm } from '@/composables/useConfirm'

const { showConfirm } = useConfirm()

const route = useRoute()
const pageId = computed(() => (route.params.pageId as string) || 'global')
Expand Down Expand Up @@ -31,8 +34,8 @@ async function handleAdd() {
newMemo.value = ''
}

function handleClearAll() {
if (confirm('Delete all memos?')) {
async function handleClearAll() {
if (await showConfirm('Delete all memos?')) {
memoStore.value.clearAll()
}
}
Expand Down
5 changes: 3 additions & 2 deletions scaffold/spec-site/src/components/SlashCommand.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Extension } from '@tiptap/core'
import Suggestion from '@tiptap/suggestion'
import { useConfirm } from '@/composables/useConfirm'

interface SlashItem {
title: string
Expand Down Expand Up @@ -30,9 +31,9 @@ const items: SlashItem[] = [
try {
const { apiPut } = await import('@/composables/useTurso')
const { error } = await apiPut(`/api/v2/docs/${slug}`, { title, content: '', contentFormat: 'markdown', parentId })
if (error) { alert(`Failed to create sub page: ${error}`); return }
if (error) { const { showAlert } = useConfirm(); await showAlert(`Failed to create sub page: ${error}`); return }
e.chain().focus().insertContent(`<p><a href="/docs/${slug}">${title}</a></p>`).run()
} catch (err) { alert(`Failed to create sub page: ${err}`) }
} catch (err) { const { showAlert } = useConfirm(); await showAlert(`Failed to create sub page: ${err}`) }
}},
]

Expand Down
9 changes: 6 additions & 3 deletions scaffold/spec-site/src/pages/AdminPage.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { apiGet, apiPost, apiPatch, apiDelete, apiPut } from '@/api/client'
import { useConfirm } from '@/composables/useConfirm'

const { showConfirm } = useConfirm()

interface MemberRow {
id: number
Expand Down Expand Up @@ -94,7 +97,7 @@ async function addMember() {
}

async function revokeToken(id: string, name: string) {
if (!confirm(`Revoke token for ${name}?`)) return
if (!await showConfirm(`Revoke token for ${name}?`)) return
const { error: apiError } = await apiPatch(`/api/v2/admin/members/${id}/revoke`, {})
if (!apiError) { statusMsg.value = `${name} token revoked`; await loadMembers() }
clearStatus()
Expand All @@ -107,15 +110,15 @@ async function reactivateToken(id: string, name: string) {
}

async function regenerateToken(oldToken: string, name: string) {
if (!confirm(`Regenerate token for ${name}? The old token will be invalidated.`)) return
if (!await showConfirm(`Regenerate token for ${name}? The old token will be invalidated.`)) return
const newToken = generateToken()
const { error: apiError } = await apiPost(`/api/v2/admin/members/${oldToken}/regenerate`, { newToken })
if (!apiError) { statusMsg.value = `${name} token regenerated`; await loadMembers() }
clearStatus()
}

async function deleteMember(id: string, name: string) {
if (!confirm(`Permanently delete ${name}? This cannot be undone.`)) return
if (!await showConfirm(`Permanently delete ${name}? This cannot be undone.`)) return
const { error: apiError } = await apiDelete(`/api/v2/admin/members/${id}`)
if (!apiError) { statusMsg.value = `${name} deleted`; await loadMembers() }
clearStatus()
Expand Down
14 changes: 8 additions & 6 deletions scaffold/spec-site/src/pages/DashboardPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { apiPatch, apiPost, apiGet } from '@/api/client'
import { getActiveSprint } from '@/composables/useNavStore'
import { useUser, TEAM_MEMBERS } from '@/composables/useUser'
import { Bell, Rocket, BarChart3, Sun, Zap, FileText, ClipboardList, ScrollText } from 'lucide-vue-next'
import { useConfirm } from '@/composables/useConfirm'

const memoTypeComponentMap: Record<string, any> = {
decision: Zap,
Expand All @@ -17,6 +18,7 @@ const memoTypeComponentMap: Record<string, any> = {

const router = useRouter()
const { currentUser, dynamicMembers, loadMembers } = useUser()
const { showConfirm, showAlert } = useConfirm()
const dashboard = useDashboard()

const sprint = computed(() => getActiveSprint().id)
Expand Down Expand Up @@ -72,24 +74,24 @@ async function handleInitiative(id: number, status: 'approved' | 'rejected') {

async function convertToEpic(item: any) {
const title = item.title || item.content?.split('\n')[0]?.slice(0, 100) || 'New Epic'
if (!confirm(`Create epic "${title}"?`)) return
if (!await showConfirm(`Create epic "${title}"?`)) return
const { error } = await apiPost('/api/v2/pm/epics', { title, description: item.content })
if (error) { alert(error); return }
if (error) { await showAlert(error); return }
await handleInitiative(item.id, 'approved' as any)
alert('Epic created')
await showAlert('Epic created')
}

async function convertToStory(item: any) {
const title = item.title || item.content?.split('\n')[0]?.slice(0, 100) || 'New Story'
if (!confirm(`Create story "${title}"?`)) return
if (!await showConfirm(`Create story "${title}"?`)) return
const { error } = await apiPost('/api/v2/pm/stories', {
title,
description: item.content,
status: 'backlog',
})
if (error) { alert(error); return }
if (error) { await showAlert(error); return }
await handleInitiative(item.id, 'approved' as any)
alert('Story created')
await showAlert('Story created')
}

async function resolveFromNudge(nudge: { title: string; body: string }) {
Expand Down
6 changes: 4 additions & 2 deletions scaffold/spec-site/src/pages/DocsEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { apiGet, apiPut } from '@/composables/useTurso'
import { useConfirm } from '@/composables/useConfirm'
import { renderMarkdown } from '@/utils/markdown'

const route = useRoute()
const router = useRouter()
const docId = computed(() => route.params.docId as string | undefined)
const isNew = computed(() => !docId.value)
const { showConfirm, showAlert } = useConfirm()

const title = ref('')
const content = ref('')
Expand All @@ -31,7 +33,7 @@ function generateId(title: string): string {
}

async function save() {
if (!title.value.trim()) { alert('Please enter a title'); return }
if (!title.value.trim()) { await showAlert('Please enter a title'); return }
saving.value = true
conflictError.value = ''
const id = docId.value || generateId(title.value)
Expand All @@ -46,7 +48,7 @@ async function save() {
conflictError.value = 'Someone else edited this document while you were working. Please refresh the page to get the latest version, then re-apply your changes.'
return
}
if (error) { alert(error); return }
if (error) { await showAlert(error); return }

// Update local version from server response
if (data?.version !== undefined) {
Expand Down
4 changes: 3 additions & 1 deletion scaffold/spec-site/src/pages/DocsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import Icon from '@/components/Icon.vue'
import { ref, onMounted, computed, nextTick, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { renderMarkdown } from '@/utils/markdown'
import { useConfirm } from '@/composables/useConfirm'
import DocsSidebar from '@/components/DocsSidebar.vue'
import DocEditor from '@/components/DocEditor.vue'
import DocComments from '@/components/DocComments.vue'

const route = useRoute()
const docId = computed(() => route.params.docId as string)
const { showConfirm } = useConfirm()
const content = ref('')
const title = ref('')
const author = ref('')
Expand Down Expand Up @@ -106,7 +108,7 @@ async function previewRev(revId: number) {
}

async function restoreRev(revId: number) {
if (!confirm('Restore this version?')) return
if (!await showConfirm('Restore this version?')) return
const { apiPost: ap } = await import('@/composables/useTurso')
await ap(`/api/v2/docs/${docId.value}/revisions/restore/${revId}`, {})
location.reload()
Expand Down
17 changes: 10 additions & 7 deletions scaffold/spec-site/src/pages/MeetingsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import { ref, onMounted } from 'vue'
import { apiGet, apiPost, apiPatch, isStaticMode } from '@/api/client'
import MemberSelect from '@/components/MemberSelect.vue'
import { useConfirm } from '@/composables/useConfirm'

const { showAlert } = useConfirm()

interface Meeting { id: number; title: string; date: string; participants: string | null; created_by: string }

Expand Down Expand Up @@ -65,7 +68,7 @@ async function uploadAudio(e: Event, meetingId: number) {
const input = e.target as HTMLInputElement
const file = input.files?.[0]
if (!file) return
if (file.size > 25 * 1024 * 1024) { alert('File size exceeds 25MB limit'); return }
if (file.size > 25 * 1024 * 1024) { await showAlert('File size exceeds 25MB limit'); return }

uploading.value = true
const formData = new FormData()
Expand All @@ -82,18 +85,18 @@ async function uploadAudio(e: Event, meetingId: number) {
uploading.value = false
input.value = ''

if (data.error) { alert(data.error); return }
alert('Transcription complete')
if (data.error) { await showAlert(data.error); return }
await showAlert('Transcription complete')
await viewMeeting(meetingId)
}

async function structurize(id: number) {
if (!selectedMeeting.value?.raw_transcript) { alert('No transcript available'); return }
if (!selectedMeeting.value?.raw_transcript) { await showAlert('No transcript available'); return }

const { data: settingsData } = await apiGet<{ settings: Record<string, string> }>('/api/v2/admin/settings')
const settings = settingsData?.settings ?? {}
const apiKey = settings.llm_api_key
if (!apiKey) { alert('Please set an API key in /admin settings'); return }
if (!apiKey) { await showAlert('Please set an API key in /admin settings'); return }

const provider = settings.llm_provider ?? (apiKey.startsWith('sk-ant') ? 'anthropic' : apiKey.startsWith('AI') ? 'gemini' : 'openai')
const model = settings.llm_model ?? (provider === 'openai' ? 'gpt-4o-mini' : provider === 'gemini' ? 'gemini-2.0-flash' : 'claude-sonnet-4-20250514')
Expand Down Expand Up @@ -164,13 +167,13 @@ Return only JSON.`
await saveMeetingEdits()
await viewMeeting(id)
} catch (e) {
alert(`AI structuring failed: ${String(e)}`)
await showAlert(`AI structuring failed: ${String(e)}`)
} finally { structurizing.value = false }
}

async function createTasks(id: number) {
const { data } = await apiPost(`/api/v2/meetings/${id}/create-tasks`, {})
if (data) alert(`${(data as any).created} tasks created`)
if (data) await showAlert(`${(data as any).created} tasks created`)
}

onMounted(loadMeetings)
Expand Down
16 changes: 9 additions & 7 deletions scaffold/spec-site/src/pages/MemosPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import MemoTimeline from '@/components/MemoTimeline.vue'
import MemoChecklist from '@/components/MemoChecklist.vue'
import MemoGraph from '@/components/MemoGraph.vue'
import { useAuth } from '@/composables/useAuth'
import { useConfirm } from '@/composables/useConfirm'

const { authUser } = useAuth()
const { showConfirm, showAlert } = useConfirm()

interface Memo {
id: number
Expand Down Expand Up @@ -265,7 +267,7 @@ async function createDocFromMemo() {
if (!selectedMemo.value) return
const { data } = await apiPost(`/api/v2/memos/${selectedMemo.value.id}/create-doc`, {})
if ((data as any)?.docId) {
alert('Document has been created.')
await showAlert('Document has been created.')
await loadLinkedDocs(selectedMemo.value.id)
}
}
Expand Down Expand Up @@ -339,7 +341,7 @@ function highlightMentions(text: string): string {
}

async function deleteReply(replyId: number) {
if (!confirm('Delete this reply?')) return
if (!await showConfirm('Delete this reply?')) return
const { apiDelete } = await import('@/composables/useTurso')
await apiDelete(`/api/v2/memos/replies/${replyId}`)
await loadMemos()
Expand All @@ -366,14 +368,14 @@ async function reopenMemo() {
async function convertToStory() {
if (!selectedMemo.value) return
const title = selectedMemo.value.content.split('\n')[0].slice(0, 100)
const ok = confirm(`Create story "${title}"?`)
const ok = await showConfirm(`Create story "${title}"?`)
if (!ok) return
const { error } = await apiPost('/api/v2/pm/stories', {
title,
description: selectedMemo.value.content,
status: 'backlog',
})
if (error) { alert(error); return }
if (error) { await showAlert(error); return }
// resolve memo
await apiPatch(`/api/v2/memos/${selectedMemo.value.id}/resolve`, {})
await loadMemos()
Expand All @@ -382,14 +384,14 @@ async function convertToStory() {
async function convertToInitiative() {
if (!selectedMemo.value) return
const title = selectedMemo.value.content.split('\n')[0].slice(0, 100)
const ok = confirm(`Create initiative "${title}"?`)
const ok = await showConfirm(`Create initiative "${title}"?`)
if (!ok) return
const { error } = await apiPost('/api/v2/initiatives', {
title,
content: selectedMemo.value.content,
source_context: `Memo #${selectedMemo.value.id}`,
})
if (error) { alert(error); return }
if (error) { await showAlert(error); return }
await apiPatch(`/api/v2/memos/${selectedMemo.value.id}/resolve`, {})
await loadMemos()
}
Expand All @@ -402,7 +404,7 @@ async function loadMembers() {
async function createMemo() {
if (!newMemoContent.value.trim()) return
if (!newMemoAssignees.value.length) {
if (!confirm('No recipients specified. Post anyway?')) return
if (!await showConfirm('No recipients specified. Post anyway?')) return
}
await apiPost('/api/v2/memos', {
pageId: 'general',
Expand Down
6 changes: 4 additions & 2 deletions scaffold/spec-site/src/pages/MockupEditorPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Icon from '@/components/Icon.vue'
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { apiGet, apiPut } from '@/composables/useTurso'
import { useConfirm } from '@/composables/useConfirm'
import ComponentPalette from '@/mockup/ComponentPalette.vue'
import MockupCanvas from '@/mockup/MockupCanvas.vue'
import PropertyPanel from '@/mockup/PropertyPanel.vue'
Expand All @@ -11,6 +12,7 @@ import { useScenarios } from '@/mockup/useScenarios'

const route = useRoute()
const slug = computed(() => route.params.slug as string)
const { showConfirm } = useConfirm()

interface CanvasComponent {
id: string; componentType: string; props: Record<string, unknown>; children: CanvasComponent[]
Expand Down Expand Up @@ -343,8 +345,8 @@ function redo() {
}
}

function onDelete(id: string) {
if (!confirm('Are you sure you want to delete this?')) return
async function onDelete(id: string) {
if (!await showConfirm('Are you sure you want to delete this?')) return
saveUndo()
components.value = removeComponent(components.value, id)
if (selectedId.value === id) selectedId.value = null
Expand Down
4 changes: 3 additions & 1 deletion scaffold/spec-site/src/pages/MockupListPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import Icon from '@/components/Icon.vue'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { apiGet, apiPost, apiDelete } from '@/composables/useTurso'
import { useConfirm } from '@/composables/useConfirm'

const router = useRouter()
const { showConfirm } = useConfirm()

interface MockupPage {
id: number; slug: string; title: string; category: string
Expand All @@ -21,7 +23,7 @@ const filteredMockups = computed(() => {
const loading = ref(true)

async function deleteMockup(slug: string) {
if (!confirm('Are you sure you want to delete this?')) return
if (!await showConfirm('Are you sure you want to delete this?')) return
await apiDelete(`/api/v2/mockups/${slug}`)
await loadMockups()
}
Expand Down
6 changes: 4 additions & 2 deletions scaffold/spec-site/src/pages/MockupViewerPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { apiGet } from '@/composables/useTurso'
import { useConfirm } from '@/composables/useConfirm'
import SplitPaneLayout from '@/layouts/SplitPaneLayout.vue'
import MockupShell from '@/components/MockupShell.vue'
import MockupCanvas from '@/mockup/MockupCanvas.vue'
Expand All @@ -13,6 +14,7 @@ import { useScenarios } from '@/mockup/useScenarios'

const { setMode } = provideViewport()
const { activeSection } = provideActiveSection()
const { showAlert } = useConfirm()

const route = useRoute()
const router = useRouter()
Expand Down Expand Up @@ -83,10 +85,10 @@ function onSelect(id: string) {
selectedId.value = id
}

function copySpec() {
async function copySpec() {
if (specDescription.value) {
navigator.clipboard.writeText(specDescription.value)
alert('Spec copied')
await showAlert('Spec copied')
}
}

Expand Down
Loading
Loading