pp-planer/resources/js/Components/ConfirmDialog.vue
Thorsten Bus 57d54ec06b feat: Wave 1 Foundation - Database, OAuth, Sync, Files, Layout, Email (T2-T7)
T2: Database Schema + All Migrations
- 10 migrations: users extension, services, songs, song_groups, song_slides,
  song_arrangements, song_arrangement_groups, service_songs, slides, cts_sync_log
- 9 Eloquent models with relationships and casts
- 9 factory classes for testing
- Tests: DatabaseSchemaTest (2 tests, 26 assertions) 

T3: ChurchTools OAuth Provider
- Custom Socialite provider for ChurchTools OAuth2
- AuthController with redirect/callback/logout
- Replaced Breeze login with OAuth-only (German UI)
- Removed all Breeze register/password-reset pages
- Tests: OAuthTest (9 tests, 54 assertions) 

T4: CTS API Service + Sync Command
- ChurchToolsService wrapping 5pm-HDH/churchtools-api
- SyncChurchToolsCommand (php artisan cts:sync)
- SyncController for refresh button
- CCLI-based song matching
- Tests: ChurchToolsSyncTest (2 tests) 

T5: File Conversion Service
- FileConversionService with letterbox/pillarbox to 1920×1080
- ConvertPowerPointJob (queued) with LibreOffice + spatie/pdf-to-image
- ZIP extraction and recursive processing
- Thumbnail generation (320×180)
- Tests: FileConversionTest (2 tests, 21 assertions) 

T6: Shared Vue Components
- AuthenticatedLayout with nav, user info, refresh button
- useAutoSave composable (500ms debounce)
- FlashMessage, ConfirmDialog, LoadingSpinner components
- HandleInertiaRequests middleware with shared props
- Tests: SharedPropsTest (7 tests) 

T7: Email Configuration
- MissingSongRequest mailable (German)
- Email template with song info and service link
- SONG_REQUEST_EMAIL config
- Tests: MissingSongMailTest (2 tests, 10 assertions) 

All tests passing: 30/30 (233 assertions)
All UI text in German with 'Du' form
Wave 1 complete: 7/7 tasks 
2026-03-01 19:39:26 +01:00

102 lines
3.3 KiB
Vue

<script setup>
import Modal from '@/Components/Modal.vue'
const props = defineProps({
show: {
type: Boolean,
default: false,
},
title: {
type: String,
default: 'Bist du sicher?',
},
message: {
type: String,
default: '',
},
confirmLabel: {
type: String,
default: 'Bestätigen',
},
cancelLabel: {
type: String,
default: 'Abbrechen',
},
variant: {
type: String,
default: 'danger',
validator: (v) => ['danger', 'warning', 'info'].includes(v),
},
})
const emit = defineEmits(['confirm', 'cancel'])
const variantClasses = {
danger: 'bg-red-600 hover:bg-red-700 focus:ring-red-500',
warning: 'bg-amber-600 hover:bg-amber-700 focus:ring-amber-500',
info: 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-500',
}
const iconColors = {
danger: 'text-red-500',
warning: 'text-amber-500',
info: 'text-blue-500',
}
</script>
<template>
<Modal :show="show" max-width="md" @close="emit('cancel')">
<div class="p-6">
<div class="flex items-start gap-4">
<!-- Warn-Icon -->
<div
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-100"
>
<svg
class="h-6 w-6"
:class="iconColors[variant]"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="1.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
</div>
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900">
{{ title }}
</h3>
<p v-if="message" class="mt-1 text-sm text-gray-600">
{{ message }}
</p>
<slot />
</div>
</div>
<div class="mt-6 flex justify-end gap-3">
<button
type="button"
class="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2"
@click="emit('cancel')"
>
{{ cancelLabel }}
</button>
<button
type="button"
class="rounded-lg px-4 py-2 text-sm font-medium text-white shadow-sm transition focus:outline-none focus:ring-2 focus:ring-offset-2"
:class="variantClasses[variant]"
@click="emit('confirm')"
>
{{ confirmLabel }}
</button>
</div>
</div>
</Modal>
</template>