Add Markdown support and update member features

The commit adds Markdown rendering capabilities and makes several UI/UX
improvements across member-related features including profile display,
peer support badges, and navigation structure.

Includes:
- Added @tailwindcss/typography plugin
- New Markdown rendering composable
- Simplified member navigation links
- Enhanced member profile layout and styling
- Added peer support badge component
- Improved mobile responsiveness
- Removed redundant icons and simplified UI
This commit is contained in:
Jennie Robinson Faber 2025-10-07 15:07:27 +01:00
parent fb02688166
commit 1f7a0f40c0
11 changed files with 375 additions and 432 deletions

View file

@ -119,8 +119,8 @@
<!-- Active Filters -->
<div
v-if="
selectedCircle ||
peerSupportFilter ||
(selectedCircle && selectedCircle !== 'all') ||
(peerSupportFilter && peerSupportFilter !== 'all') ||
selectedSkills.length > 0 ||
selectedTopics.length > 0
"
@ -128,7 +128,7 @@
>
<span class="text-ghost-400">Active filters:</span>
<span
v-if="selectedCircle"
v-if="selectedCircle && selectedCircle !== 'all'"
class="px-2 py-1 bg-purple-500/20 text-purple-300 rounded-full border border-purple-500/30 flex items-center gap-1"
>
{{ circleLabels[selectedCircle] }}
@ -141,7 +141,7 @@
</button>
</span>
<span
v-if="peerSupportFilter"
v-if="peerSupportFilter && peerSupportFilter !== 'all'"
class="px-2 py-1 bg-purple-500/20 text-purple-300 rounded-full border border-purple-500/30 flex items-center gap-1"
>
Offering Peer Support
@ -187,8 +187,14 @@
<div
v-for="member in members"
:key="member._id"
class="backdrop-blur-sm bg-ghost-900/50 border border-ghost-700/50 rounded-lg p-6 hover:border-purple-500/50 transition-all group"
class="relative backdrop-blur-sm bg-ghost-900/50 border border-ghost-700/50 rounded-lg p-6 hover:border-purple-500/50 transition-all group"
>
<!-- Peer Support Sticker Badge -->
<PeerSupportBadge
v-if="member.peerSupport?.enabled"
type="sticker"
/>
<!-- Header Section -->
<div class="flex items-start gap-4 mb-4">
<!-- Avatar -->
@ -207,18 +213,15 @@
<!-- Name and Meta Info -->
<div class="flex-1 min-w-0">
<div class="flex items-baseline gap-2 flex-wrap mb-2">
<NuxtLink
:to="`/updates/user/${member._id}`"
class="font-semibold text-lg text-ghost-100 hover:text-purple-300 transition-colors"
>
<h3 class="font-semibold text-lg text-ghost-100">
{{ member.name }}
</NuxtLink>
</h3>
<span v-if="member.pronouns" class="text-sm text-ghost-400">
{{ member.pronouns }}
</span>
</div>
<div class="flex items-center gap-2 flex-wrap mb-2">
<div class="flex items-center gap-2 flex-wrap">
<span
class="px-2 py-0.5 bg-purple-500/20 text-purple-300 rounded text-xs border border-purple-500/30"
>
@ -234,114 +237,25 @@
🕐 {{ member.timeZone }}
</span>
</div>
<!-- Social Links -->
<div
v-if="
member.socialLinks && hasSocialLinks(member.socialLinks)
"
class="flex gap-3"
>
<a
v-if="member.socialLinks.mastodon"
:href="member.socialLinks.mastodon"
target="_blank"
rel="noopener noreferrer"
class="text-ghost-400 hover:text-purple-400 transition-colors"
title="Mastodon"
>
<svg
class="w-5 h-5"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z"
/>
</svg>
</a>
<a
v-if="member.socialLinks.linkedin"
:href="member.socialLinks.linkedin"
target="_blank"
rel="noopener noreferrer"
class="text-ghost-400 hover:text-purple-400 transition-colors"
title="LinkedIn"
>
<svg
class="w-5 h-5"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
/>
</svg>
</a>
<a
v-if="member.socialLinks.website"
:href="member.socialLinks.website"
target="_blank"
rel="noopener noreferrer"
class="text-ghost-400 hover:text-purple-400 transition-colors"
title="Website"
>
<svg
class="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"
/>
</svg>
</a>
<a
v-if="member.socialLinks.other"
:href="member.socialLinks.other"
target="_blank"
rel="noopener noreferrer"
class="text-ghost-400 hover:text-purple-400 transition-colors"
title="Other link"
>
<svg
class="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
/>
</svg>
</a>
</div>
</div>
</div>
<!-- Bio -->
<div v-if="member.bio" class="mb-4">
<p class="text-ghost-300 text-sm leading-relaxed">
{{ member.bio }}
</p>
</div>
<div
v-if="member.bio"
class="mb-4 text-ghost-300 text-sm leading-relaxed prose prose-invert prose-sm max-w-none"
v-html="renderMarkdown(member.bio)"
></div>
<!-- Peer Support Section -->
<div
v-if="member.peerSupport?.enabled"
class="mb-4 p-4 bg-purple-500/10 border border-purple-500/30 rounded-lg"
>
<div class="flex items-center gap-2 mb-2">
<span class="text-purple-300 font-medium text-sm">
💜 Offering Peer Support
</span>
<div class="mb-3">
<p class="text-purple-300 font-medium text-sm mb-2">
{{ member.name }} offers 1:1 chats on:
</p>
</div>
<!-- Topics -->
@ -379,74 +293,85 @@
Availability: {{ member.peerSupport.availability }}
</div>
<!-- Contact Button -->
<a
v-if="member.peerSupport.slackUsername"
:href="`slack://user?team=T03A96LV4&id=${member.slackUserId}`"
@click.prevent="openSlackDM(member)"
class="inline-block px-3 py-1.5 bg-purple-500/20 text-purple-300 rounded border border-purple-500/30 hover:bg-purple-500/30 transition-colors text-sm font-medium cursor-pointer"
>
Message {{ member.peerSupport.slackUsername }} on Slack
</a>
<!-- Contact Section -->
<div v-if="member.peerSupport.slackUsername" class="space-y-2">
<p class="text-sm text-purple-300 font-medium">
Book a Peer Support call now:
</p>
<a
:href="`slack://user?team=T03A96LV4&id=${member.slackUserId}`"
@click.prevent="openSlackDM(member)"
class="inline-block px-3 py-1.5 bg-purple-500/20 text-purple-300 rounded border border-purple-500/30 hover:bg-purple-500/30 transition-colors text-sm font-medium cursor-pointer"
>
Message {{ member.peerSupport.slackUsername }} on Slack
</a>
</div>
</div>
<!-- Offering and Looking For -->
<div
v-if="member.offering || member.lookingFor"
class="grid grid-cols-1 md:grid-cols-2 gap-4"
class="space-y-4"
>
<!-- Offering -->
<div v-if="member.offering" class="space-y-2">
<h4 class="text-xs font-semibold text-purple-400 uppercase">
Offering
</h4>
<p
v-if="member.offering.description"
class="text-ghost-300 text-sm"
>
{{ member.offering.description }}
</p>
<div
v-if="
member.offering.tags && member.offering.tags.length > 0
"
class="flex flex-wrap gap-1"
>
<span
v-for="tag in member.offering.tags"
:key="tag"
class="px-2 py-0.5 bg-green-500/20 text-green-300 rounded text-xs border border-green-500/30"
<h4
class="text-sm font-semibold text-purple-300 uppercase tracking-wide"
>
Skills Exchange
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Offering -->
<div v-if="member.offering" class="space-y-2">
<h5 class="text-xs font-semibold text-purple-400 uppercase">
Can share
</h5>
<p
v-if="member.offering.description"
class="text-ghost-300 text-sm"
>
{{ tag }}
</span>
{{ member.offering.description }}
</p>
<div
v-if="
member.offering.tags && member.offering.tags.length > 0
"
class="flex flex-wrap gap-1"
>
<span
v-for="tag in member.offering.tags"
:key="tag"
class="px-2 py-0.5 bg-green-500/20 text-green-300 rounded text-xs border border-green-500/30"
>
{{ tag }}
</span>
</div>
</div>
</div>
<!-- Looking For -->
<div v-if="member.lookingFor" class="space-y-2">
<h4 class="text-xs font-semibold text-purple-400 uppercase">
Looking For
</h4>
<p
v-if="member.lookingFor.description"
class="text-ghost-300 text-sm"
>
{{ member.lookingFor.description }}
</p>
<div
v-if="
member.lookingFor.tags &&
member.lookingFor.tags.length > 0
"
class="flex flex-wrap gap-1"
>
<span
v-for="tag in member.lookingFor.tags"
:key="tag"
class="px-2 py-0.5 bg-blue-500/20 text-blue-300 rounded text-xs border border-blue-500/30"
<!-- Looking For -->
<div v-if="member.lookingFor" class="space-y-2">
<h5 class="text-xs font-semibold text-purple-400 uppercase">
Looking to learn
</h5>
<p
v-if="member.lookingFor.description"
class="text-ghost-300 text-sm"
>
{{ tag }}
</span>
{{ member.lookingFor.description }}
</p>
<div
v-if="
member.lookingFor.tags &&
member.lookingFor.tags.length > 0
"
class="flex flex-wrap gap-1"
>
<span
v-for="tag in member.lookingFor.tags"
:key="tag"
class="px-2 py-0.5 bg-blue-500/20 text-blue-300 rounded text-xs border border-blue-500/30"
>
{{ tag }}
</span>
</div>
</div>
</div>
</div>
@ -502,6 +427,7 @@
<script setup>
const { isAuthenticated } = useAuth();
const { render: renderMarkdown } = useMarkdown();
// State
const members = ref([]);
@ -610,10 +536,12 @@ const toggleTopic = (topic) => {
// Clear filters
const clearCircleFilter = () => {
selectedCircle.value = "all";
loadMembers();
};
const clearPeerSupportFilter = () => {
peerSupportFilter.value = "all";
loadMembers();
};
const clearAllFilters = () => {