Rename communityEcology → board across frontend, add Board nav, update redirects

- Add Board to exploreItems in AppNavigation
- Update ecology.vue + connections.vue redirects to /board
- Rename all communityEcology refs to board in member profiles, dashboard, admin, onboarding
- Update API path /api/members/me/community-ecology → /api/members/me/board
This commit is contained in:
Jennie Robinson Faber 2026-04-14 12:15:51 +01:00
parent 3e5cedb1a6
commit cdef868256
9 changed files with 75 additions and 76 deletions

View file

@ -210,6 +210,7 @@ const youItems = [
const exploreItems = [ const exploreItems = [
{ label: "Events", path: "/events" }, { label: "Events", path: "/events" },
{ label: "Members", path: "/members" }, { label: "Members", path: "/members" },
{ label: "Board", path: "/board" },
{ label: "Wiki", path: "https://wiki.ghostguild.org", external: true }, { label: "Wiki", path: "https://wiki.ghostguild.org", external: true },
{ label: "About", path: "/about" }, { label: "About", path: "/about" },
]; ];

View file

@ -66,7 +66,7 @@ const { goals, isComplete, currentSuggestion, trackGoal, loading } = useOnboardi
const completedCount = computed(() => { const completedCount = computed(() => {
const g = goals.value const g = goals.value
return [g.hasProfileTags, g.hasVisitedEvent, g.hasEngagedEcology, g.hasClickedWiki] return [g.hasProfileTags, g.hasVisitedEvent, g.hasEngagedBoard, g.hasClickedWiki]
.filter(Boolean).length .filter(Boolean).length
}) })

View file

@ -6,7 +6,7 @@ export function useOnboarding(options = {}) {
const goals = useState('onboarding.goals', () => ({ const goals = useState('onboarding.goals', () => ({
hasProfileTags: false, hasProfileTags: false,
hasVisitedEvent: false, hasVisitedEvent: false,
hasEngagedEcology: false, hasEngagedBoard: false,
hasClickedWiki: false, hasClickedWiki: false,
})) }))
@ -14,7 +14,7 @@ export function useOnboarding(options = {}) {
const loading = useState('onboarding.loading', () => false) const loading = useState('onboarding.loading', () => false)
const recommendations = useState('onboarding.recommendations', () => ({ const recommendations = useState('onboarding.recommendations', () => ({
events: [], events: [],
ecology: [], board: [],
wiki: [], wiki: [],
})) }))
@ -25,7 +25,7 @@ export function useOnboarding(options = {}) {
!!completedAt.value || !!completedAt.value ||
(goals.value.hasProfileTags && (goals.value.hasProfileTags &&
goals.value.hasVisitedEvent && goals.value.hasVisitedEvent &&
goals.value.hasEngagedEcology && goals.value.hasEngagedBoard &&
goals.value.hasClickedWiki) goals.value.hasClickedWiki)
) )
@ -52,12 +52,12 @@ export function useOnboarding(options = {}) {
actionText: 'Browse events', actionText: 'Browse events',
} }
} }
if (!goals.value.hasEngagedEcology) { if (!goals.value.hasEngagedBoard) {
return { return {
key: 'ecology', key: 'board',
text: 'Explore the community ecology to find collaborators', text: 'Explore the board to find collaborators',
action: '/ecology', action: '/board',
actionText: 'Explore ecology', actionText: 'Explore board',
} }
} }
if (!goals.value.hasClickedWiki) { if (!goals.value.hasClickedWiki) {
@ -72,7 +72,7 @@ export function useOnboarding(options = {}) {
} }
// Graduated — suggestion mode // Graduated — suggestion mode
const cats = ['events', 'ecology', 'wiki'].filter( const cats = ['events', 'board', 'wiki'].filter(
(c) => recommendations.value[c]?.length > 0 (c) => recommendations.value[c]?.length > 0
) )
@ -99,12 +99,12 @@ export function useOnboarding(options = {}) {
actionText: 'View event', actionText: 'View event',
} }
} }
if (category === 'ecology') { if (category === 'board') {
return { return {
key: 'ecology', key: 'board',
text: `Connect with ${item.name || 'a member'} in the ecology`, text: `Connect with ${item.name || 'a member'} on the board`,
action: '/ecology', action: '/board',
actionText: 'Explore ecology', actionText: 'Explore board',
} }
} }
if (category === 'wiki') { if (category === 'wiki') {
@ -144,14 +144,14 @@ export function useOnboarding(options = {}) {
} }
async function fetchRecommendations() { async function fetchRecommendations() {
const [events, ecology, wiki] = await Promise.allSettled([ const [events, board, wiki] = await Promise.allSettled([
$fetch('/api/events/recommended'), $fetch('/api/events/recommended'),
$fetch('/api/ecology/suggestions'), $fetch('/api/board/suggestions'),
$fetch('/api/wiki/recommended'), $fetch('/api/wiki/recommended'),
]) ])
recommendations.value = { recommendations.value = {
events: events.status === 'fulfilled' ? (events.value || []) : [], events: events.status === 'fulfilled' ? (events.value || []) : [],
ecology: ecology.status === 'fulfilled' ? (ecology.value?.suggestions || []) : [], board: board.status === 'fulfilled' ? (board.value?.suggestions || []) : [],
wiki: wiki.status === 'fulfilled' ? (wiki.value || []) : [], wiki: wiki.status === 'fulfilled' ? (wiki.value || []) : [],
} }
} }

View file

@ -351,13 +351,13 @@ function statusClass(status) {
const hasProfileTags = computed(() => { const hasProfileTags = computed(() => {
const m = member.value const m = member.value
if (!m) return false if (!m) return false
return m.craftTags?.length > 0 && m.communityEcology?.topics?.length > 0 return m.craftTags?.length > 0 && m.board?.topics?.length > 0
}) })
const hasEcologyEngaged = computed(() => { const hasEcologyEngaged = computed(() => {
const m = member.value const m = member.value
if (!m) return false if (!m) return false
return m.onboarding?.ecologyPageVisited && m.communityEcology?.topics?.some( return m.onboarding?.boardPageVisited && m.board?.topics?.some(
t => ['help', 'interested', 'seeking'].includes(t.state) t => ['help', 'interested', 'seeking'].includes(t.state)
) )
}) })

View file

@ -1,4 +1,3 @@
<script setup> <script setup>
definePageMeta({ middleware: "auth" }); await navigateTo("/board", { replace: true });
await navigateTo("/members?view=ecology", { replace: true });
</script> </script>

View file

@ -1,4 +1,3 @@
<script setup> <script setup>
definePageMeta({ middleware: "auth" }); await navigateTo("/board", { replace: true });
await navigateTo("/members?view=ecology", { replace: true });
</script> </script>

View file

@ -120,16 +120,16 @@
<div class="content-block"> <div class="content-block">
<div class="section-label">Quick Actions</div> <div class="section-label">Quick Actions</div>
<NuxtLink <NuxtLink
to="/ecology" to="/board"
class="quick-action" class="quick-action"
:class="{ disabled: !canPeerSupport }" :class="{ disabled: !canPeerSupport }"
:title=" :title="
!canPeerSupport !canPeerSupport
? 'Complete your membership to access community ecology' ? 'Complete your membership to access the board'
: '' : ''
" "
> >
Community ecology<span class="arrow">&rarr;</span> Board<span class="arrow">&rarr;</span>
</NuxtLink> </NuxtLink>
<NuxtLink to="/member/profile" class="quick-action"> <NuxtLink to="/member/profile" class="quick-action">
Update your profile<span class="arrow">&rarr;</span> Update your profile<span class="arrow">&rarr;</span>
@ -198,8 +198,8 @@
Connect with other members through shared interests and Connect with other members through shared interests and
cooperative topics. cooperative topics.
</p> </p>
<NuxtLink to="/ecology" class="section-link"> <NuxtLink to="/board" class="section-link">
Browse community ecology &rarr; Browse the board &rarr;
</NuxtLink> </NuxtLink>
</DashedBox> </DashedBox>
</div> </div>

View file

@ -162,34 +162,34 @@
<template #right> <template #right>
<PageSection> <PageSection>
<div class="section-label">Community Ecology</div> <div class="section-label">Board</div>
<div class="field"> <div class="field">
<label>Topics</label> <label>Topics</label>
<CooperativeTagSelector <CooperativeTagSelector
v-model="formData.communityEcologyTopics" v-model="formData.boardTopics"
:tags="cooperativeTags" :tags="cooperativeTags"
@suggest="openTagSuggest('cooperative')" @suggest="openTagSuggest('cooperative')"
/> />
<PrivacyToggle v-model="formData.communityEcologyPrivacy" /> <PrivacyToggle v-model="formData.boardPrivacy" />
</div> </div>
<div class="field"> <div class="field">
<label>Details</label> <label>Details</label>
<textarea <textarea
v-model="formData.communityEcologyDetails" v-model="formData.boardDetails"
rows="3" rows="3"
placeholder="What are you hoping to connect about?" placeholder="What are you hoping to connect about?"
maxlength="300" maxlength="300"
></textarea> ></textarea>
<div class="char-count"> <div class="char-count">
{{ formData.communityEcologyDetails?.length || 0 }} / 300 {{ formData.boardDetails?.length || 0 }} / 300
</div> </div>
</div> </div>
<div class="toggle-field"> <div class="toggle-field">
<USwitch <USwitch
v-model="formData.communityEcologyOfferPeerSupport" v-model="formData.boardOfferPeerSupport"
aria-label="Offer Peer Support" aria-label="Offer Peer Support"
/> />
<div class="toggle-label"> <div class="toggle-label">
@ -200,11 +200,11 @@
</div> </div>
</div> </div>
<div v-if="formData.communityEcologyOfferPeerSupport" class="connections-panel"> <div v-if="formData.boardOfferPeerSupport" class="connections-panel">
<div class="field"> <div class="field">
<label>Availability</label> <label>Availability</label>
<textarea <textarea
v-model="formData.communityEcologyAvailability" v-model="formData.boardAvailability"
rows="3" rows="3"
placeholder="e.g. Weekday afternoons ET" placeholder="e.g. Weekday afternoons ET"
></textarea> ></textarea>
@ -213,7 +213,7 @@
<div class="field"> <div class="field">
<label>Slack Handle</label> <label>Slack Handle</label>
<input <input
v-model="formData.communityEcologySlackHandle" v-model="formData.boardSlackHandle"
type="text" type="text"
placeholder="@yourslackname" placeholder="@yourslackname"
/> />
@ -222,13 +222,13 @@
<div class="field"> <div class="field">
<label>Personal Message</label> <label>Personal Message</label>
<textarea <textarea
v-model="formData.communityEcologyPersonalMessage" v-model="formData.boardPersonalMessage"
rows="3" rows="3"
maxlength="200" maxlength="200"
placeholder="Brief note shown alongside your Slack handle" placeholder="Brief note shown alongside your Slack handle"
></textarea> ></textarea>
<div class="char-count"> <div class="char-count">
{{ formData.communityEcologyPersonalMessage?.length || 0 }} / 200 {{ formData.boardPersonalMessage?.length || 0 }} / 200
</div> </div>
</div> </div>
</div> </div>
@ -339,13 +339,13 @@ const formData = reactive({
showInDirectory: true, showInDirectory: true,
craftTags: [], craftTags: [],
craftTagsPrivacy: "members", craftTagsPrivacy: "members",
communityEcologyTopics: [], boardTopics: [],
communityEcologyPrivacy: "members", boardPrivacy: "members",
communityEcologyDetails: "", boardDetails: "",
communityEcologyOfferPeerSupport: false, boardOfferPeerSupport: false,
communityEcologyAvailability: "", boardAvailability: "",
communityEcologySlackHandle: "", boardSlackHandle: "",
communityEcologyPersonalMessage: "", boardPersonalMessage: "",
pronounsPrivacy: "members", pronounsPrivacy: "members",
timeZonePrivacy: "members", timeZonePrivacy: "members",
avatarPrivacy: "members", avatarPrivacy: "members",
@ -387,13 +387,13 @@ const loadProfile = () => {
? [...memberData.value.craftTags] ? [...memberData.value.craftTags]
: []; : [];
const ecology = memberData.value.communityEcology || {}; const board = memberData.value.board || {};
formData.communityEcologyTopics = Array.isArray(ecology.topics) ? [...ecology.topics] : []; formData.boardTopics = Array.isArray(board.topics) ? [...board.topics] : [];
formData.communityEcologyOfferPeerSupport = ecology.offerPeerSupport ?? false; formData.boardOfferPeerSupport = board.offerPeerSupport ?? false;
formData.communityEcologyAvailability = ecology.availability || ""; formData.boardAvailability = board.availability || "";
formData.communityEcologySlackHandle = ecology.slackHandle || ""; formData.boardSlackHandle = board.slackHandle || "";
formData.communityEcologyPersonalMessage = ecology.personalMessage || ""; formData.boardPersonalMessage = board.personalMessage || "";
formData.communityEcologyDetails = ecology.details || ""; formData.boardDetails = board.details || "";
const privacy = memberData.value.privacy || {}; const privacy = memberData.value.privacy || {};
formData.pronounsPrivacy = privacy.pronouns || "members"; formData.pronounsPrivacy = privacy.pronouns || "members";
@ -403,7 +403,7 @@ const loadProfile = () => {
formData.bioPrivacy = privacy.bio || "members"; formData.bioPrivacy = privacy.bio || "members";
formData.locationPrivacy = privacy.location || "members"; formData.locationPrivacy = privacy.location || "members";
formData.craftTagsPrivacy = privacy.craftTags || "members"; formData.craftTagsPrivacy = privacy.craftTags || "members";
formData.communityEcologyPrivacy = privacy.communityEcology || "members"; formData.boardPrivacy = privacy.board || "members";
const notifs = memberData.value.notifications || {}; const notifs = memberData.value.notifications || {};
formData.notifications.events = notifs.events ?? true; formData.notifications.events = notifs.events ?? true;
@ -423,15 +423,15 @@ const handleSubmit = async () => {
method: "PATCH", method: "PATCH",
body: { ...formData }, body: { ...formData },
}), }),
$fetch("/api/members/me/community-ecology", { $fetch("/api/members/me/board", {
method: "PATCH", method: "PATCH",
body: { body: {
topics: formData.communityEcologyTopics, topics: formData.boardTopics,
offerPeerSupport: formData.communityEcologyOfferPeerSupport, offerPeerSupport: formData.boardOfferPeerSupport,
availability: formData.communityEcologyAvailability, availability: formData.boardAvailability,
slackHandle: formData.communityEcologySlackHandle, slackHandle: formData.boardSlackHandle,
personalMessage: formData.communityEcologyPersonalMessage, personalMessage: formData.boardPersonalMessage,
details: formData.communityEcologyDetails, details: formData.boardDetails,
}, },
}), }),
]); ]);

