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 = [
{ label: "Events", path: "/events" },
{ label: "Members", path: "/members" },
{ label: "Board", path: "/board" },
{ label: "Wiki", path: "https://wiki.ghostguild.org", external: true },
{ label: "About", path: "/about" },
];

View file

@ -66,7 +66,7 @@ const { goals, isComplete, currentSuggestion, trackGoal, loading } = useOnboardi
const completedCount = computed(() => {
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
})

View file

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

View file

@ -351,13 +351,13 @@ function statusClass(status) {
const hasProfileTags = computed(() => {
const m = member.value
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 m = member.value
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)
)
})

View file

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

View file

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

View file

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

View file

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

View file

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