171 lines
5.8 KiB
Vue
171 lines
5.8 KiB
Vue
<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> |