refactor(community): rename Community Connections → Community Ecology
Simplify the feature to pure discovery (filter by topic, see matching members, copy Slack handle). Drop the connection request/confirm flow entirely — Connection model, 7 API endpoints, useConnections composable, and TagInput component deleted. - Rename communityConnections → communityEcology in schema, API, pages - Delete legacy fields: offering, lookingFor, peerSupport - New /ecology page, /api/ecology/suggestions, community-ecology.patch - Nav: "Connections" → "Ecology", remove pending-count badge - Fix auth/member.get.js missing craftTags + communityEcology - Add community_ecology_updated activity log type - Expose slackHandle conditionally when offerPeerSupport is true - Add migration script at scripts/migrate-to-ecology.js (run before deploy)
This commit is contained in:
parent
9577929e0d
commit
0b3896d984
33 changed files with 1002 additions and 2635 deletions
|
|
@ -108,9 +108,9 @@
|
|||
<div class="profile-bio" v-html="renderMarkdown(member.bio)"></div>
|
||||
</div>
|
||||
|
||||
<!-- Two-column: Craft Tags + Community Connections -->
|
||||
<!-- Two-column: Craft Tags + Community Ecology -->
|
||||
<div
|
||||
v-if="craftTagsDisplay.length > 0 || member.offering?.text || connectionTopicsDisplay.length > 0 || member.lookingFor?.text || member.communityConnections?.details"
|
||||
v-if="craftTagsDisplay.length > 0 || ecologyTopics.length > 0 || member.communityEcology?.details"
|
||||
class="profile-two-col"
|
||||
>
|
||||
<!-- Left: What I Do -->
|
||||
|
|
@ -123,59 +123,39 @@
|
|||
class="tag-pill"
|
||||
>{{ tagLabel('craft', tag) }}</span>
|
||||
</div>
|
||||
<p v-if="member.offering?.text" class="profile-detail offering-text">
|
||||
{{ member.offering.text }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Right: Community Connections -->
|
||||
<!-- Right: Community Ecology -->
|
||||
<div class="profile-section">
|
||||
<div class="section-label">Community Connections</div>
|
||||
<div v-if="connectionTopicsDisplay.length > 0" class="tag-list">
|
||||
<div class="section-label">Community Ecology</div>
|
||||
<div v-if="ecologyTopics.length > 0" class="tag-list">
|
||||
<span
|
||||
v-for="topic in connectionTopicsDisplay"
|
||||
:key="topic.tagSlug || topic"
|
||||
v-for="topic in ecologyTopics"
|
||||
:key="topic.tagSlug"
|
||||
class="tag-pill connection-pill"
|
||||
>
|
||||
<span v-if="topic.state" class="connection-state">{{ stateLabel(topic.state) }}</span>
|
||||
{{ tagLabel('cooperative', topic.tagSlug || topic) }}
|
||||
{{ tagLabel('cooperative', topic.tagSlug) }}
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="member.communityConnections?.details" class="profile-detail connection-details">
|
||||
{{ member.communityConnections.details }}
|
||||
</p>
|
||||
<p v-else-if="member.lookingFor?.text" class="profile-detail looking-text">
|
||||
{{ member.lookingFor.text }}
|
||||
<p v-if="member.communityEcology?.details" class="profile-detail connection-details">
|
||||
{{ member.communityEcology.details }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Peer Support -->
|
||||
<div v-if="showPeerSupport" class="profile-section">
|
||||
<div v-if="member.communityEcology?.offerPeerSupport" class="profile-section">
|
||||
<div class="section-label">Peer Support</div>
|
||||
<div class="dashed-box no-hover">
|
||||
<div v-if="member.peerSupport?.skillTopics?.length" class="peer-group">
|
||||
<span class="peer-label">Skills</span>
|
||||
<div class="tag-list">
|
||||
<span
|
||||
v-for="topic in member.peerSupport.skillTopics"
|
||||
:key="topic"
|
||||
class="tag-pill"
|
||||
>{{ topic }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="member.peerSupport?.supportTopics?.length" class="peer-group">
|
||||
<span class="peer-label">Topics</span>
|
||||
<div class="tag-list">
|
||||
<span
|
||||
v-for="topic in member.peerSupport.supportTopics"
|
||||
:key="topic"
|
||||
class="tag-pill"
|
||||
>{{ topic }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="peerAvailability" class="profile-detail peer-availability">
|
||||
{{ peerAvailability }}
|
||||
<p v-if="member.communityEcology?.personalMessage" class="profile-detail">
|
||||
{{ member.communityEcology.personalMessage }}
|
||||
</p>
|
||||
<p v-if="member.communityEcology?.availability" class="profile-detail peer-availability">
|
||||
{{ member.communityEcology.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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -290,48 +270,11 @@ const tagLabel = (pool, slug) => {
|
|||
return found ? found.label : slug;
|
||||
};
|
||||
|
||||
// Craft tags display: new field, falling back to offering.tags
|
||||
const craftTagsDisplay = computed(() => {
|
||||
if (!member.value) return [];
|
||||
if (member.value.craftTags && member.value.craftTags.length > 0) {
|
||||
return member.value.craftTags;
|
||||
}
|
||||
return member.value.offering?.tags || [];
|
||||
});
|
||||
const craftTagsDisplay = computed(() => member.value?.craftTags || []);
|
||||
|
||||
// Connection topics display: new field, falling back to lookingFor.tags
|
||||
const connectionTopicsDisplay = computed(() => {
|
||||
if (!member.value) return [];
|
||||
if (
|
||||
member.value.communityConnections?.topics &&
|
||||
member.value.communityConnections.topics.length > 0
|
||||
) {
|
||||
return member.value.communityConnections.topics;
|
||||
}
|
||||
if (member.value.lookingFor?.tags && member.value.lookingFor.tags.length > 0) {
|
||||
return member.value.lookingFor.tags.map((tag) => ({ tagSlug: tag, state: null }));
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// Peer support: check both new communityConnections and old peerSupport
|
||||
const showPeerSupport = computed(() => {
|
||||
if (!member.value) return false;
|
||||
return (
|
||||
member.value.communityConnections?.offerPeerSupport ||
|
||||
member.value.peerSupport?.enabled
|
||||
);
|
||||
});
|
||||
|
||||
// Peer availability: prefer new field, fall back to old
|
||||
const peerAvailability = computed(() => {
|
||||
if (!member.value) return "";
|
||||
return (
|
||||
member.value.communityConnections?.availability ||
|
||||
member.value.peerSupport?.availability ||
|
||||
""
|
||||
);
|
||||
});
|
||||
const ecologyTopics = computed(
|
||||
() => member.value?.communityEcology?.topics || [],
|
||||
);
|
||||
|
||||
// Whether the member has any social links (for hero layout)
|
||||
const hasSocialLinks = computed(() =>
|
||||
|
|
@ -587,8 +530,6 @@ useHead({
|
|||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
.offering-text,
|
||||
.looking-text,
|
||||
.connection-details {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
|
@ -623,26 +564,15 @@ useHead({
|
|||
PEER SUPPORT
|
||||
==================================================== */
|
||||
|
||||
.peer-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.peer-group:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.peer-label {
|
||||
font-family: "Commit Mono", monospace;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-faint);
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.peer-availability {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed var(--border);
|
||||
}
|
||||
.slack-handle {
|
||||
font-family: "Commit Mono", monospace;
|
||||
color: var(--candle-dim);
|
||||
}
|
||||
|
||||
/* ====================================================
|
||||
ACTIVITY TIMELINE
|
||||
|
|
|
|||
|
|
@ -183,32 +183,30 @@
|
|||
}}
|
||||
</div>
|
||||
|
||||
<!-- Craft tags (fall back to offering.tags) -->
|
||||
<div
|
||||
v-if="getMemberCraftTags(member).length > 0"
|
||||
v-if="member.craftTags?.length > 0"
|
||||
class="mc-tags"
|
||||
>
|
||||
<span class="tag-label">Craft:</span>
|
||||
<span
|
||||
v-for="tag in getMemberCraftTags(member)"
|
||||
v-for="tag in member.craftTags"
|
||||
:key="tag"
|
||||
class="skill-tag"
|
||||
>{{ craftTagLabel(tag) }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Community connections topics (fall back to lookingFor.tags) -->
|
||||
<div
|
||||
v-if="getMemberConnectionTopics(member).length > 0"
|
||||
v-if="member.communityEcology?.topics?.length > 0"
|
||||
class="mc-looking"
|
||||
>
|
||||
<span
|
||||
v-for="topic in getMemberConnectionTopics(member)"
|
||||
:key="topic.tagSlug || topic"
|
||||
v-for="topic in member.communityEcology.topics"
|
||||
:key="topic.tagSlug"
|
||||
class="connection-topic"
|
||||
>
|
||||
<span class="connection-state">{{ stateLabel(topic.state) }}</span>
|
||||
{{ connectionTagLabel(topic.tagSlug || topic) }}
|
||||
{{ connectionTagLabel(topic.tagSlug) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -319,36 +317,8 @@ const connectionTagLabel = (slug) => {
|
|||
return found ? found.label : slug;
|
||||
};
|
||||
|
||||
// Get craft tags for a member (new field, falling back to offering.tags)
|
||||
const getMemberCraftTags = (member) => {
|
||||
if (member.craftTags && member.craftTags.length > 0) {
|
||||
return member.craftTags;
|
||||
}
|
||||
return member.offering?.tags || [];
|
||||
};
|
||||
|
||||
// Get connection topics for a member (new field, falling back to lookingFor.tags)
|
||||
const getMemberConnectionTopics = (member) => {
|
||||
if (
|
||||
member.communityConnections?.topics &&
|
||||
member.communityConnections.topics.length > 0
|
||||
) {
|
||||
return member.communityConnections.topics;
|
||||
}
|
||||
// Fallback: wrap old lookingFor.tags as plain strings
|
||||
if (member.lookingFor?.tags && member.lookingFor.tags.length > 0) {
|
||||
return member.lookingFor.tags.map((tag) => ({ tagSlug: tag, state: null }));
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// Show peer support link (check both old and new fields)
|
||||
const showPeerSupport = (member) => {
|
||||
if (member.communityConnections?.offerPeerSupport) return true;
|
||||
if (member.peerSupport?.enabled && member.peerSupport?.slackUsername)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
const showPeerSupport = (member) =>
|
||||
!!member.communityEcology?.offerPeerSupport;
|
||||
|
||||
// Computed: has active filters
|
||||
const hasActiveFilters = computed(() => {
|
||||
|
|
@ -486,10 +456,7 @@ const clearAllFilters = () => {
|
|||
|
||||
// Slack DM functionality
|
||||
const openSlackDM = async (member) => {
|
||||
const username =
|
||||
member.communityConnections?.slackHandle ||
|
||||
member.peerSupport?.slackUsername ||
|
||||
member.name;
|
||||
const username = member.communityEcology?.slackHandle || member.name;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(username);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue