Add peer support functionality and UI
This commit is contained in:
parent
2b55ca4104
commit
1b8dacf92a
11 changed files with 1159 additions and 35 deletions
366
app/pages/member/settings/peer-support.vue
Normal file
366
app/pages/member/settings/peer-support.vue
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
<template>
|
||||
<div>
|
||||
<PageHeader
|
||||
title="Peer Support Settings"
|
||||
subtitle="Offer guidance and support to fellow Ghost Guild members"
|
||||
theme="purple"
|
||||
size="medium"
|
||||
/>
|
||||
|
||||
<section class="py-12">
|
||||
<UContainer>
|
||||
<div
|
||||
v-if="loading && !formData"
|
||||
class="flex justify-center items-center py-20"
|
||||
>
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="w-8 h-8 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||
></div>
|
||||
<p class="text-stone-400">Loading settings...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="max-w-3xl">
|
||||
<!-- Info Box -->
|
||||
<div
|
||||
class="mb-8 backdrop-blur-sm bg-purple-500/10 border border-purple-500/30 rounded-lg p-6"
|
||||
>
|
||||
<h3 class="text-lg font-semibold text-purple-200 mb-2">
|
||||
About Peer Support
|
||||
</h3>
|
||||
<p class="text-stone-300 text-sm leading-relaxed">
|
||||
Peer support is a way to share your knowledge and experience with
|
||||
fellow members. When enabled, you'll appear in the
|
||||
<NuxtLink
|
||||
to="/peer-support"
|
||||
class="text-purple-400 hover:text-purple-300 underline"
|
||||
>
|
||||
Peer Support directory
|
||||
</NuxtLink>
|
||||
where members can reach out to you for guidance on topics you're
|
||||
comfortable discussing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<UForm :state="formData" @submit="handleSubmit" class="space-y-8">
|
||||
<!-- Enable Toggle -->
|
||||
<UFormField
|
||||
label="Offer Peer Support"
|
||||
name="enabled"
|
||||
description="Make yourself available to support other members"
|
||||
>
|
||||
<USwitch v-model="formData.enabled" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<!-- Conditional Fields -->
|
||||
<div
|
||||
v-if="formData.enabled"
|
||||
class="space-y-6 pl-4 border-l-2 border-purple-500/30"
|
||||
>
|
||||
<!-- Topics -->
|
||||
<UFormField
|
||||
label="Topics You Can Help With"
|
||||
name="topics"
|
||||
description="Select all topics where you can offer guidance"
|
||||
required
|
||||
>
|
||||
<div class="space-y-2 mt-2">
|
||||
<label
|
||||
v-for="topic in availableTopics"
|
||||
:key="topic"
|
||||
class="flex items-center gap-3 p-3 rounded-lg border border-stone-700 hover:border-purple-500/50 transition-colors cursor-pointer"
|
||||
:class="
|
||||
formData.topics.includes(topic)
|
||||
? 'bg-purple-500/10 border-purple-500/50'
|
||||
: 'bg-stone-900/50'
|
||||
"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="topic"
|
||||
v-model="formData.topics"
|
||||
class="rounded border-stone-600 text-purple-500 focus:ring-purple-500 focus:ring-offset-0 bg-stone-800"
|
||||
/>
|
||||
<span class="text-stone-200">{{ topic }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</UFormField>
|
||||
|
||||
<!-- Availability -->
|
||||
<UFormField
|
||||
label="Your Availability"
|
||||
name="availability"
|
||||
description="When are you generally available for peer support sessions?"
|
||||
required
|
||||
>
|
||||
<UTextarea
|
||||
v-model="formData.availability"
|
||||
placeholder="e.g., Weekday evenings (6-9pm EST), weekends flexible, or by appointment"
|
||||
rows="3"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<!-- Personal Message -->
|
||||
<UFormField
|
||||
label="Personal Message to Potential Chatters"
|
||||
name="personalMessage"
|
||||
description="A friendly message to encourage people to reach out"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="formData.personalMessage"
|
||||
placeholder="e.g., I love talking about this stuff! No question is too basic."
|
||||
rows="2"
|
||||
maxlength="200"
|
||||
/>
|
||||
<template #hint>
|
||||
<span class="text-xs text-stone-500">
|
||||
{{ formData.personalMessage?.length || 0 }}/200 characters
|
||||
</span>
|
||||
</template>
|
||||
</UFormField>
|
||||
|
||||
<!-- Slack Username -->
|
||||
<UFormField
|
||||
label="Slack Username"
|
||||
name="slackUsername"
|
||||
description="Your Slack username so members can message you (required)"
|
||||
required
|
||||
>
|
||||
<UInput
|
||||
v-model="formData.slackUsername"
|
||||
placeholder="@yourslackname"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<!-- Preview -->
|
||||
<div
|
||||
class="mt-8 p-6 bg-stone-900/50 border border-stone-700/50 rounded-lg"
|
||||
>
|
||||
<h3 class="text-sm font-semibold text-stone-400 mb-4">
|
||||
Preview: How you'll appear in the directory
|
||||
</h3>
|
||||
<div
|
||||
class="backdrop-blur-sm bg-stone-800/50 border border-stone-700/50 rounded-lg p-6"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div
|
||||
class="w-12 h-12 rounded-lg bg-stone-800 border border-stone-700 flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<span class="text-xl">👻</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-semibold text-stone-100">
|
||||
{{ memberData?.name || "Your Name" }}
|
||||
</h3>
|
||||
<span
|
||||
class="px-2 py-0.5 bg-purple-500/20 text-purple-300 rounded text-xs border border-purple-500/30 inline-block"
|
||||
>
|
||||
{{
|
||||
memberData?.circle
|
||||
? circleLabels[memberData.circle]
|
||||
: "Your Circle"
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="formData.topics.length > 0" class="mb-4">
|
||||
<div class="text-xs text-stone-500 mb-2">Topics:</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-for="topic in formData.topics"
|
||||
:key="topic"
|
||||
class="px-2 py-0.5 bg-stone-800/50 text-stone-300 rounded text-xs border border-stone-700"
|
||||
>
|
||||
{{ topic }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="formData.availability"
|
||||
class="mb-4 text-sm text-stone-400"
|
||||
>
|
||||
<div class="text-xs text-stone-500 mb-1">Availability:</div>
|
||||
{{ formData.availability }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="formData.personalMessage"
|
||||
class="mb-4 text-sm text-stone-300 italic"
|
||||
>
|
||||
"{{ formData.personalMessage }}"
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="formData.slackUsername"
|
||||
class="text-sm text-stone-400"
|
||||
>
|
||||
<div class="text-xs text-stone-500 mb-1">Slack:</div>
|
||||
{{ formData.slackUsername }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex gap-3 pt-6 border-t border-stone-700">
|
||||
<UButton
|
||||
type="submit"
|
||||
:loading="saving"
|
||||
:disabled="
|
||||
formData.enabled &&
|
||||
(!formData.topics.length ||
|
||||
!formData.availability ||
|
||||
!formData.slackUsername)
|
||||
"
|
||||
>
|
||||
Save Settings
|
||||
</UButton>
|
||||
<UButton variant="outline" @click="resetForm" :disabled="saving">
|
||||
Cancel
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div
|
||||
v-if="showSuccess"
|
||||
class="mt-6 p-4 bg-green-500/10 border border-green-500/30 rounded-lg text-green-300 text-sm"
|
||||
>
|
||||
✓ Peer support settings saved successfully!
|
||||
<NuxtLink
|
||||
to="/peer-support"
|
||||
class="underline hover:text-green-200 ml-2"
|
||||
>
|
||||
View the directory
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div
|
||||
v-if="error"
|
||||
class="mt-6 p-4 bg-red-500/10 border border-red-500/30 rounded-lg text-red-300 text-sm"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth",
|
||||
});
|
||||
|
||||
const { isAuthenticated, memberData, fetchMember } = useAuth();
|
||||
const { updateSettings } = usePeerSupport();
|
||||
|
||||
// State
|
||||
const loading = ref(true);
|
||||
const saving = ref(false);
|
||||
const showSuccess = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
// Available topics
|
||||
const availableTopics = [
|
||||
"Governance & decision-making",
|
||||
"Financial modeling",
|
||||
"Fundraising",
|
||||
"Team building",
|
||||
"Legal structures",
|
||||
"Marketing & outreach",
|
||||
"Conflict resolution",
|
||||
"General chat & support",
|
||||
];
|
||||
|
||||
// Circle labels
|
||||
const circleLabels = {
|
||||
community: "Community",
|
||||
founder: "Founder",
|
||||
practitioner: "Practitioner",
|
||||
};
|
||||
|
||||
// Form data
|
||||
const formData = ref({
|
||||
enabled: false,
|
||||
topics: [],
|
||||
availability: "",
|
||||
personalMessage: "",
|
||||
slackUsername: "",
|
||||
});
|
||||
|
||||
// Initialize form with existing data
|
||||
const initializeForm = () => {
|
||||
if (memberData.value?.peerSupport) {
|
||||
formData.value = {
|
||||
enabled: memberData.value.peerSupport.enabled || false,
|
||||
topics: memberData.value.peerSupport.topics || [],
|
||||
availability: memberData.value.peerSupport.availability || "",
|
||||
personalMessage: memberData.value.peerSupport.personalMessage || "",
|
||||
slackUsername: memberData.value.peerSupport.slackUsername || "",
|
||||
};
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
// Reset form
|
||||
const resetForm = () => {
|
||||
initializeForm();
|
||||
showSuccess.value = false;
|
||||
error.value = null;
|
||||
};
|
||||
|
||||
// Handle form submission
|
||||
const handleSubmit = async () => {
|
||||
saving.value = true;
|
||||
error.value = null;
|
||||
showSuccess.value = false;
|
||||
|
||||
try {
|
||||
await updateSettings({
|
||||
enabled: formData.value.enabled,
|
||||
topics: formData.value.topics,
|
||||
availability: formData.value.availability,
|
||||
personalMessage: formData.value.personalMessage,
|
||||
slackUsername: formData.value.slackUsername,
|
||||
});
|
||||
|
||||
// Refresh member data
|
||||
await fetchMember();
|
||||
|
||||
showSuccess.value = true;
|
||||
|
||||
// Scroll to top to show success message
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
} catch (err) {
|
||||
console.error("Failed to update peer support settings:", err);
|
||||
error.value = "Failed to save settings. Please try again.";
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Load on mount
|
||||
onMounted(async () => {
|
||||
if (!memberData.value) {
|
||||
await fetchMember();
|
||||
}
|
||||
initializeForm();
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: "Peer Support Settings - Ghost Guild",
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content:
|
||||
"Configure your peer support settings and offer guidance to Ghost Guild members.",
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue