diff --git a/app/composables/useBoard.js b/app/composables/useBoard.js new file mode 100644 index 0000000..9e1fc47 --- /dev/null +++ b/app/composables/useBoard.js @@ -0,0 +1,6 @@ +export const useBoard = () => { + const getSuggestions = (params = {}) => + $fetch('/api/board/suggestions', { params }) + + return { getSuggestions } +} diff --git a/app/composables/useEcology.js b/app/composables/useEcology.js deleted file mode 100644 index ab7eec6..0000000 --- a/app/composables/useEcology.js +++ /dev/null @@ -1,6 +0,0 @@ -export const useEcology = () => { - const getSuggestions = (params = {}) => - $fetch('/api/ecology/suggestions', { params }) - - return { getSuggestions } -} diff --git a/app/pages/board.vue b/app/pages/board.vue new file mode 100644 index 0000000..bace328 --- /dev/null +++ b/app/pages/board.vue @@ -0,0 +1,628 @@ + + + + + diff --git a/app/pages/members/index.vue b/app/pages/members/index.vue index 133826b..9c072ff 100644 --- a/app/pages/members/index.vue +++ b/app/pages/members/index.vue @@ -1,101 +1,76 @@ @@ -289,18 +160,7 @@ definePageMeta({ middleware: ['members-auth'] }) const route = useRoute() -const router = useRouter() -const { memberData } = useAuth() const { render: renderMarkdown } = useMarkdown() -const { getSuggestions } = useEcology() -const { trackGoal, isComplete } = useOnboarding() - -// ---- View mode (URL-driven) ---- -const viewMode = ref(route.query.view === 'ecology' ? 'ecology' : 'directory') - -watch(viewMode, (val) => { - router.replace({ query: { ...route.query, view: val === 'ecology' ? 'ecology' : undefined } }) -}) // ---- Directory state ---- const members = ref([]) @@ -310,26 +170,11 @@ const searchQuery = ref('') const selectedCircle = ref('all') const peerSupportFilter = ref('all') const directoryCraftTags = ref([]) -const directoryCraftTagOptions = ref([]) -const directoryConnectionTagOptions = ref([]) +const craftTagOptions = ref([]) const showAllTags = ref(false) const showTagsDrawer = ref(false) -// ---- Ecology state ---- -const suggestions = ref([]) -const ecologyLoading = ref(false) -const ecologyTagOptions = ref([]) -const ecologyFilterTags = ref([]) -const copiedHandle = ref(null) - -// ---- Shared helpers ---- -const stateLabels = { - help: 'Can help', - interested: 'Interested', - seeking: 'Need help', -} -const stateLabel = (state) => stateLabels[state] || state || '' - +// ---- Helpers ---- const circleOptions = [ { label: 'All Circles', value: 'all' }, { label: 'Community', value: 'community' }, @@ -344,18 +189,10 @@ const circleLabels = { } const craftTagLabel = (slug) => { - const found = directoryCraftTagOptions.value.find((t) => t.slug === slug) + const found = craftTagOptions.value.find((t) => t.slug === slug) return found ? found.label : slug } -const ecologyTagLabel = (slug) => { - const found = ecologyTagOptions.value.find((t) => t.slug === slug) - if (found) return found.label - // Fallback to directory connection tags - const fallback = directoryConnectionTagOptions.value.find((t) => t.slug === slug) - return fallback ? fallback.label : slug -} - const getInitials = (name) => { if (!name) return '?' return name @@ -374,75 +211,22 @@ const capitalize = (str) => { .join('-') } -// ---- Computed: tag options & active tags based on mode ---- -const currentTagOptions = computed(() => - viewMode.value === 'ecology' ? ecologyTagOptions.value : directoryCraftTagOptions.value -) - -const currentActiveTags = computed(() => - viewMode.value === 'ecology' ? ecologyFilterTags.value : directoryCraftTags.value -) - +// ---- Computed ---- const visibleTagOptions = computed(() => - showAllTags.value ? currentTagOptions.value : currentTagOptions.value.slice(0, 10) + showAllTags.value ? craftTagOptions.value : craftTagOptions.value.slice(0, 10) ) -const hasNoTopics = computed(() => { - if (!memberData.value) return false - const topics = memberData.value?.communityEcology?.topics - return !topics || topics.length === 0 -}) - -const filteredSuggestions = computed(() => { - if (ecologyFilterTags.value.length === 0) return suggestions.value - return suggestions.value.filter((s) => - s.matchingTags.some((m) => ecologyFilterTags.value.includes(m.tagSlug)) - ) -}) - const hasActiveFilters = computed(() => (selectedCircle.value && selectedCircle.value !== 'all') || peerSupportFilter.value === 'true' || directoryCraftTags.value.length > 0 ) -const pageSubtitle = computed(() => { - if (viewMode.value === 'ecology') { - const count = suggestions.value.length - return `${count} connection${count === 1 ? '' : 's'}` - } - return `${totalCount.value} member${totalCount.value === 1 ? '' : 's'} across 3 circles` -}) +const pageSubtitle = computed(() => + `${totalCount.value} member${totalCount.value === 1 ? '' : 's'} across 3 circles` +) -// ---- View mode switching ---- -const setViewMode = (mode) => { - if (viewMode.value === mode) return - // Reset all filter state - searchQuery.value = '' - selectedCircle.value = 'all' - peerSupportFilter.value = 'all' - directoryCraftTags.value = [] - ecologyFilterTags.value = [] - showTagsDrawer.value = false - showAllTags.value = false - - viewMode.value = mode - - if (mode === 'directory') { - loadMembers() - } else { - loadEcology() - } -} - -// ---- Onboarding tracking ---- -watch(viewMode, (val) => { - if (val === 'ecology' && !isComplete.value) { - trackGoal('ecologyPageVisited') - } -}) - -// ---- Directory: load members ---- +// ---- Load members ---- const loadMembers = async () => { loading.value = true try { @@ -456,11 +240,8 @@ const loadMembers = async () => { members.value = data.members || [] totalCount.value = data.totalCount || 0 - if (data.filters?.craftTags && directoryCraftTagOptions.value.length === 0) { - directoryCraftTagOptions.value = data.filters.craftTags - } - if (data.filters?.cooperativeTags && directoryConnectionTagOptions.value.length === 0) { - directoryConnectionTagOptions.value = data.filters.cooperativeTags + if (data.filters?.craftTags && craftTagOptions.value.length === 0) { + craftTagOptions.value = data.filters.craftTags } } catch (error) { console.error('Failed to load members:', error) @@ -471,43 +252,19 @@ const loadMembers = async () => { } } -// ---- Directory: load tag options ---- +// ---- Load tag options ---- const loadTagOptions = async () => { try { const data = await $fetch('/api/tags') const tags = data.tags || [] - directoryCraftTagOptions.value = tags + craftTagOptions.value = tags .filter((t) => t.pool === 'craft') .map((t) => ({ slug: t.slug, label: t.label })) - directoryConnectionTagOptions.value = tags - .filter((t) => t.pool === 'cooperative') - .map((t) => ({ slug: t.slug, label: t.label })) } catch (error) { console.error('Failed to load tags:', error) } } -// ---- Ecology: load suggestions ---- -const loadEcology = async () => { - ecologyLoading.value = true - try { - const data = await getSuggestions() - suggestions.value = data.suggestions || [] - - // Build ecology tag options from user's own topics - const allCoopTags = directoryConnectionTagOptions.value - const myTopicSlugs = (memberData.value?.communityEcology?.topics || []).map((t) => t.tagSlug) - ecologyTagOptions.value = myTopicSlugs.length - ? allCoopTags.filter((t) => myTopicSlugs.includes(t.slug)) - : allCoopTags - } catch (error) { - console.error('Failed to load ecology:', error) - suggestions.value = [] - } finally { - ecologyLoading.value = false - } -} - // ---- Filter helpers ---- const togglePeerSupport = (e) => { peerSupportFilter.value = e.target.checked ? 'true' : 'all' @@ -522,19 +279,6 @@ const debouncedSearch = () => { }, 300) } -const toggleTag = (slug) => { - if (viewMode.value === 'ecology') { - const idx = ecologyFilterTags.value.indexOf(slug) - if (idx > -1) { - ecologyFilterTags.value.splice(idx, 1) - } else { - ecologyFilterTags.value.push(slug) - } - } else { - toggleDirectoryCraftTag(slug) - } -} - const toggleDirectoryCraftTag = (slug) => { const idx = directoryCraftTags.value.indexOf(slug) if (idx > -1) { @@ -564,41 +308,20 @@ const clearAllFilters = () => { loadMembers() } -// ---- Clipboard ---- -let copyTimer = null -const copyHandle = async (handle) => { - try { - await navigator.clipboard.writeText(`@${handle}`) - copiedHandle.value = handle - if (copyTimer) clearTimeout(copyTimer) - copyTimer = setTimeout(() => { - copiedHandle.value = null - copyTimer = null - }, 1500) - } catch (error) { - console.error('Clipboard write failed:', error) - } -} - onBeforeUnmount(() => { - if (copyTimer) clearTimeout(copyTimer) clearTimeout(searchTimeout) }) -// ---- useHead (reactive) ---- -useHead(computed(() => ({ - title: viewMode.value === 'ecology' - ? 'Community Ecology - Ghost Guild' - : 'Member Directory - Ghost Guild', +// ---- useHead ---- +useHead({ + title: 'Member Directory - Ghost Guild', meta: [ { name: 'description', - content: viewMode.value === 'ecology' - ? 'Find Ghost Guild members who share your cooperative interests and reach out on Slack.' - : 'Connect with members of the Ghost Guild community - game developers, founders, and practitioners building solidarity economy studios.', + content: 'Connect with members of the Ghost Guild community - game developers, founders, and practitioners building solidarity economy studios.', }, ], -}))) +}) // ---- Init ---- onMounted(async () => { @@ -607,47 +330,11 @@ onMounted(async () => { } await loadTagOptions() - - if (viewMode.value === 'ecology') { - await loadEcology() - } else { - await loadMembers() - } + await loadMembers() })