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:
parent
fb02688166
commit
1f7a0f40c0
11 changed files with 375 additions and 432 deletions
|
|
@ -2,6 +2,8 @@
|
|||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@theme {
|
||||
/* Font families */
|
||||
--font-sans: "Inter", sans-serif;
|
||||
|
|
|
|||
|
|
@ -157,7 +157,6 @@ const memberNavigationItems = [
|
|||
{ label: "Events", path: "/events" },
|
||||
{ label: "Members", path: "/members" },
|
||||
{ label: "Resources", path: "/resources" },
|
||||
{ label: "Peer Support", path: "/peer-support" },
|
||||
{ label: "Profile", path: "/member/profile" },
|
||||
];
|
||||
|
||||
|
|
|
|||
87
app/components/PeerSupportBadge.vue
Normal file
87
app/components/PeerSupportBadge.vue
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<!-- Corner Sticker Badge -->
|
||||
<div
|
||||
v-if="type === 'sticker'"
|
||||
class="absolute top-2 right-2 z-10"
|
||||
:title="title"
|
||||
>
|
||||
<div
|
||||
class="relative bg-gradient-to-br from-purple-500 to-purple-600 text-white px-3 py-2 rounded-lg shadow-lg border-2 border-purple-400/50 transform rotate-3 hover:rotate-0 transition-transform"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-0.5">
|
||||
<Icon name="heroicons:chat-bubble-left-right-solid" class="w-4 h-4" />
|
||||
<span
|
||||
class="text-[10px] font-bold uppercase tracking-wide leading-tight"
|
||||
>Peer<br />Support</span
|
||||
>
|
||||
</div>
|
||||
<!-- Sparkle effect -->
|
||||
<div
|
||||
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-300 rounded-full animate-pulse"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inline Badge -->
|
||||
<div
|
||||
v-else
|
||||
:class="[
|
||||
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-xs font-medium transition-all',
|
||||
variant === 'default' &&
|
||||
'bg-purple-500/20 text-purple-300 border-purple-500/40 hover:bg-purple-500/30',
|
||||
variant === 'subtle' &&
|
||||
'bg-purple-500/10 text-purple-400 border-purple-500/20',
|
||||
variant === 'solid' &&
|
||||
'bg-purple-500 text-white border-purple-600 hover:bg-purple-600',
|
||||
]"
|
||||
:title="title"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:chat-bubble-left-right"
|
||||
:class="[
|
||||
'w-3.5 h-3.5',
|
||||
variant === 'default' && 'text-purple-300',
|
||||
variant === 'subtle' && 'text-purple-400',
|
||||
variant === 'solid' && 'text-white',
|
||||
]"
|
||||
/>
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
/**
|
||||
* Badge type - inline or corner sticker
|
||||
* @values inline, sticker
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: "inline",
|
||||
validator: (value) => ["inline", "sticker"].includes(value),
|
||||
},
|
||||
/**
|
||||
* Display variant of the badge (for inline type)
|
||||
* @values default, subtle, solid
|
||||
*/
|
||||
variant: {
|
||||
type: String,
|
||||
default: "default",
|
||||
validator: (value) => ["default", "subtle", "solid"].includes(value),
|
||||
},
|
||||
/**
|
||||
* Custom label text (defaults to "Offering Peer Support")
|
||||
*/
|
||||
label: {
|
||||
type: String,
|
||||
default: "Offering Peer Support",
|
||||
},
|
||||
/**
|
||||
* Tooltip/title text
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: "This member offers 1:1 peer support sessions",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
15
app/composables/useMarkdown.js
Normal file
15
app/composables/useMarkdown.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { marked } from 'marked'
|
||||
|
||||
export const useMarkdown = () => {
|
||||
const render = (markdown) => {
|
||||
if (!markdown) return ''
|
||||
return marked(markdown, {
|
||||
breaks: true,
|
||||
gfm: true
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
render
|
||||
}
|
||||
}
|
||||
|
|
@ -16,10 +16,6 @@
|
|||
class="min-h-screen bg-ghost-900 flex items-center justify-center"
|
||||
>
|
||||
<div class="text-center">
|
||||
<Icon
|
||||
name="heroicons:exclamation-triangle"
|
||||
class="w-16 h-16 text-red-500 mx-auto mb-4"
|
||||
/>
|
||||
<h2 class="text-2xl font-bold text-ghost-100 mb-2">Event Not Found</h2>
|
||||
<p class="text-ghost-300 mb-6">
|
||||
The event you're looking for doesn't exist.
|
||||
|
|
@ -70,37 +66,25 @@
|
|||
<!-- Event Meta Info -->
|
||||
<div class="bg-ghost-800 rounded-xl p-6 mb-8 border border-ghost-700">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<Icon
|
||||
name="heroicons:calendar-days"
|
||||
class="w-6 h-6 text-blue-400"
|
||||
/>
|
||||
<div>
|
||||
<p class="text-sm text-ghost-400">Date</p>
|
||||
<p class="font-semibold text-ghost-100">
|
||||
{{ formatDate(event.startDate) }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-ghost-400">Date</p>
|
||||
<p class="font-semibold text-ghost-100">
|
||||
{{ formatDate(event.startDate) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-3">
|
||||
<Icon name="heroicons:clock" class="w-6 h-6 text-blue-400" />
|
||||
<div>
|
||||
<p class="text-sm text-ghost-400">Time</p>
|
||||
<p class="font-semibold text-ghost-100">
|
||||
{{ formatTime(event.startDate, event.endDate) }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-ghost-400">Time</p>
|
||||
<p class="font-semibold text-ghost-100">
|
||||
{{ formatTime(event.startDate, event.endDate) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-3">
|
||||
<Icon name="heroicons:map-pin" class="w-6 h-6 text-blue-400" />
|
||||
<div>
|
||||
<p class="text-sm text-ghost-400">Location</p>
|
||||
<p class="font-semibold text-ghost-100">
|
||||
{{ event.location }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-ghost-400">Location</p>
|
||||
<p class="font-semibold text-ghost-100">
|
||||
{{ event.location }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -108,24 +92,16 @@
|
|||
<!-- Event Cancelled Notice -->
|
||||
<div v-if="event.isCancelled" class="mb-8">
|
||||
<div class="p-6 bg-red-900/20 rounded-xl border border-red-800">
|
||||
<div class="flex items-start">
|
||||
<Icon
|
||||
name="heroicons:exclamation-triangle"
|
||||
class="w-6 h-6 text-red-400 mr-3 mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-red-300 mb-2">
|
||||
Event Cancelled
|
||||
</h3>
|
||||
<p class="text-red-400" v-if="event.cancellationMessage">
|
||||
{{ event.cancellationMessage }}
|
||||
</p>
|
||||
<p class="text-red-400" v-else>
|
||||
This event has been cancelled. We apologize for any
|
||||
inconvenience.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-red-300 mb-2">
|
||||
Event Cancelled
|
||||
</h3>
|
||||
<p class="text-red-400" v-if="event.cancellationMessage">
|
||||
{{ event.cancellationMessage }}
|
||||
</p>
|
||||
<p class="text-red-400" v-else>
|
||||
This event has been cancelled. We apologize for any
|
||||
inconvenience.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -134,10 +110,6 @@
|
|||
<div
|
||||
class="inline-flex items-center px-4 py-2 bg-purple-100 dark:bg-purple-900/30 rounded-full"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:lock-closed"
|
||||
class="w-5 h-5 text-purple-600 dark:text-purple-400 mr-2"
|
||||
/>
|
||||
<span
|
||||
class="text-sm font-medium text-purple-700 dark:text-purple-300"
|
||||
>
|
||||
|
|
@ -152,7 +124,6 @@
|
|||
class="mb-8"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Icon name="heroicons:user-group" class="w-5 h-5 text-blue-400" />
|
||||
<span class="text-sm font-medium text-ghost-200"
|
||||
>Recommended for:</span
|
||||
>
|
||||
|
|
@ -210,14 +181,6 @@
|
|||
:key="speaker.name"
|
||||
class="flex items-start space-x-4"
|
||||
>
|
||||
<div
|
||||
class="w-16 h-16 bg-ghost-700 rounded-full flex items-center justify-center"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:user"
|
||||
class="w-8 h-8 text-ghost-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-ghost-100">
|
||||
{{ speaker.name }}
|
||||
|
|
@ -249,19 +212,13 @@
|
|||
class="p-4 bg-green-900/20 rounded-lg border border-green-800"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center">
|
||||
<Icon
|
||||
name="heroicons:check-circle"
|
||||
class="w-6 h-6 text-green-400 mr-3"
|
||||
/>
|
||||
<div>
|
||||
<p class="font-semibold text-green-300">
|
||||
You're registered!
|
||||
</p>
|
||||
<p class="text-sm text-green-400">
|
||||
We've sent a confirmation to your email
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-green-300">
|
||||
You're registered!
|
||||
</p>
|
||||
<p class="text-sm text-green-400">
|
||||
We've sent a confirmation to your email
|
||||
</p>
|
||||
</div>
|
||||
<UButton
|
||||
color="red"
|
||||
|
|
@ -286,28 +243,19 @@
|
|||
class="mb-6"
|
||||
>
|
||||
<div
|
||||
class="flex items-start p-4 bg-amber-900/20 rounded-lg border border-amber-800"
|
||||
class="p-4 bg-amber-900/20 rounded-lg border border-amber-800"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:exclamation-triangle"
|
||||
class="w-6 h-6 text-amber-400 mr-3 mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<p class="font-semibold text-amber-300">
|
||||
Membership Required
|
||||
</p>
|
||||
<p class="text-sm text-amber-400 mt-1">
|
||||
This event is exclusive to Ghost Guild members. Join any
|
||||
circle to gain access.
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/join"
|
||||
class="inline-flex items-center text-sm font-medium text-amber-300 hover:underline mt-2"
|
||||
>
|
||||
Become a member
|
||||
<Icon name="heroicons:arrow-right" class="w-4 h-4 ml-1" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<p class="font-semibold text-amber-300">Membership Required</p>
|
||||
<p class="text-sm text-amber-400 mt-1">
|
||||
This event is exclusive to Ghost Guild members. Join any
|
||||
circle to gain access.
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/join"
|
||||
class="inline-flex items-center text-sm font-medium text-amber-300 hover:underline mt-2"
|
||||
>
|
||||
Become a member →
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -417,9 +365,8 @@
|
|||
</p>
|
||||
<a
|
||||
href="mailto:events@ghostguild.org"
|
||||
class="inline-flex items-center text-blue-400 hover:underline"
|
||||
class="text-blue-400 hover:underline"
|
||||
>
|
||||
<Icon name="heroicons:envelope" class="w-4 h-4 mr-2" />
|
||||
events@ghostguild.org
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -430,9 +430,10 @@
|
|||
Join the community
|
||||
</h3>
|
||||
<p class="text-[--ui-text]">
|
||||
Get instant access to everything. Fill out your profile, agree
|
||||
to our community guidelines, and complete payment (if
|
||||
applicable). You'll get instant access to our community.
|
||||
Get access to everything. Fill out your profile, agree to our
|
||||
community guidelines, and complete payment (if applicable).
|
||||
You'll get access to our community as soon as we review your
|
||||
application.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -99,9 +99,6 @@
|
|||
block
|
||||
title="Coming soon"
|
||||
>
|
||||
<template #leading>
|
||||
<Icon name="heroicons:calendar-days" class="w-5 h-5" />
|
||||
</template>
|
||||
Propose an Event
|
||||
</UButton>
|
||||
|
||||
|
|
@ -112,9 +109,6 @@
|
|||
block
|
||||
title="Coming soon"
|
||||
>
|
||||
<template #leading>
|
||||
<Icon name="heroicons:user-group" class="w-5 h-5" />
|
||||
</template>
|
||||
Book a Peer Session
|
||||
</UButton>
|
||||
|
||||
|
|
@ -125,9 +119,6 @@
|
|||
block
|
||||
title="Coming soon"
|
||||
>
|
||||
<template #leading>
|
||||
<Icon name="heroicons:book-open" class="w-5 h-5" />
|
||||
</template>
|
||||
Browse Resources
|
||||
</UButton>
|
||||
|
||||
|
|
@ -137,130 +128,38 @@
|
|||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
||||
block
|
||||
>
|
||||
<template #leading>
|
||||
<Icon name="heroicons:user-circle" class="w-5 h-5" />
|
||||
</template>
|
||||
Update Profile
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
to="/events"
|
||||
variant="outline"
|
||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
||||
block
|
||||
>
|
||||
View Events
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
to="/members"
|
||||
variant="outline"
|
||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
||||
block
|
||||
>
|
||||
Browse Members
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
to="/member/profile#account"
|
||||
variant="outline"
|
||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
||||
block
|
||||
>
|
||||
Manage Account
|
||||
</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- Quick Actions Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<UCard
|
||||
:ui="{
|
||||
root: 'bg-ghost-900 border border-ghost-700 hover:border-whisper-600 transition-colors',
|
||||
header: 'border-b-0',
|
||||
body: 'bg-ghost-900',
|
||||
footer: 'border-t-0 bg-ghost-900',
|
||||
}"
|
||||
class="hover:border-whisper-600 transition-colors"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
class="w-12 h-12 bg-ghost-800 border border-ghost-600 flex items-center justify-center"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:calendar-days"
|
||||
class="w-6 h-6 text-whisper-400"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<h3 class="text-lg font-semibold mb-2 text-ghost-100">
|
||||
Upcoming Events
|
||||
</h3>
|
||||
<p class="text-ghost-300 mb-4">
|
||||
Discover and register for community events and workshops.
|
||||
</p>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
to="/events"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
||||
>
|
||||
View Events
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
<UCard
|
||||
:ui="{
|
||||
root: 'bg-ghost-900 border border-ghost-700 hover:border-whisper-600 transition-colors',
|
||||
header: 'border-b-0',
|
||||
body: 'bg-ghost-900',
|
||||
footer: 'border-t-0 bg-ghost-900',
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
class="w-12 h-12 bg-ghost-800 border border-ghost-600 flex items-center justify-center"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:user-group"
|
||||
class="w-6 h-6 text-whisper-400"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<h3 class="text-lg font-semibold mb-2 text-ghost-100">Community</h3>
|
||||
<p class="text-ghost-300 mb-4">
|
||||
Connect with other members in your circle and beyond.
|
||||
</p>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
to="/members"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
||||
>
|
||||
Browse Members
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
<UCard
|
||||
:ui="{
|
||||
root: 'bg-ghost-900 border border-ghost-700 hover:border-whisper-600 transition-colors',
|
||||
header: 'border-b-0',
|
||||
body: 'bg-ghost-900',
|
||||
footer: 'border-t-0 bg-ghost-900',
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
class="w-12 h-12 bg-ghost-800 border border-ghost-600 flex items-center justify-center"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:cog-6-tooth"
|
||||
class="w-6 h-6 text-whisper-400"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<h3 class="text-lg font-semibold mb-2 text-ghost-100">
|
||||
Account Settings
|
||||
</h3>
|
||||
<p class="text-ghost-300 mb-4">
|
||||
Manage your profile and membership settings.
|
||||
</p>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
to="/member/profile#account"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
||||
>
|
||||
Manage Account
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<!-- Your Registered Events -->
|
||||
<UCard
|
||||
:ui="{
|
||||
|
|
|
|||
|
|
@ -429,28 +429,12 @@
|
|||
<UFormField
|
||||
label="Conversational Support Topics"
|
||||
name="peerSupportSupportTopics"
|
||||
description="Select emotional and conversational support areas"
|
||||
>
|
||||
<div class="space-y-2 mt-2">
|
||||
<label
|
||||
v-for="topic in availableSupportTopics"
|
||||
:key="topic"
|
||||
class="flex items-center gap-3 p-3 rounded-lg border border-ghost-700 hover:border-purple-500/50 transition-colors cursor-pointer"
|
||||
:class="
|
||||
formData.peerSupportSupportTopics.includes(topic)
|
||||
? 'bg-purple-500/10 border-purple-500/50'
|
||||
: 'bg-ghost-900/50'
|
||||
"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="topic"
|
||||
v-model="formData.peerSupportSupportTopics"
|
||||
class="rounded border-ghost-600 text-purple-500 focus:ring-purple-500 focus:ring-offset-0 bg-ghost-800"
|
||||
/>
|
||||
<span class="text-ghost-200">{{ topic }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<UCheckboxGroup
|
||||
v-model="formData.peerSupportSupportTopics"
|
||||
:items="availableSupportTopics"
|
||||
color="primary"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<!-- Availability -->
|
||||
|
|
@ -927,6 +911,9 @@ const hasChanges = computed(() => {
|
|||
// Offering tag management
|
||||
const addOfferingTag = () => {
|
||||
const tag = currentOfferingTagInput.value.trim().replace(/,$/, "");
|
||||
if (!Array.isArray(formData.offering.tags)) {
|
||||
formData.offering.tags = [];
|
||||
}
|
||||
if (tag && !formData.offering.tags.includes(tag)) {
|
||||
formData.offering.tags.push(tag);
|
||||
currentOfferingTagInput.value = "";
|
||||
|
|
@ -934,12 +921,16 @@ const addOfferingTag = () => {
|
|||
};
|
||||
|
||||
const removeOfferingTag = (index) => {
|
||||
if (!formData.offering.tags) return;
|
||||
formData.offering.tags.splice(index, 1);
|
||||
};
|
||||
|
||||
// Looking For tag management
|
||||
const addLookingForTag = () => {
|
||||
const tag = currentLookingForTagInput.value.trim().replace(/,$/, "");
|
||||
if (!Array.isArray(formData.lookingFor.tags)) {
|
||||
formData.lookingFor.tags = [];
|
||||
}
|
||||
if (tag && !formData.lookingFor.tags.includes(tag)) {
|
||||
formData.lookingFor.tags.push(tag);
|
||||
currentLookingForTagInput.value = "";
|
||||
|
|
@ -947,6 +938,7 @@ const addLookingForTag = () => {
|
|||
};
|
||||
|
||||
const removeLookingForTag = (index) => {
|
||||
if (!formData.lookingFor.tags) return;
|
||||
formData.lookingFor.tags.splice(index, 1);
|
||||
};
|
||||
|
||||
|
|
@ -964,6 +956,9 @@ const removePeerSkillTopic = (index) => {
|
|||
};
|
||||
|
||||
const addSuggestedSkillTopic = (tag) => {
|
||||
if (!Array.isArray(formData.peerSupportSkillTopics)) {
|
||||
formData.peerSupportSkillTopics = [];
|
||||
}
|
||||
if (!formData.peerSupportSkillTopics.includes(tag)) {
|
||||
formData.peerSupportSkillTopics.push(tag);
|
||||
}
|
||||
|
|
@ -982,22 +977,32 @@ const loadProfile = () => {
|
|||
|
||||
// Load offering (handle both old string and new object format)
|
||||
if (typeof memberData.value.offering === "string") {
|
||||
formData.offering = { text: memberData.value.offering, tags: [] };
|
||||
formData.offering.text = memberData.value.offering;
|
||||
formData.offering.tags = [];
|
||||
} else if (memberData.value.offering) {
|
||||
formData.offering.text = memberData.value.offering?.text || "";
|
||||
formData.offering.tags = Array.isArray(memberData.value.offering?.tags)
|
||||
? [...memberData.value.offering.tags]
|
||||
: [];
|
||||
} else {
|
||||
formData.offering = {
|
||||
text: memberData.value.offering?.text || "",
|
||||
tags: memberData.value.offering?.tags || [],
|
||||
};
|
||||
formData.offering.text = "";
|
||||
formData.offering.tags = [];
|
||||
}
|
||||
|
||||
// Load lookingFor (handle both old string and new object format)
|
||||
if (typeof memberData.value.lookingFor === "string") {
|
||||
formData.lookingFor = { text: memberData.value.lookingFor, tags: [] };
|
||||
formData.lookingFor.text = memberData.value.lookingFor;
|
||||
formData.lookingFor.tags = [];
|
||||
} else if (memberData.value.lookingFor) {
|
||||
formData.lookingFor.text = memberData.value.lookingFor?.text || "";
|
||||
formData.lookingFor.tags = Array.isArray(
|
||||
memberData.value.lookingFor?.tags,
|
||||
)
|
||||
? [...memberData.value.lookingFor.tags]
|
||||
: [];
|
||||
} else {
|
||||
formData.lookingFor = {
|
||||
text: memberData.value.lookingFor?.text || "",
|
||||
tags: memberData.value.lookingFor?.tags || [],
|
||||
};
|
||||
formData.lookingFor.text = "";
|
||||
formData.lookingFor.tags = [];
|
||||
}
|
||||
|
||||
formData.showInDirectory = memberData.value.showInDirectory ?? true;
|
||||
|
|
@ -1006,16 +1011,29 @@ const loadProfile = () => {
|
|||
if (memberData.value.peerSupport) {
|
||||
formData.peerSupportEnabled =
|
||||
memberData.value.peerSupport.enabled || false;
|
||||
formData.peerSupportSkillTopics =
|
||||
memberData.value.peerSupport.skillTopics || [];
|
||||
formData.peerSupportSupportTopics =
|
||||
memberData.value.peerSupport.supportTopics || [];
|
||||
formData.peerSupportSkillTopics = Array.isArray(
|
||||
memberData.value.peerSupport.skillTopics,
|
||||
)
|
||||
? [...memberData.value.peerSupport.skillTopics]
|
||||
: [];
|
||||
formData.peerSupportSupportTopics = Array.isArray(
|
||||
memberData.value.peerSupport.supportTopics,
|
||||
)
|
||||
? [...memberData.value.peerSupport.supportTopics]
|
||||
: [];
|
||||
formData.peerSupportAvailability =
|
||||
memberData.value.peerSupport.availability || "";
|
||||
formData.peerSupportMessage =
|
||||
memberData.value.peerSupport.personalMessage || "";
|
||||
formData.peerSupportSlackUsername =
|
||||
memberData.value.peerSupport.slackUsername || "";
|
||||
} else {
|
||||
formData.peerSupportEnabled = false;
|
||||
formData.peerSupportSkillTopics = [];
|
||||
formData.peerSupportSupportTopics = [];
|
||||
formData.peerSupportAvailability = "";
|
||||
formData.peerSupportMessage = "";
|
||||
formData.peerSupportSlackUsername = "";
|
||||
}
|
||||
|
||||
// Load privacy settings (with defaults)
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
43
package-lock.json
generated
43
package-lock.json
generated
|
|
@ -19,6 +19,7 @@
|
|||
"cloudinary": "^2.7.0",
|
||||
"eslint": "^9.34.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"marked": "^16.4.0",
|
||||
"mongoose": "^8.18.0",
|
||||
"nitro-cors": "^0.7.1",
|
||||
"nuxt": "^4.0.3",
|
||||
|
|
@ -28,6 +29,9 @@
|
|||
"vue-cal": "^5.0.1-rc.28",
|
||||
"vue-router": "^4.5.1",
|
||||
"zod": "^4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/gateway": {
|
||||
|
|
@ -5234,6 +5238,33 @@
|
|||
"tailwindcss": "4.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.13.tgz",
|
||||
|
|
@ -11598,6 +11629,18 @@
|
|||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "16.4.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-16.4.0.tgz",
|
||||
"integrity": "sha512-CTPAcRBq57cn3R8n3hwc2REddc28hjR7RzDXQ+lXLmMJYqn20BaI2cGw6QjgZGIgVfp2Wdfw4aMzgNteQ6qJgQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"cloudinary": "^2.7.0",
|
||||
"eslint": "^9.34.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"marked": "^16.4.0",
|
||||
"mongoose": "^8.18.0",
|
||||
"nitro-cors": "^0.7.1",
|
||||
"nuxt": "^4.0.3",
|
||||
|
|
@ -31,5 +32,8 @@
|
|||
"vue-cal": "^5.0.1-rc.28",
|
||||
"vue-router": "^4.5.1",
|
||||
"zod": "^4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue