diff --git a/.gitignore b/.gitignore index 8916e2a..0944c34 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ e2e/.auth/ # Worktrees .worktrees/ +.claude/worktrees/ diff --git a/app/components/CirclePicker.vue b/app/components/CirclePicker.vue index 19f4900..227bb3c 100644 --- a/app/components/CirclePicker.vue +++ b/app/components/CirclePicker.vue @@ -26,7 +26,7 @@ defineProps({ default: () => [ { value: 'community', label: 'Community', description: 'Learning together, exploring cooperative models' }, { value: 'founder', label: 'Founder', description: 'Actively building a cooperative studio' }, - { value: 'practitioner', label: 'Practitioner', description: 'Experienced in cooperative business' }, + { value: 'practitioner', label: 'Practitioner', description: 'Experienced in cooperative practice' }, ], }, }) diff --git a/app/components/ColumnsLayout.vue b/app/components/ColumnsLayout.vue index b0fa072..3ea07c4 100644 --- a/app/components/ColumnsLayout.vue +++ b/app/components/ColumnsLayout.vue @@ -53,6 +53,7 @@ if (props.cols === 'events-sidebar') { /* cols="events-sidebar" */ .columns-events-sidebar { grid-template-columns: 1fr 200px; + flex: 1; } /* Ensure grid children don't overflow */ @@ -60,11 +61,14 @@ if (props.cols === 'events-sidebar') { min-width: 0; } -/* Dashed divider: right border on the first column child */ +/* Dashed divider: right border on the first column child (except events-sidebar, which owns its own border-left) */ .divider-dashed .col:first-child, .divider-dashed .col-main { border-right: 1px dashed var(--border); } +.divider-dashed.columns-events-sidebar .col-main { + border-right: none; +} /* Responsive collapse at 1024px (default) */ .collapse-1024 { diff --git a/app/components/TopStrip.vue b/app/components/TopStrip.vue index decb4cb..e34682f 100644 --- a/app/components/TopStrip.vue +++ b/app/components/TopStrip.vue @@ -57,8 +57,8 @@ {{ memberData.name }} - A cooperative for game developers - A cooperative for game developers + The Baby Ghosts member program + The Baby Ghosts member program diff --git a/app/composables/useMemberStatus.js b/app/composables/useMemberStatus.js index 44f37e9..6c4acb9 100644 --- a/app/composables/useMemberStatus.js +++ b/app/composables/useMemberStatus.js @@ -4,137 +4,149 @@ */ export const MEMBER_STATUSES = { - PENDING_PAYMENT: 'pending_payment', - ACTIVE: 'active', - SUSPENDED: 'suspended', - CANCELLED: 'cancelled', -} + PENDING_PAYMENT: "pending_payment", + ACTIVE: "active", + SUSPENDED: "suspended", + CANCELLED: "cancelled", +}; export const MEMBER_STATUS_CONFIG = { pending_payment: { - label: 'Payment Pending', - color: 'orange', - bgColor: 'bg-orange-500/10', - borderColor: 'border-orange-500/30', - textColor: 'text-orange-300', - icon: 'heroicons:exclamation-triangle', - severity: 'warning', + label: "Payment Pending", + color: "orange", + bgColor: "bg-orange-500/10", + borderColor: "border-orange-500/30", + textColor: "text-orange-300", + icon: "heroicons:exclamation-triangle", + severity: "warning", canRSVP: false, canAccessMembers: true, canPeerSupport: false, }, active: { - label: 'Active Member', - color: 'green', - bgColor: 'bg-green-500/10', - borderColor: 'border-green-500/30', - textColor: 'text-green-300', - icon: 'heroicons:check-circle', - severity: 'success', + label: "Active Member", + color: "green", + bgColor: "bg-green-500/10", + borderColor: "border-green-500/30", + textColor: "text-green-300", + icon: "heroicons:check-circle", + severity: "success", canRSVP: true, canAccessMembers: true, canPeerSupport: true, }, suspended: { - label: 'Membership Suspended', - color: 'red', - bgColor: 'bg-red-500/10', - borderColor: 'border-red-500/30', - textColor: 'text-red-300', - icon: 'heroicons:no-symbol', - severity: 'error', + label: "Membership Suspended", + color: "red", + bgColor: "bg-red-500/10", + borderColor: "border-red-500/30", + textColor: "text-red-300", + icon: "heroicons:no-symbol", + severity: "error", canRSVP: false, canAccessMembers: false, canPeerSupport: false, }, cancelled: { - label: 'Membership Cancelled', - color: 'gray', - bgColor: 'bg-gray-500/10', - borderColor: 'border-gray-500/30', - textColor: 'text-gray-300', - icon: 'heroicons:x-circle', - severity: 'error', + label: "Membership Cancelled", + color: "gray", + bgColor: "bg-gray-500/10", + borderColor: "border-gray-500/30", + textColor: "text-gray-300", + icon: "heroicons:x-circle", + severity: "error", canRSVP: false, canAccessMembers: false, canPeerSupport: false, }, -} +}; export const useMemberStatus = () => { - const { memberData } = useAuth() + const { memberData } = useAuth(); // Get current member status - const status = computed(() => memberData.value?.status || MEMBER_STATUSES.PENDING_PAYMENT) + const status = computed( + () => memberData.value?.status || MEMBER_STATUSES.PENDING_PAYMENT, + ); // Get status configuration - const statusConfig = computed(() => MEMBER_STATUS_CONFIG[status.value] || MEMBER_STATUS_CONFIG.pending_payment) + const statusConfig = computed( + () => + MEMBER_STATUS_CONFIG[status.value] || + MEMBER_STATUS_CONFIG.pending_payment, + ); // Helper methods - const isActive = computed(() => status.value === MEMBER_STATUSES.ACTIVE) - const isPendingPayment = computed(() => status.value === MEMBER_STATUSES.PENDING_PAYMENT) - const isSuspended = computed(() => status.value === MEMBER_STATUSES.SUSPENDED) - const isCancelled = computed(() => status.value === MEMBER_STATUSES.CANCELLED) - const isInactive = computed(() => !isActive.value) + const isActive = computed(() => status.value === MEMBER_STATUSES.ACTIVE); + const isPendingPayment = computed( + () => status.value === MEMBER_STATUSES.PENDING_PAYMENT, + ); + const isSuspended = computed( + () => status.value === MEMBER_STATUSES.SUSPENDED, + ); + const isCancelled = computed( + () => status.value === MEMBER_STATUSES.CANCELLED, + ); + const isInactive = computed(() => !isActive.value); // Check if member can perform action - const canRSVP = computed(() => statusConfig.value.canRSVP) - const canAccessMembers = computed(() => statusConfig.value.canAccessMembers) - const canPeerSupport = computed(() => statusConfig.value.canPeerSupport) + const canRSVP = computed(() => statusConfig.value.canRSVP); + const canAccessMembers = computed(() => statusConfig.value.canAccessMembers); + const canPeerSupport = computed(() => statusConfig.value.canPeerSupport); // Get action button text and link based on status const getNextAction = () => { if (isPendingPayment.value) { return { - label: 'Complete Payment', - link: '/member/profile#account', - icon: 'heroicons:credit-card', - color: 'orange', - } + label: "Complete Payment", + link: "/member/account", + icon: "heroicons:credit-card", + color: "orange", + }; } if (isCancelled.value) { return { - label: 'Reactivate Membership', - link: '/member/profile#account', - icon: 'heroicons:arrow-path', - color: 'blue', - } + label: "Reactivate Membership", + link: "/member/account", + icon: "heroicons:arrow-path", + color: "blue", + }; } if (isSuspended.value) { return { - label: 'Contact Support', - link: 'mailto:support@ghostguild.org', - icon: 'heroicons:envelope', - color: 'gray', - } + label: "Contact Support", + link: "mailto:support@ghostguild.org", + icon: "heroicons:envelope", + color: "gray", + }; } - return null - } + return null; + }; // Get banner message based on status const getBannerMessage = () => { if (isPendingPayment.value) { - return 'Your membership is pending payment. Please complete your payment to unlock full features.' + return "Your membership is pending payment. Please complete your payment to unlock full features."; } if (isSuspended.value) { - return 'Your membership has been suspended. Please contact support to reactivate your account.' + return "Your membership has been suspended. Please contact support to reactivate your account."; } if (isCancelled.value) { - return 'Your membership has been cancelled. Would you like to reactivate?' + return "Your membership has been cancelled. Would you like to reactivate?"; } - return null - } + return null; + }; // Get RSVP restriction message const getRSVPMessage = () => { if (isPendingPayment.value) { - return 'Complete your payment to register for events' + return "Complete your payment to register for events"; } if (isSuspended.value || isCancelled.value) { - return 'Your membership status prevents RSVP. Please reactivate your account.' + return "Your membership status prevents RSVP. Please reactivate your account."; } - return null - } + return null; + }; return { status, @@ -151,5 +163,5 @@ export const useMemberStatus = () => { getBannerMessage, getRSVPMessage, MEMBER_STATUSES, - } -} + }; +}; diff --git a/app/config/circles.js b/app/config/circles.js index 0e2faa2..ccd5875 100644 --- a/app/config/circles.js +++ b/app/config/circles.js @@ -21,7 +21,7 @@ export const CIRCLES = { shortDescription: "Building your studio", description: "For those actively establishing or growing their coop", features: [ - "Teams working toward applying for the Peer Accelerator", + "Teams working toward applying for Cooperative Foundations", "Early-stage coop studios", "Studios transitioning to coop model", ], @@ -33,7 +33,7 @@ export const CIRCLES = { value: "practitioner", label: "Practitioners", shortDescription: "Leading and mentoring", - description: "For Peer Accelerator alumni and experienced studio founders", + description: "For alumni and experienced studio founders", features: [ "Those implementing cooperative models", "Industry mentors and advisors", diff --git a/app/middleware/members-auth.js b/app/middleware/members-auth.js new file mode 100644 index 0000000..bba27db --- /dev/null +++ b/app/middleware/members-auth.js @@ -0,0 +1,12 @@ +export default defineNuxtRouteMiddleware(async (to, from) => { + if (process.server) return; + + const { memberData, checkMemberStatus } = useAuth(); + + if (!memberData.value) { + const isAuthenticated = await checkMemberStatus(); + if (!isAuthenticated) { + return navigateTo("/join"); + } + } +}); diff --git a/app/pages/about.vue b/app/pages/about.vue index e3a8b6f..a423811 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -6,26 +6,27 @@
A membership community for game developers exploring cooperative - business models. + models.
Ghost Guild grew out of Baby Ghosts, a Canadian nonprofit that's been - supporting indie game developers since 2018. We noticed a gap: game - developers interested in cooperative models had nowhere to learn, - practice, and connect with others doing the same work. + advancing cooperative and worker-centric models in the game industry + since 2023.
- Ghost Guild is the response — a membership program where - developers at every stage of cooperative practice can find resources, - events, mentorship, and community. + Developers interested in co-op practice had few places to learn, + connect, and figure things out alongside others doing the same work. + Ghost Guild is that place: a membership community for developers at + every stage of cooperative practice, with resources, events, and peers + to learn from.
- We don't prescribe a single model. We're a place to explore the - options, learn from people who've tried them, and build something that - works for your team. + We don't prescribe a single model. We're here to explore the options, + learn from people who've tried them, and build something that works + for your team.
- For anyone exploring cooperative models. Wiki access, public - events, Slack community, monthly meetings. -
+ +For anyone exploring cooperative models.
- For people actively building cooperatives. Peer accelerator, - mentorship, governance templates. -
+For people actively building cooperatives.
- For experienced practitioners. Mentoring, teaching, shaping the - program direction. -
+For experienced practitioners sharing what they know.
- Ghost Guild is a program of Baby Ghosts, a Canadian nonprofit - advancing cooperative models in game development. No tracking. No ads. - No venture capital. + Ghost Guild is part of Baby Ghosts, a Canadian nonprofit advancing + cooperative models in game development.
- babyghosts.fund →babyghosts.org →
Every dollar above $0 goes to the Solidarity Fund. Your contribution is never a gate -- it is a gift.
+Pay what you can. If you can pay more, you're making room for someone who can't.
| - - | -- Collection - {{ sortDir === 'asc' ? '↑' : '↓' }} - | -- Title - {{ sortDir === 'asc' ? '↑' : '↓' }} - | -Tags | -Actions | -
|---|---|---|---|---|
| - - | -{{ article.collection || '—' }} | -- - {{ article.title }} - - | -
-
- {{ tagLabel(tag) }}
-
-
-
-
-
-
-
-
-
-
- |
- - |
| + + | ++ Title {{ sortDir === 'asc' ? '▲' : '▼' }} + | ++ Collection {{ sortDir === 'asc' ? '▲' : '▼' }} + | +Tags | ++ Vis {{ sortDir === 'asc' ? '▲' : '▼' }} + | ++ Updated {{ sortDir === 'asc' ? '▲' : '▼' }} + | +Actions | +
|---|---|---|---|---|---|---|
| + + | ++ + {{ article.title }} + + | +{{ article.collection || '—' }} | +
+
+ {{ tagLabel(tag) }}
+
+
+
+
+
+
+
+
+ |
+
+ |
+ + {{ formatDate(article.outlineUpdatedAt) }} + | +
+ |
+