From 418d3cc402920c5bf8c76d53a03d749c8343af7d Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sun, 5 Apr 2026 12:28:41 +0100 Subject: [PATCH] UI/UX tweaks and improvements. --- .gitignore | 2 +- CLAUDE.md | 99 -- app/assets/css/fonts.css | 16 +- app/assets/css/main.css | 105 ++- app/assets/images/noise.webp | Bin 0 -> 21016 bytes app/components/AppNavigation.vue | 161 ++-- app/components/ColorModeToggle.vue | 31 +- app/components/DevLoginPanel.vue | 93 ++ app/components/EventsMiniSidebar.vue | 19 +- app/components/MemberStatusBanner.vue | 146 ++- app/components/PrivacyToggle.vue | 29 +- app/components/TagInput.vue | 38 +- app/components/TierPicker.vue | 22 +- app/components/TopStrip.vue | 122 ++- app/pages/about.vue | 158 +++- app/pages/index.vue | 147 ++- app/pages/member/account.vue | 437 +++++++-- app/pages/member/dashboard.vue | 404 ++++---- app/pages/member/index.vue | 3 + app/pages/member/profile.vue | 886 ++++++++++-------- app/pages/members/[id].vue | 514 ++++++++++ app/pages/{members.vue => members/index.vue} | 146 +-- nuxt.config.ts | 15 +- package-lock.json | 20 + package.json | 2 + server/api/dev/member-login.get.js | 2 +- server/api/dev/members.get.js | 19 + server/api/dev/test-login.get.js | 2 +- server/api/members/[id].get.js | 96 ++ server/api/members/update-email.post.js | 77 ++ .../import-babyghosts-preregistrations.js | 109 +++ server/models/member.js | 6 + 32 files changed, 2725 insertions(+), 1201 deletions(-) delete mode 100644 CLAUDE.md create mode 100644 app/assets/images/noise.webp create mode 100644 app/components/DevLoginPanel.vue create mode 100644 app/pages/member/index.vue create mode 100644 app/pages/members/[id].vue rename app/pages/{members.vue => members/index.vue} (85%) create mode 100644 server/api/dev/members.get.js create mode 100644 server/api/members/[id].get.js create mode 100644 server/api/members/update-email.post.js create mode 100644 server/migrations/import-babyghosts-preregistrations.js diff --git a/.gitignore b/.gitignore index 15f2a76..a0df783 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ logs .fleet .idea /docs/ -*.md/ +/*.md # Local env files .env diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 3b1685d..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,99 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Ghost Guild is a membership community platform for game developers exploring cooperative business models. Built with Nuxt 4, Vue 3, MongoDB, and Nuxt UI 4. - -## Commands - -```bash -npm run dev # Start dev server at http://localhost:3000 -npm run build # Production build -npm run preview # Preview production build -npm run test:run # Vitest single run (pre-push hook) -npm run test:e2e # Playwright E2E (needs dev server + MongoDB) -npm run test:a11y # Accessibility scans -npm run test:all # Vitest + Playwright -``` - -**Dev helpers:** `GET /api/dev/test-login` — creates a test admin user and sets auth cookie (dev only, blocked in production). Navigate to this URL to access admin pages during development. - -**Testing:** Vitest for unit/handler tests (`tests/`), Playwright for E2E (`e2e/`). Husky pre-push hook runs Vitest. See `TESTING.md` for details. - -## Architecture - -### Stack - -- **Framework:** Nuxt 4 (Vue 3 + Nitro server) -- **UI:** Nuxt UI 4 (`@nuxt/ui@^4`) with Tailwind CSS -- **Database:** MongoDB via Mongoose -- **Auth:** JWT magic link (email-only, no passwords) -- **Payments:** Helcim (recurring subscriptions + ticket sales) -- **Email:** Resend -- **Slack:** `@slack/web-api` for member invitations and notifications -- **Images:** Cloudinary -- **Analytics:** Plausible (`ghostguild.org`) - -### Key Directories - -- `app/composables/` — State management via `useState()` (no Pinia/Vuex). Key composables: `useAuth`, `useHelcim`, `useMemberPayment`, `useMemberStatus` -- `app/config/` — Circle definitions (`circles.js`) and contribution tiers (`contributions.js`) used across frontend and forms -- `app/middleware/` — Route guards: `auth.js` (member pages), `admin.js` (admin pages), `coming-soon.global.js` (launch gate) -- `app/layouts/` — `default` (sidebar, member/public), `admin` (sidebar, admin pages), `landing`, `coming-soon` -- `server/api/` — Nitro API routes organized by feature: `auth/`, `events/`, `members/`, `helcim/`, `series/`, `updates/`, `admin/`, `slack/`, `dev/` (dev-only helpers) -- `server/models/` — Mongoose schemas: `Member`, `Event`, `Series`, `Update` -- `server/utils/` — Service integrations: `mongoose.js`, `helcim.js`, `resend.js`, `slack.ts`, `tickets.js` - -### Domain Model - -Three membership **circles**: Community, Founder, Practitioner — each with different access and context. Five **contribution tiers**: $0, $5, $15, $30, $50/month via Helcim subscriptions. - -Member statuses: `pending_payment`, `active`, `suspended`, `cancelled`. - -Events support ticketing with circle-specific pricing overrides and can be grouped into Series with bundled passes. - -### Design System (Zine Direction) - -- **Palette:** CSS custom properties in `:root` / `.dark` blocks in `app/assets/css/main.css` — `--bg` (cream/#f4efe4), `--surface`, `--border`, `--candle` (gold accent), `--ember` (rust accent), `--text`, `--text-bright`, `--text-dim`, `--text-faint`, `--parch` (inverted blocks), `--c-community`, `--c-founder`, `--c-practitioner` -- **Typography:** Brygada 1918 (serif, display/headings) + Commit Mono (monospace, body/UI/everything structural) — loaded via Google Fonts in `nuxt.config.ts` -- **Theme:** `primary: amber`, `neutral: stone` — configured in `app/app.config.ts`. Tailwind `@theme` maps `--font-sans` and `--font-mono` to Commit Mono, `--font-display` to Brygada 1918 -- **Key classes:** `.btn` / `.btn-primary` / `.btn-danger` (buttons), `.field` (form groups), `.badge` (circle badges), `.section-label` (10px uppercase headers), `.dashed-box` (bordered containers), `.section-divider` -- **Visual language:** Dashed borders (1px dashed), cream backgrounds, no rounded corners, text-forward density, minimal decoration -- **Color mode:** `@nuxtjs/color-mode` with preference `system`, fallback `light`. Dark mode via `.dark` class on `` -- **Layouts:** `default` (sidebar + main, member/public pages), `admin` (sidebar + main, admin pages), `landing` (horizontal nav, unused) - -### Environment - -Copy `.env.example` to `.env`. Required: `MONGODB_URI`, `JWT_SECRET`, `RESEND_API_KEY`, `HELCIM_API_TOKEN`, `SLACK_BOT_TOKEN`. Public vars are prefixed `NUXT_PUBLIC_`. The `NUXT_PUBLIC_COMING_SOON` flag gates access behind a launch page. - -## Conventions - -- All frontend code is plain JavaScript (not TypeScript), using Vue 3 Composition API -- Server utilities auto-imported by Nitro — no explicit imports needed in API routes -- Use `USwitch` (not `UToggle`) — this is the correct Nuxt UI 3+ component name -- No fallback/placeholder data — always use real data -- Follow Nuxt 4 file-based routing conventions for route naming -- Always check Nuxt UI 4 latest documentation on the web when implementing UI components -- Auth API responses (`/api/auth/status`, `/api/auth/member`) must include `status` in the returned member object — `useMemberStatus` defaults to `PENDING_PAYMENT` if missing -- Helcim payment testing requires ngrok: `npx nuxi dev --https` then `ngrok http https://localhost:3000` — Helcim blocks localhost origins -- The `/api/helcim/initialize-payment` endpoint skips auth for `event_ticket` type payments (public users can buy tickets) - -## Product Spec - -The sections below describe planned and in-progress features for reference. - -### Member Features -- Profiles with privacy controls (public/members-only/private per field) -- Member updates/mini blog with rich text and images -- Peer support system with Cal.com integration for 1:1 scheduling - -### Events System -- RSVP with capacity limits and waitlist management -- Calendar export (.ics), ticketing, series passes -- Member-proposed events with interest threshold - -### Resources (Planned) -- Learning paths by circle, templates and tools, case studies -- Tag by circle relevance, download tracking, version control diff --git a/app/assets/css/fonts.css b/app/assets/css/fonts.css index 9b5bae6..62767ff 100644 --- a/app/assets/css/fonts.css +++ b/app/assets/css/fonts.css @@ -1,8 +1,16 @@ /* - * Font declarations for Ghost Guild — Zine Direction + * Self-hosted font declarations for Ghost Guild — Zine Direction * - * Brygada 1918: Display/heading serif (Google Fonts, variable 400-700, italic) - * Commit Mono: Body/UI monospace (Google Fonts) + * Brygada 1918: Display/heading serif + * Commit Mono: Body/UI monospace * - * Loaded via Google Fonts link in nuxt.config.ts head. + * Fonts are bundled locally via Fontsource. */ + +@import "@fontsource-variable/brygada-1918/wght.css"; +@import "@fontsource-variable/brygada-1918/wght-italic.css"; + +@import "@fontsource/commit-mono/400.css"; +@import "@fontsource/commit-mono/500.css"; +@import "@fontsource/commit-mono/600.css"; +@import "@fontsource/commit-mono/700.css"; diff --git a/app/assets/css/main.css b/app/assets/css/main.css index b4f6ac2..de0d0a7 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -15,6 +15,7 @@ :root { --bg: #f4efe4; + --input-bg: #faf8f2; --surface: #e8dfc8; --surface-hover: #e0d6bc; --border: #b8a880; @@ -36,10 +37,12 @@ --c-practitioner: #2a4650; --green: #4a6a38; --green-bg: rgba(74, 106, 56, 0.08); + --ember-bg: rgba(138, 68, 32, 0.1); } .dark { --bg: #131210; + --input-bg: #1c1a17; --surface: #1a1815; --surface-hover: #252220; --border: #2a2520; @@ -59,16 +62,17 @@ --c-community: #a06850; --c-founder: #c06030; --c-practitioner: #4a7080; + --ember-bg: rgba(192, 96, 48, 0.14); } /* ---- TAILWIND @THEME MAPPING ---- */ @theme { - --font-sans: 'Commit Mono', monospace; - --font-body: 'Commit Mono', monospace; - --font-mono: 'Commit Mono', monospace; - --font-display: 'Brygada 1918', serif; - --font-serif: 'Brygada 1918', serif; + --font-sans: "Commit Mono", monospace; + --font-body: "Commit Mono", monospace; + --font-mono: "Commit Mono", monospace; + --font-display: "Brygada 1918", serif; + --font-serif: "Brygada 1918", serif; /* Map primary to candle for Nuxt UI components */ --color-primary-500: var(--candle); @@ -81,14 +85,30 @@ body { background: var(--bg); color: var(--text); - font-family: 'Commit Mono', monospace; + font-family: "Commit Mono", monospace; font-size: 13px; line-height: 1.6; -webkit-font-smoothing: antialiased; } -a { color: var(--candle); text-decoration: none; } -a:hover { text-decoration: underline; } +/* ---- NOISE TEXTURE OVERLAY ---- */ +body::after { + content: ""; + position: fixed; + inset: 0; + z-index: 9999; + pointer-events: none; + background: url("~/assets/images/noise.webp") repeat; + opacity: 0.025; +} + +a { + color: var(--candle); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} /* ---- SECTION LABELS ---- */ .section-label { @@ -108,14 +128,26 @@ a:hover { text-decoration: underline; } padding: 2px 8px; border: 1px dashed; } -.badge.community { color: var(--c-community); border-color: rgba(122, 72, 56, 0.35); } -.badge.founder { color: var(--c-founder); border-color: rgba(138, 68, 32, 0.35); } -.badge.practitioner { color: var(--c-practitioner); border-color: rgba(42, 70, 80, 0.35); } -.badge.all { color: var(--text-dim); border-color: var(--border); } +.badge.community { + color: var(--c-community); + border-color: rgba(122, 72, 56, 0.35); +} +.badge.founder { + color: var(--c-founder); + border-color: rgba(138, 68, 32, 0.35); +} +.badge.practitioner { + color: var(--c-practitioner); + border-color: rgba(42, 70, 80, 0.35); +} +.badge.all { + color: var(--text-dim); + border-color: var(--border); +} /* ---- BUTTONS ---- */ .btn { - font-family: 'Commit Mono', monospace; + font-family: "Commit Mono", monospace; font-size: 12px; padding: 7px 18px; border: 1px dashed var(--border); @@ -125,14 +157,20 @@ a:hover { text-decoration: underline; } letter-spacing: 0.04em; transition: all 0.15s; } -.btn:hover { background: var(--surface-hover); border-color: var(--border-d); } +.btn:hover { + background: var(--surface-hover); + border-color: var(--border-d); +} .btn-primary { background: var(--candle); color: var(--bg); border-color: var(--candle); border-style: solid; } -.btn-primary:hover { background: var(--candle-dim); border-color: var(--candle-dim); } +.btn-primary:hover { + background: var(--candle-dim); + border-color: var(--candle-dim); +} .btn-danger { color: var(--ember); border-color: var(--ember); @@ -144,7 +182,9 @@ a:hover { text-decoration: underline; } } /* ---- FORM FIELDS ---- */ -.field { margin-bottom: 12px; } +.field { + margin-bottom: 12px; +} .field label { font-size: 10px; letter-spacing: 0.08em; @@ -153,17 +193,21 @@ a:hover { text-decoration: underline; } margin-bottom: 3px; display: block; } -.field input, .field select, .field textarea { +.field input, +.field select, +.field textarea { width: 100%; padding: 5px 8px; - font-family: 'Commit Mono', monospace; + font-family: "Commit Mono", monospace; font-size: 13px; color: var(--text-bright); - background: var(--bg); + background: var(--input-bg); border: 1px dashed var(--border); outline: none; } -.field input:focus, .field select:focus, .field textarea:focus { +.field input:focus, +.field select:focus, +.field textarea:focus { border-color: var(--candle); border-style: solid; } @@ -174,8 +218,25 @@ a:hover { text-decoration: underline; } padding: 20px 24px; transition: border-color 0.2s; } -.dashed-box:hover { border-color: var(--candle-faint); } -.dashed-box.no-hover:hover { border-color: var(--border); } +.dashed-box:hover { + border-color: var(--candle-faint); +} +.dashed-box.no-hover:hover { + border-color: var(--border); +} + +/* ---- SEGMENTED CONTROL (flush dashed-border groups) ---- */ +/* Negative-margin overlap: every item keeps all 4 borders, + siblings overlap by 1px, active item paints on top via z-index. */ +.segmented { + display: flex; +} +.segmented > * { + position: relative; +} +.segmented > * + * { + margin-left: -1px; +} /* ---- SECTION DIVIDERS ---- */ .section-divider { diff --git a/app/assets/images/noise.webp b/app/assets/images/noise.webp new file mode 100644 index 0000000000000000000000000000000000000000..d9f0fa20394d5f16a43bf708993fa6acbe03304f GIT binary patch literal 21016 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 literal 0 HcmV?d00001 diff --git a/app/components/AppNavigation.vue b/app/components/AppNavigation.vue index 6a82cd0..8fef28e 100644 --- a/app/components/AppNavigation.vue +++ b/app/components/AppNavigation.vue @@ -17,7 +17,13 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} + >{{ item.label }} + +
  • + Sign out
  • @@ -28,18 +34,8 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} - - - - - @@ -53,7 +49,8 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} + >{{ item.label }} @@ -64,7 +61,8 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} + >{{ item.label }} @@ -78,7 +76,8 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} + >{{ item.label }} @@ -89,7 +88,8 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} + >{{ item.label }} @@ -99,29 +99,18 @@ @@ -134,68 +123,59 @@ const props = defineProps({ type: Boolean, default: false, }, -}) +}); -const emit = defineEmits(['navigate']) +const emit = defineEmits(["navigate"]); -const route = useRoute() -const { isAuthenticated, logout, memberData } = useAuth() -const { openLoginModal } = useLoginModal() +const route = useRoute(); +const { isAuthenticated, logout } = useAuth(); +const isDev = import.meta.dev; const handleNavigate = () => { if (props.isMobile) { - emit('navigate') + emit("navigate"); } -} +}; const handleLogout = async () => { - await logout() - handleNavigate() -} - -const openLogin = () => { - openLoginModal() - handleNavigate() -} + await logout(); + handleNavigate(); + navigateTo("/"); +}; const isActive = (path) => { - if (path === '/') return route.path === '/' - return route.path.startsWith(path) -} + if (path === "/") return route.path === "/"; + return route.path.startsWith(path); +}; // Public nav items const publicItems = [ - { label: 'Home', path: '/' }, - { label: 'About', path: '/about' }, - { label: 'Events', path: '/events' }, - { label: 'Members', path: '/members' }, - { label: 'Wiki', path: '/wiki' }, -] + { label: "Home", path: "/" }, + { label: "About", path: "/about" }, + { label: "Events", path: "/events" }, + { label: "Members", path: "/members" }, + { label: "Wiki", path: "https://wiki.ghostguild.org" }, +]; const joinItems = [ - { label: 'Become a member', path: '/join' }, - { label: 'Propose an event', path: '/events' }, -] + { label: "Become a member", path: "/join" }, + { label: "Propose an event", path: "/events" }, +]; // Logged-in nav items const youItems = [ - { label: 'Dashboard', path: '/member/dashboard' }, - { label: 'Profile', path: '/member/profile' }, - { label: 'Account', path: '/member/account' }, - { label: 'My Updates', path: '/member/my-updates' }, -] + { label: "Dashboard", path: "/member/dashboard" }, + { label: "Profile", path: "/member/profile" }, + { label: "Account", path: "/member/account" }, + { label: "My Updates", path: "/member/my-updates" }, +]; const exploreItems = [ - { label: 'Events', path: '/events' }, - { label: 'Members', path: '/members' }, - { label: 'Wiki', path: '/wiki' }, - { label: 'About', path: '/about' }, -] - -const communityItems = [ - { label: 'Peer Support', path: '/members' }, - { label: 'Propose an Event', path: '/events' }, -] + { label: "Events", path: "/events" }, + { label: "Members", path: "/members" }, + { label: "Wiki", path: "/wiki" }, + { label: "About", path: "/about" }, +]; diff --git a/app/components/ColorModeToggle.vue b/app/components/ColorModeToggle.vue index fc7b28d..b8d0ab1 100644 --- a/app/components/ColorModeToggle.vue +++ b/app/components/ColorModeToggle.vue @@ -1,22 +1,24 @@ diff --git a/app/components/DevLoginPanel.vue b/app/components/DevLoginPanel.vue new file mode 100644 index 0000000..858c610 --- /dev/null +++ b/app/components/DevLoginPanel.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/app/components/EventsMiniSidebar.vue b/app/components/EventsMiniSidebar.vue index 84486de..1cf35ce 100644 --- a/app/components/EventsMiniSidebar.vue +++ b/app/components/EventsMiniSidebar.vue @@ -6,13 +6,16 @@
    - {{ formatDate(event.date) }} - {{ event.title }} + {{ formatDate(event.startDate) }} + {{ + event.title + }} {{ event.circle }} + >{{ event.circle }}
    @@ -30,13 +33,13 @@ diff --git a/app/components/PrivacyToggle.vue b/app/components/PrivacyToggle.vue index bafa0ce..2245e1a 100644 --- a/app/components/PrivacyToggle.vue +++ b/app/components/PrivacyToggle.vue @@ -1,26 +1,27 @@ diff --git a/app/components/TagInput.vue b/app/components/TagInput.vue index ca8140f..ab77fcb 100644 --- a/app/components/TagInput.vue +++ b/app/components/TagInput.vue @@ -17,37 +17,37 @@ diff --git a/app/pages/about.vue b/app/pages/about.vue index 4adbce6..93b028c 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -4,67 +4,115 @@

    About Ghost Guild

    -

    A membership community for game developers exploring cooperative business models.

    +

    + A membership community for game developers exploring cooperative + business 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.

    -

    Ghost Guild is the response — a membership program where developers at every stage of cooperative practice can find resources, events, mentorship, and community.

    -

    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.

    +

    + 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. +

    +

    + Ghost Guild is the response — a membership program where + developers at every stage of cooperative practice can find resources, + events, mentorship, and community. +

    +

    + 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. +

    -
    -

    Community

    +

    Community

    "The open hall"
    -

    For anyone exploring cooperative models. Wiki access, public events, Slack community, monthly meetings.

    +

    + For anyone exploring cooperative models. Wiki access, public + events, Slack community, monthly meetings. +

    -

    Founder

    +

    Founder

    "The workshop"
    -

    For people actively building cooperatives. Peer accelerator, mentorship, governance templates.

    +

    + For people actively building cooperatives. Peer accelerator, + mentorship, governance templates. +

    -

    Practitioner

    +

    Practitioner

    "The alcove"
    -

    For experienced practitioners. Mentoring, teaching, shaping the program direction.

    +

    + For experienced practitioners. Mentoring, teaching, shaping the + program direction. +

    - -
    - -

    Membership is $0–50/month, pay what you can. Nobody is excluded for lack of funds. Your contribution supports infrastructure, events, and community resources.

    -
      -
    • $0 I need support right now
    • -
    • $5 I can contribute
    • -
    • $15 I can sustain the community
    • -
    • $30 I can support others too
    • -
    • $50 I want to sponsor multiple members
    • -
    -
    - - -
    - -

    We gather in Slack, at monthly meetings, and through peer support sessions. The wiki is our shared knowledge base — growing as members contribute. Events range from workshops to social hangs to deep-dive series.

    - Join the Guild → + +
    +
    + +

    + Membership is $0–50/month, pay what you can. Nobody is + excluded for lack of funds. Your contribution supports + infrastructure, events, and community resources. +

    +
      +
    • $0 I need support right now
    • +
    • $5 I can contribute
    • +
    • + $15 I can sustain the community +
    • +
    • + $30 I can support others too +
    • +
    • + $50 I want to sponsor multiple + members +
    • +
    +
    +
    + +

    + We gather in Slack, at monthly meetings, and through peer support + sessions. The wiki is our shared knowledge base — growing as + members contribute. Events range from workshops to social hangs to + deep-dive series. +

    + Join the Guild → +
    -

    Ghost Guild is a program of Baby Ghosts, a Canadian nonprofit advancing cooperative models in game development. No tracking. No ads. No venture capital.

    -

    babyghosts.fund →

    +

    + Ghost Guild is a program of Baby Ghosts, a Canadian nonprofit + advancing cooperative models in game development. No tracking. No + ads. No venture capital. +

    +

    + babyghosts.fund → +

    @@ -75,10 +123,10 @@ diff --git a/app/pages/members.vue b/app/pages/members/index.vue similarity index 85% rename from app/pages/members.vue rename to app/pages/members/index.vue index 9c7e30f..a7d4658 100644 --- a/app/pages/members.vue +++ b/app/pages/members/index.vue @@ -14,13 +14,17 @@ class="filter-search" placeholder="Search members..." @input="debouncedSearch" - > + /> @@ -29,17 +33,25 @@ type="checkbox" :checked="peerSupportFilter === 'true'" @change="togglePeerSupport" - > + /> Offering support - Showing {{ totalCount }} member{{ totalCount === 1 ? '' : 's' }} + Showing {{ totalCount }} member{{ totalCount === 1 ? "" : "s" }}
    -
    +
    Skills:
    -
    +
    Topics:
    -
    +
    Active filters: - + {{ circleLabels[selectedCircle] }} @@ -101,19 +117,11 @@ Offering Peer Support - + {{ skill }} - + {{ topic }} @@ -134,11 +142,7 @@
    -
    +
    + /> {{ getInitials(member.name) }}
    - {{ member.name }} - {{ member.name }} - {{ member.pronouns }} + {{ + member.name + }} + {{ + member.pronouns + }}
    - {{ circleLabels[member.circle] }} + {{ + circleLabels[member.circle] + }}