View file

@ -108,9 +108,9 @@
<div class="profile-bio" v-html="renderMarkdown(member.bio)"></div> <div class="profile-bio" v-html="renderMarkdown(member.bio)"></div>
</div> </div>
<!-- Two-column: Craft Tags + Community Ecology --> <!-- Two-column: Craft Tags + Board -->
<div <div
v-if="craftTagsDisplay.length > 0 || ecologyTopics.length > 0 || member.communityEcology?.details" v-if="craftTagsDisplay.length > 0 || ecologyTopics.length > 0 || member.board?.details"
class="profile-two-col" class="profile-two-col"
> >
<!-- Left: What I Do --> <!-- Left: What I Do -->
@ -125,9 +125,9 @@
</div> </div>
</div> </div>
<!-- Right: Community Ecology --> <!-- Right: Board -->
<div class="profile-section"> <div class="profile-section">
<div class="section-label">Community Ecology</div> <div class="section-label">Board</div>
<div v-if="ecologyTopics.length > 0" class="tag-list"> <div v-if="ecologyTopics.length > 0" class="tag-list">
<span <span
v-for="topic in ecologyTopics" v-for="topic in ecologyTopics"
@ -138,24 +138,24 @@
{{ tagLabel('cooperative', topic.tagSlug) }} {{ tagLabel('cooperative', topic.tagSlug) }}
</span> </span>
</div> </div>
<p v-if="member.communityEcology?.details" class="profile-detail connection-details"> <p v-if="member.board?.details" class="profile-detail connection-details">
{{ member.communityEcology.details }} {{ member.board.details }}
</p> </p>
</div> </div>
</div> </div>
<!-- Peer Support --> <!-- Peer Support -->
<div v-if="member.communityEcology?.offerPeerSupport" class="profile-section"> <div v-if="member.board?.offerPeerSupport" class="profile-section">
<div class="section-label">Peer Support</div> <div class="section-label">Peer Support</div>
<div class="dashed-box no-hover"> <div class="dashed-box no-hover">
<p v-if="member.communityEcology?.personalMessage" class="profile-detail"> <p v-if="member.board?.personalMessage" class="profile-detail">
{{ member.communityEcology.personalMessage }} {{ member.board.personalMessage }}
</p> </p>
<p v-if="member.communityEcology?.availability" class="profile-detail peer-availability"> <p v-if="member.board?.availability" class="profile-detail peer-availability">
{{ member.communityEcology.availability }} {{ member.board.availability }}
</p> </p>
<p v-if="member.communityEcology?.slackHandle" class="profile-detail peer-availability"> <p v-if="member.board?.slackHandle" class="profile-detail peer-availability">
Reach out on Slack: <span class="slack-handle">@{{ member.communityEcology.slackHandle }}</span> Reach out on Slack: <span class="slack-handle">@{{ member.board.slackHandle }}</span>
</p> </p>
</div> </div>
</div> </div>
@ -275,7 +275,7 @@ const tagLabel = (pool, slug) => {
const craftTagsDisplay = computed(() => member.value?.craftTags || []); const craftTagsDisplay = computed(() => member.value?.craftTags || []);
const ecologyTopics = computed( const ecologyTopics = computed(
() => member.value?.communityEcology?.topics || [], () => member.value?.board?.topics || [],
); );
// Whether the member has any social links (for hero layout) // Whether the member has any social links (for hero layout)