Add light/dark mode support with CSS variables
This commit is contained in:
parent
970b185151
commit
fb02688166
25 changed files with 1293 additions and 1177 deletions
|
|
@ -6,8 +6,13 @@ export default defineAppConfig({
|
||||||
},
|
},
|
||||||
formField: {
|
formField: {
|
||||||
slots: {
|
slots: {
|
||||||
label: "block font-medium text-stone-200",
|
label: "block font-medium text-[--ui-text-dimmed]",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
colorMode: {
|
||||||
|
preference: "system", // default value of $colorMode.preference
|
||||||
|
fallback: "dark", // fallback value if not system preference found
|
||||||
|
classSuffix: "", // default is '', set to '-mode' to have 'dark-mode' and 'light-mode'
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,52 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "@nuxt/ui";
|
@import "@nuxt/ui";
|
||||||
|
|
||||||
@theme static {
|
@theme {
|
||||||
/* Font families */
|
/* Font families */
|
||||||
--font-sans: "Inter", sans-serif;
|
--font-sans: "Inter", sans-serif;
|
||||||
--font-body: "Inter", sans-serif;
|
--font-body: "Inter", sans-serif;
|
||||||
--font-mono: "Ubuntu Mono", monospace;
|
--font-mono: "Ubuntu Mono", monospace;
|
||||||
--font-display: "NB Television Pro", monospace;
|
--font-display: "NB Television Pro", monospace;
|
||||||
|
|
||||||
/* Ethereal color palette - grays, blacks, minimal color */
|
/* Ethereal color palette - light mode (inverted for light backgrounds) */
|
||||||
|
--color-ghost-50: #0a0a0a;
|
||||||
|
--color-ghost-100: #1a1a1a;
|
||||||
|
--color-ghost-200: #2a2a2a;
|
||||||
|
--color-ghost-300: #3a3a3a;
|
||||||
|
--color-ghost-400: #4a4a4a;
|
||||||
|
--color-ghost-500: #6a6a6a;
|
||||||
|
--color-ghost-600: #8a8a8a;
|
||||||
|
--color-ghost-700: #b0b0b0;
|
||||||
|
--color-ghost-800: #d0d0d0;
|
||||||
|
--color-ghost-900: #f0f0f0;
|
||||||
|
|
||||||
|
/* Subtle accent - barely visible blue-gray (light mode) */
|
||||||
|
--color-whisper-50: #0f1419;
|
||||||
|
--color-whisper-100: #1a1f2e;
|
||||||
|
--color-whisper-200: #252d40;
|
||||||
|
--color-whisper-300: #2f3b52;
|
||||||
|
--color-whisper-400: #3a4964;
|
||||||
|
--color-whisper-500: #4f5d7a;
|
||||||
|
--color-whisper-600: #687291;
|
||||||
|
--color-whisper-700: #8491a8;
|
||||||
|
--color-whisper-800: #a8b3c7;
|
||||||
|
--color-whisper-900: #d4dae6;
|
||||||
|
|
||||||
|
/* Sparkle accent (light mode) */
|
||||||
|
--color-sparkle-50: #202020;
|
||||||
|
--color-sparkle-100: #404040;
|
||||||
|
--color-sparkle-200: #606060;
|
||||||
|
--color-sparkle-300: #808080;
|
||||||
|
--color-sparkle-400: #a0a0a0;
|
||||||
|
--color-sparkle-500: #c0c0c0;
|
||||||
|
--color-sparkle-600: #d0d0d0;
|
||||||
|
--color-sparkle-700: #e8e8e8;
|
||||||
|
--color-sparkle-800: #f0f0f0;
|
||||||
|
--color-sparkle-900: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
/* Ethereal color palette - dark mode (original values) */
|
||||||
--color-ghost-50: #f0f0f0;
|
--color-ghost-50: #f0f0f0;
|
||||||
--color-ghost-100: #d0d0d0;
|
--color-ghost-100: #d0d0d0;
|
||||||
--color-ghost-200: #b0b0b0;
|
--color-ghost-200: #b0b0b0;
|
||||||
|
|
@ -20,8 +58,8 @@
|
||||||
--color-ghost-700: #2a2a2a;
|
--color-ghost-700: #2a2a2a;
|
||||||
--color-ghost-800: #1a1a1a;
|
--color-ghost-800: #1a1a1a;
|
||||||
--color-ghost-900: #0a0a0a;
|
--color-ghost-900: #0a0a0a;
|
||||||
|
|
||||||
/* Subtle accent - barely visible blue-gray */
|
/* Subtle accent - barely visible blue-gray (dark mode) */
|
||||||
--color-whisper-50: #d4dae6;
|
--color-whisper-50: #d4dae6;
|
||||||
--color-whisper-100: #a8b3c7;
|
--color-whisper-100: #a8b3c7;
|
||||||
--color-whisper-200: #8491a8;
|
--color-whisper-200: #8491a8;
|
||||||
|
|
@ -33,7 +71,7 @@
|
||||||
--color-whisper-800: #1a1f2e;
|
--color-whisper-800: #1a1f2e;
|
||||||
--color-whisper-900: #0f1419;
|
--color-whisper-900: #0f1419;
|
||||||
|
|
||||||
/* Sparkle accent */
|
/* Sparkle accent (dark mode) */
|
||||||
--color-sparkle-50: #fafafa;
|
--color-sparkle-50: #fafafa;
|
||||||
--color-sparkle-100: #f0f0f0;
|
--color-sparkle-100: #f0f0f0;
|
||||||
--color-sparkle-200: #e8e8e8;
|
--color-sparkle-200: #e8e8e8;
|
||||||
|
|
@ -46,33 +84,79 @@
|
||||||
--color-sparkle-900: #202020;
|
--color-sparkle-900: #202020;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global ethereal background */
|
/* Global ethereal background - light mode */
|
||||||
:root {
|
:root {
|
||||||
--ethereal-bg: radial-gradient(circle at 20% 80%, rgba(232, 232, 232, 0.03) 0%, transparent 50%),
|
--ethereal-bg:
|
||||||
radial-gradient(circle at 80% 20%, rgba(232, 232, 232, 0.02) 0%, transparent 50%),
|
radial-gradient(
|
||||||
radial-gradient(circle at 40% 40%, rgba(232, 232, 232, 0.01) 0%, transparent 50%);
|
circle at 20% 80%,
|
||||||
|
rgba(40, 40, 40, 0.03) 0%,
|
||||||
--halftone-pattern: radial-gradient(circle, rgba(255,255,255,0.1) 1px, transparent 1px);
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 80% 20%,
|
||||||
|
rgba(40, 40, 40, 0.02) 0%,
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 40% 40%,
|
||||||
|
rgba(40, 40, 40, 0.01) 0%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
|
||||||
|
--halftone-pattern: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(0, 0, 0, 0.1) 1px,
|
||||||
|
transparent 1px
|
||||||
|
);
|
||||||
--halftone-size: 8px 8px;
|
--halftone-size: 8px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode background */
|
||||||
|
.dark:root {
|
||||||
|
--ethereal-bg:
|
||||||
|
radial-gradient(
|
||||||
|
circle at 20% 80%,
|
||||||
|
rgba(232, 232, 232, 0.03) 0%,
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 80% 20%,
|
||||||
|
rgba(232, 232, 232, 0.02) 0%,
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 40% 40%,
|
||||||
|
rgba(232, 232, 232, 0.01) 0%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
|
||||||
|
--halftone-pattern: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(255, 255, 255, 0.1) 1px,
|
||||||
|
transparent 1px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background: var(--color-ghost-900);
|
@apply text-[--ui-text];
|
||||||
color: var(--color-ghost-200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--ethereal-bg), var(--color-ghost-900);
|
background: var(--ethereal-bg), #f0f0f0;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark body {
|
||||||
|
background: var(--ethereal-bg), #0a0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
/* Halftone texture overlay */
|
/* Halftone texture overlay */
|
||||||
.halftone-texture {
|
.halftone-texture {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.halftone-texture::before {
|
.halftone-texture::before {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
@ -86,14 +170,28 @@ body {
|
||||||
|
|
||||||
/* Sparkle effects */
|
/* Sparkle effects */
|
||||||
@keyframes sparkle {
|
@keyframes sparkle {
|
||||||
0%, 100% { opacity: 0.3; transform: scale(0.8); }
|
0%,
|
||||||
50% { opacity: 1; transform: scale(1.2); }
|
100% {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes twinkle {
|
@keyframes twinkle {
|
||||||
0%, 100% { opacity: 0.2; }
|
0%,
|
||||||
25% { opacity: 0.8; }
|
100% {
|
||||||
75% { opacity: 0.4; }
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sparkle-field {
|
.sparkle-field {
|
||||||
|
|
@ -102,20 +200,50 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sparkle-field::after {
|
.sparkle-field::after {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-image:
|
background-image:
|
||||||
radial-gradient(circle at 10% 20%, var(--color-sparkle-200) 1px, transparent 1px),
|
radial-gradient(
|
||||||
radial-gradient(circle at 90% 80%, var(--color-sparkle-400) 1px, transparent 1px),
|
circle at 10% 20%,
|
||||||
radial-gradient(circle at 30% 70%, var(--color-sparkle-200) 0.5px, transparent 0.5px),
|
var(--color-sparkle-200) 1px,
|
||||||
radial-gradient(circle at 70% 30%, var(--color-sparkle-400) 0.5px, transparent 0.5px),
|
transparent 1px
|
||||||
radial-gradient(circle at 50% 10%, var(--color-sparkle-200) 1px, transparent 1px),
|
),
|
||||||
radial-gradient(circle at 20% 90%, var(--color-sparkle-400) 0.5px, transparent 0.5px);
|
radial-gradient(
|
||||||
background-size: 200px 200px, 300px 300px, 150px 150px, 250px 250px, 180px 180px, 220px 220px;
|
circle at 90% 80%,
|
||||||
|
var(--color-sparkle-400) 1px,
|
||||||
|
transparent 1px
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 30% 70%,
|
||||||
|
var(--color-sparkle-200) 0.5px,
|
||||||
|
transparent 0.5px
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 70% 30%,
|
||||||
|
var(--color-sparkle-400) 0.5px,
|
||||||
|
transparent 0.5px
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 50% 10%,
|
||||||
|
var(--color-sparkle-200) 1px,
|
||||||
|
transparent 1px
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 20% 90%,
|
||||||
|
var(--color-sparkle-400) 0.5px,
|
||||||
|
transparent 0.5px
|
||||||
|
);
|
||||||
|
background-size:
|
||||||
|
200px 200px,
|
||||||
|
300px 300px,
|
||||||
|
150px 150px,
|
||||||
|
250px 250px,
|
||||||
|
180px 180px,
|
||||||
|
220px 220px;
|
||||||
animation: twinkle 4s infinite ease-in-out;
|
animation: twinkle 4s infinite ease-in-out;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|
@ -123,7 +251,7 @@ body {
|
||||||
|
|
||||||
/* Ethereal glow effects */
|
/* Ethereal glow effects */
|
||||||
.ethereal-glow {
|
.ethereal-glow {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0 20px rgba(232, 232, 232, 0.1),
|
0 0 20px rgba(232, 232, 232, 0.1),
|
||||||
0 0 40px rgba(232, 232, 232, 0.05),
|
0 0 40px rgba(232, 232, 232, 0.05),
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
|
@ -135,12 +263,28 @@ body {
|
||||||
|
|
||||||
/* Dithered gradients */
|
/* Dithered gradients */
|
||||||
.dithered-bg {
|
.dithered-bg {
|
||||||
background:
|
background:
|
||||||
linear-gradient(45deg, var(--color-ghost-800) 25%, transparent 25%),
|
linear-gradient(45deg, var(--color-ghost-800) 25%, transparent 25%),
|
||||||
linear-gradient(-45deg, var(--color-ghost-800) 25%, transparent 25%),
|
linear-gradient(-45deg, var(--color-ghost-800) 25%, transparent 25%),
|
||||||
linear-gradient(45deg, transparent 75%, var(--color-ghost-700) 75%),
|
linear-gradient(45deg, transparent 75%, var(--color-ghost-700) 75%),
|
||||||
linear-gradient(-45deg, transparent 75%, var(--color-ghost-700) 75%);
|
linear-gradient(-45deg, transparent 75%, var(--color-ghost-700) 75%);
|
||||||
background-size: 4px 4px;
|
background-size: 4px 4px;
|
||||||
background-position: 0 0, 0 2px, 2px -2px, -2px 0px;
|
background-position:
|
||||||
|
0 0,
|
||||||
|
0 2px,
|
||||||
|
2px -2px,
|
||||||
|
-2px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile responsive utilities */
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
/* Prevent horizontal scroll on mobile */
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust halftone pattern for mobile */
|
||||||
|
.halftone-texture::before {
|
||||||
|
background-size: 6px 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
>
|
>
|
||||||
<!-- Left: Copyright and minimal info -->
|
<!-- Left: Copyright and minimal info -->
|
||||||
<div>
|
<div>
|
||||||
<p class="text-stone-500 text-xs mb-2">
|
<p class="text-ghost-500 text-xs mb-2">
|
||||||
© {{ currentYear }} Ghost Guild
|
© {{ currentYear }} Ghost Guild
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<div class="flex flex-wrap gap-6 text-xs">
|
<div class="flex flex-wrap gap-6 text-xs">
|
||||||
<a
|
<a
|
||||||
href="mailto:hello@ghostguild.org"
|
href="mailto:hello@ghostguild.org"
|
||||||
class="text-stone-500 hover:text-stone-300 transition-colors"
|
class="text-ghost-500 hover:text-ghost-300 transition-colors"
|
||||||
>
|
>
|
||||||
Contact
|
Contact
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<nav
|
<nav
|
||||||
class="w-64 lg:w-80 backdrop-blur-sm h-screen sticky top-0 flex flex-col"
|
:class="[
|
||||||
|
isMobile
|
||||||
|
? 'w-full flex flex-col bg-transparent'
|
||||||
|
: 'w-64 lg:w-80 backdrop-blur-sm h-screen sticky top-0 flex flex-col bg-ghost-900 border-r border-ghost-700',
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<!-- Logo/Brand at top -->
|
<!-- Logo/Brand at top (desktop only) -->
|
||||||
<div class="p-8 border-b border-ghost-800 bg-blue-400">
|
<div v-if="!isMobile" class="p-8 border-b border-ghost-700 bg-primary-500">
|
||||||
<NuxtLink to="/" class="flex flex-col items-center gap-3 group">
|
<NuxtLink to="/" class="flex flex-col items-center gap-3 group">
|
||||||
<span
|
<span class="text-xl font-bold text-white ethereal-text tracking-wider"
|
||||||
class="text-xl font-bold text-stone-100 ethereal-text tracking-wider"
|
|
||||||
>Ghost Guild Logo</span
|
>Ghost Guild Logo</span
|
||||||
>
|
>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vertical Navigation -->
|
<!-- Vertical Navigation -->
|
||||||
<div class="flex-1 p-8 overflow-y-auto">
|
<div
|
||||||
<ul class="space-y-6">
|
:class="
|
||||||
|
isMobile ? 'flex-1 p-6 overflow-y-auto' : 'flex-1 p-8 overflow-y-auto'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ul :class="isMobile ? 'space-y-4' : 'space-y-6'">
|
||||||
<li v-for="item in navigationItems" :key="item.path">
|
<li v-for="item in navigationItems" :key="item.path">
|
||||||
<NuxtLink :to="item.path" class="block group relative">
|
<NuxtLink
|
||||||
|
:to="item.path"
|
||||||
|
class="block group relative"
|
||||||
|
@click="handleNavigate"
|
||||||
|
>
|
||||||
<!-- Hover indicator -->
|
<!-- Hover indicator -->
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="text-stone-200 hover:text-stone-100 transition-all duration-300 text-lg tracking-wide block py-2 hover:ethereal-text"
|
class="text-ghost-200 hover:text-ghost-100 transition-all duration-300 text-lg tracking-wide block py-2 hover:ethereal-text"
|
||||||
active-class="text-stone-100 ethereal-text translate-x-2"
|
active-class="text-ghost-100 ethereal-text translate-x-2"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -29,12 +40,24 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Color Mode Switcher -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<UColorModeButton size="md" class="w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Auth section -->
|
<!-- Auth section -->
|
||||||
<div class="mt-12 pt-8 border-t border-ghost-800/50">
|
<div
|
||||||
|
:class="
|
||||||
|
isMobile
|
||||||
|
? 'mt-8 pt-6 border-t border-ghost-800/50'
|
||||||
|
: 'mt-12 pt-8 border-t border-ghost-800/50'
|
||||||
|
"
|
||||||
|
>
|
||||||
<div v-if="isAuthenticated" class="space-y-4">
|
<div v-if="isAuthenticated" class="space-y-4">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/member/dashboard"
|
to="/member/dashboard"
|
||||||
class="block text-stone-300 hover:text-stone-100 hover:ethereal-text transition-all duration-300 py-2"
|
class="block text-ghost-300 hover:text-ghost-100 hover:ethereal-text transition-all duration-300 py-2"
|
||||||
|
@click="handleNavigate"
|
||||||
>
|
>
|
||||||
<span class="block text-sm text-whisper-400 mb-1">{{
|
<span class="block text-sm text-whisper-400 mb-1">{{
|
||||||
memberData?.name || "Member"
|
memberData?.name || "Member"
|
||||||
|
|
@ -42,14 +65,14 @@
|
||||||
Dashboard
|
Dashboard
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<button
|
<button
|
||||||
@click="logout"
|
@click="handleLogout"
|
||||||
class="text-stone-500 hover:text-stone-300 transition-all duration-300 text-sm"
|
class="text-ghost-500 hover:text-ghost-300 transition-all duration-300 text-sm"
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="space-y-4">
|
<div v-else class="space-y-4">
|
||||||
<p class="text-stone-400 text-sm mb-4">
|
<p class="text-ghost-400 text-sm mb-4">
|
||||||
Enter your email to receive a login link
|
Enter your email to receive a login link
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -97,8 +120,30 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, computed } from "vue";
|
import { reactive, ref, computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isMobile: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["navigate"]);
|
||||||
|
|
||||||
const { isAuthenticated, logout, memberData } = useAuth();
|
const { isAuthenticated, logout, memberData } = useAuth();
|
||||||
|
|
||||||
|
const handleNavigate = () => {
|
||||||
|
if (props.isMobile) {
|
||||||
|
emit("navigate");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await logout();
|
||||||
|
if (props.isMobile) {
|
||||||
|
emit("navigate");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const publicNavigationItems = [
|
const publicNavigationItems = [
|
||||||
{ label: "Home", path: "/", accent: "entry point" },
|
{ label: "Home", path: "/", accent: "entry point" },
|
||||||
{ label: "About", path: "/about", accent: "who we are" },
|
{ label: "About", path: "/about", accent: "who we are" },
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<!-- Current Image Preview -->
|
<!-- Current Image Preview -->
|
||||||
<div v-if="modelValue?.url" class="relative">
|
<div v-if="modelValue?.url" class="relative">
|
||||||
<img
|
<img
|
||||||
:src="transformedImageUrl"
|
:src="transformedImageUrl"
|
||||||
:alt="modelValue.alt || 'Event image'"
|
:alt="modelValue.alt || 'Event image'"
|
||||||
class="w-full h-48 object-cover rounded-lg border border-gray-300"
|
class="w-full h-48 object-cover rounded-lg border border-neutral-200"
|
||||||
@error="console.log('Image failed to load:', transformedImageUrl)"
|
@error="console.log('Image failed to load:', transformedImageUrl)"
|
||||||
@load="console.log('Image loaded successfully:', transformedImageUrl)"
|
@load="console.log('Image loaded successfully:', transformedImageUrl)"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@click="removeImage"
|
@click="removeImage"
|
||||||
type="button"
|
type="button"
|
||||||
class="absolute top-2 right-2 p-1 bg-red-500 text-white rounded-full hover:bg-red-600 transition-colors"
|
class="absolute top-2 right-2 p-1 bg-red-500 text-white rounded-full hover:bg-red-600 transition-colors"
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Upload Area -->
|
<!-- Upload Area -->
|
||||||
<div
|
<div
|
||||||
v-if="!modelValue?.url"
|
v-if="!modelValue?.url"
|
||||||
class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors"
|
class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors"
|
||||||
@dragover.prevent="isDragging = true"
|
@dragover.prevent="isDragging = true"
|
||||||
|
|
@ -34,12 +34,12 @@
|
||||||
@change="handleFileSelect"
|
@change="handleFileSelect"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<Icon name="heroicons:photo" class="w-12 h-12 text-gray-400 mx-auto" />
|
<Icon name="heroicons:photo" class="w-12 h-12 text-gray-400 mx-auto" />
|
||||||
<div>
|
<div>
|
||||||
<p class="text-gray-600">
|
<p class="text-gray-600">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="$refs.fileInput.click()"
|
@click="$refs.fileInput.click()"
|
||||||
class="text-blue-600 hover:text-blue-500 font-medium"
|
class="text-blue-600 hover:text-blue-500 font-medium"
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
:value="modelValue.alt || ''"
|
:value="modelValue.alt || ''"
|
||||||
@input="updateAltText($event.target.value)"
|
@input="updateAltText($event.target.value)"
|
||||||
placeholder="Describe this image..."
|
placeholder="Describe this image..."
|
||||||
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
class="w-full border border-neutral-200 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
<span class="text-gray-600">{{ uploadProgress }}%</span>
|
<span class="text-gray-600">{{ uploadProgress }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
class="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
class="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
||||||
:style="`width: ${uploadProgress}%`"
|
:style="`width: ${uploadProgress}%`"
|
||||||
/>
|
/>
|
||||||
|
|
@ -91,111 +91,113 @@
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => null
|
default: () => null,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false);
|
||||||
const isUploading = ref(false)
|
const isUploading = ref(false);
|
||||||
const uploadProgress = ref(0)
|
const uploadProgress = ref(0);
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref("");
|
||||||
const fileInput = ref()
|
const fileInput = ref();
|
||||||
|
|
||||||
// Transform image URL for preview (smaller size)
|
// Transform image URL for preview (smaller size)
|
||||||
const transformedImageUrl = computed(() => {
|
const transformedImageUrl = computed(() => {
|
||||||
console.log('modelValue in computed:', props.modelValue)
|
console.log("modelValue in computed:", props.modelValue);
|
||||||
|
|
||||||
// If we have the direct URL, use it
|
// If we have the direct URL, use it
|
||||||
if (props.modelValue?.url) {
|
if (props.modelValue?.url) {
|
||||||
console.log('Using direct URL:', props.modelValue.url)
|
console.log("Using direct URL:", props.modelValue.url);
|
||||||
return props.modelValue.url
|
return props.modelValue.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise try to construct from publicId
|
// Otherwise try to construct from publicId
|
||||||
if (props.modelValue?.publicId) {
|
if (props.modelValue?.publicId) {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig();
|
||||||
const constructedUrl = `https://res.cloudinary.com/${config.public.cloudinaryCloudName}/image/upload/w_400,h_200,c_fill,f_auto,q_auto/${props.modelValue.publicId}`
|
const constructedUrl = `https://res.cloudinary.com/${config.public.cloudinaryCloudName}/image/upload/w_400,h_200,c_fill,f_auto,q_auto/${props.modelValue.publicId}`;
|
||||||
console.log('Constructed URL:', constructedUrl)
|
console.log("Constructed URL:", constructedUrl);
|
||||||
return constructedUrl
|
return constructedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('No URL or publicId found')
|
console.log("No URL or publicId found");
|
||||||
return ''
|
return "";
|
||||||
})
|
});
|
||||||
|
|
||||||
const handleFileSelect = (event) => {
|
const handleFileSelect = (event) => {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
uploadFile(file)
|
uploadFile(file);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleDrop = (event) => {
|
const handleDrop = (event) => {
|
||||||
isDragging.value = false
|
isDragging.value = false;
|
||||||
const files = event.dataTransfer.files
|
const files = event.dataTransfer.files;
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
uploadFile(files[0])
|
uploadFile(files[0]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const uploadFile = async (file) => {
|
const uploadFile = async (file) => {
|
||||||
// Validate file
|
// Validate file
|
||||||
if (!file.type.startsWith('image/')) {
|
if (!file.type.startsWith("image/")) {
|
||||||
errorMessage.value = 'Please select an image file'
|
errorMessage.value = "Please select an image file";
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size > 10 * 1024 * 1024) { // 10MB
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
errorMessage.value = 'File size must be less than 10MB'
|
// 10MB
|
||||||
return
|
errorMessage.value = "File size must be less than 10MB";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorMessage.value = ''
|
errorMessage.value = "";
|
||||||
isUploading.value = true
|
isUploading.value = true;
|
||||||
uploadProgress.value = 0
|
uploadProgress.value = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create form data for upload
|
// Create form data for upload
|
||||||
const formData = new FormData()
|
const formData = new FormData();
|
||||||
formData.append('file', file)
|
formData.append("file", file);
|
||||||
|
|
||||||
// Upload to Cloudinary
|
// Upload to Cloudinary
|
||||||
const response = await $fetch(`/api/upload/image`, {
|
const response = await $fetch(`/api/upload/image`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
onUploadProgress: (progress) => {
|
onUploadProgress: (progress) => {
|
||||||
uploadProgress.value = Math.round((progress.loaded / progress.total) * 100)
|
uploadProgress.value = Math.round(
|
||||||
}
|
(progress.loaded / progress.total) * 100,
|
||||||
})
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
console.log('Upload response:', response)
|
console.log("Upload response:", response);
|
||||||
|
|
||||||
// Update the model value
|
// Update the model value
|
||||||
emit('update:modelValue', {
|
emit("update:modelValue", {
|
||||||
url: response.secure_url,
|
url: response.secure_url,
|
||||||
publicId: response.public_id,
|
publicId: response.public_id,
|
||||||
alt: ''
|
alt: "",
|
||||||
})
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Upload failed:', error)
|
console.error("Upload failed:", error);
|
||||||
errorMessage.value = 'Upload failed. Please try again.'
|
errorMessage.value = "Upload failed. Please try again.";
|
||||||
} finally {
|
} finally {
|
||||||
isUploading.value = false
|
isUploading.value = false;
|
||||||
uploadProgress.value = 0
|
uploadProgress.value = 0;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const removeImage = () => {
|
const removeImage = () => {
|
||||||
emit('update:modelValue', null)
|
emit("update:modelValue", null);
|
||||||
}
|
};
|
||||||
|
|
||||||
const updateAltText = (altText) => {
|
const updateAltText = (altText) => {
|
||||||
emit('update:modelValue', {
|
emit("update:modelValue", {
|
||||||
...props.modelValue,
|
...props.modelValue,
|
||||||
alt: altText
|
alt: altText,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,21 @@
|
||||||
'rounded-2xl p-6 md:p-8 mb-12 backdrop-blur-sm',
|
'rounded-2xl p-6 md:p-8 mb-12 backdrop-blur-sm',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'ethereal'
|
||||||
? 'bg-ghost-800/60 border border-ghost-700 ethereal-glow halftone-texture'
|
? 'bg-ghost-800/60 border border-ghost-700 ethereal-glow halftone-texture'
|
||||||
: 'bg-white dark:bg-gray-800 shadow-xl border border-blue-200 dark:border-blue-800',
|
: 'bg-[--ui-bg-elevated] shadow-xl border border-blue-200',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between gap-3 md:gap-4">
|
||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
'p-3 rounded-full transition-all duration-300',
|
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'ethereal'
|
||||||
? 'bg-whisper-600/80 text-stone-100 hover:bg-whisper-500 ethereal-glow'
|
? 'bg-whisper-600/80 text-ghost-100 hover:bg-whisper-500 ethereal-glow'
|
||||||
: 'bg-blue-500 text-white hover:bg-blue-600',
|
: 'bg-blue-500 text-white hover:bg-blue-600',
|
||||||
]"
|
]"
|
||||||
@click="$emit('prev')"
|
@click="$emit('prev')"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-6 h-6"
|
class="w-5 h-5 md:w-6 md:h-6"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
|
@ -56,14 +56,14 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="text-center flex-1">
|
<div class="text-center flex-1 min-w-0">
|
||||||
<slot name="interactive-content">
|
<slot name="interactive-content">
|
||||||
<p
|
<p
|
||||||
:class="[
|
:class="[
|
||||||
'text-lg',
|
'text-base md:text-lg',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'ethereal'
|
||||||
? 'text-stone-200'
|
? 'text-ghost-200'
|
||||||
: 'text-gray-600 dark:text-gray-300',
|
: 'text-[--ui-text-muted]',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
|
|
@ -76,15 +76,15 @@
|
||||||
|
|
||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
'p-3 rounded-full transition-all duration-300',
|
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'ethereal'
|
||||||
? 'bg-whisper-600/80 text-stone-100 hover:bg-whisper-500 ethereal-glow'
|
? 'bg-whisper-600/80 text-ghost-100 hover:bg-whisper-500 ethereal-glow'
|
||||||
: 'bg-blue-500 text-white hover:bg-blue-600',
|
: 'bg-blue-500 text-white hover:bg-blue-600',
|
||||||
]"
|
]"
|
||||||
@click="$emit('next')"
|
@click="$emit('next')"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-6 h-6"
|
class="w-5 h-5 md:w-6 md:h-6"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
|
@ -178,12 +178,10 @@ defineEmits(["prev", "next"]);
|
||||||
|
|
||||||
const backgroundClass = computed(() => {
|
const backgroundClass = computed(() => {
|
||||||
const themes = {
|
const themes = {
|
||||||
blue: "bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800",
|
blue: "bg-gradient-to-br from-blue-50 to-indigo-100",
|
||||||
purple:
|
purple: "bg-gradient-to-br from-purple-50 to-violet-100",
|
||||||
"bg-gradient-to-br from-purple-50 to-violet-100 dark:from-gray-900 dark:to-purple-900/20",
|
emerald: "bg-gradient-to-br from-emerald-50 to-teal-100",
|
||||||
emerald:
|
gray: "bg-neutral-100",
|
||||||
"bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-emerald-900/20",
|
|
||||||
gray: "bg-gray-50 dark:bg-gray-900",
|
|
||||||
ethereal:
|
ethereal:
|
||||||
"bg-gradient-to-br from-ghost-900 via-ghost-800 to-whisper-900 halftone-texture",
|
"bg-gradient-to-br from-ghost-900 via-ghost-800 to-whisper-900 halftone-texture",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-stone-300 font-medium">{{ label }}:</span>
|
<span class="text-ghost-300 font-medium">{{ label }}:</span>
|
||||||
<UButtonGroup size="sm" class="privacy-toggle-group">
|
<UButtonGroup size="sm" class="privacy-toggle-group">
|
||||||
<UButton
|
<UButton
|
||||||
:variant="modelValue === 'members' ? 'solid' : 'outline'"
|
:variant="modelValue === 'members' ? 'solid' : 'outline'"
|
||||||
|
|
@ -46,14 +46,14 @@ const updateValue = (value) => {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Unselected buttons - lighter background for visibility */
|
/* Unselected buttons - lighter background for visibility */
|
||||||
:deep(.privacy-toggle-btn:not(.is-selected)) {
|
:deep(.privacy-toggle-btn:not(.is-selected)) {
|
||||||
background-color: rgb(68 64 60) !important; /* stone-700 */
|
background-color: rgb(68 64 60) !important; /* ghost-700 equivalent */
|
||||||
border-color: rgb(87 83 78) !important; /* stone-600 */
|
border-color: rgb(87 83 78) !important; /* ghost-600 equivalent */
|
||||||
color: rgb(214 211 209) !important; /* stone-300 */
|
color: rgb(214 211 209) !important; /* ghost-300 equivalent */
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.privacy-toggle-btn:not(.is-selected):hover) {
|
:deep(.privacy-toggle-btn:not(.is-selected):hover) {
|
||||||
background-color: rgb(87 83 78) !important; /* stone-600 */
|
background-color: rgb(87 83 78) !important; /* ghost-600 equivalent */
|
||||||
border-color: rgb(120 113 108) !important; /* stone-500 */
|
border-color: rgb(120 113 108) !important; /* ghost-500 equivalent */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Selected buttons - bright blue to stand out */
|
/* Selected buttons - bright blue to stand out */
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="w-12 h-12 rounded-full bg-stone-700 flex items-center justify-center text-stone-300 font-bold"
|
class="w-12 h-12 rounded-full bg-ghost-700 flex items-center justify-center text-ghost-300 font-bold"
|
||||||
>
|
>
|
||||||
{{ update.author?.name?.charAt(0)?.toUpperCase() || "?" }}
|
{{ update.author?.name?.charAt(0)?.toUpperCase() || "?" }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,30 +23,30 @@
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-start justify-between gap-4 mb-2">
|
<div class="flex items-start justify-between gap-4 mb-2">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold text-stone-100">
|
<h3 class="font-semibold text-ghost-100">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="update.author?._id"
|
v-if="update.author?._id"
|
||||||
:to="`/updates/user/${update.author._id}`"
|
:to="`/updates/user/${update.author._id}`"
|
||||||
class="hover:text-stone-300 transition-colors"
|
class="hover:text-ghost-300 transition-colors"
|
||||||
>
|
>
|
||||||
{{ update.author.name }}
|
{{ update.author.name }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<span v-else>Unknown Member</span>
|
<span v-else>Unknown Member</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex items-center gap-2 text-sm text-stone-400">
|
<div class="flex items-center gap-2 text-sm text-ghost-400">
|
||||||
<time :datetime="update.createdAt">
|
<time :datetime="update.createdAt">
|
||||||
{{ formatDate(update.createdAt) }}
|
{{ formatDate(update.createdAt) }}
|
||||||
</time>
|
</time>
|
||||||
<span v-if="isEdited" class="text-stone-500">(edited)</span>
|
<span v-if="isEdited" class="text-ghost-500">(edited)</span>
|
||||||
<span
|
<span
|
||||||
v-if="update.privacy === 'private'"
|
v-if="update.privacy === 'private'"
|
||||||
class="px-2 py-0.5 bg-stone-700 text-stone-300 rounded text-xs"
|
class="px-2 py-0.5 bg-ghost-700 text-ghost-300 rounded text-xs"
|
||||||
>
|
>
|
||||||
Private
|
Private
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="update.privacy === 'public'"
|
v-if="update.privacy === 'public'"
|
||||||
class="px-2 py-0.5 bg-stone-700 text-stone-300 rounded text-xs"
|
class="px-2 py-0.5 bg-ghost-700 text-ghost-300 rounded text-xs"
|
||||||
>
|
>
|
||||||
Public
|
Public
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -73,12 +73,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="text-stone-200 whitespace-pre-wrap break-words mb-3">
|
<div class="text-ghost-200 whitespace-pre-wrap break-words mb-3">
|
||||||
<template v-if="showPreview && update.content.length > 300">
|
<template v-if="showPreview && update.content.length > 300">
|
||||||
{{ update.content.substring(0, 300) }}...
|
{{ update.content.substring(0, 300) }}...
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="`/updates/${update._id}`"
|
:to="`/updates/${update._id}`"
|
||||||
class="text-stone-400 hover:text-stone-300 ml-1"
|
class="text-ghost-400 hover:text-ghost-300 ml-1"
|
||||||
>
|
>
|
||||||
Read more
|
Read more
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -100,14 +100,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer actions -->
|
<!-- Footer actions -->
|
||||||
<div class="flex items-center gap-4 text-sm text-stone-400">
|
<div class="flex items-center gap-4 text-sm text-ghost-400">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="`/updates/${update._id}`"
|
:to="`/updates/${update._id}`"
|
||||||
class="hover:text-stone-300 transition-colors"
|
class="hover:text-ghost-300 transition-colors"
|
||||||
>
|
>
|
||||||
View full update
|
View full update
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<span v-if="update.commentsEnabled" class="text-stone-500">
|
<span v-if="update.commentsEnabled" class="text-ghost-500">
|
||||||
Comments (coming soon)
|
Comments (coming soon)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<!-- Privacy Settings -->
|
<!-- Privacy Settings -->
|
||||||
<div class="border border-stone-700 rounded-lg p-4 bg-stone-800/30">
|
<div class="border border-ghost-700 rounded-lg p-4 bg-ghost-800/30">
|
||||||
<h3 class="text-sm font-medium text-stone-200 mb-4">Privacy Settings</h3>
|
<h3 class="text-sm font-medium text-ghost-200 mb-4">Privacy Settings</h3>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<label class="flex items-center gap-3 cursor-pointer">
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
v-model="formData.privacy"
|
v-model="formData.privacy"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="public"
|
value="public"
|
||||||
class="w-4 h-4 text-stone-400"
|
class="w-4 h-4 text-ghost-400"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-stone-200 font-medium">Public</div>
|
<div class="text-ghost-200 font-medium">Public</div>
|
||||||
<div class="text-sm text-stone-400">
|
<div class="text-sm text-ghost-400">
|
||||||
Visible to everyone, including non-members
|
Visible to everyone, including non-members
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -34,11 +34,11 @@
|
||||||
v-model="formData.privacy"
|
v-model="formData.privacy"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="members"
|
value="members"
|
||||||
class="w-4 h-4 text-stone-400"
|
class="w-4 h-4 text-ghost-400"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-stone-200 font-medium">Members Only</div>
|
<div class="text-ghost-200 font-medium">Members Only</div>
|
||||||
<div class="text-sm text-stone-400">
|
<div class="text-sm text-ghost-400">
|
||||||
Only visible to Ghost Guild members
|
Only visible to Ghost Guild members
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -49,11 +49,11 @@
|
||||||
v-model="formData.privacy"
|
v-model="formData.privacy"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="private"
|
value="private"
|
||||||
class="w-4 h-4 text-stone-400"
|
class="w-4 h-4 text-ghost-400"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-stone-200 font-medium">Private</div>
|
<div class="text-ghost-200 font-medium">Private</div>
|
||||||
<div class="text-sm text-stone-400">Only visible to you</div>
|
<div class="text-sm text-ghost-400">Only visible to you</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,15 +66,17 @@
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<USwitch v-model="formData.commentsEnabled" />
|
<USwitch v-model="formData.commentsEnabled" />
|
||||||
<div>
|
<div>
|
||||||
<div class="text-stone-200 font-medium">Enable Comments</div>
|
<div class="text-ghost-200 font-medium">Enable Comments</div>
|
||||||
<div class="text-sm text-stone-400">
|
<div class="text-sm text-ghost-400">
|
||||||
Allow members to comment on this update
|
Allow members to comment on this update
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="flex justify-between items-center pt-4 border-t border-stone-700">
|
<div
|
||||||
|
class="flex justify-between items-center pt-4 border-t border-ghost-700"
|
||||||
|
>
|
||||||
<UButton variant="ghost" color="neutral" @click="$emit('cancel')">
|
<UButton variant="ghost" color="neutral" @click="$emit('cancel')">
|
||||||
Cancel
|
Cancel
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-stone-800 flex relative">
|
<div class="min-h-screen bg-ghost-900 flex relative">
|
||||||
<!-- Background image at top - full page width -->
|
<!-- Background image at top - full page width -->
|
||||||
<div
|
<div
|
||||||
class="absolute inset-x-0 pointer-events-none z-0"
|
class="absolute inset-x-0 pointer-events-none z-0"
|
||||||
|
|
@ -21,15 +21,48 @@
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Mobile Header -->
|
||||||
|
<div
|
||||||
|
class="lg:hidden fixed top-0 left-0 right-0 z-50 bg-ghost-900/95 backdrop-blur-md border-b border-ghost-700"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between p-4">
|
||||||
|
<NuxtLink
|
||||||
|
to="/"
|
||||||
|
class="text-lg font-bold text-white ethereal-text tracking-wider"
|
||||||
|
>
|
||||||
|
Ghost Guild
|
||||||
|
</NuxtLink>
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-menu"
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
size="lg"
|
||||||
|
@click="isMobileMenuOpen = true"
|
||||||
|
aria-label="Open menu"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Main Content Column - Left -->
|
<!-- Main Content Column - Left -->
|
||||||
<div class="flex-1 overflow-y-auto relative z-[5]">
|
<div class="flex-1 overflow-y-auto relative z-[5]">
|
||||||
<div class="p-8 md:p-12 lg:p-16 max-w-4xl relative">
|
<div class="p-4 pt-20 md:p-8 md:pt-8 lg:p-16 max-w-4xl relative">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<AppFooter />
|
<AppFooter />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation Column - Right -->
|
<!-- Desktop Navigation Column - Right -->
|
||||||
<AppNavigation class="relative z-20" />
|
<AppNavigation class="hidden lg:block relative z-20" />
|
||||||
|
|
||||||
|
<!-- Mobile Navigation Drawer -->
|
||||||
|
<USlideover v-model:open="isMobileMenuOpen" side="right">
|
||||||
|
<template #body>
|
||||||
|
<AppNavigation :is-mobile="true" @navigate="isMobileMenuOpen = false" />
|
||||||
|
</template>
|
||||||
|
</USlideover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const isMobileMenuOpen = ref(false);
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,22 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- How Ghost Guild Works -->
|
<!-- How Ghost Guild Works -->
|
||||||
<section class="py-20 bg-white dark:bg-gray-900">
|
<section class="py-20 bg-[--ui-bg]">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-6">
|
<h2 class="text-3xl font-bold text-[--ui-text] mb-6">
|
||||||
How Ghost Guild Works
|
How Ghost Guild Works
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="prose prose-lg dark:prose-invert max-w-none">
|
<div class="prose prose-lg dark:prose-invert max-w-none">
|
||||||
<p class="text-xl font-semibold text-gray-900 dark:text-white mb-6">
|
<p class="text-xl font-semibold text-[--ui-text] mb-6">
|
||||||
Everyone gets everything. Your circle reflects where you are in
|
Everyone gets everything. Your circle reflects where you are in
|
||||||
your cooperative journey. Your financial contribution reflects
|
your cooperative journey. Your financial contribution reflects
|
||||||
what you can afford. These are completely separate choices.
|
what you can afford. These are completely separate choices.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
class="text-lg leading-relaxed text-gray-700 dark:text-gray-300 space-y-3 mb-12"
|
class="text-lg leading-relaxed text-[--ui-text-muted] space-y-3 mb-12"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<strong>Equal access:</strong> The entire knowledge commons, all
|
<strong>Equal access:</strong> The entire knowledge commons, all
|
||||||
|
|
@ -49,13 +49,13 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Find Your Circle -->
|
<!-- Find Your Circle -->
|
||||||
<section class="py-20 bg-gray-50 dark:bg-gray-800">
|
<section class="py-20 bg-[--ui-bg-elevated]">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-3xl font-bold text-[--ui-text] mb-4">
|
||||||
Find Your Circle
|
Find Your Circle
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-gray-700 dark:text-gray-300 mb-12">
|
<p class="text-lg text-[--ui-text-muted] mb-12">
|
||||||
Circles help us provide relevant guidance and connect you with
|
Circles help us provide relevant guidance and connect you with
|
||||||
others at similar stages. Choose based on where you are, not what
|
others at similar stages. Choose based on where you are, not what
|
||||||
you want to access.
|
you want to access.
|
||||||
|
|
@ -63,21 +63,19 @@
|
||||||
|
|
||||||
<div class="space-y-12">
|
<div class="space-y-12">
|
||||||
<!-- Community Circle -->
|
<!-- Community Circle -->
|
||||||
<div class="bg-white dark:bg-gray-900 rounded-xl p-8">
|
<div class="bg-[--ui-bg] rounded-xl p-8">
|
||||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
<h3 class="text-2xl font-bold text-[--ui-text] mb-2">
|
||||||
Community Circle
|
Community Circle
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-lg text-gray-600 dark:text-gray-400 mb-6">
|
<p class="text-lg text-[--ui-text-muted] mb-6">
|
||||||
You're exploring what cooperatives could mean for your work
|
You're exploring what cooperatives could mean for your work
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h4
|
<h4 class="text-lg font-semibold text-[--ui-text] mb-3">
|
||||||
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
||||||
>
|
|
||||||
Where you might be:
|
Where you might be:
|
||||||
</h4>
|
</h4>
|
||||||
<ul class="text-gray-700 dark:text-gray-300 space-y-2">
|
<ul class="text-[--ui-text-muted] space-y-2">
|
||||||
<li>
|
<li>
|
||||||
Curious about alternatives to traditional studio structures
|
Curious about alternatives to traditional studio structures
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -88,12 +86,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h4
|
<h4 class="text-lg font-semibold text-[--ui-text] mb-3">
|
||||||
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
||||||
>
|
|
||||||
We'll help you navigate:
|
We'll help you navigate:
|
||||||
</h4>
|
</h4>
|
||||||
<ul class="text-gray-700 dark:text-gray-300 space-y-2">
|
<ul class="text-[--ui-text-muted] space-y-2">
|
||||||
<li>Understanding cooperative basics</li>
|
<li>Understanding cooperative basics</li>
|
||||||
<li>Connecting with others asking similar questions</li>
|
<li>Connecting with others asking similar questions</li>
|
||||||
<li>Exploring real examples from game studios</li>
|
<li>Exploring real examples from game studios</li>
|
||||||
|
|
@ -102,12 +98,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h4
|
<h4 class="text-lg font-semibold text-[--ui-text] mb-3">
|
||||||
class="text-lg font-semibold text-gray-900 dark:text-white mb-3"
|
|
||||||
>
|
|
||||||
You might be:
|
You might be:
|
||||||
</h4>
|
</h4>
|
||||||
<ul class="text-gray-700 dark:text-gray-300 space-y-2">
|
<ul class="text-[--ui-text-muted] space-y-2">
|
||||||
<li>Individual game workers</li>
|
<li>Individual game workers</li>
|
||||||
<li>Researchers and students</li>
|
<li>Researchers and students</li>
|
||||||
<li>Industry allies and supporters</li>
|
<li>Industry allies and supporters</li>
|
||||||
|
|
@ -117,11 +111,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Founder Circle -->
|
<!-- Founder Circle -->
|
||||||
<div class="bg-white dark:bg-gray-900 rounded-xl p-8">
|
<div class="bg-[--ui-bg] rounded-xl p-8">
|
||||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
<h3 class="text-2xl font-bold text-[--ui-text] mb-2">
|
||||||
Founder Circle
|
Founder Circle
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-lg text-gray-600 dark:text-gray-400 mb-6">
|
<p class="text-lg text-[--ui-text-muted] mb-6">
|
||||||
You're actively building or transitioning to a cooperative model
|
You're actively building or transitioning to a cooperative model
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -188,11 +182,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Practitioner Circle -->
|
<!-- Practitioner Circle -->
|
||||||
<div class="bg-white dark:bg-gray-900 rounded-xl p-8">
|
<div class="bg-[--ui-bg] rounded-xl p-8">
|
||||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
<h3 class="text-2xl font-bold text-[--ui-text] mb-2">
|
||||||
Practitioner Circle
|
Practitioner Circle
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-lg text-gray-600 dark:text-gray-400 mb-6">
|
<p class="text-lg text-[--ui-text-muted] mb-6">
|
||||||
You're operating a cooperative and contributing to the field
|
You're operating a cooperative and contributing to the field
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -246,14 +240,14 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Important Notes -->
|
<!-- Important Notes -->
|
||||||
<section class="py-20 bg-white dark:bg-gray-900">
|
<section class="py-20 bg-[--ui-bg]">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-8">
|
<h2 class="text-3xl font-bold text-[--ui-text] mb-8">
|
||||||
Important Notes
|
Important Notes
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="space-y-6 text-lg text-gray-700 dark:text-gray-300">
|
<div class="space-y-6 text-lg text-[--ui-text-muted]">
|
||||||
<p>
|
<p>
|
||||||
<strong>Movement between circles is fluid.</strong> As you move
|
<strong>Movement between circles is fluid.</strong> As you move
|
||||||
along in your journey, you can shift circles anytime. Just let us
|
along in your journey, you can shift circles anytime. Just let us
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
<section class="mb-24">
|
<section class="mb-24">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<h1
|
<h1
|
||||||
class="text-6xl md:text-8xl font-bold text-stone-100 ethereal-text leading-tight mb-8"
|
class="text-6xl md:text-8xl font-bold text-ghost-100 ethereal-text leading-tight mb-8"
|
||||||
>
|
>
|
||||||
Get in Touch
|
Get in Touch
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="mb-16">
|
<div class="mb-16">
|
||||||
<p class="text-stone-100 text-lg max-w-md">
|
<p class="text-ghost-100 text-lg max-w-md">
|
||||||
We'd be happy to answer any questions<br />
|
We'd be happy to answer any questions<br />
|
||||||
you might have about Ghost Guild
|
you might have about Ghost Guild
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<!-- Contact Form -->
|
<!-- Contact Form -->
|
||||||
<section class="mb-32 relative">
|
<section class="mb-32 relative">
|
||||||
<div class="mb-12">
|
<div class="mb-12">
|
||||||
<h2 class="text-3xl font-light text-stone-200 mb-4">
|
<h2 class="text-3xl font-light text-ghost-200 mb-4">
|
||||||
Send us a message (or email hello@ghostguild.org)
|
Send us a message (or email hello@ghostguild.org)
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
:loading="isSubmitting"
|
:loading="isSubmitting"
|
||||||
:disabled="!isFormValid"
|
:disabled="!isFormValid"
|
||||||
size="xl"
|
size="xl"
|
||||||
class="px-12 border border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500 hover:ethereal-text transition-all duration-500"
|
class="px-12 border border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 hover:ethereal-text transition-all duration-500"
|
||||||
>
|
>
|
||||||
Send Message
|
Send Message
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -98,7 +98,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="error" class="mt-6 p-4 border border-ghost-700 bg-ghost-900">
|
<div v-if="error" class="mt-6 p-4 border border-ghost-700 bg-ghost-900">
|
||||||
<p class="text-stone-300 text-center">
|
<p class="text-ghost-300 text-center">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="pending"
|
v-if="pending"
|
||||||
class="min-h-screen bg-stone-900 flex items-center justify-center"
|
class="min-h-screen bg-ghost-900 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"
|
class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-stone-200">Loading event details...</p>
|
<p class="text-ghost-200">Loading event details...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="error"
|
v-else-if="error"
|
||||||
class="min-h-screen bg-stone-900 flex items-center justify-center"
|
class="min-h-screen bg-ghost-900 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:exclamation-triangle"
|
name="heroicons:exclamation-triangle"
|
||||||
class="w-16 h-16 text-red-500 mx-auto mb-4"
|
class="w-16 h-16 text-red-500 mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<h2 class="text-2xl font-bold text-stone-100 mb-2">Event Not Found</h2>
|
<h2 class="text-2xl font-bold text-ghost-100 mb-2">Event Not Found</h2>
|
||||||
<p class="text-stone-300 mb-6">
|
<p class="text-ghost-300 mb-6">
|
||||||
The event you're looking for doesn't exist.
|
The event you're looking for doesn't exist.
|
||||||
</p>
|
</p>
|
||||||
<NuxtLink to="/events" class="text-blue-400 hover:underline">
|
<NuxtLink to="/events" class="text-blue-400 hover:underline">
|
||||||
|
|
@ -64,11 +64,11 @@
|
||||||
<PageHeader v-else :title="event.title" theme="blue" size="medium" />
|
<PageHeader v-else :title="event.title" theme="blue" size="medium" />
|
||||||
|
|
||||||
<!-- Event Details Section -->
|
<!-- Event Details Section -->
|
||||||
<section class="py-16 bg-stone-900">
|
<section class="py-16 bg-ghost-900">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<!-- Event Meta Info -->
|
<!-- Event Meta Info -->
|
||||||
<div class="bg-stone-800 rounded-xl p-6 mb-8 border border-stone-700">
|
<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="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<Icon
|
<Icon
|
||||||
|
|
@ -76,8 +76,8 @@
|
||||||
class="w-6 h-6 text-blue-400"
|
class="w-6 h-6 text-blue-400"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-stone-400">Date</p>
|
<p class="text-sm text-ghost-400">Date</p>
|
||||||
<p class="font-semibold text-stone-100">
|
<p class="font-semibold text-ghost-100">
|
||||||
{{ formatDate(event.startDate) }}
|
{{ formatDate(event.startDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,8 +86,8 @@
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<Icon name="heroicons:clock" class="w-6 h-6 text-blue-400" />
|
<Icon name="heroicons:clock" class="w-6 h-6 text-blue-400" />
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-stone-400">Time</p>
|
<p class="text-sm text-ghost-400">Time</p>
|
||||||
<p class="font-semibold text-stone-100">
|
<p class="font-semibold text-ghost-100">
|
||||||
{{ formatTime(event.startDate, event.endDate) }}
|
{{ formatTime(event.startDate, event.endDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -96,8 +96,8 @@
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<Icon name="heroicons:map-pin" class="w-6 h-6 text-blue-400" />
|
<Icon name="heroicons:map-pin" class="w-6 h-6 text-blue-400" />
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-stone-400">Location</p>
|
<p class="text-sm text-ghost-400">Location</p>
|
||||||
<p class="font-semibold text-stone-100">
|
<p class="font-semibold text-ghost-100">
|
||||||
{{ event.location }}
|
{{ event.location }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<Icon name="heroicons:user-group" class="w-5 h-5 text-blue-400" />
|
<Icon name="heroicons:user-group" class="w-5 h-5 text-blue-400" />
|
||||||
<span class="text-sm font-medium text-stone-200"
|
<span class="text-sm font-medium text-ghost-200"
|
||||||
>Recommended for:</span
|
>Recommended for:</span
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
|
|
@ -170,15 +170,15 @@
|
||||||
|
|
||||||
<!-- Event Description -->
|
<!-- Event Description -->
|
||||||
<div class="prose prose-lg dark:prose-invert max-w-none mb-12">
|
<div class="prose prose-lg dark:prose-invert max-w-none mb-12">
|
||||||
<h2 class="text-2xl font-bold text-stone-100 mb-4">
|
<h2 class="text-2xl font-bold text-ghost-100 mb-4">
|
||||||
About This Event
|
About This Event
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-stone-200">
|
<p class="text-ghost-200">
|
||||||
{{ event.description }}
|
{{ event.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="event.agenda && event.agenda.length > 0" class="mt-8">
|
<div v-if="event.agenda && event.agenda.length > 0" class="mt-8">
|
||||||
<h3 class="text-xl font-semibold text-stone-100 mb-4">
|
<h3 class="text-xl font-semibold text-ghost-100 mb-4">
|
||||||
Event Agenda
|
Event Agenda
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="space-y-3">
|
<ul class="space-y-3">
|
||||||
|
|
@ -192,7 +192,7 @@
|
||||||
>
|
>
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-stone-200">{{ item }}</span>
|
<span class="text-ghost-200">{{ item }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -201,7 +201,7 @@
|
||||||
v-if="event.speakers && event.speakers.length > 0"
|
v-if="event.speakers && event.speakers.length > 0"
|
||||||
class="mt-8"
|
class="mt-8"
|
||||||
>
|
>
|
||||||
<h3 class="text-xl font-semibold text-stone-100 mb-4">
|
<h3 class="text-xl font-semibold text-ghost-100 mb-4">
|
||||||
Speakers
|
Speakers
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
|
@ -211,21 +211,21 @@
|
||||||
class="flex items-start space-x-4"
|
class="flex items-start space-x-4"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-16 h-16 bg-stone-700 rounded-full flex items-center justify-center"
|
class="w-16 h-16 bg-ghost-700 rounded-full flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:user"
|
name="heroicons:user"
|
||||||
class="w-8 h-8 text-stone-500"
|
class="w-8 h-8 text-ghost-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold text-stone-100">
|
<p class="font-semibold text-ghost-100">
|
||||||
{{ speaker.name }}
|
{{ speaker.name }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-stone-300">
|
<p class="text-sm text-ghost-300">
|
||||||
{{ speaker.role }}
|
{{ speaker.role }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-stone-400 mt-1">
|
<p class="text-sm text-ghost-400 mt-1">
|
||||||
{{ speaker.bio }}
|
{{ speaker.bio }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -237,9 +237,9 @@
|
||||||
<!-- Registration Section -->
|
<!-- Registration Section -->
|
||||||
<div
|
<div
|
||||||
v-if="!event.isCancelled"
|
v-if="!event.isCancelled"
|
||||||
class="bg-stone-800 rounded-xl p-8 border border-stone-700"
|
class="bg-ghost-800 rounded-xl p-8 border border-ghost-700"
|
||||||
>
|
>
|
||||||
<h3 class="text-xl font-bold text-stone-100 mb-6">
|
<h3 class="text-xl font-bold text-ghost-100 mb-6">
|
||||||
Register for This Event
|
Register for This Event
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -322,7 +322,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="name"
|
for="name"
|
||||||
class="block text-sm font-medium text-stone-200 mb-2"
|
class="block text-sm font-medium text-ghost-200 mb-2"
|
||||||
>
|
>
|
||||||
Full Name
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="email"
|
for="email"
|
||||||
class="block text-sm font-medium text-stone-200 mb-2"
|
class="block text-sm font-medium text-ghost-200 mb-2"
|
||||||
>
|
>
|
||||||
Email Address
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -354,7 +354,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="membershipLevel"
|
for="membershipLevel"
|
||||||
class="block text-sm font-medium text-stone-200 mb-2"
|
class="block text-sm font-medium text-ghost-200 mb-2"
|
||||||
>
|
>
|
||||||
Membership Status
|
Membership Status
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -388,16 +388,16 @@
|
||||||
<!-- Event Capacity -->
|
<!-- Event Capacity -->
|
||||||
<div
|
<div
|
||||||
v-if="event.maxAttendees"
|
v-if="event.maxAttendees"
|
||||||
class="mt-6 pt-6 border-t border-stone-700"
|
class="mt-6 pt-6 border-t border-ghost-700"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm text-stone-300">Event Capacity</span>
|
<span class="text-sm text-ghost-300">Event Capacity</span>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span class="text-sm font-semibold text-stone-100">
|
<span class="text-sm font-semibold text-ghost-100">
|
||||||
{{ event.registeredCount || 0 }} / {{ event.maxAttendees }}
|
{{ event.registeredCount || 0 }} / {{ event.maxAttendees }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="w-24 h-2 bg-stone-700 rounded-full overflow-hidden"
|
class="w-24 h-2 bg-ghost-700 rounded-full overflow-hidden"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="h-full bg-blue-500 rounded-full"
|
class="h-full bg-blue-500 rounded-full"
|
||||||
|
|
@ -410,9 +410,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Additional Information -->
|
<!-- Additional Information -->
|
||||||
<div class="mt-8 p-6 bg-stone-800 rounded-xl border border-stone-700">
|
<div class="mt-8 p-6 bg-ghost-800 rounded-xl border border-ghost-700">
|
||||||
<h4 class="font-semibold text-stone-100 mb-3">Questions?</h4>
|
<h4 class="font-semibold text-ghost-100 mb-3">Questions?</h4>
|
||||||
<p class="text-sm text-stone-200 mb-3">
|
<p class="text-sm text-ghost-200 mb-3">
|
||||||
If you have any questions about this event please drop us a line.
|
If you have any questions about this event please drop us a line.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Events Section with Tabs -->
|
<!-- Events Section with Tabs -->
|
||||||
<section class="py-20 bg-stone-900 dark:bg-stone-950">
|
<section class="py-20 bg-ghost-900 dark:bg-ghost-950">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<UTabs
|
<UTabs
|
||||||
v-model="activeTab"
|
v-model="activeTab"
|
||||||
|
|
@ -27,10 +27,10 @@
|
||||||
class="group flex items-start gap-4 py-2 hover:opacity-80 transition-opacity"
|
class="group flex items-start gap-4 py-2 hover:opacity-80 transition-opacity"
|
||||||
>
|
>
|
||||||
<div class="flex-shrink-0 text-center">
|
<div class="flex-shrink-0 text-center">
|
||||||
<div class="text-2xl font-bold text-stone-100">
|
<div class="text-2xl font-bold text-ghost-100">
|
||||||
{{ event.start.getDate() }}
|
{{ event.start.getDate() }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-stone-400 uppercase">
|
<div class="text-xs text-ghost-400 uppercase">
|
||||||
{{
|
{{
|
||||||
event.start.toLocaleDateString("en-US", {
|
event.start.toLocaleDateString("en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-start gap-2 mb-1">
|
<div class="flex items-start gap-2 mb-1">
|
||||||
<h3
|
<h3
|
||||||
class="text-lg font-semibold text-stone-100 group-hover:text-blue-400 transition-colors"
|
class="text-lg font-semibold text-ghost-100 group-hover:text-blue-400 transition-colors"
|
||||||
>
|
>
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-sm text-stone-300 mb-2 line-clamp-2">
|
<p class="text-sm text-ghost-300 mb-2 line-clamp-2">
|
||||||
{{ event.content }}
|
{{ event.content }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:arrow-right"
|
name="heroicons:arrow-right"
|
||||||
class="w-5 h-5 text-stone-400 group-hover:text-blue-400 group-hover:translate-x-1 transition-all flex-shrink-0 mt-1"
|
class="w-5 h-5 text-ghost-400 group-hover:text-blue-400 group-hover:translate-x-1 transition-all flex-shrink-0 mt-1"
|
||||||
/>
|
/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -83,10 +83,10 @@
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<div
|
<div
|
||||||
v-if="pending"
|
v-if="pending"
|
||||||
class="min-h-[400px] bg-stone-700 rounded-xl flex items-center justify-center"
|
class="min-h-[400px] bg-ghost-700 rounded-xl flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-stone-200">Loading events...</p>
|
<p class="text-ghost-200">Loading events...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VueCal
|
<VueCal
|
||||||
|
|
@ -110,13 +110,13 @@
|
||||||
/>
|
/>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<div
|
<div
|
||||||
class="min-h-[400px] bg-stone-700 rounded-xl flex items-center justify-center"
|
class="min-h-[400px] bg-ghost-700 rounded-xl flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"
|
class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-stone-200">Loading calendar...</p>
|
<p class="text-ghost-200">Loading calendar...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -130,14 +130,14 @@
|
||||||
<!-- Event Series -->
|
<!-- Event Series -->
|
||||||
<section
|
<section
|
||||||
v-if="activeSeries.length > 0"
|
v-if="activeSeries.length > 0"
|
||||||
class="py-20 bg-stone-800 dark:bg-stone-900"
|
class="py-20 bg-ghost-800 dark:bg-ghost-900"
|
||||||
>
|
>
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-3xl font-bold text-stone-100 mb-8">
|
<h2 class="text-3xl font-bold text-ghost-100 mb-8">
|
||||||
Active Event Series
|
Active Event Series
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-stone-300 max-w-2xl mx-auto">
|
<p class="text-ghost-300 max-w-2xl mx-auto">
|
||||||
Multi-part workshops and recurring events designed to deepen your
|
Multi-part workshops and recurring events designed to deepen your
|
||||||
knowledge and build community connections.
|
knowledge and build community connections.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
<div
|
<div
|
||||||
v-for="series in activeSeries.slice(0, 6)"
|
v-for="series in activeSeries.slice(0, 6)"
|
||||||
:key="series.id"
|
:key="series.id"
|
||||||
class="bg-stone-900 rounded-xl p-6 shadow-lg border border-stone-700"
|
class="bg-ghost-900 rounded-xl p-6 shadow-lg border border-ghost-700"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between mb-4">
|
<div class="flex items-start justify-between mb-4">
|
||||||
<div
|
<div
|
||||||
|
|
@ -160,17 +160,17 @@
|
||||||
>
|
>
|
||||||
{{ formatSeriesType(series.type) }}
|
{{ formatSeriesType(series.type) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 text-xs text-stone-400">
|
<div class="flex items-center gap-1 text-xs text-ghost-400">
|
||||||
<Icon name="heroicons:calendar-days" class="w-4 h-4" />
|
<Icon name="heroicons:calendar-days" class="w-4 h-4" />
|
||||||
<span>{{ series.eventCount }} events</span>
|
<span>{{ series.eventCount }} events</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-stone-100 mb-2">
|
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
||||||
{{ series.title }}
|
{{ series.title }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="text-sm text-stone-300 mb-4 line-clamp-2">
|
<p class="text-sm text-ghost-300 mb-4 line-clamp-2">
|
||||||
{{ series.description }}
|
{{ series.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -186,22 +186,22 @@
|
||||||
>
|
>
|
||||||
{{ event.series?.position || "?" }}
|
{{ event.series?.position || "?" }}
|
||||||
</div>
|
</div>
|
||||||
<span class="text-stone-300 truncate">{{ event.title }}</span>
|
<span class="text-ghost-300 truncate">{{ event.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-stone-400">
|
<span class="text-ghost-400">
|
||||||
{{ formatEventDate(event.startDate) }}
|
{{ formatEventDate(event.startDate) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="series.events.length > 3"
|
v-if="series.events.length > 3"
|
||||||
class="text-xs text-stone-400 text-center pt-1"
|
class="text-xs text-ghost-400 text-center pt-1"
|
||||||
>
|
>
|
||||||
+{{ series.events.length - 3 }} more events
|
+{{ series.events.length - 3 }} more events
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between text-sm">
|
<div class="flex items-center justify-between text-sm">
|
||||||
<div class="text-stone-400">
|
<div class="text-ghost-400">
|
||||||
{{ formatDateRange(series.startDate, series.endDate) }}
|
{{ formatDateRange(series.startDate, series.endDate) }}
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
|
|
@ -223,20 +223,20 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Attend Our Events -->
|
<!-- Attend Our Events -->
|
||||||
<section class="py-20 bg-stone-800 dark:bg-stone-900">
|
<section class="py-20 bg-ghost-800 dark:bg-ghost-900">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="text-center mb-16">
|
<div class="text-center mb-16">
|
||||||
<h2 class="text-3xl font-bold text-stone-100 mb-8">
|
<h2 class="text-3xl font-bold text-ghost-100 mb-8">
|
||||||
Attend Our Events
|
Attend Our Events
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<div
|
<div
|
||||||
class="bg-stone-900 rounded-2xl p-8 border border-stone-700 mb-12"
|
class="bg-ghost-900 rounded-2xl p-8 border border-ghost-700 mb-12"
|
||||||
>
|
>
|
||||||
<div class="prose prose-lg dark:prose-invert max-w-none">
|
<div class="prose prose-lg dark:prose-invert max-w-none">
|
||||||
<p class="text-lg leading-relaxed text-stone-200 mb-6">
|
<p class="text-lg leading-relaxed text-ghost-200 mb-6">
|
||||||
Our events are ,Lorem ipsum, dolor sit amet consectetur
|
Our events are ,Lorem ipsum, dolor sit amet consectetur
|
||||||
adipisicing elit. Quibusdam exercitationem delectus ab
|
adipisicing elit. Quibusdam exercitationem delectus ab
|
||||||
voluptates aspernatur, quia deleniti aut maxime, veniam
|
voluptates aspernatur, quia deleniti aut maxime, veniam
|
||||||
|
|
@ -244,7 +244,7 @@
|
||||||
dolorum alias nulla!
|
dolorum alias nulla!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="text-lg leading-relaxed text-stone-200 mb-6">
|
<p class="text-lg leading-relaxed text-ghost-200 mb-6">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||||
|
|
@ -265,31 +265,31 @@
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="text-lg font-semibold text-stone-100 mb-2">
|
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
||||||
Monthly Meetups
|
Monthly Meetups
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="text-sm text-stone-300">
|
<p class="text-sm text-ghost-300">
|
||||||
Casual knowledge sharing sessions
|
Casual knowledge sharing sessions
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="text-lg font-semibold text-stone-100 mb-2">
|
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
||||||
Workshops
|
Workshops
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="text-sm text-stone-300">
|
<p class="text-sm text-ghost-300">
|
||||||
Hands-on learning about cooperative and worker-centric business
|
Hands-on learning about cooperative and worker-centric business
|
||||||
models
|
models
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="text-lg font-semibold text-stone-100 mb-2">
|
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
||||||
Social Events
|
Social Events
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-stone-300">
|
<p class="text-sm text-ghost-300">
|
||||||
Game nights, socials, and more
|
Game nights, socials, and more
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<!-- Large artistic title -->
|
<!-- Large artistic title -->
|
||||||
<h1
|
<h1
|
||||||
class="text-6xl md:text-8xl font-bold text-stone-100 ethereal-text leading-tight mb-8"
|
class="text-6xl md:text-8xl font-bold text-ghost-100 ethereal-text leading-tight mb-8"
|
||||||
>
|
>
|
||||||
Become a Ghostie
|
Become a Ghostie
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- Floating subtitle -->
|
<!-- Floating subtitle -->
|
||||||
<div class="mb-16">
|
<div class="mb-16">
|
||||||
<p class="text-stone-100 text-lg max-w-md">
|
<p class="text-ghost-100 text-lg max-w-md">
|
||||||
A community for creatives and game devs<br />
|
A community for creatives and game devs<br />
|
||||||
exploring cooperative models
|
exploring cooperative models
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
<div>
|
<div>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/join"
|
to="/join"
|
||||||
class="inline-block px-8 py-3 border border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500 hover:ethereal-text transition-all duration-500"
|
class="inline-block px-8 py-3 border border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 hover:ethereal-text transition-all duration-500"
|
||||||
>
|
>
|
||||||
Join Us Today →
|
Join Us Today →
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -55,13 +55,13 @@
|
||||||
>
|
>
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="flex-1 max-w-lg">
|
<div class="flex-1 max-w-lg">
|
||||||
<h3 class="text-xl text-stone-100 mb-3">{{ circle.label }}</h3>
|
<h3 class="text-xl text-ghost-100 mb-3">{{ circle.label }}</h3>
|
||||||
<p class="text-stone-200 text-sm leading-relaxed mb-4">
|
<p class="text-ghost-200 text-sm leading-relaxed mb-4">
|
||||||
{{ circle.description }}
|
{{ circle.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Features as inline text -->
|
<!-- Features as inline text -->
|
||||||
<div class="text-sm text-stone-400">
|
<div class="text-sm text-ghost-400">
|
||||||
<span v-for="(feature, i) in circle.features" :key="feature">
|
<span v-for="(feature, i) in circle.features" :key="feature">
|
||||||
{{ feature
|
{{ feature
|
||||||
}}<span v-if="i < circle.features.length - 1"> • </span>
|
}}<span v-if="i < circle.features.length - 1"> • </span>
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
<!-- Why Join? - Diagonal Layout -->
|
<!-- Why Join? - Diagonal Layout -->
|
||||||
<section class="mb-32 relative">
|
<section class="mb-32 relative">
|
||||||
<div class="transform -rotate-1">
|
<div class="transform -rotate-1">
|
||||||
<h2 class="text-3xl font-light text-stone-200 mb-12">Why Join?</h2>
|
<h2 class="text-3xl font-light text-ghost-200 mb-12">Why Join?</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-12 relative">
|
<div class="ml-12 relative">
|
||||||
|
|
@ -86,11 +86,11 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
<p class="text-stone-300 leading-loose text-lg mb-8">
|
<p class="text-ghost-300 leading-loose text-lg mb-8">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="text-stone-400 leading-relaxed ml-8">
|
<p class="text-ghost-400 leading-relaxed ml-8">
|
||||||
Sed do eiusmod tempor incididunt ut labore et dolore magna
|
Sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||||
aliqua.<br />
|
aliqua.<br />
|
||||||
Ut enim ad minim veniam, quis nostrud exercitation.
|
Ut enim ad minim veniam, quis nostrud exercitation.
|
||||||
|
|
@ -98,7 +98,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="absolute -bottom-8 right-0 text-6xl text-stone-800 opacity-20 font-bold"
|
class="absolute -bottom-8 right-0 text-6xl text-ghost-800 opacity-20 font-bold"
|
||||||
>
|
>
|
||||||
?
|
?
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Membership Sign Up Form -->
|
<!-- Membership Sign Up Form -->
|
||||||
<section class="py-20 bg-white dark:bg-gray-900">
|
<section class="py-20 bg-[--ui-bg]">
|
||||||
<UContainer class="max-w-4xl">
|
<UContainer class="max-w-4xl">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-3xl font-bold text-[--ui-text] mb-4">
|
||||||
Membership Sign Up
|
Membership Sign Up
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-gray-700 dark:text-gray-300">
|
<p class="text-lg text-[--ui-text]">
|
||||||
Choose your circle to connect with others at your stage. Choose your
|
Choose your circle to connect with others at your stage. Choose your
|
||||||
contribution based on what you can afford. Everyone gets full
|
contribution based on what you can afford. Everyone gets full
|
||||||
access.
|
access.
|
||||||
|
|
@ -30,8 +30,8 @@
|
||||||
:class="[
|
:class="[
|
||||||
'w-10 h-10 rounded-full flex items-center justify-center font-semibold',
|
'w-10 h-10 rounded-full flex items-center justify-center font-semibold',
|
||||||
currentStep >= 1
|
currentStep >= 1
|
||||||
? 'bg-gray-900 dark:bg-white text-white dark:text-gray-900'
|
? 'bg-neutral-900 text-neutral-50'
|
||||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-500',
|
: 'bg-neutral-200 text-neutral-500',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
1
|
1
|
||||||
|
|
@ -39,21 +39,16 @@
|
||||||
<span
|
<span
|
||||||
class="ml-2 font-medium"
|
class="ml-2 font-medium"
|
||||||
:class="
|
:class="
|
||||||
currentStep === 1
|
currentStep === 1 ? 'text-[--ui-text]' : 'text-neutral-500'
|
||||||
? 'text-gray-900 dark:text-white'
|
|
||||||
: 'text-gray-500'
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Information
|
Information
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div v-if="needsPayment" class="w-16 h-1 bg-neutral-200">
|
||||||
v-if="needsPayment"
|
|
||||||
class="w-16 h-1 bg-gray-200 dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="h-full bg-gray-900 dark:bg-white transition-all"
|
class="h-full bg-neutral-900 transition-all"
|
||||||
:style="{ width: currentStep >= 2 ? '100%' : '0%' }"
|
:style="{ width: currentStep >= 2 ? '100%' : '0%' }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,8 +58,8 @@
|
||||||
:class="[
|
:class="[
|
||||||
'w-10 h-10 rounded-full flex items-center justify-center font-semibold',
|
'w-10 h-10 rounded-full flex items-center justify-center font-semibold',
|
||||||
currentStep >= 2
|
currentStep >= 2
|
||||||
? 'bg-gray-900 dark:bg-white text-white dark:text-gray-900'
|
? 'bg-neutral-900 text-neutral-50'
|
||||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-500',
|
: 'bg-neutral-200 text-neutral-500',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
2
|
2
|
||||||
|
|
@ -72,18 +67,16 @@
|
||||||
<span
|
<span
|
||||||
class="ml-2 font-medium"
|
class="ml-2 font-medium"
|
||||||
:class="
|
:class="
|
||||||
currentStep === 2
|
currentStep === 2 ? 'text-[--ui-text]' : 'text-neutral-500'
|
||||||
? 'text-gray-900 dark:text-white'
|
|
||||||
: 'text-gray-500'
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Payment
|
Payment
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-16 h-1 bg-gray-200 dark:bg-gray-700">
|
<div class="w-16 h-1 bg-neutral-200">
|
||||||
<div
|
<div
|
||||||
class="h-full bg-gray-900 dark:bg-white transition-all"
|
class="h-full bg-neutral-900 transition-all"
|
||||||
:style="{ width: currentStep >= 3 ? '100%' : '0%' }"
|
:style="{ width: currentStep >= 3 ? '100%' : '0%' }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -93,8 +86,8 @@
|
||||||
:class="[
|
:class="[
|
||||||
'w-10 h-10 rounded-full flex items-center justify-center font-semibold',
|
'w-10 h-10 rounded-full flex items-center justify-center font-semibold',
|
||||||
currentStep >= 3
|
currentStep >= 3
|
||||||
? 'bg-gray-900 dark:bg-white text-white dark:text-gray-900'
|
? 'bg-neutral-900 text-neutral-50'
|
||||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-500',
|
: 'bg-neutral-200 text-neutral-500',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<span v-if="needsPayment">3</span>
|
<span v-if="needsPayment">3</span>
|
||||||
|
|
@ -103,9 +96,7 @@
|
||||||
<span
|
<span
|
||||||
class="ml-2 font-medium"
|
class="ml-2 font-medium"
|
||||||
:class="
|
:class="
|
||||||
currentStep === 3
|
currentStep === 3 ? 'text-[--ui-text]' : 'text-neutral-500'
|
||||||
? 'text-gray-900 dark:text-white'
|
|
||||||
: 'text-gray-500'
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Confirmation
|
Confirmation
|
||||||
|
|
@ -120,7 +111,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 1: Information -->
|
<!-- Step 1: Information -->
|
||||||
<div v-if="currentStep === 1" class="bg-white dark:bg-gray-800">
|
<div v-if="currentStep === 1" class="bg-[--ui-bg-elevated]">
|
||||||
<UForm :state="form" class="space-y-8" @submit="handleSubmit">
|
<UForm :state="form" class="space-y-8" @submit="handleSubmit">
|
||||||
<!-- Personal Information -->
|
<!-- Personal Information -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
|
@ -154,8 +145,8 @@
|
||||||
class="flex flex-col p-6 rounded-lg border-2 cursor-pointer transition-all hover:shadow-md"
|
class="flex flex-col p-6 rounded-lg border-2 cursor-pointer transition-all hover:shadow-md"
|
||||||
:class="
|
:class="
|
||||||
form.circle === option.value
|
form.circle === option.value
|
||||||
? 'border-gray-900 dark:border-white bg-gray-50 dark:bg-gray-800'
|
? 'border-neutral-900 bg-[--ui-bg]'
|
||||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-500'
|
: 'border-neutral-200 hover:border-neutral-400'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|
@ -166,12 +157,12 @@
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
/>
|
/>
|
||||||
<div class="font-medium text-lg mb-2">{{ option.label }}</div>
|
<div class="font-medium text-lg mb-2">{{ option.label }}</div>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
<div class="text-sm text-[--ui-text-muted]">
|
||||||
{{ option.description }}
|
{{ option.description }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-3 italic">
|
<p class="text-sm text-[--ui-text-muted] mt-3 italic">
|
||||||
Not sure? Start with Community - you can always move.
|
Not sure? Start with Community - you can always move.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -213,23 +204,23 @@
|
||||||
<!-- Step 2: Payment -->
|
<!-- Step 2: Payment -->
|
||||||
<div
|
<div
|
||||||
v-if="currentStep === 2"
|
v-if="currentStep === 2"
|
||||||
class="bg-white dark:bg-gray-800 rounded-xl p-8"
|
class="bg-[--ui-bg-elevated] rounded-xl p-8"
|
||||||
>
|
>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
<h3 class="text-2xl font-bold text-[--ui-text] mb-2">
|
||||||
Payment Information
|
Payment Information
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
<p class="text-[--ui-text-muted]">
|
||||||
You're signing up for the {{ selectedTier.label }} plan
|
You're signing up for the {{ selectedTier.label }} plan
|
||||||
</p>
|
</p>
|
||||||
<p class="text-lg font-semibold text-gray-900 dark:text-white mt-2">
|
<p class="text-lg font-semibold text-[--ui-text] mt-2">
|
||||||
${{ selectedTier.amount }} CAD / month
|
${{ selectedTier.amount }} CAD / month
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Payment Instructions -->
|
<!-- Payment Instructions -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 mb-6">
|
<div class="bg-[--ui-bg] rounded-lg p-6 mb-6">
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-[--ui-text]">
|
||||||
Click "Complete Payment" below to open the secure payment modal
|
Click "Complete Payment" below to open the secure payment modal
|
||||||
and verify your payment method.
|
and verify your payment method.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -254,11 +245,11 @@
|
||||||
<!-- Step 3: Confirmation -->
|
<!-- Step 3: Confirmation -->
|
||||||
<div
|
<div
|
||||||
v-if="currentStep === 3"
|
v-if="currentStep === 3"
|
||||||
class="bg-white dark:bg-gray-800 rounded-xl p-8"
|
class="bg-[--ui-bg-elevated] rounded-xl p-8"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="w-20 h-20 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-6"
|
class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="w-10 h-10 text-green-500"
|
class="w-10 h-10 text-green-500"
|
||||||
|
|
@ -275,7 +266,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
<h3 class="text-2xl font-bold text-[--ui-text] mb-4">
|
||||||
Welcome to Ghost Guild!
|
Welcome to Ghost Guild!
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -283,43 +274,39 @@
|
||||||
<UAlert color="green" :title="successMessage" />
|
<UAlert color="green" :title="successMessage" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="bg-[--ui-bg] rounded-lg p-6 mb-6 text-left">
|
||||||
class="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 mb-6 text-left"
|
|
||||||
>
|
|
||||||
<h4 class="font-semibold mb-3">Membership Details:</h4>
|
<h4 class="font-semibold mb-3">Membership Details:</h4>
|
||||||
<dl class="space-y-2">
|
<dl class="space-y-2">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Name:</dt>
|
<dt class="text-[--ui-text-muted]">Name:</dt>
|
||||||
<dd class="font-medium">{{ form.name }}</dd>
|
<dd class="font-medium">{{ form.name }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Email:</dt>
|
<dt class="text-[--ui-text-muted]">Email:</dt>
|
||||||
<dd class="font-medium">{{ form.email }}</dd>
|
<dd class="font-medium">{{ form.email }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Circle:</dt>
|
<dt class="text-[--ui-text-muted]">Circle:</dt>
|
||||||
<dd class="font-medium capitalize">{{ form.circle }}</dd>
|
<dd class="font-medium capitalize">{{ form.circle }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">
|
<dt class="text-[--ui-text-muted]">Contribution:</dt>
|
||||||
Contribution:
|
|
||||||
</dt>
|
|
||||||
<dd class="font-medium">{{ selectedTier.label }}</dd>
|
<dd class="font-medium">{{ selectedTier.label }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="customerCode" class="flex justify-between">
|
<div v-if="customerCode" class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Member ID:</dt>
|
<dt class="text-[--ui-text-muted]">Member ID:</dt>
|
||||||
<dd class="font-medium">{{ customerCode }}</dd>
|
<dd class="font-medium">{{ customerCode }}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-[--ui-text-muted] mb-4">
|
||||||
We've sent a confirmation email to {{ form.email }} with your
|
We've sent a confirmation email to {{ form.email }} with your
|
||||||
membership details.
|
membership details.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4 mb-8">
|
<div class="bg-[--ui-bg] rounded-lg p-4 mb-8">
|
||||||
<p class="text-gray-700 dark:text-gray-300 text-center">
|
<p class="text-[--ui-text] text-center">
|
||||||
You will be automatically redirected to your dashboard in a few
|
You will be automatically redirected to your dashboard in a few
|
||||||
seconds...
|
seconds...
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -339,14 +326,14 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- How Ghost Guild Works -->
|
<!-- How Ghost Guild Works -->
|
||||||
<section class="py-20 bg-gray-50 dark:bg-gray-800">
|
<section class="py-20 bg-[--ui-bg-elevated]">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-3xl font-bold text-[--ui-text] mb-4">
|
||||||
How Ghost Guild Works
|
How Ghost Guild Works
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-gray-700 dark:text-gray-300">
|
<p class="text-lg text-[--ui-text]">
|
||||||
Every member gets everything. Your circle helps you find relevant
|
Every member gets everything. Your circle helps you find relevant
|
||||||
content and peers. Your contribution helps sustain our solidarity
|
content and peers. Your contribution helps sustain our solidarity
|
||||||
economy.
|
economy.
|
||||||
|
|
@ -355,13 +342,11 @@
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
<!-- Full Access -->
|
<!-- Full Access -->
|
||||||
<div class="bg-white dark:bg-gray-900 rounded-xl p-6">
|
<div class="bg-[--ui-bg] rounded-xl p-6">
|
||||||
<h3
|
<h3 class="text-xl font-semibold mb-4 text-[--ui-text]">
|
||||||
class="text-xl font-semibold mb-4 text-gray-900 dark:text-white"
|
|
||||||
>
|
|
||||||
Full Access
|
Full Access
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="text-gray-700 dark:text-gray-300 space-y-2">
|
<ul class="text-[--ui-text] space-y-2">
|
||||||
<li>Complete resource library</li>
|
<li>Complete resource library</li>
|
||||||
<li>All workshops and events</li>
|
<li>All workshops and events</li>
|
||||||
<li>Slack community</li>
|
<li>Slack community</li>
|
||||||
|
|
@ -371,13 +356,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Circle-Specific Guidance -->
|
<!-- Circle-Specific Guidance -->
|
||||||
<div class="bg-white dark:bg-gray-900 rounded-xl p-6">
|
<div class="bg-[--ui-bg] rounded-xl p-6">
|
||||||
<h3
|
<h3 class="text-xl font-semibold mb-4 text-[--ui-text]">
|
||||||
class="text-xl font-semibold mb-4 text-gray-900 dark:text-white"
|
|
||||||
>
|
|
||||||
Circle-Specific Guidance
|
Circle-Specific Guidance
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="text-gray-700 dark:text-gray-300 space-y-2">
|
<ul class="text-[--ui-text] space-y-2">
|
||||||
<li>Curated resources for your stage</li>
|
<li>Curated resources for your stage</li>
|
||||||
<li>Connection with peers on similar journeys</li>
|
<li>Connection with peers on similar journeys</li>
|
||||||
<li>Relevant workshop recommendations</li>
|
<li>Relevant workshop recommendations</li>
|
||||||
|
|
@ -390,25 +373,23 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- How to Join -->
|
<!-- How to Join -->
|
||||||
<section class="py-20 bg-white dark:bg-gray-900">
|
<section class="py-20 bg-[--ui-bg]">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div class="flex items-start gap-6">
|
<div class="flex items-start gap-6">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-gray-900 dark:bg-white rounded-full flex items-center justify-center text-white dark:text-gray-900 font-bold text-xl"
|
class="w-12 h-12 bg-neutral-900 rounded-full flex items-center justify-center text-neutral-50 font-bold text-xl"
|
||||||
>
|
>
|
||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3
|
<h3 class="text-xl font-semibold mb-2 text-[--ui-text]">
|
||||||
class="text-xl font-semibold mb-2 text-gray-900 dark:text-white"
|
|
||||||
>
|
|
||||||
Pick your circle
|
Pick your circle
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-[--ui-text]">
|
||||||
Where are you in your co-op journey? Select based on where you
|
Where are you in your co-op journey? Select based on where you
|
||||||
are in your cooperative journey - exploring, building, or
|
are in your cooperative journey - exploring, building, or
|
||||||
practicing. Not sure? Start with Community.
|
practicing. Not sure? Start with Community.
|
||||||
|
|
@ -419,18 +400,16 @@
|
||||||
<div class="flex items-start gap-6">
|
<div class="flex items-start gap-6">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-gray-900 dark:bg-white rounded-full flex items-center justify-center text-white dark:text-gray-900 font-bold text-xl"
|
class="w-12 h-12 bg-neutral-900 rounded-full flex items-center justify-center text-neutral-50 font-bold text-xl"
|
||||||
>
|
>
|
||||||
2
|
2
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3
|
<h3 class="text-xl font-semibold mb-2 text-[--ui-text]">
|
||||||
class="text-xl font-semibold mb-2 text-gray-900 dark:text-white"
|
|
||||||
>
|
|
||||||
Choose your contribution
|
Choose your contribution
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-[--ui-text]">
|
||||||
What can you afford? ($0-50+/month) Choose based on your
|
What can you afford? ($0-50+/month) Choose based on your
|
||||||
financial capacity. From $0 for those who need support to $50+
|
financial capacity. From $0 for those who need support to $50+
|
||||||
for those who can sponsor others. You can adjust anytime.
|
for those who can sponsor others. You can adjust anytime.
|
||||||
|
|
@ -441,18 +420,16 @@
|
||||||
<div class="flex items-start gap-6">
|
<div class="flex items-start gap-6">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-gray-900 dark:bg-white rounded-full flex items-center justify-center text-white dark:text-gray-900 font-bold text-xl"
|
class="w-12 h-12 bg-neutral-900 rounded-full flex items-center justify-center text-neutral-50 font-bold text-xl"
|
||||||
>
|
>
|
||||||
3
|
3
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3
|
<h3 class="text-xl font-semibold mb-2 text-[--ui-text]">
|
||||||
class="text-xl font-semibold mb-2 text-gray-900 dark:text-white"
|
|
||||||
>
|
|
||||||
Join the community
|
Join the community
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-[--ui-text]">
|
||||||
Get instant access to everything. Fill out your profile, agree
|
Get instant access to everything. Fill out your profile, agree
|
||||||
to our community guidelines, and complete payment (if
|
to our community guidelines, and complete payment (if
|
||||||
applicable). You'll get instant access to our community.
|
applicable). You'll get instant access to our community.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- Page Header -->
|
<!-- Page Header -->
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Login"
|
title="Login"
|
||||||
subtitle="Welcome back! Sign in to access your Ghost Guild account and connect with the cooperative community."
|
subtitle="Welcome back! Sign in to access your Ghost Guild account and connect with the cooperative community."
|
||||||
theme="blue"
|
theme="blue"
|
||||||
|
|
@ -9,18 +9,20 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Login Form -->
|
<!-- Login Form -->
|
||||||
<section class="py-20 bg-white dark:bg-gray-900">
|
<section class="py-20 bg-[--ui-bg]">
|
||||||
<UContainer class="max-w-md">
|
<UContainer class="max-w-md">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-4">
|
<h2 class="text-3xl font-bold text-primary-500 mb-4">
|
||||||
Passwordless Login
|
Passwordless Login
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-600 dark:text-gray-300">
|
<p class="text-[--ui-text-muted]">
|
||||||
Enter your email to receive a secure login link
|
Enter your email to receive a secure login link
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-xl border border-blue-200 dark:border-blue-800">
|
<div
|
||||||
|
class="bg-[--ui-bg-elevated] rounded-2xl p-8 shadow-xl border border-primary-200"
|
||||||
|
>
|
||||||
<UForm :state="loginForm" class="space-y-6" @submit="handleLogin">
|
<UForm :state="loginForm" class="space-y-6" @submit="handleLogin">
|
||||||
<!-- Email Field -->
|
<!-- Email Field -->
|
||||||
<UFormField label="Email Address" name="email" required>
|
<UFormField label="Email Address" name="email" required>
|
||||||
|
|
@ -34,7 +36,7 @@
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<!-- Passwordless Info -->
|
<!-- Passwordless Info -->
|
||||||
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-800">
|
<div class="bg-primary-50 p-4 rounded-lg border border-primary-200">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<div class="space-y-1 flex-shrink-0 mt-1">
|
<div class="space-y-1 flex-shrink-0 mt-1">
|
||||||
<div class="w-2 h-2 bg-blue-500 rounded-full" />
|
<div class="w-2 h-2 bg-blue-500 rounded-full" />
|
||||||
|
|
@ -46,8 +48,9 @@
|
||||||
<div class="h-1 bg-blue-200 rounded-full w-1/2" />
|
<div class="h-1 bg-blue-200 rounded-full w-1/2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-blue-700 dark:text-blue-300 text-sm mt-3">
|
<p class="text-primary-700 text-sm mt-3">
|
||||||
We'll send you a secure login link via email. No password needed!
|
We'll send you a secure login link via email. No password
|
||||||
|
needed!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -66,23 +69,31 @@
|
||||||
</UForm>
|
</UForm>
|
||||||
|
|
||||||
<!-- Success/Error Messages -->
|
<!-- Success/Error Messages -->
|
||||||
<div v-if="loginSuccess" class="mt-6 p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
|
<div
|
||||||
<p class="text-green-700 dark:text-green-300 text-center">
|
v-if="loginSuccess"
|
||||||
✅ Magic link sent! Check your email and click the link to sign in.
|
class="mt-6 p-4 bg-green-50 rounded-lg border border-green-200"
|
||||||
|
>
|
||||||
|
<p class="text-green-700 text-center">
|
||||||
|
✅ Magic link sent! Check your email and click the link to sign
|
||||||
|
in.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="loginError" class="mt-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
|
<div
|
||||||
<p class="text-red-700 dark:text-red-300 text-center">
|
v-if="loginError"
|
||||||
❌ {{ loginError }}
|
class="mt-6 p-4 bg-red-50 rounded-lg border border-red-200"
|
||||||
</p>
|
>
|
||||||
|
<p class="text-red-700 text-center">❌ {{ loginError }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sign Up Link -->
|
<!-- Sign Up Link -->
|
||||||
<div class="mt-6 text-center">
|
<div class="mt-6 text-center">
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
<p class="text-[--ui-text-muted]">
|
||||||
Don't have an account?
|
Don't have an account?
|
||||||
<NuxtLink to="/join" class="text-blue-600 dark:text-blue-400 hover:underline font-medium">
|
<NuxtLink
|
||||||
|
to="/join"
|
||||||
|
class="text-primary-500 hover:underline font-medium"
|
||||||
|
>
|
||||||
Join Ghost Guild
|
Join Ghost Guild
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -92,19 +103,25 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Forgot Password -->
|
<!-- Forgot Password -->
|
||||||
<section id="forgot-password" class="py-20 bg-gray-50 dark:bg-gray-800">
|
<section id="forgot-password" class="py-20 bg-neutral-50">
|
||||||
<UContainer class="max-w-md">
|
<UContainer class="max-w-md">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-4">
|
<h2 class="text-3xl font-bold text-primary-500 mb-4">
|
||||||
Forgot Password
|
Forgot Password
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-600 dark:text-gray-300">
|
<p class="text-[--ui-text-muted]">
|
||||||
Enter your email to receive a password reset link
|
Enter your email to receive a password reset link
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-xl border border-blue-200 dark:border-blue-800">
|
<div
|
||||||
<UForm :state="forgotPasswordForm" class="space-y-6" @submit="handleForgotPassword">
|
class="bg-[--ui-bg-elevated] rounded-2xl p-8 shadow-xl border border-primary-200"
|
||||||
|
>
|
||||||
|
<UForm
|
||||||
|
:state="forgotPasswordForm"
|
||||||
|
class="space-y-6"
|
||||||
|
@submit="handleForgotPassword"
|
||||||
|
>
|
||||||
<!-- Email Field -->
|
<!-- Email Field -->
|
||||||
<UFormField label="Email Address" name="email" required>
|
<UFormField label="Email Address" name="email" required>
|
||||||
<UInput
|
<UInput
|
||||||
|
|
@ -117,7 +134,7 @@
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<!-- Reset Instructions -->
|
<!-- Reset Instructions -->
|
||||||
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-800">
|
<div class="bg-primary-50 p-4 rounded-lg border border-primary-200">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<div class="space-y-1 flex-shrink-0 mt-1">
|
<div class="space-y-1 flex-shrink-0 mt-1">
|
||||||
<div class="w-2 h-2 bg-blue-500 rounded-full" />
|
<div class="w-2 h-2 bg-blue-500 rounded-full" />
|
||||||
|
|
@ -129,8 +146,9 @@
|
||||||
<div class="h-1 bg-blue-200 rounded-full w-1/2" />
|
<div class="h-1 bg-blue-200 rounded-full w-1/2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-blue-700 dark:text-blue-300 text-sm mt-3">
|
<p class="text-primary-700 text-sm mt-3">
|
||||||
We'll send you a secure link to reset your password. Check your email inbox and spam folder.
|
We'll send you a secure link to reset your password. Check your
|
||||||
|
email inbox and spam folder.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -150,41 +168,44 @@
|
||||||
</UForm>
|
</UForm>
|
||||||
|
|
||||||
<!-- Success/Error Messages -->
|
<!-- Success/Error Messages -->
|
||||||
<div v-if="resetSuccess" class="mt-6 p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
|
<div
|
||||||
<p class="text-green-700 dark:text-green-300 text-center">
|
v-if="resetSuccess"
|
||||||
|
class="mt-6 p-4 bg-green-50 rounded-lg border border-green-200"
|
||||||
|
>
|
||||||
|
<p class="text-green-700 text-center">
|
||||||
✅ Password reset link sent! Check your email.
|
✅ Password reset link sent! Check your email.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="resetError" class="mt-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
|
<div
|
||||||
<p class="text-red-700 dark:text-red-300 text-center">
|
v-if="resetError"
|
||||||
❌ {{ resetError }}
|
class="mt-6 p-4 bg-red-50 rounded-lg border border-red-200"
|
||||||
</p>
|
>
|
||||||
|
<p class="text-red-700 text-center">❌ {{ resetError }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UContainer>
|
</UContainer>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Sign In CTA -->
|
<!-- Sign In CTA -->
|
||||||
<section class="py-20 bg-white dark:bg-gray-900">
|
<section class="py-20 bg-[--ui-bg]">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="text-center max-w-2xl mx-auto">
|
<div class="text-center max-w-2xl mx-auto">
|
||||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
|
<h2 class="text-3xl font-bold text-primary-500 mb-8">Sign In</h2>
|
||||||
Sign In
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="space-y-4 mb-8">
|
<div class="space-y-4 mb-8">
|
||||||
<div class="h-2 bg-blue-500 rounded-full w-64 mx-auto" />
|
<div class="h-2 bg-blue-500 rounded-full w-64 mx-auto" />
|
||||||
<div class="h-2 bg-blue-300 rounded-full w-48 mx-auto" />
|
<div class="h-2 bg-blue-300 rounded-full w-48 mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-lg text-gray-600 dark:text-gray-300 mb-8">
|
<p class="text-lg text-[--ui-text-muted] mb-8">
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ready to access your account and connect with the community?
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ready to
|
||||||
|
access your account and connect with the community?
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
@click="scrollToLoginForm"
|
@click="scrollToLoginForm"
|
||||||
size="xl"
|
size="xl"
|
||||||
color="primary"
|
color="primary"
|
||||||
class="px-12"
|
class="px-12"
|
||||||
>
|
>
|
||||||
|
|
@ -195,13 +216,13 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Access Your Dashboard -->
|
<!-- Access Your Dashboard -->
|
||||||
<section class="py-20 bg-blue-50 dark:bg-blue-900/20">
|
<section class="py-20 bg-primary-50">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="text-center max-w-3xl mx-auto">
|
<div class="text-center max-w-3xl mx-auto">
|
||||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
|
<h2 class="text-3xl font-bold text-primary-500 mb-8">
|
||||||
Access Your Dashboard
|
Access Your Dashboard
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="space-y-3 mb-8">
|
<div class="space-y-3 mb-8">
|
||||||
<div class="h-2 bg-blue-500 rounded-full w-full max-w-lg mx-auto" />
|
<div class="h-2 bg-blue-500 rounded-full w-full max-w-lg mx-auto" />
|
||||||
<div class="h-2 bg-blue-400 rounded-full w-full max-w-md mx-auto" />
|
<div class="h-2 bg-blue-400 rounded-full w-full max-w-md mx-auto" />
|
||||||
|
|
@ -209,53 +230,59 @@
|
||||||
<div class="h-2 bg-blue-200 rounded-full w-full max-w-xs mx-auto" />
|
<div class="h-2 bg-blue-200 rounded-full w-full max-w-xs mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-lg border border-blue-200 dark:border-blue-800 mb-8">
|
<div
|
||||||
<p class="text-lg text-gray-600 dark:text-gray-300 mb-6">
|
class="bg-[--ui-bg-elevated] rounded-2xl p-8 shadow-lg border border-primary-200 mb-8"
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Once you're logged in, you'll have access to:
|
>
|
||||||
|
<p class="text-lg text-[--ui-text-muted] mb-6">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Once
|
||||||
|
you're logged in, you'll have access to:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-left">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-left">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-2 h-2 bg-blue-500 rounded-full" />
|
<div class="w-2 h-2 bg-blue-500 rounded-full" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">Community forums and discussions</span>
|
<span class="text-[--ui-text]"
|
||||||
|
>Community forums and discussions</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-2 h-2 bg-blue-400 rounded-full" />
|
<div class="w-2 h-2 bg-blue-400 rounded-full" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">Member directory and networking</span>
|
<span class="text-[--ui-text]"
|
||||||
|
>Member directory and networking</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-2 h-2 bg-blue-300 rounded-full" />
|
<div class="w-2 h-2 bg-blue-300 rounded-full" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">Educational resources and workshops</span>
|
<span class="text-[--ui-text]"
|
||||||
|
>Educational resources and workshops</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-2 h-2 bg-emerald-500 rounded-full" />
|
<div class="w-2 h-2 bg-emerald-500 rounded-full" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">Cooperative development tools</span>
|
<span class="text-[--ui-text]"
|
||||||
|
>Cooperative development tools</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-2 h-2 bg-emerald-400 rounded-full" />
|
<div class="w-2 h-2 bg-emerald-400 rounded-full" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">Mentorship opportunities</span>
|
<span class="text-[--ui-text]">Mentorship opportunities</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-2 h-2 bg-emerald-300 rounded-full" />
|
<div class="w-2 h-2 bg-emerald-300 rounded-full" />
|
||||||
<span class="text-gray-700 dark:text-gray-300">Project collaboration spaces</span>
|
<span class="text-[--ui-text]"
|
||||||
|
>Project collaboration spaces</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-gray-600 dark:text-gray-300 mb-4">
|
<p class="text-[--ui-text-muted] mb-4">New to Ghost Guild?</p>
|
||||||
New to Ghost Guild?
|
<UButton to="/join" variant="outline" size="lg" class="px-8">
|
||||||
</p>
|
|
||||||
<UButton
|
|
||||||
to="/join"
|
|
||||||
variant="outline"
|
|
||||||
size="lg"
|
|
||||||
class="px-8"
|
|
||||||
>
|
|
||||||
Create Your Account
|
Create Your Account
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -266,112 +293,112 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, computed } from 'vue'
|
import { reactive, ref, computed } from "vue";
|
||||||
|
|
||||||
// Login form state
|
// Login form state
|
||||||
const loginForm = reactive({
|
const loginForm = reactive({
|
||||||
email: ''
|
email: "",
|
||||||
})
|
});
|
||||||
|
|
||||||
// Forgot password form state
|
// Forgot password form state
|
||||||
const forgotPasswordForm = reactive({
|
const forgotPasswordForm = reactive({
|
||||||
email: ''
|
email: "",
|
||||||
})
|
});
|
||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
const isLoggingIn = ref(false)
|
const isLoggingIn = ref(false);
|
||||||
const isResettingPassword = ref(false)
|
const isResettingPassword = ref(false);
|
||||||
const loginSuccess = ref(false)
|
const loginSuccess = ref(false);
|
||||||
const loginError = ref('')
|
const loginError = ref("");
|
||||||
const resetSuccess = ref(false)
|
const resetSuccess = ref(false);
|
||||||
const resetError = ref('')
|
const resetError = ref("");
|
||||||
|
|
||||||
// Form validation
|
// Form validation
|
||||||
const isLoginFormValid = computed(() => {
|
const isLoginFormValid = computed(() => {
|
||||||
return loginForm.email && loginForm.email.includes('@')
|
return loginForm.email && loginForm.email.includes("@");
|
||||||
})
|
});
|
||||||
|
|
||||||
// Login handler
|
// Login handler
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (isLoggingIn.value) return
|
if (isLoggingIn.value) return;
|
||||||
|
|
||||||
isLoggingIn.value = true
|
isLoggingIn.value = true;
|
||||||
loginError.value = ''
|
loginError.value = "";
|
||||||
loginSuccess.value = false
|
loginSuccess.value = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the passwordless login API
|
// Call the passwordless login API
|
||||||
const response = await $fetch('/api/auth/login', {
|
const response = await $fetch("/api/auth/login", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
email: loginForm.email
|
email: loginForm.email,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
loginSuccess.value = true
|
loginSuccess.value = true;
|
||||||
loginError.value = ''
|
loginError.value = "";
|
||||||
|
|
||||||
// Clear the form
|
// Clear the form
|
||||||
loginForm.email = ''
|
loginForm.email = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Login error:', err)
|
console.error("Login error:", err);
|
||||||
|
|
||||||
// Handle different error types
|
// Handle different error types
|
||||||
if (err.statusCode === 404) {
|
if (err.statusCode === 404) {
|
||||||
loginError.value = 'No account found with that email address. Please check your email or create an account.'
|
loginError.value =
|
||||||
|
"No account found with that email address. Please check your email or create an account.";
|
||||||
} else if (err.statusCode === 500) {
|
} else if (err.statusCode === 500) {
|
||||||
loginError.value = 'Failed to send login email. Please try again later.'
|
loginError.value = "Failed to send login email. Please try again later.";
|
||||||
} else {
|
} else {
|
||||||
loginError.value = err.statusMessage || 'Something went wrong. Please try again.'
|
loginError.value =
|
||||||
|
err.statusMessage || "Something went wrong. Please try again.";
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isLoggingIn.value = false
|
isLoggingIn.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Forgot password handler
|
// Forgot password handler
|
||||||
const handleForgotPassword = async () => {
|
const handleForgotPassword = async () => {
|
||||||
if (isResettingPassword.value) return
|
if (isResettingPassword.value) return;
|
||||||
|
|
||||||
isResettingPassword.value = true
|
isResettingPassword.value = true;
|
||||||
resetError.value = ''
|
resetError.value = "";
|
||||||
resetSuccess.value = false
|
resetSuccess.value = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Simulate API call
|
// Simulate API call
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
resetSuccess.value = true
|
resetSuccess.value = true;
|
||||||
|
|
||||||
// Reset form after success
|
// Reset form after success
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
forgotPasswordForm.email = ''
|
forgotPasswordForm.email = "";
|
||||||
resetSuccess.value = false
|
resetSuccess.value = false;
|
||||||
}, 5000)
|
}, 5000);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Password reset error:', err)
|
console.error("Password reset error:", err);
|
||||||
resetError.value = 'Failed to send reset email. Please try again.'
|
resetError.value = "Failed to send reset email. Please try again.";
|
||||||
} finally {
|
} finally {
|
||||||
isResettingPassword.value = false
|
isResettingPassword.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Scroll functions
|
// Scroll functions
|
||||||
const scrollToLoginForm = () => {
|
const scrollToLoginForm = () => {
|
||||||
const formSection = document.querySelector('form')
|
const formSection = document.querySelector("form");
|
||||||
if (formSection) {
|
if (formSection) {
|
||||||
formSection.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
formSection.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const scrollToForgotPassword = () => {
|
const scrollToForgotPassword = () => {
|
||||||
const forgotSection = document.getElementById('forgot-password')
|
const forgotSection = document.getElementById("forgot-password");
|
||||||
if (forgotSection) {
|
if (forgotSection) {
|
||||||
forgotSection.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
forgotSection.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-whisper-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-whisper-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-stone-300">Loading your dashboard...</p>
|
<p class="text-ghost-300">Loading your dashboard...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -36,10 +36,10 @@
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-start justify-between gap-4">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h1 class="text-2xl font-bold text-stone-100 ethereal-text">
|
<h1 class="text-2xl font-bold text-ghost-100 ethereal-text">
|
||||||
Welcome to Ghost Guild, {{ memberData?.name }}!
|
Welcome to Ghost Guild, {{ memberData?.name }}!
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-stone-300 mt-2">
|
<p class="text-ghost-300 mt-2">
|
||||||
Your membership is active and you're part of our cooperative
|
Your membership is active and you're part of our cooperative
|
||||||
community.
|
community.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex-shrink-0">
|
<div v-else class="flex-shrink-0">
|
||||||
<div
|
<div
|
||||||
class="w-16 h-16 bg-ghost-700 border border-ghost-600 flex items-center justify-center text-stone-200 font-bold text-xl"
|
class="w-16 h-16 bg-ghost-700 border border-ghost-600 flex items-center justify-center text-ghost-200 font-bold text-xl"
|
||||||
>
|
>
|
||||||
{{ memberData?.name?.charAt(0)?.toUpperCase() }}
|
{{ memberData?.name?.charAt(0)?.toUpperCase() }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,13 +63,13 @@
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-4 text-sm">
|
<div class="flex flex-wrap gap-4 text-sm">
|
||||||
<div class="bg-ghost-800 border border-ghost-600 px-4 py-2">
|
<div class="bg-ghost-800 border border-ghost-600 px-4 py-2">
|
||||||
<span class="text-stone-400">Circle:</span>
|
<span class="text-ghost-400">Circle:</span>
|
||||||
<span class="font-medium text-whisper-300 ml-1 capitalize">{{
|
<span class="font-medium text-whisper-300 ml-1 capitalize">{{
|
||||||
memberData?.circle
|
memberData?.circle
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-ghost-800 border border-ghost-600 px-4 py-2">
|
<div class="bg-ghost-800 border border-ghost-600 px-4 py-2">
|
||||||
<span class="text-stone-400">Contribution:</span>
|
<span class="text-ghost-400">Contribution:</span>
|
||||||
<span class="font-medium text-whisper-300 ml-1"
|
<span class="font-medium text-whisper-300 ml-1"
|
||||||
>${{ memberData?.contributionTier }} CAD/month</span
|
>${{ memberData?.contributionTier }} CAD/month</span
|
||||||
>
|
>
|
||||||
|
|
@ -86,28 +86,16 @@
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<h2 class="text-xl font-bold text-stone-100 ethereal-text">
|
<h2 class="text-xl font-bold text-ghost-100 ethereal-text">
|
||||||
Quick Links
|
Quick Links
|
||||||
</h2>
|
</h2>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
<UButton
|
|
||||||
to="/member/my-updates"
|
|
||||||
variant="outline"
|
|
||||||
class="border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
|
||||||
block
|
|
||||||
>
|
|
||||||
<template #leading>
|
|
||||||
<Icon name="heroicons:pencil-square" class="w-5 h-5" />
|
|
||||||
</template>
|
|
||||||
Post an Update
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
disabled
|
disabled
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-stone-500 cursor-not-allowed justify-start"
|
class="border-ghost-600 text-ghost-500 cursor-not-allowed justify-start"
|
||||||
block
|
block
|
||||||
title="Coming soon"
|
title="Coming soon"
|
||||||
>
|
>
|
||||||
|
|
@ -120,7 +108,7 @@
|
||||||
<UButton
|
<UButton
|
||||||
disabled
|
disabled
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-stone-500 cursor-not-allowed justify-start"
|
class="border-ghost-600 text-ghost-500 cursor-not-allowed justify-start"
|
||||||
block
|
block
|
||||||
title="Coming soon"
|
title="Coming soon"
|
||||||
>
|
>
|
||||||
|
|
@ -133,7 +121,7 @@
|
||||||
<UButton
|
<UButton
|
||||||
disabled
|
disabled
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-stone-500 cursor-not-allowed justify-start"
|
class="border-ghost-600 text-ghost-500 cursor-not-allowed justify-start"
|
||||||
block
|
block
|
||||||
title="Coming soon"
|
title="Coming soon"
|
||||||
>
|
>
|
||||||
|
|
@ -146,7 +134,7 @@
|
||||||
<UButton
|
<UButton
|
||||||
to="/member/profile"
|
to="/member/profile"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
<template #leading>
|
<template #leading>
|
||||||
|
|
@ -179,10 +167,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold mb-2 text-stone-100">
|
<h3 class="text-lg font-semibold mb-2 text-ghost-100">
|
||||||
Upcoming Events
|
Upcoming Events
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-stone-300 mb-4">
|
<p class="text-ghost-300 mb-4">
|
||||||
Discover and register for community events and workshops.
|
Discover and register for community events and workshops.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -191,7 +179,7 @@
|
||||||
to="/events"
|
to="/events"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500"
|
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
||||||
>
|
>
|
||||||
View Events
|
View Events
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -217,8 +205,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold mb-2 text-stone-100">Community</h3>
|
<h3 class="text-lg font-semibold mb-2 text-ghost-100">Community</h3>
|
||||||
<p class="text-stone-300 mb-4">
|
<p class="text-ghost-300 mb-4">
|
||||||
Connect with other members in your circle and beyond.
|
Connect with other members in your circle and beyond.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -227,7 +215,7 @@
|
||||||
to="/members"
|
to="/members"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500"
|
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
||||||
>
|
>
|
||||||
Browse Members
|
Browse Members
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -253,10 +241,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold mb-2 text-stone-100">
|
<h3 class="text-lg font-semibold mb-2 text-ghost-100">
|
||||||
Account Settings
|
Account Settings
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-stone-300 mb-4">
|
<p class="text-ghost-300 mb-4">
|
||||||
Manage your profile and membership settings.
|
Manage your profile and membership settings.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -265,7 +253,7 @@
|
||||||
to="/member/profile#account"
|
to="/member/profile#account"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500"
|
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
||||||
>
|
>
|
||||||
Manage Account
|
Manage Account
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -283,14 +271,14 @@
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="text-xl font-bold text-stone-100 ethereal-text">
|
<h2 class="text-xl font-bold text-ghost-100 ethereal-text">
|
||||||
Your Upcoming Events
|
Your Upcoming Events
|
||||||
</h2>
|
</h2>
|
||||||
<UButton
|
<UButton
|
||||||
to="/events"
|
to="/events"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="text-stone-300 hover:text-stone-100"
|
class="text-ghost-300 hover:text-ghost-100"
|
||||||
>
|
>
|
||||||
Browse All Events
|
Browse All Events
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -334,10 +322,10 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<h3 class="font-semibold text-stone-100 mb-1">
|
<h3 class="font-semibold text-ghost-100 mb-1">
|
||||||
{{ evt.title }}
|
{{ evt.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex items-center gap-4 text-sm text-stone-400">
|
<div class="flex items-center gap-4 text-sm text-ghost-400">
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
<Icon name="heroicons:calendar" class="w-4 h-4" />
|
<Icon name="heroicons:calendar" class="w-4 h-4" />
|
||||||
{{ formatEventDate(evt.startDate) }}
|
{{ formatEventDate(evt.startDate) }}
|
||||||
|
|
@ -351,7 +339,7 @@
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:chevron-right"
|
name="heroicons:chevron-right"
|
||||||
class="w-5 h-5 text-stone-500"
|
class="w-5 h-5 text-ghost-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -361,108 +349,20 @@
|
||||||
<div v-else class="text-center py-8">
|
<div v-else class="text-center py-8">
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:calendar-days"
|
name="heroicons:calendar-days"
|
||||||
class="w-12 h-12 text-stone-600 mx-auto mb-3"
|
class="w-12 h-12 text-ghost-600 mx-auto mb-3"
|
||||||
/>
|
/>
|
||||||
<p class="text-stone-400 mb-4">
|
<p class="text-ghost-400 mb-4">
|
||||||
You haven't registered for any upcoming events
|
You haven't registered for any upcoming events
|
||||||
</p>
|
</p>
|
||||||
<UButton
|
<UButton
|
||||||
to="/events"
|
to="/events"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500"
|
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
||||||
>
|
>
|
||||||
Browse Events
|
Browse Events
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</UCard>
|
</UCard>
|
||||||
|
|
||||||
<!-- Community Pulse - Recent Updates -->
|
|
||||||
<UCard
|
|
||||||
class="sparkle-field"
|
|
||||||
:ui="{
|
|
||||||
root: 'bg-ghost-900 border border-ghost-700',
|
|
||||||
header: 'border-b border-ghost-700 bg-ghost-900',
|
|
||||||
body: 'bg-ghost-900',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h2 class="text-xl font-bold text-stone-100 ethereal-text">
|
|
||||||
Community Pulse
|
|
||||||
</h2>
|
|
||||||
<UButton
|
|
||||||
to="/updates"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
class="text-stone-300 hover:text-stone-100"
|
|
||||||
>
|
|
||||||
View All
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="loadingUpdates" class="text-center py-8">
|
|
||||||
<div
|
|
||||||
class="w-6 h-6 border-2 border-whisper-500 border-t-transparent rounded-full animate-spin mx-auto"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="recentUpdates.length" class="space-y-4">
|
|
||||||
<div
|
|
||||||
v-for="update in recentUpdates"
|
|
||||||
:key="update._id"
|
|
||||||
class="border-l-2 border-ghost-600 pl-4 py-2"
|
|
||||||
>
|
|
||||||
<div class="flex items-start gap-3">
|
|
||||||
<img
|
|
||||||
v-if="
|
|
||||||
update.author?.avatar && isValidAvatar(update.author.avatar)
|
|
||||||
"
|
|
||||||
:src="`/ghosties/Ghost-${capitalize(update.author.avatar)}.png`"
|
|
||||||
:alt="update.author.name"
|
|
||||||
class="w-8 h-8 flex-shrink-0"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else-if="update.author?.name"
|
|
||||||
class="w-8 h-8 bg-ghost-700 border border-ghost-600 flex items-center justify-center text-stone-200 text-xs font-bold flex-shrink-0"
|
|
||||||
>
|
|
||||||
{{ update.author.name.charAt(0).toUpperCase() }}
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<div class="flex items-baseline gap-2 mb-1">
|
|
||||||
<span class="font-semibold text-stone-100 text-sm">
|
|
||||||
{{ update.author?.name }}
|
|
||||||
</span>
|
|
||||||
<span class="text-xs text-stone-500">
|
|
||||||
{{ formatTimeAgo(update.createdAt) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-stone-300 text-sm line-clamp-2">
|
|
||||||
{{ update.content }}
|
|
||||||
</p>
|
|
||||||
<NuxtLink
|
|
||||||
:to="`/updates/${update._id}`"
|
|
||||||
class="text-xs text-whisper-400 hover:text-whisper-300 mt-1 inline-block"
|
|
||||||
>
|
|
||||||
Read more
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="text-center py-8">
|
|
||||||
<p class="text-stone-400 mb-4">No community updates yet</p>
|
|
||||||
<UButton
|
|
||||||
to="/updates/new"
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
class="border-ghost-600 text-stone-200 hover:bg-ghost-800 hover:border-whisper-500"
|
|
||||||
>
|
|
||||||
Post the First Update
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</UCard>
|
|
||||||
</div>
|
</div>
|
||||||
</UContainer>
|
</UContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -471,8 +371,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
const { memberData, checkMemberStatus } = useAuth();
|
const { memberData, checkMemberStatus } = useAuth();
|
||||||
|
|
||||||
const recentUpdates = ref([]);
|
|
||||||
const loadingUpdates = ref(false);
|
|
||||||
const registeredEvents = ref([]);
|
const registeredEvents = ref([]);
|
||||||
const loadingEvents = ref(false);
|
const loadingEvents = ref(false);
|
||||||
|
|
||||||
|
|
@ -506,21 +404,6 @@ const { pending: authPending } = await useLazyAsyncData(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load recent updates
|
|
||||||
const loadRecentUpdates = async () => {
|
|
||||||
loadingUpdates.value = true;
|
|
||||||
try {
|
|
||||||
const response = await $fetch("/api/updates", {
|
|
||||||
params: { limit: 5, skip: 0 },
|
|
||||||
});
|
|
||||||
recentUpdates.value = response.updates;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load recent updates:", error);
|
|
||||||
} finally {
|
|
||||||
loadingUpdates.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load registered events
|
// Load registered events
|
||||||
const loadRegisteredEvents = async () => {
|
const loadRegisteredEvents = async () => {
|
||||||
console.log(
|
console.log(
|
||||||
|
|
@ -575,23 +458,6 @@ const capitalize = (str) => {
|
||||||
.join("-");
|
.join("-");
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTimeAgo = (date) => {
|
|
||||||
const now = new Date();
|
|
||||||
const updateDate = new Date(date);
|
|
||||||
const diffInSeconds = Math.floor((now - updateDate) / 1000);
|
|
||||||
|
|
||||||
if (diffInSeconds < 60) return "just now";
|
|
||||||
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
|
|
||||||
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
|
|
||||||
if (diffInSeconds < 604800)
|
|
||||||
return `${Math.floor(diffInSeconds / 86400)}d ago`;
|
|
||||||
|
|
||||||
return updateDate.toLocaleDateString("en-US", {
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper functions for event display
|
// Helper functions for event display
|
||||||
const getEventImageUrl = (featureImage) => {
|
const getEventImageUrl = (featureImage) => {
|
||||||
if (!featureImage) return "";
|
if (!featureImage) return "";
|
||||||
|
|
@ -626,7 +492,6 @@ const formatEventTime = (dateString) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadRecentUpdates();
|
|
||||||
loadRegisteredEvents();
|
loadRegisteredEvents();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
<UContainer class="px-4">
|
<UContainer class="px-4">
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div v-if="!pending" class="mb-8 flex items-center justify-between">
|
<div v-if="!pending" class="mb-8 flex items-center justify-between">
|
||||||
<div class="text-stone-300">
|
<div class="text-ghost-300">
|
||||||
<span class="text-2xl font-bold text-stone-100">{{ total }}</span>
|
<span class="text-2xl font-bold text-ghost-100">{{ total }}</span>
|
||||||
{{ total === 1 ? "update" : "updates" }} posted
|
{{ total === 1 ? "update" : "updates" }} posted
|
||||||
</div>
|
</div>
|
||||||
<UButton to="/updates/new" icon="i-lucide-plus"> New Update </UButton>
|
<UButton to="/updates/new" icon="i-lucide-plus"> New Update </UButton>
|
||||||
|
|
@ -25,9 +25,9 @@
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-stone-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-ghost-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-stone-400">Loading your updates...</p>
|
<p class="text-ghost-400">Loading your updates...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
class="text-stone-600"
|
class="text-ghost-600"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
|
|
@ -72,10 +72,10 @@
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-medium text-stone-300 mb-2">
|
<h3 class="text-lg font-medium text-ghost-300 mb-2">
|
||||||
No updates yet
|
No updates yet
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-stone-400 mb-6">
|
<p class="text-ghost-400 mb-6">
|
||||||
Share your first update with the community
|
Share your first update with the community
|
||||||
</p>
|
</p>
|
||||||
<UButton to="/updates/new" icon="i-lucide-plus">
|
<UButton to="/updates/new" icon="i-lucide-plus">
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-stone-400">Loading your profile...</p>
|
<p class="text-ghost-400">Loading your profile...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
<!-- Basic Information -->
|
<!-- Basic Information -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Basic Information
|
Basic Information
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
:class="
|
:class="
|
||||||
formData.avatar === ghost.value
|
formData.avatar === ghost.value
|
||||||
? 'border-blue-400 bg-blue-500/20'
|
? 'border-blue-400 bg-blue-500/20'
|
||||||
: 'border-stone-700 bg-stone-800/50 hover:border-stone-600'
|
: 'border-ghost-700 bg-ghost-800/50 hover:border-ghost-600'
|
||||||
"
|
"
|
||||||
@click="formData.avatar = ghost.value"
|
@click="formData.avatar = ghost.value"
|
||||||
>
|
>
|
||||||
|
|
@ -134,7 +134,7 @@
|
||||||
<!-- Professional Info -->
|
<!-- Professional Info -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Professional Information
|
Professional Information
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -203,7 +203,7 @@
|
||||||
<!-- Community Connections -->
|
<!-- Community Connections -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Community Connections
|
Community Connections
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -219,7 +219,7 @@
|
||||||
<!-- Tags input -->
|
<!-- Tags input -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-stone-200 mb-2"
|
class="block text-sm font-medium text-ghost-200 mb-2"
|
||||||
>
|
>
|
||||||
Skills & Topics
|
Skills & Topics
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -251,7 +251,7 @@
|
||||||
<!-- Description textarea -->
|
<!-- Description textarea -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-stone-200 mb-2"
|
class="block text-sm font-medium text-ghost-200 mb-2"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -281,7 +281,7 @@
|
||||||
<!-- Tags input -->
|
<!-- Tags input -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-stone-200 mb-2"
|
class="block text-sm font-medium text-ghost-200 mb-2"
|
||||||
>
|
>
|
||||||
Skills & Topics
|
Skills & Topics
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -313,7 +313,7 @@
|
||||||
<!-- Description textarea -->
|
<!-- Description textarea -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-stone-200 mb-2"
|
class="block text-sm font-medium text-ghost-200 mb-2"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
<!-- Peer Support -->
|
<!-- Peer Support -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Peer Support
|
Peer Support
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -346,7 +346,7 @@
|
||||||
<div
|
<div
|
||||||
class="mb-6 backdrop-blur-sm bg-purple-500/10 border border-purple-500/30 rounded-lg p-4"
|
class="mb-6 backdrop-blur-sm bg-purple-500/10 border border-purple-500/30 rounded-lg p-4"
|
||||||
>
|
>
|
||||||
<p class="text-stone-300 text-sm leading-relaxed">
|
<p class="text-ghost-300 text-sm leading-relaxed">
|
||||||
Offer guidance to fellow members through the
|
Offer guidance to fellow members through the
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/peer-support"
|
to="/peer-support"
|
||||||
|
|
@ -362,10 +362,10 @@
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<USwitch v-model="formData.peerSupportEnabled" />
|
<USwitch v-model="formData.peerSupportEnabled" />
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-stone-200">
|
<p class="font-medium text-ghost-200">
|
||||||
Offer Peer Support
|
Offer Peer Support
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-stone-400 mt-1">
|
<p class="text-sm text-ghost-400 mt-1">
|
||||||
Make yourself available to support other members
|
Make yourself available to support other members
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -435,20 +435,20 @@
|
||||||
<label
|
<label
|
||||||
v-for="topic in availableSupportTopics"
|
v-for="topic in availableSupportTopics"
|
||||||
:key="topic"
|
: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="flex items-center gap-3 p-3 rounded-lg border border-ghost-700 hover:border-purple-500/50 transition-colors cursor-pointer"
|
||||||
:class="
|
:class="
|
||||||
formData.peerSupportSupportTopics.includes(topic)
|
formData.peerSupportSupportTopics.includes(topic)
|
||||||
? 'bg-purple-500/10 border-purple-500/50'
|
? 'bg-purple-500/10 border-purple-500/50'
|
||||||
: 'bg-stone-900/50'
|
: 'bg-ghost-900/50'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:value="topic"
|
:value="topic"
|
||||||
v-model="formData.peerSupportSupportTopics"
|
v-model="formData.peerSupportSupportTopics"
|
||||||
class="rounded border-stone-600 text-purple-500 focus:ring-purple-500 focus:ring-offset-0 bg-stone-800"
|
class="rounded border-ghost-600 text-purple-500 focus:ring-purple-500 focus:ring-offset-0 bg-ghost-800"
|
||||||
/>
|
/>
|
||||||
<span class="text-stone-200">{{ topic }}</span>
|
<span class="text-ghost-200">{{ topic }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
@ -484,7 +484,7 @@
|
||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
<template #hint>
|
<template #hint>
|
||||||
<span class="text-xs text-stone-500">
|
<span class="text-xs text-ghost-500">
|
||||||
{{ formData.peerSupportMessage?.length || 0 }}/200
|
{{ formData.peerSupportMessage?.length || 0 }}/200
|
||||||
characters
|
characters
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -511,7 +511,7 @@
|
||||||
<!-- Directory Settings -->
|
<!-- Directory Settings -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Directory Visibility
|
Directory Visibility
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -519,10 +519,10 @@
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<USwitch v-model="formData.showInDirectory" />
|
<USwitch v-model="formData.showInDirectory" />
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-stone-200">
|
<p class="font-medium text-ghost-200">
|
||||||
Show in Member Directory
|
Show in Member Directory
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-stone-400 mt-1">
|
<p class="text-sm text-ghost-400 mt-1">
|
||||||
Allow other members to discover and connect with you
|
Allow other members to discover and connect with you
|
||||||
through the directory
|
through the directory
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -576,34 +576,34 @@
|
||||||
<!-- Current Membership -->
|
<!-- Current Membership -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-6 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-6 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Current Membership
|
Current Membership
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop-blur-sm bg-stone-800/50 border border-stone-700 rounded-lg p-6 space-y-4"
|
class="backdrop-blur-sm bg-ghost-800/50 border border-ghost-700 rounded-lg p-6 space-y-4"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-stone-400">Circle</p>
|
<p class="text-sm text-ghost-400">Circle</p>
|
||||||
<p
|
<p
|
||||||
class="text-lg font-medium text-stone-100 capitalize"
|
class="text-lg font-medium text-ghost-100 capitalize"
|
||||||
>
|
>
|
||||||
{{ memberData.circle }}
|
{{ memberData.circle }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-stone-400">Contribution Level</p>
|
<p class="text-sm text-ghost-400">Contribution Level</p>
|
||||||
<p class="text-lg font-medium text-stone-100">
|
<p class="text-lg font-medium text-ghost-100">
|
||||||
${{ contributionTierDetails?.amount }}/month
|
${{ contributionTierDetails?.amount }}/month
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="memberData.subscriptionStartDate">
|
<div v-if="memberData.subscriptionStartDate">
|
||||||
<p class="text-sm text-stone-400">Member Since</p>
|
<p class="text-sm text-ghost-400">Member Since</p>
|
||||||
<p class="text-stone-100">
|
<p class="text-ghost-100">
|
||||||
{{ formatDate(memberData.subscriptionStartDate) }}
|
{{ formatDate(memberData.subscriptionStartDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -614,8 +614,8 @@
|
||||||
memberData.contributionTier !== '0'
|
memberData.contributionTier !== '0'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<p class="text-sm text-stone-400">Next Billing Date</p>
|
<p class="text-sm text-ghost-400">Next Billing Date</p>
|
||||||
<p class="text-stone-100">
|
<p class="text-ghost-100">
|
||||||
{{ formatDate(memberData.nextBillingDate) }}
|
{{ formatDate(memberData.nextBillingDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -625,15 +625,15 @@
|
||||||
<!-- Change Contribution Level -->
|
<!-- Change Contribution Level -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-6 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-6 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Change Contribution Level
|
Change Contribution Level
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop-blur-sm bg-stone-800/50 border border-stone-700 rounded-lg p-6"
|
class="backdrop-blur-sm bg-ghost-800/50 border border-ghost-700 rounded-lg p-6"
|
||||||
>
|
>
|
||||||
<p class="text-stone-300 mb-6">
|
<p class="text-ghost-300 mb-6">
|
||||||
Choose a new contribution level that works for you.
|
Choose a new contribution level that works for you.
|
||||||
Changes will take effect on your next billing cycle.
|
Changes will take effect on your next billing cycle.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -647,16 +647,16 @@
|
||||||
'w-full text-left p-4 rounded-lg border-2 transition-all',
|
'w-full text-left p-4 rounded-lg border-2 transition-all',
|
||||||
selectedContributionTier === tier.value
|
selectedContributionTier === tier.value
|
||||||
? 'border-blue-400 bg-blue-500/20'
|
? 'border-blue-400 bg-blue-500/20'
|
||||||
: 'border-stone-600 bg-stone-900/30 hover:border-stone-500',
|
: 'border-ghost-600 bg-ghost-900/30 hover:border-ghost-500',
|
||||||
]"
|
]"
|
||||||
@click="selectedContributionTier = tier.value"
|
@click="selectedContributionTier = tier.value"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-stone-100">
|
<p class="font-medium text-ghost-100">
|
||||||
{{ tier.label }}
|
{{ tier.label }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-stone-400 mt-1">
|
<p class="text-sm text-ghost-400 mt-1">
|
||||||
{{ tier.features[0] }}
|
{{ tier.features[0] }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -702,20 +702,20 @@
|
||||||
<!-- Cancel Membership -->
|
<!-- Cancel Membership -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-6 text-stone-100 ethereal-text"
|
class="text-2xl font-semibold mb-6 text-ghost-100 ethereal-text"
|
||||||
>
|
>
|
||||||
Cancel Membership
|
Cancel Membership
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop-blur-sm bg-stone-800/50 border border-stone-700 rounded-lg p-6"
|
class="backdrop-blur-sm bg-ghost-800/50 border border-ghost-700 rounded-lg p-6"
|
||||||
>
|
>
|
||||||
<p class="text-stone-300 mb-4">
|
<p class="text-ghost-300 mb-4">
|
||||||
We're sorry to see you go. If you cancel, you'll lose
|
We're sorry to see you go. If you cancel, you'll lose
|
||||||
access to member benefits at the end of your current
|
access to member benefits at the end of your current
|
||||||
billing period.
|
billing period.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-stone-400 mb-6">
|
<p class="text-sm text-ghost-400 mb-6">
|
||||||
Need a break? Consider switching to the free tier instead.
|
Need a break? Consider switching to the free tier instead.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -1325,7 +1325,7 @@ useHead({
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Field labels - bright and readable */
|
/* Field labels - bright and readable */
|
||||||
:deep(label) {
|
:deep(label) {
|
||||||
color: rgb(231 229 228) !important; /* stone-200 */
|
color: rgb(231 229 228) !important; /* ghost-200 equivalent */
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
}
|
}
|
||||||
|
|
@ -1334,7 +1334,7 @@ useHead({
|
||||||
:deep([class*="description"]) {
|
:deep([class*="description"]) {
|
||||||
color: rgb(
|
color: rgb(
|
||||||
168 162 158
|
168 162 158
|
||||||
) !important; /* stone-400 - lighter than the dark background */
|
) !important; /* ghost-400 equivalent - lighter than the dark background */
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1344,22 +1344,22 @@ useHead({
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Input fields - ensure good contrast */
|
/* Input fields - respect light/dark mode */
|
||||||
:deep(input),
|
:deep(input),
|
||||||
:deep(textarea) {
|
:deep(textarea) {
|
||||||
background-color: rgb(41 37 36) !important; /* stone-800 */
|
background-color: transparent !important;
|
||||||
color: rgb(231 229 228) !important; /* stone-200 */
|
color: var(--color-ghost-100) !important;
|
||||||
border-color: rgb(87 83 78) !important; /* stone-600 */
|
border-color: var(--color-ghost-600) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(input::placeholder),
|
:deep(input::placeholder),
|
||||||
:deep(textarea::placeholder) {
|
:deep(textarea::placeholder) {
|
||||||
color: rgb(120 113 108) !important; /* stone-500 */
|
color: var(--color-ghost-500) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(input:focus),
|
:deep(input:focus),
|
||||||
:deep(textarea:focus) {
|
:deep(textarea:focus) {
|
||||||
border-color: rgb(147 197 253) !important; /* blue-300 */
|
border-color: rgb(147 197 253) !important;
|
||||||
background-color: rgb(44 40 39) !important; /* slightly lighter stone */
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<UContainer class="px-4">
|
<UContainer class="px-4">
|
||||||
<!-- Search and Filters -->
|
<!-- Search and Filters -->
|
||||||
<div class="mb-8 space-y-4">
|
<div class="mb-8 space-y-4">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<UInput
|
<UInput
|
||||||
|
|
@ -26,17 +26,24 @@
|
||||||
<!-- Circle Filter -->
|
<!-- Circle Filter -->
|
||||||
<USelect
|
<USelect
|
||||||
v-model="selectedCircle"
|
v-model="selectedCircle"
|
||||||
:options="circleOptions"
|
:items="circleOptions"
|
||||||
placeholder="All Circles"
|
|
||||||
size="lg"
|
size="lg"
|
||||||
@change="loadMembers"
|
@update:model-value="loadMembers"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Peer Support Filter -->
|
||||||
|
<USelect
|
||||||
|
v-model="peerSupportFilter"
|
||||||
|
:items="peerSupportOptions"
|
||||||
|
size="lg"
|
||||||
|
@update:model-value="loadMembers"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Skills Filter -->
|
<!-- Skills Filter -->
|
||||||
<div v-if="availableSkills && availableSkills.length > 0">
|
<div v-if="availableSkills && availableSkills.length > 0">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span class="text-sm text-stone-400 mr-2 self-center"
|
<span class="text-sm text-ghost-400 mr-2 self-center"
|
||||||
>Filter by skill:</span
|
>Filter by skill:</span
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
@ -50,7 +57,7 @@
|
||||||
:class="
|
:class="
|
||||||
selectedSkills.includes(skill)
|
selectedSkills.includes(skill)
|
||||||
? 'bg-purple-500/20 text-purple-300 border-purple-500/50'
|
? 'bg-purple-500/20 text-purple-300 border-purple-500/50'
|
||||||
: 'bg-stone-800/50 text-stone-400 border-stone-700 hover:border-stone-600'
|
: 'bg-ghost-800/50 text-ghost-400 border-ghost-700 hover:border-ghost-600'
|
||||||
"
|
"
|
||||||
@click="toggleSkill(skill)"
|
@click="toggleSkill(skill)"
|
||||||
>
|
>
|
||||||
|
|
@ -71,12 +78,55 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Peer Support Topics Filter -->
|
||||||
|
<div v-if="availableTopics && availableTopics.length > 0">
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<span class="text-sm text-ghost-400 mr-2 self-center"
|
||||||
|
>Filter by peer support topic:</span
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-for="topic in (availableTopics || []).slice(
|
||||||
|
0,
|
||||||
|
showAllTopics ? undefined : 10,
|
||||||
|
)"
|
||||||
|
:key="topic"
|
||||||
|
type="button"
|
||||||
|
class="px-3 py-1 rounded-full text-sm transition-all border"
|
||||||
|
:class="
|
||||||
|
selectedTopics.includes(topic)
|
||||||
|
? 'bg-purple-500/20 text-purple-300 border-purple-500/50'
|
||||||
|
: 'bg-ghost-800/50 text-ghost-400 border-ghost-700 hover:border-ghost-600'
|
||||||
|
"
|
||||||
|
@click="toggleTopic(topic)"
|
||||||
|
>
|
||||||
|
{{ topic }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="availableTopics && availableTopics.length > 10"
|
||||||
|
type="button"
|
||||||
|
class="px-3 py-1 text-sm text-purple-400 hover:text-purple-300"
|
||||||
|
@click="showAllTopics = !showAllTopics"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
showAllTopics
|
||||||
|
? "Show less"
|
||||||
|
: `+${availableTopics.length - 10} more`
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Active Filters -->
|
<!-- Active Filters -->
|
||||||
<div
|
<div
|
||||||
v-if="selectedCircle || selectedSkills.length > 0"
|
v-if="
|
||||||
class="flex items-center gap-2 text-sm"
|
selectedCircle ||
|
||||||
|
peerSupportFilter ||
|
||||||
|
selectedSkills.length > 0 ||
|
||||||
|
selectedTopics.length > 0
|
||||||
|
"
|
||||||
|
class="flex items-center gap-2 text-sm flex-wrap"
|
||||||
>
|
>
|
||||||
<span class="text-stone-400">Active filters:</span>
|
<span class="text-ghost-400">Active filters:</span>
|
||||||
<span
|
<span
|
||||||
v-if="selectedCircle"
|
v-if="selectedCircle"
|
||||||
class="px-2 py-1 bg-purple-500/20 text-purple-300 rounded-full border border-purple-500/30 flex items-center gap-1"
|
class="px-2 py-1 bg-purple-500/20 text-purple-300 rounded-full border border-purple-500/30 flex items-center gap-1"
|
||||||
|
|
@ -90,8 +140,21 @@
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="peerSupportFilter"
|
||||||
|
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
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="hover:text-purple-200"
|
||||||
|
@click="clearPeerSupportFilter"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
v-if="selectedSkills.length > 0"
|
v-if="selectedSkills.length > 0 || selectedTopics.length > 0"
|
||||||
type="button"
|
type="button"
|
||||||
class="text-purple-400 hover:text-purple-300"
|
class="text-purple-400 hover:text-purple-300"
|
||||||
@click="clearAllFilters"
|
@click="clearAllFilters"
|
||||||
|
|
@ -110,138 +173,282 @@
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-stone-400">Loading members...</p>
|
<p class="text-ghost-400">Loading members...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Members List -->
|
<!-- Members List -->
|
||||||
<div v-else-if="members.length > 0">
|
<div v-else-if="members.length > 0">
|
||||||
<div class="mb-4 text-stone-400 text-sm">
|
<div class="mb-4 text-ghost-400 text-sm">
|
||||||
{{ totalCount }} {{ totalCount === 1 ? "member" : "members" }} found
|
{{ totalCount }} {{ totalCount === 1 ? "member" : "members" }} found
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-4">
|
||||||
<div
|
<div
|
||||||
v-for="member in members"
|
v-for="member in members"
|
||||||
:key="member._id"
|
:key="member._id"
|
||||||
class="backdrop-blur-sm bg-stone-900/50 border border-stone-700/50 rounded-lg p-4 hover:border-purple-500/50 transition-all group flex items-center gap-4"
|
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"
|
||||||
>
|
>
|
||||||
<!-- Avatar -->
|
<!-- Header Section -->
|
||||||
<div
|
<div class="flex items-start gap-4 mb-4">
|
||||||
class="w-12 h-12 rounded-lg bg-stone-800 border border-stone-700 flex items-center justify-center flex-shrink-0 group-hover:border-purple-500/50 transition-colors"
|
<!-- Avatar -->
|
||||||
>
|
<div
|
||||||
<img
|
class="w-16 h-16 rounded-lg bg-ghost-800 border border-ghost-700 flex items-center justify-center flex-shrink-0 group-hover:border-purple-500/50 transition-colors"
|
||||||
v-if="member.avatar"
|
>
|
||||||
:src="`/ghosties/Ghost-${member.avatar.charAt(0).toUpperCase() + member.avatar.slice(1)}.png`"
|
<img
|
||||||
:alt="member.name"
|
v-if="member.avatar"
|
||||||
class="w-8 h-8 object-contain"
|
:src="`/ghosties/Ghost-${member.avatar.charAt(0).toUpperCase() + member.avatar.slice(1)}.png`"
|
||||||
/>
|
:alt="member.name"
|
||||||
<span v-else class="text-xl text-stone-600">👻</span>
|
class="w-12 h-12 object-contain"
|
||||||
</div>
|
/>
|
||||||
|
<span v-else class="text-2xl text-ghost-600">👻</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Name and Info -->
|
<!-- Name and Meta Info -->
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-baseline gap-2 flex-wrap">
|
<div class="flex items-baseline gap-2 flex-wrap mb-2">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="`/updates/user/${member._id}`"
|
:to="`/updates/user/${member._id}`"
|
||||||
class="font-semibold text-stone-100 hover:text-purple-300 transition-colors"
|
class="font-semibold text-lg text-ghost-100 hover:text-purple-300 transition-colors"
|
||||||
|
>
|
||||||
|
{{ member.name }}
|
||||||
|
</NuxtLink>
|
||||||
|
<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">
|
||||||
|
<span
|
||||||
|
class="px-2 py-0.5 bg-purple-500/20 text-purple-300 rounded text-xs border border-purple-500/30"
|
||||||
|
>
|
||||||
|
{{ circleLabels[member.circle] }}
|
||||||
|
</span>
|
||||||
|
<span v-if="member.studio" class="text-sm text-ghost-400">
|
||||||
|
{{ member.studio }}
|
||||||
|
</span>
|
||||||
|
<span v-if="member.location" class="text-sm text-ghost-500">
|
||||||
|
📍 {{ member.location }}
|
||||||
|
</span>
|
||||||
|
<span v-if="member.timeZone" class="text-sm text-ghost-500">
|
||||||
|
🕐 {{ member.timeZone }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Links -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
member.socialLinks && hasSocialLinks(member.socialLinks)
|
||||||
|
"
|
||||||
|
class="flex gap-3"
|
||||||
>
|
>
|
||||||
{{ member.name }}
|
<a
|
||||||
</NuxtLink>
|
v-if="member.socialLinks.mastodon"
|
||||||
<span v-if="member.pronouns" class="text-sm text-stone-400">
|
:href="member.socialLinks.mastodon"
|
||||||
{{ member.pronouns }}
|
target="_blank"
|
||||||
</span>
|
rel="noopener noreferrer"
|
||||||
<span
|
class="text-ghost-400 hover:text-purple-400 transition-colors"
|
||||||
class="px-2 py-0.5 bg-purple-500/20 text-purple-300 rounded text-xs border border-purple-500/30"
|
title="Mastodon"
|
||||||
>
|
>
|
||||||
{{ circleLabels[member.circle] }}
|
<svg
|
||||||
</span>
|
class="w-5 h-5"
|
||||||
<span v-if="member.studio" class="text-sm text-stone-400">
|
fill="currentColor"
|
||||||
{{ member.studio }}
|
viewBox="0 0 24 24"
|
||||||
</span>
|
>
|
||||||
<span v-if="member.location" class="text-sm text-stone-500">
|
<path
|
||||||
{{ member.location }}
|
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"
|
||||||
</span>
|
/>
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Social Links -->
|
<!-- Bio -->
|
||||||
|
<div v-if="member.bio" class="mb-4">
|
||||||
|
<p class="text-ghost-300 text-sm leading-relaxed">
|
||||||
|
{{ member.bio }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Peer Support Section -->
|
||||||
<div
|
<div
|
||||||
v-if="member.socialLinks && hasSocialLinks(member.socialLinks)"
|
v-if="member.peerSupport?.enabled"
|
||||||
class="flex gap-3 flex-shrink-0"
|
class="mb-4 p-4 bg-purple-500/10 border border-purple-500/30 rounded-lg"
|
||||||
>
|
>
|
||||||
<a
|
<div class="flex items-center gap-2 mb-2">
|
||||||
v-if="member.socialLinks.mastodon"
|
<span class="text-purple-300 font-medium text-sm">
|
||||||
:href="member.socialLinks.mastodon"
|
💜 Offering Peer Support
|
||||||
target="_blank"
|
</span>
|
||||||
rel="noopener noreferrer"
|
</div>
|
||||||
class="text-stone-400 hover:text-purple-400 transition-colors"
|
|
||||||
title="Mastodon"
|
<!-- Topics -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
member.peerSupport.topics &&
|
||||||
|
member.peerSupport.topics.length > 0
|
||||||
|
"
|
||||||
|
class="mb-2"
|
||||||
>
|
>
|
||||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
<div class="flex flex-wrap gap-1">
|
||||||
<path
|
<span
|
||||||
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"
|
v-for="topic in member.peerSupport.topics"
|
||||||
/>
|
:key="topic"
|
||||||
</svg>
|
class="px-2 py-0.5 bg-purple-500/20 text-purple-200 rounded text-xs border border-purple-500/40"
|
||||||
|
>
|
||||||
|
{{ topic }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Personal Message -->
|
||||||
|
<div
|
||||||
|
v-if="member.peerSupport.personalMessage"
|
||||||
|
class="text-sm text-ghost-300 italic mb-2"
|
||||||
|
>
|
||||||
|
"{{ member.peerSupport.personalMessage }}"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Availability -->
|
||||||
|
<div
|
||||||
|
v-if="member.peerSupport.availability"
|
||||||
|
class="text-xs text-ghost-400 mb-2"
|
||||||
|
>
|
||||||
|
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>
|
</a>
|
||||||
<a
|
</div>
|
||||||
v-if="member.socialLinks.linkedin"
|
|
||||||
:href="member.socialLinks.linkedin"
|
<!-- Offering and Looking For -->
|
||||||
target="_blank"
|
<div
|
||||||
rel="noopener noreferrer"
|
v-if="member.offering || member.lookingFor"
|
||||||
class="text-stone-400 hover:text-purple-400 transition-colors"
|
class="grid grid-cols-1 md:grid-cols-2 gap-4"
|
||||||
title="LinkedIn"
|
>
|
||||||
>
|
<!-- Offering -->
|
||||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
<div v-if="member.offering" class="space-y-2">
|
||||||
<path
|
<h4 class="text-xs font-semibold text-purple-400 uppercase">
|
||||||
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"
|
Offering
|
||||||
/>
|
</h4>
|
||||||
</svg>
|
<p
|
||||||
</a>
|
v-if="member.offering.description"
|
||||||
<a
|
class="text-ghost-300 text-sm"
|
||||||
v-if="member.socialLinks.website"
|
|
||||||
:href="member.socialLinks.website"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="text-stone-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
|
{{ member.offering.description }}
|
||||||
stroke-linecap="round"
|
</p>
|
||||||
stroke-linejoin="round"
|
<div
|
||||||
stroke-width="2"
|
v-if="
|
||||||
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"
|
member.offering.tags && member.offering.tags.length > 0
|
||||||
/>
|
"
|
||||||
</svg>
|
class="flex flex-wrap gap-1"
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
v-if="member.socialLinks.other"
|
|
||||||
:href="member.socialLinks.other"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="text-stone-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
|
<span
|
||||||
stroke-linecap="round"
|
v-for="tag in member.offering.tags"
|
||||||
stroke-linejoin="round"
|
:key="tag"
|
||||||
stroke-width="2"
|
class="px-2 py-0.5 bg-green-500/20 text-green-300 rounded text-xs border border-green-500/30"
|
||||||
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"
|
>
|
||||||
/>
|
{{ tag }}
|
||||||
</svg>
|
</span>
|
||||||
</a>
|
</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"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -254,7 +461,7 @@
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
class="text-stone-600"
|
class="text-ghost-600"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
|
|
@ -264,10 +471,10 @@
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-medium text-stone-300 mb-2">
|
<h3 class="text-lg font-medium text-ghost-300 mb-2">
|
||||||
No members found
|
No members found
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-stone-400 mb-6">
|
<p class="text-ghost-400 mb-6">
|
||||||
Try adjusting your search or filters
|
Try adjusting your search or filters
|
||||||
</p>
|
</p>
|
||||||
<UButton variant="outline" @click="clearAllFilters">
|
<UButton variant="outline" @click="clearAllFilters">
|
||||||
|
|
@ -300,15 +507,19 @@ const { isAuthenticated } = useAuth();
|
||||||
const members = ref([]);
|
const members = ref([]);
|
||||||
const totalCount = ref(0);
|
const totalCount = ref(0);
|
||||||
const availableSkills = ref([]);
|
const availableSkills = ref([]);
|
||||||
|
const availableTopics = ref([]);
|
||||||
const loading = ref(true); // Start with loading true
|
const loading = ref(true); // Start with loading true
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
const selectedCircle = ref("");
|
const selectedCircle = ref("all");
|
||||||
|
const peerSupportFilter = ref("all");
|
||||||
const selectedSkills = ref([]);
|
const selectedSkills = ref([]);
|
||||||
|
const selectedTopics = ref([]);
|
||||||
const showAllSkills = ref(false);
|
const showAllSkills = ref(false);
|
||||||
|
const showAllTopics = ref(false);
|
||||||
|
|
||||||
// Circle options
|
// Circle options
|
||||||
const circleOptions = [
|
const circleOptions = [
|
||||||
{ label: "All Circles", value: "" },
|
{ label: "All Circles", value: "all" },
|
||||||
{ label: "Community", value: "community" },
|
{ label: "Community", value: "community" },
|
||||||
{ label: "Founder", value: "founder" },
|
{ label: "Founder", value: "founder" },
|
||||||
{ label: "Practitioner", value: "practitioner" },
|
{ label: "Practitioner", value: "practitioner" },
|
||||||
|
|
@ -320,6 +531,12 @@ const circleLabels = {
|
||||||
practitioner: "Practitioner",
|
practitioner: "Practitioner",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Peer support filter options
|
||||||
|
const peerSupportOptions = [
|
||||||
|
{ label: "All Members", value: "all" },
|
||||||
|
{ label: "Offering Peer Support", value: "true" },
|
||||||
|
];
|
||||||
|
|
||||||
// Helper to check if member has social links
|
// Helper to check if member has social links
|
||||||
const hasSocialLinks = (links) => {
|
const hasSocialLinks = (links) => {
|
||||||
if (!links) return false;
|
if (!links) return false;
|
||||||
|
|
@ -333,20 +550,27 @@ const loadMembers = async () => {
|
||||||
try {
|
try {
|
||||||
const params = {};
|
const params = {};
|
||||||
if (searchQuery.value) params.search = searchQuery.value;
|
if (searchQuery.value) params.search = searchQuery.value;
|
||||||
if (selectedCircle.value) params.circle = selectedCircle.value;
|
if (selectedCircle.value && selectedCircle.value !== "all")
|
||||||
|
params.circle = selectedCircle.value;
|
||||||
|
if (peerSupportFilter.value && peerSupportFilter.value !== "all")
|
||||||
|
params.peerSupport = peerSupportFilter.value;
|
||||||
if (selectedSkills.value.length > 0)
|
if (selectedSkills.value.length > 0)
|
||||||
params.skills = selectedSkills.value.join(",");
|
params.skills = selectedSkills.value.join(",");
|
||||||
|
if (selectedTopics.value.length > 0)
|
||||||
|
params.topics = selectedTopics.value.join(",");
|
||||||
|
|
||||||
const data = await $fetch("/api/members/directory", { params });
|
const data = await $fetch("/api/members/directory", { params });
|
||||||
|
|
||||||
members.value = data.members || [];
|
members.value = data.members || [];
|
||||||
totalCount.value = data.totalCount || 0;
|
totalCount.value = data.totalCount || 0;
|
||||||
availableSkills.value = data.filters?.availableSkills || [];
|
availableSkills.value = data.filters?.availableSkills || [];
|
||||||
|
availableTopics.value = data.filters?.availableTopics || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load members:", error);
|
console.error("Failed to load members:", error);
|
||||||
members.value = [];
|
members.value = [];
|
||||||
totalCount.value = 0;
|
totalCount.value = 0;
|
||||||
availableSkills.value = [];
|
availableSkills.value = [];
|
||||||
|
availableTopics.value = [];
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
@ -372,19 +596,53 @@ const toggleSkill = (skill) => {
|
||||||
loadMembers();
|
loadMembers();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Toggle topic filter
|
||||||
|
const toggleTopic = (topic) => {
|
||||||
|
const index = selectedTopics.value.indexOf(topic);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedTopics.value.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
selectedTopics.value.push(topic);
|
||||||
|
}
|
||||||
|
loadMembers();
|
||||||
|
};
|
||||||
|
|
||||||
// Clear filters
|
// Clear filters
|
||||||
const clearCircleFilter = () => {
|
const clearCircleFilter = () => {
|
||||||
selectedCircle.value = "";
|
selectedCircle.value = "all";
|
||||||
loadMembers();
|
};
|
||||||
|
|
||||||
|
const clearPeerSupportFilter = () => {
|
||||||
|
peerSupportFilter.value = "all";
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearAllFilters = () => {
|
const clearAllFilters = () => {
|
||||||
searchQuery.value = "";
|
searchQuery.value = "";
|
||||||
selectedCircle.value = "";
|
selectedCircle.value = "all";
|
||||||
|
peerSupportFilter.value = "all";
|
||||||
selectedSkills.value = [];
|
selectedSkills.value = [];
|
||||||
|
selectedTopics.value = [];
|
||||||
loadMembers();
|
loadMembers();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Slack DM functionality
|
||||||
|
const openSlackDM = async (member) => {
|
||||||
|
const username = member.peerSupport?.slackUsername || member.name;
|
||||||
|
|
||||||
|
// Copy username to clipboard
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(username);
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Could not copy to clipboard:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show alert and open Slack
|
||||||
|
alert(
|
||||||
|
`Opening Slack...\n\nSearch for: ${username}\n\n(Username copied to clipboard)`,
|
||||||
|
);
|
||||||
|
window.open("https://gammaspace.slack.com", "_blank");
|
||||||
|
};
|
||||||
|
|
||||||
// Load on mount
|
// Load on mount
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadMembers();
|
loadMembers();
|
||||||
|
|
|
||||||
|
|
@ -1,338 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div></div>
|
||||||
<PageHeader
|
|
||||||
title="Peer Support"
|
|
||||||
subtitle="Connect with fellow members for 1:1 guidance and support"
|
|
||||||
theme="purple"
|
|
||||||
size="medium"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<section class="py-12 px-4">
|
|
||||||
<UContainer class="px-4">
|
|
||||||
<!-- Intro Text -->
|
|
||||||
<div
|
|
||||||
class="mb-8 backdrop-blur-sm bg-stone-900/50 border border-stone-700/50 rounded-lg p-6"
|
|
||||||
>
|
|
||||||
<p class="text-stone-300 mb-2">
|
|
||||||
Ghost Guild members offering peer support on various topics. Reach
|
|
||||||
out to schedule a conversation, ask questions, or get feedback on
|
|
||||||
your cooperative journey.
|
|
||||||
</p>
|
|
||||||
<p class="text-sm text-stone-400">
|
|
||||||
Interested in offering peer support?
|
|
||||||
<NuxtLink
|
|
||||||
to="/member/settings/peer-support"
|
|
||||||
class="text-purple-400 hover:text-purple-300 underline"
|
|
||||||
>
|
|
||||||
Enable it in your settings
|
|
||||||
</NuxtLink>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Topic Filter -->
|
|
||||||
<div v-if="availableTopics.length > 0" class="mb-8">
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<span class="text-sm text-stone-400 mr-2 self-center"
|
|
||||||
>Filter by topic:</span
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="px-3 py-1 rounded-full text-sm transition-all border"
|
|
||||||
:class="
|
|
||||||
!selectedTopic
|
|
||||||
? 'bg-purple-500/20 text-purple-300 border-purple-500/50'
|
|
||||||
: 'bg-stone-800/50 text-stone-400 border-stone-700 hover:border-stone-600'
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
selectedTopic = null;
|
|
||||||
loadSupporters();
|
|
||||||
"
|
|
||||||
>
|
|
||||||
All Topics
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-for="topic in availableTopics"
|
|
||||||
:key="topic"
|
|
||||||
type="button"
|
|
||||||
class="px-3 py-1 rounded-full text-sm transition-all border"
|
|
||||||
:class="
|
|
||||||
selectedTopic === topic
|
|
||||||
? 'bg-purple-500/20 text-purple-300 border-purple-500/50'
|
|
||||||
: 'bg-stone-800/50 text-stone-400 border-stone-700 hover:border-stone-600'
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
selectedTopic = topic;
|
|
||||||
loadSupporters();
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ topic }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading State -->
|
|
||||||
<div
|
|
||||||
v-if="loading && !supporters.length"
|
|
||||||
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 peer supporters...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Supporters Grid -->
|
|
||||||
<div v-else-if="supporters.length > 0">
|
|
||||||
<div class="mb-4 text-stone-400 text-sm">
|
|
||||||
{{ totalCount }}
|
|
||||||
{{ totalCount === 1 ? "peer supporter" : "peer supporters" }}
|
|
||||||
available
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4 max-w-3xl">
|
|
||||||
<div
|
|
||||||
v-for="supporter in supporters"
|
|
||||||
:key="supporter._id"
|
|
||||||
class="backdrop-blur-sm bg-stone-900/50 border border-stone-700/50 rounded-lg p-6 hover:border-purple-500/50 transition-all group"
|
|
||||||
>
|
|
||||||
<!-- Avatar and Name -->
|
|
||||||
<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 group-hover:border-purple-500/50 transition-colors"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-if="supporter.avatar"
|
|
||||||
:src="`/ghosties/Ghost-${supporter.avatar.charAt(0).toUpperCase() + supporter.avatar.slice(1)}.png`"
|
|
||||||
:alt="supporter.name"
|
|
||||||
class="w-8 h-8 object-contain"
|
|
||||||
/>
|
|
||||||
<span v-else class="text-xl text-stone-600">👻</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<h3 class="font-semibold text-stone-100 truncate">
|
|
||||||
{{ supporter.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"
|
|
||||||
>
|
|
||||||
{{ circleLabels[supporter.circle] }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Topics -->
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
supporter.peerSupport?.topics &&
|
|
||||||
supporter.peerSupport.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 supporter.peerSupport.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>
|
|
||||||
|
|
||||||
<!-- Availability -->
|
|
||||||
<div
|
|
||||||
v-if="supporter.peerSupport?.availability"
|
|
||||||
class="mb-4 text-sm text-stone-400"
|
|
||||||
>
|
|
||||||
<div class="text-xs text-stone-500 mb-1">Availability:</div>
|
|
||||||
{{ supporter.peerSupport.availability }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Personal Message -->
|
|
||||||
<div
|
|
||||||
v-if="supporter.peerSupport?.personalMessage"
|
|
||||||
class="mb-4 text-sm text-stone-300 italic bg-stone-800/30 rounded-lg p-3 border-l-2 border-purple-500/50"
|
|
||||||
>
|
|
||||||
"{{ supporter.peerSupport.personalMessage }}"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<div class="flex gap-2 mt-4">
|
|
||||||
<a
|
|
||||||
v-if="supporter.peerSupport?.slackUsername"
|
|
||||||
:href="getSlackDMLink(supporter)"
|
|
||||||
@click.prevent="openSlackDM(supporter)"
|
|
||||||
class="flex-1 px-3 py-2 bg-purple-500/20 text-purple-300 rounded border border-purple-500/30 hover:bg-purple-500/30 transition-colors text-center text-sm font-medium cursor-pointer"
|
|
||||||
>
|
|
||||||
Message {{ supporter.peerSupport.slackUsername }} on Slack
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
v-else
|
|
||||||
href="slack://open"
|
|
||||||
@click.prevent="openSlackApp"
|
|
||||||
class="flex-1 px-3 py-2 bg-stone-800/50 text-stone-300 rounded border border-stone-700 hover:border-stone-600 transition-colors text-center text-sm font-medium cursor-pointer"
|
|
||||||
>
|
|
||||||
Find on Slack
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Empty State -->
|
|
||||||
<div v-else class="text-center py-20">
|
|
||||||
<div class="w-16 h-16 mx-auto mb-4 opacity-50">
|
|
||||||
<svg
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="text-stone-600"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-lg font-medium text-stone-300 mb-2">
|
|
||||||
No peer supporters yet
|
|
||||||
</h3>
|
|
||||||
<p class="text-stone-400 mb-6">
|
|
||||||
Be the first to offer peer support to the community!
|
|
||||||
</p>
|
|
||||||
<UButton to="/member/settings/peer-support">
|
|
||||||
Enable Peer Support
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- CTA for Members -->
|
|
||||||
<div
|
|
||||||
v-if="isAuthenticated && supporters.length > 0"
|
|
||||||
class="mt-8 backdrop-blur-sm bg-purple-500/10 border border-purple-500/30 rounded-lg p-6 text-center"
|
|
||||||
>
|
|
||||||
<p class="text-purple-200 mb-4">
|
|
||||||
💜 Want to offer peer support to fellow members?
|
|
||||||
</p>
|
|
||||||
<UButton to="/member/settings/peer-support" variant="outline">
|
|
||||||
Set Up Peer Support
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Not Authenticated Notice -->
|
|
||||||
<div
|
|
||||||
v-if="!isAuthenticated"
|
|
||||||
class="mt-8 backdrop-blur-sm bg-purple-500/10 border border-purple-500/30 rounded-lg p-6 text-center"
|
|
||||||
>
|
|
||||||
<p class="text-purple-200 mb-4">
|
|
||||||
🔒 Peer support is available to Ghost Guild members
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-3 justify-center">
|
|
||||||
<UButton to="/login" variant="outline"> Log In </UButton>
|
|
||||||
<UButton to="/join"> Join Ghost Guild </UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</UContainer>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const { isAuthenticated } = useAuth();
|
// Redirect to members directory with peer support filter
|
||||||
const { getSupporters } = usePeerSupport();
|
definePageMeta({
|
||||||
|
middleware: defineNuxtRouteMiddleware(() => {
|
||||||
// State
|
return navigateTo("/members?peerSupport=true");
|
||||||
const supporters = ref([]);
|
}),
|
||||||
const totalCount = ref(0);
|
|
||||||
const availableTopics = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
const selectedTopic = ref(null);
|
|
||||||
|
|
||||||
// Gamma Space Slack team ID
|
|
||||||
const slackTeamId = "T03A96LV4";
|
|
||||||
|
|
||||||
// Circle labels
|
|
||||||
const circleLabels = {
|
|
||||||
community: "Community",
|
|
||||||
founder: "Founder",
|
|
||||||
practitioner: "Practitioner",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get Slack DM deep link
|
|
||||||
const getSlackDMLink = (supporter) => {
|
|
||||||
// If we have the DM channel ID, use it for direct DM
|
|
||||||
if (supporter.peerSupport?.slackDMChannelId) {
|
|
||||||
return `slack://channel?team=${slackTeamId}&id=${supporter.peerSupport.slackDMChannelId}`;
|
|
||||||
}
|
|
||||||
// Otherwise fall back to opening workspace
|
|
||||||
return "slack://open";
|
|
||||||
};
|
|
||||||
|
|
||||||
// Open Slack DM
|
|
||||||
const openSlackDM = async (supporter) => {
|
|
||||||
console.log("Opening Slack DM for supporter:", supporter);
|
|
||||||
const username = supporter.peerSupport?.slackUsername || supporter.name;
|
|
||||||
|
|
||||||
// Copy username to clipboard
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(username);
|
|
||||||
console.log("Copied username to clipboard:", username);
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Could not copy to clipboard:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a toast/alert (you can replace this with a proper toast notification)
|
|
||||||
alert(
|
|
||||||
`Opening Slack...\n\nSearch for: ${username}\n\n(Username copied to clipboard)`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Open Slack workspace
|
|
||||||
window.open("https://gammaspace.slack.com", "_blank");
|
|
||||||
};
|
|
||||||
|
|
||||||
const openSlackApp = () => {
|
|
||||||
// Try to open Slack app
|
|
||||||
window.location.href = "slack://open";
|
|
||||||
|
|
||||||
// Fallback to web after a short delay if app doesn't open
|
|
||||||
setTimeout(() => {
|
|
||||||
window.open("https://gammaspace.slack.com", "_blank");
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load supporters
|
|
||||||
const loadSupporters = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await getSupporters(selectedTopic.value);
|
|
||||||
supporters.value = data.supporters;
|
|
||||||
totalCount.value = data.totalCount;
|
|
||||||
availableTopics.value = data.filters.availableTopics;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load peer supporters:", error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load on mount
|
|
||||||
onMounted(() => {
|
|
||||||
loadSupporters();
|
|
||||||
});
|
|
||||||
|
|
||||||
useHead({
|
|
||||||
title: "Peer Support - Ghost Guild",
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
content:
|
|
||||||
"Connect with Ghost Guild members for 1:1 guidance on governance, fundraising, team building, and more.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ export default defineEventHandler(async (event) => {
|
||||||
const search = query.search || "";
|
const search = query.search || "";
|
||||||
const circle = query.circle || "";
|
const circle = query.circle || "";
|
||||||
const tags = query.tags ? query.tags.split(",") : [];
|
const tags = query.tags ? query.tags.split(",") : [];
|
||||||
|
const peerSupport = query.peerSupport || "";
|
||||||
|
const topics = query.topics ? query.topics.split(",") : [];
|
||||||
|
|
||||||
// Build query
|
// Build query
|
||||||
const dbQuery = {
|
const dbQuery = {
|
||||||
|
|
@ -37,6 +39,11 @@ export default defineEventHandler(async (event) => {
|
||||||
dbQuery.circle = circle;
|
dbQuery.circle = circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by peer support availability
|
||||||
|
if (peerSupport === "true") {
|
||||||
|
dbQuery["peerSupport.enabled"] = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Search by name or bio
|
// Search by name or bio
|
||||||
if (search) {
|
if (search) {
|
||||||
dbQuery.$or = [
|
dbQuery.$or = [
|
||||||
|
|
@ -71,10 +78,15 @@ export default defineEventHandler(async (event) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by peer support topics
|
||||||
|
if (topics.length > 0) {
|
||||||
|
dbQuery["peerSupport.topics"] = { $in: topics };
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const members = await Member.find(dbQuery)
|
const members = await Member.find(dbQuery)
|
||||||
.select(
|
.select(
|
||||||
"name pronouns timeZone avatar studio bio location socialLinks offering lookingFor privacy circle createdAt",
|
"name pronouns timeZone avatar studio bio location socialLinks offering lookingFor privacy circle peerSupport slackUserId createdAt",
|
||||||
)
|
)
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.lean();
|
.lean();
|
||||||
|
|
@ -109,6 +121,12 @@ export default defineEventHandler(async (event) => {
|
||||||
if (isVisible("offering")) filtered.offering = member.offering;
|
if (isVisible("offering")) filtered.offering = member.offering;
|
||||||
if (isVisible("lookingFor")) filtered.lookingFor = member.lookingFor;
|
if (isVisible("lookingFor")) filtered.lookingFor = member.lookingFor;
|
||||||
|
|
||||||
|
// Always show peer support if enabled (it's opt-in, so public by nature)
|
||||||
|
if (member.peerSupport?.enabled) {
|
||||||
|
filtered.peerSupport = member.peerSupport;
|
||||||
|
filtered.slackUserId = member.slackUserId;
|
||||||
|
}
|
||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -121,11 +139,19 @@ export default defineEventHandler(async (event) => {
|
||||||
.filter((tag, index, self) => self.indexOf(tag) === index)
|
.filter((tag, index, self) => self.indexOf(tag) === index)
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
|
// Get unique peer support topics
|
||||||
|
const allTopics = members
|
||||||
|
.filter((m) => m.peerSupport?.enabled)
|
||||||
|
.flatMap((m) => m.peerSupport?.topics || [])
|
||||||
|
.filter((topic, index, self) => self.indexOf(topic) === index)
|
||||||
|
.sort();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
members: filteredMembers,
|
members: filteredMembers,
|
||||||
totalCount: filteredMembers.length,
|
totalCount: filteredMembers.length,
|
||||||
filters: {
|
filters: {
|
||||||
availableTags: allTags,
|
availableSkills: allTags,
|
||||||
|
availableTopics: allTopics,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,16 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
// Handle offering and lookingFor separately (nested objects)
|
// Handle offering and lookingFor separately (nested objects)
|
||||||
if (body.offering !== undefined) {
|
if (body.offering !== undefined) {
|
||||||
updateData["offering.text"] = body.offering.text || "";
|
updateData.offering = {
|
||||||
updateData["offering.tags"] = body.offering.tags || [];
|
text: body.offering.text || "",
|
||||||
|
tags: body.offering.tags || [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (body.lookingFor !== undefined) {
|
if (body.lookingFor !== undefined) {
|
||||||
updateData["lookingFor.text"] = body.lookingFor.text || "";
|
updateData.lookingFor = {
|
||||||
updateData["lookingFor.tags"] = body.lookingFor.tags || [];
|
text: body.lookingFor.text || "",
|
||||||
|
tags: body.lookingFor.tags || [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle privacy settings
|
// Handle privacy settings
|
||||||
|
|
|
||||||
62
server/migrations/fix-offering-lookingfor-structure.js
Normal file
62
server/migrations/fix-offering-lookingfor-structure.js
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Migration to fix offering and lookingFor field structure
|
||||||
|
// Run this once to convert string values to object structure
|
||||||
|
import mongoose from "mongoose";
|
||||||
|
import Member from "../models/member.js";
|
||||||
|
import { connectDB } from "../utils/mongoose.js";
|
||||||
|
|
||||||
|
async function migrateOfferingLookingFor() {
|
||||||
|
await connectDB();
|
||||||
|
|
||||||
|
console.log("Starting migration: fixing offering and lookingFor structure...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Find all members where offering or lookingFor is a string (not an object)
|
||||||
|
const members = await Member.find({
|
||||||
|
$or: [
|
||||||
|
{ offering: { $type: "string" } },
|
||||||
|
{ lookingFor: { $type: "string" } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Found ${members.length} members to migrate`);
|
||||||
|
|
||||||
|
for (const member of members) {
|
||||||
|
const updates = {};
|
||||||
|
|
||||||
|
// Convert offering if it's a string
|
||||||
|
if (typeof member.offering === "string") {
|
||||||
|
updates.offering = {
|
||||||
|
text: member.offering,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
console.log(
|
||||||
|
`Converting offering for member ${member._id}: "${member.offering}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert lookingFor if it's a string
|
||||||
|
if (typeof member.lookingFor === "string") {
|
||||||
|
updates.lookingFor = {
|
||||||
|
text: member.lookingFor,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
console.log(
|
||||||
|
`Converting lookingFor for member ${member._id}: "${member.lookingFor}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the member
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
await Member.findByIdAndUpdate(member._id, { $set: updates });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Migration completed successfully!");
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Migration failed:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrateOfferingLookingFor();
|
||||||
Loading…
Add table
Add a link
Reference in a new issue