<template> <main class="container page-padding"> <div v-if="loading" class="text-center"> <div class="spinner-dots" role="status"><span/><span/><span/></div> <p>Loading group details...</p> </div> <div v-else-if="error" class="alert alert-error" role="alert"> <div class="alert-content"> <svg class="icon" aria-hidden="true"><use xlink:href="#icon-alert-triangle" /></svg> {{ error }} </div> </div> <div v-else-if="group"> <h1 class="mb-3">Group: {{ group.name }}</h1> <!-- Invite Members Section --> <div class="card mt-3"> <div class="card-header"> <h3>Invite Members</h3> </div> <div class="card-body"> <button class="btn btn-secondary" @click="generateInviteCode" :disabled="generatingInvite" > <span v-if="generatingInvite" class="spinner-dots-sm" role="status"><span/><span/><span/></span> Generate Invite Code </button> <div v-if="inviteCode" class="form-group mt-2"> <label for="inviteCodeInput" class="form-label">Invite Code:</label> <div class="flex items-center"> <input id="inviteCodeInput" type="text" :value="inviteCode" class="form-input flex-grow" readonly /> <button class="btn btn-neutral btn-icon-only ml-1" @click="copyInviteCodeHandler" aria-label="Copy invite code"> <svg class="icon"><use xlink:href="#icon-clipboard"></use></svg> <!-- Assuming #icon-clipboard is 'content_copy' --> </button> </div> <p v-if="copySuccess" class="form-success-text mt-1">Invite code copied to clipboard!</p> </div> </div> </div> <!-- Placeholder for lists related to this group --> <div class="mt-4"> <h2>Lists in this Group</h2> <ListsPage :group-id="groupId" /> </div> </div> <div v-else class="alert alert-info" role="status"> <div class="alert-content">Group not found or an error occurred.</div> </div> </main> </template> <script setup lang="ts"> import { ref, onMounted, computed } from 'vue'; // import { useRoute } from 'vue-router'; import { apiClient, API_ENDPOINTS } from '@/config/api'; // Assuming path import { useClipboard } from '@vueuse/core'; import ListsPage from './ListsPage.vue'; // Import ListsPage import { useNotificationStore } from '@/stores/notifications'; interface Group { id: string | number; // API might return number name: string; // other properties if needed } const props = defineProps<{ id: string; // From router param, always string }>(); // const route = useRoute(); // const $q = useQuasar(); // Not used anymore const notificationStore = useNotificationStore(); const group = ref<Group | null>(null); const loading = ref(true); const error = ref<string | null>(null); const inviteCode = ref<string | null>(null); const generatingInvite = ref(false); const copySuccess = ref(false); // groupId is directly from props.id now, which comes from the route path param const groupId = computed(() => props.id); const { copy, copied, isSupported: clipboardIsSupported } = useClipboard({ source: inviteCode }); const fetchGroupDetails = async () => { if (!groupId.value) return; loading.value = true; error.value = null; try { const response = await apiClient.get(API_ENDPOINTS.GROUPS.BY_ID(String(groupId.value))); group.value = response.data; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to fetch group details.'; error.value = message; console.error('Error fetching group details:', err); notificationStore.addNotification({ message, type: 'error' }); } finally { loading.value = false; } }; const generateInviteCode = async () => { if (!groupId.value) return; generatingInvite.value = true; inviteCode.value = null; copySuccess.value = false; try { const response = await apiClient.post(API_ENDPOINTS.INVITES.BASE, { group_id: groupId.value, // Ensure this matches API expectation (string or number) }); inviteCode.value = response.data.invite_code; notificationStore.addNotification({ message: 'Invite code generated successfully!', type: 'success' }); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to generate invite code.'; console.error('Error generating invite code:', err); notificationStore.addNotification({ message, type: 'error' }); } finally { generatingInvite.value = false; } }; const copyInviteCodeHandler = async () => { if (!clipboardIsSupported.value || !inviteCode.value) { notificationStore.addNotification({ message: 'Clipboard not supported or no code to copy.', type: 'warning' }); return; } await copy(inviteCode.value); if (copied.value) { copySuccess.value = true; setTimeout(() => (copySuccess.value = false), 2000); // Optionally, notify success via store if preferred over inline message // notificationStore.addNotification({ message: 'Invite code copied!', type: 'info' }); } else { notificationStore.addNotification({ message: 'Failed to copy invite code.', type: 'error' }); } }; onMounted(() => { fetchGroupDetails(); }); </script> <style scoped> .page-padding { padding: 1rem; } .mt-1 { margin-top: 0.5rem; } .mt-2 { margin-top: 1rem; } .mt-3 { margin-top: 1.5rem; } .mt-4 { margin-top: 2rem; } .mb-3 { margin-bottom: 1.5rem; } .ml-1 { margin-left: 0.25rem; } /* Adjusted from Valerie UI for tighter fit */ .form-success-text { color: var(--success); /* Or a darker green for text */ font-size: 0.9rem; font-weight: bold; } .flex-grow { flex-grow: 1; } </style>