diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 0abd815..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,329 +0,0 @@ -## 2. Member Features - -### Member Profiles - -**Core Fields:** - -- Name, pronouns, time zone -- Avatar/photo - choose from ghosts -- Studio/organization affiliation -- Bio (rich text) -- Skills tags (searchable) -- Location (city/region) -- Social links (Mastodon, LinkedIn, etc.) -- **Offering:** What I can contribute -- **Looking For:** What I need support with - -**Privacy Controls:** - -- Public/members-only/private toggle per field -- Opt-in to member directory - -### Member Updates/Mini Blog - -- Post updates about projects, learnings, questions -- Rich text with image support -- Comments enabled -- Filter by circle or topic tags - -## 3. Events System - -### Core Features - -- RSVP with capacity limits -- Waitlist management -- Add to calendar (.ics download) -- Pre-event discussion threads -- Post-event recordings archive -- Speaker/facilitator profiles - -### Member-Proposed Events - -**Proposal Flow:** - -1. Member submits event idea via form -2. Include: Topic, format, target circle, time commitment -3. Admin quick review (spam check only) -4. Published to "Proposed Events" board -5. Members can express interest (like feature upvote pages) -6. If threshold met (e.g., 5 interested), event is scheduled -7. Proposer gets facilitator support if needed - -## 4. Resources Integration - -### Consolidating Existing Assets - -**Import and organize from:** - -- learn.weirdghosts.ca content -- Existing tools and templates -- PA curriculum materials (where appropriate) -- Case studies and examples - -**Organization Structure:** - -``` -Resources/ -├── Start Here/ -│ ├── Welcome Letter from Jennie & Eileen -│ ├── How Ghost Guild Works -│ └── Solidarity Economics Explained -├── Learning Paths/ -│ ├── Community Track → links to learn.weirdghosts.ca -│ ├── Founder Track → practical tools -│ └── Practitioner Track → advanced resources -├── Templates & Tools/ -│ ├── Governance/ -│ ├── Financial/ -│ ├── Operations/ -│ └── Legal/ -├── Case Studies/ -│ └── Member stories and examples -└── External Resources/ - └── Curated links and recommendations -``` - -### Resource Features - -- Tag by circle relevance (but accessible to all) -- Download tracking for impact metrics -- Version control for templates -- Comment threads on resources -- "Request a resource" feature - -## 5. Peer Support System - -### Cal.com Integration for 1:1s - -**Setup:** - -- Each member can enable peer support availability -- Set their own hours/frequency -- Cal.com handles scheduling -- Types of sessions: - - Peer support (30 min) - - Co-founder check-in (45 min) - - Practitioner office hours (60 min) - -**Matching System:** - -- Simple questionnaire about current needs -- Suggest 3 potential peers based on: - - Complementary skills/needs - - Time zone compatibility - - Circle alignment (optional) -- Book directly via Cal.com links - -## 6. Dashboard Design - -### Personalized Sections - -**Welcome Block:** - -- "Welcome back, [Name]" -- Your circle: [Circle] | Your contribution: $X/month -- Quick stats: Days as member, events attended, peers met - -**Community Pulse:** - -- Recent member updates (mini blog posts) -- Upcoming events this week -- New resources added -- New members to welcome - -**Your Activity:** - -- Your upcoming events -- Scheduled peer sessions -- Recent discussions you're in -- Resources you've bookmarked - -**Take Action:** - -- Post an update -- Propose an event -- Book a peer session -- Browse resources -- Update profile - -**Impact Metrics:** - -- Total solidarity spots funded -- Events hosted this month -- Active members this week -- Resources shared - -## 7. Collaborative Tools - -### Etherpad Integration - -**Use Cases:** - -- Meeting notes templates -- Collaborative resource creation -- Event planning documents -- Shared learning notes - -**Implementation:** - -- Self-hosted Etherpad instance -- SSO with Ghost Guild accounts -- Auto-save and version history -- Export to multiple formats -- Embed in event pages for notes - -### Living Documents - -- Community-maintained guides -- Glossaries and definitions -- Frequently asked questions -- Best practices collections - -## 8. Technical Infrastructure - -### Notification System - -**Channels:** - -- Email (via Resend) -- In-app notifications -- Slack integration via bot - -**Configurable Preferences:** - -- Event reminders -- New resources in your area -- Peer session invitations -- Member updates digest -- Community announcements - -### Search & Discovery - -- Full-text search across: - - Resources - - Member profiles - - Event descriptions - - Member updates -- Filter by circle, tags, date -- Save searches for alerts - -### Analytics & Reporting - -- Member engagement metrics -- Resource usage stats -- Event attendance patterns -- Contribution distribution -- Circle movement tracking - -## 9. Content for Launch - -### Essential Content Pieces - -1. **Welcome Video** - Jennie & Eileen introduce Ghost Guild -2. **How This Works** - Clear explanation of circles and contributions -3. **Circle Guides** - What to expect in each circle -4. **Solidarity Economics** - Practical examples from gaming -5. **Getting Started Checklist** - First week actions - -### Pre-Populated Content - -- 10-15 essential resources per circle -- 3-5 upcoming events scheduled -- Sample member updates to show activity -- FAQ based on pre-registration questions - -## 10. Launch Strategy - -### Soft Launch (Week Before) - -- Invite 10-15 friendly testers -- Each from different backgrounds/circles -- Gather feedback on: - - Onboarding flow - - Resource organization - - Event system - - Profile creation - -### Launch Week - -**Day 1-2:** PA alumni and close network - -- Personal invitations -- Extra support available -- Gather testimonials - -**Day 3-4:** Gamma Space announcement - -- Post in relevant channels -- Host info session - -**Day 5-7:** Public launch - -- Email pre-registration list -- Social media announcement -- Open registration - -### Success Metrics - -**Week 1:** - -- 30 members across all circles -- 80% complete profiles -- 50% attend first event - -**Month 1:** - -- 75 active members -- 5 member-proposed events -- 20 peer sessions booked -- 90% Slack participation - -## 11. Ongoing Operations - -### Weekly Tasks - -- Review member proposals for events -- Process Gamma Space channel access -- Update resource library -- Send member spotlight - -### Monthly Tasks - -- Impact report to members -- Review and adjust contribution distribution -- Plan next month's events -- Gather member feedback - -### Quarterly Reviews - -- Assess circle definitions -- Evaluate pricing model -- Review platform features -- Plan new initiatives - ---- - -## Implementation Priority Order - -### Must Have for Launch - -1. Payment processing (Helcim) -2. Basic Slack automation -3. Member dashboard -4. Simple resource library -5. Event listing and RSVP - -### Nice to Have for Launch - -7. Member profiles -8. Peer matching system -9. Cal.com integration -10. Member updates/blog - -### Can Build Post-Launch - -11. Etherpad integration -12. Member-proposed events -13. Advanced search -14. Analytics dashboard -15. Monthly themes diff --git a/HELCIM_TEST_INSTRUCTIONS.md b/HELCIM_TEST_INSTRUCTIONS.md deleted file mode 100644 index d1534e8..0000000 --- a/HELCIM_TEST_INSTRUCTIONS.md +++ /dev/null @@ -1,120 +0,0 @@ -# Helcim Integration Testing Guide - -## Setup Complete -The Helcim Recurring API integration has been set up with the following components: - -### 1. Composables -- `/app/composables/useHelcim.js` - Server-side Helcim API interactions -- `/app/composables/useHelcimPay.js` - Client-side HelcimPay.js integration - -### 2. Server API Endpoints -- `/server/api/helcim/customer.post.js` - Creates Helcim customer and member record -- `/server/api/helcim/subscription.post.js` - Creates subscription for paid tiers -- `/server/api/helcim/verify-payment.post.js` - Verifies payment token - -### 3. Updated Pages -- `/app/pages/join.vue` - Multi-step signup flow with payment integration - -### 4. Database Schema -- Updated `/server/models/member.js` with subscription fields - -## Testing Instructions - -### Prerequisites -1. Ensure your `.env` file has the test Helcim token: - ``` - NUXT_PUBLIC_HELCIM_TOKEN=your_test_token_here - ``` - -2. Ensure you have test payment plans created in Helcim dashboard matching these IDs: - - `supporter-monthly-5` - - `member-monthly-15` - - `advocate-monthly-30` - - `champion-monthly-50` - -### Test Flow - -#### 1. Start the Development Server -```bash -npm run dev -``` - -#### 2. Test Free Tier Signup -1. Navigate to `/join` -2. Fill in name and email -3. Select any circle -4. Choose "$0 - I need support right now" -5. Click "Complete Registration" -6. Should go directly to confirmation without payment - -#### 3. Test Paid Tier Signup -1. Navigate to `/join` -2. Fill in test details: - - Name: Test User - - Email: test@example.com -3. Select any circle -4. Choose a paid contribution tier (e.g., "$15 - I can sustain the community") -5. Click "Continue to Payment" -6. On the payment step, use Helcim test card numbers: - - **Success**: 4111 1111 1111 1111 - - **Decline**: 4000 0000 0000 0002 - - CVV: Any 3 digits - - Expiry: Any future date -7. Click "Complete Payment" -8. Should see confirmation with member details - -### Test Card Numbers (Helcim Test Mode) -- **Visa Success**: 4111 1111 1111 1111 -- **Mastercard Success**: 5500 0000 0000 0004 -- **Amex Success**: 3400 0000 0000 009 -- **Decline**: 4000 0000 0000 0002 -- **Insufficient Funds**: 4000 0000 0000 0051 - -### Debugging - -#### Check API Responses -Open browser DevTools Network tab to monitor: -- `/api/helcim/customer` - Should return customer ID and token -- `/api/helcim/verify-payment` - Should return card details -- `/api/helcim/subscription` - Should return subscription ID - -#### Common Issues - -1. **HelcimPay.js not loading** - - Check console for script loading errors - - Verify token is correctly set in environment - -2. **Customer creation fails** - - Check API token permissions in Helcim dashboard - - Verify MongoDB connection - -3. **Payment verification fails** - - Ensure you're using test card numbers - - Check that Helcim account is in test mode - -4. **Subscription creation fails** - - Verify payment plan IDs exist in Helcim - - Check that card token was successfully captured - -### Database Verification - -Check MongoDB for created records: -```javascript -// In MongoDB shell or client -db.members.findOne({ email: "test@example.com" }) -``` - -Should see: -- `helcimCustomerId` populated -- `helcimSubscriptionId` for paid tiers -- `status: "active"` after successful payment -- `paymentMethod: "card"` for paid tiers - -## Next Steps - -Once testing is successful: -1. Switch to production Helcim token -2. Create production payment plans in Helcim -3. Update plan IDs in `/app/config/contributions.js` if needed -4. Test with real payment card (small amount) -5. Set up webhook endpoints for subscription events (renewals, failures, cancellations) \ No newline at end of file diff --git a/UPDATE_SUMMARY.md b/UPDATE_SUMMARY.md deleted file mode 100644 index 5c7d439..0000000 --- a/UPDATE_SUMMARY.md +++ /dev/null @@ -1,57 +0,0 @@ -# Helcim Integration - Issues Fixed - -## Problem -The API was returning 401 Unauthorized when trying to create customers. - -## Root Cause -The runtime config wasn't properly accessing the Helcim token in server-side endpoints. - -## Solution Applied - -### 1. Fixed Runtime Config Access -Updated all server endpoints to: -- Pass the `event` parameter to `useRuntimeConfig(event)` -- Fallback to `process.env.NUXT_PUBLIC_HELCIM_TOKEN` if config doesn't load - -### 2. Files Updated -- `/server/api/helcim/customer.post.js` -- `/server/api/helcim/subscription.post.js` -- `/server/api/helcim/verify-payment.post.js` -- `/server/api/helcim/test-connection.get.js` - -### 3. Fixed Import Path -Created `/server/config/contributions.js` to re-export the contributions config for server-side imports. - -### 4. Verified Token Works -Created `test-helcim-direct.js` which successfully: -- Connected to Helcim API -- Created a test customer (ID: 32854583, Code: CST1000) - -## Testing Instructions - -1. Restart your development server: - ```bash - npm run dev - ``` - -2. Test the connection: - ```bash - curl http://localhost:3000/api/helcim/test-connection - ``` - -3. Try the signup flow at `/join` - -## Important Notes - -- The token in your `.env` file is working correctly -- The Helcim API is accessible and responding -- Customer creation is functional when called directly -- The issue was specifically with how Nuxt's runtime config was being accessed in server endpoints - -## Next Steps - -Once you confirm the signup flow works: -1. Test with different contribution tiers -2. Verify payment capture with test cards -3. Check that subscriptions are created correctly -4. Consider adding webhook endpoints for subscription events \ No newline at end of file diff --git a/app/app.config.ts b/app/app.config.ts index 363e07f..51f28f0 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -1,13 +1,8 @@ export default defineAppConfig({ ui: { colors: { - primary: "emerald", - neutral: "stone", - }, - formField: { - slots: { - label: "block font-medium text-stone-200", - }, + primary: "pink", + neutral: "zinc", }, }, }); diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 22cb675..a5e24e1 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -2,145 +2,13 @@ @import "tailwindcss"; @import "@nuxt/ui"; -@theme static { +@theme { /* Font families */ --font-sans: "Inter", sans-serif; --font-body: "Inter", sans-serif; --font-mono: "Ubuntu Mono", monospace; --font-display: "NB Television Pro", monospace; - /* Ethereal color palette - grays, blacks, minimal color */ - --color-ghost-50: #f0f0f0; - --color-ghost-100: #d0d0d0; - --color-ghost-200: #b0b0b0; - --color-ghost-300: #8a8a8a; - --color-ghost-400: #6a6a6a; - --color-ghost-500: #4a4a4a; - --color-ghost-600: #3a3a3a; - --color-ghost-700: #2a2a2a; - --color-ghost-800: #1a1a1a; - --color-ghost-900: #0a0a0a; - - /* Subtle accent - barely visible blue-gray */ - --color-whisper-50: #d4dae6; - --color-whisper-100: #a8b3c7; - --color-whisper-200: #8491a8; - --color-whisper-300: #687291; - --color-whisper-400: #4f5d7a; - --color-whisper-500: #3a4964; - --color-whisper-600: #2f3b52; - --color-whisper-700: #252d40; - --color-whisper-800: #1a1f2e; - --color-whisper-900: #0f1419; - /* Sparkle accent */ - --color-sparkle-50: #fafafa; - --color-sparkle-100: #f0f0f0; - --color-sparkle-200: #e8e8e8; - --color-sparkle-300: #d0d0d0; - --color-sparkle-400: #c0c0c0; - --color-sparkle-500: #a0a0a0; - --color-sparkle-600: #808080; - --color-sparkle-700: #606060; - --color-sparkle-800: #404040; - --color-sparkle-900: #202020; -} - -/* Global ethereal background */ -:root { - --ethereal-bg: radial-gradient(circle at 20% 80%, rgba(232, 232, 232, 0.03) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(232, 232, 232, 0.02) 0%, transparent 50%), - radial-gradient(circle at 40% 40%, rgba(232, 232, 232, 0.01) 0%, transparent 50%); - - --halftone-pattern: radial-gradient(circle, rgba(255,255,255,0.1) 1px, transparent 1px); - --halftone-size: 8px 8px; -} - -html { - background: var(--color-ghost-900); - color: var(--color-ghost-200); -} - -body { - background: var(--ethereal-bg), var(--color-ghost-900); - background-attachment: fixed; -} - -/* Halftone texture overlay */ -.halftone-texture { - position: relative; -} - -.halftone-texture::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--halftone-pattern); - background-size: var(--halftone-size); - opacity: 0.1; - pointer-events: none; -} - -/* Sparkle effects */ -@keyframes sparkle { - 0%, 100% { opacity: 0.3; transform: scale(0.8); } - 50% { opacity: 1; transform: scale(1.2); } -} - -@keyframes twinkle { - 0%, 100% { opacity: 0.2; } - 25% { opacity: 0.8; } - 75% { opacity: 0.4; } -} - -.sparkle-field { - position: relative; - overflow: hidden; -} - -.sparkle-field::after { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: - radial-gradient(circle at 10% 20%, var(--color-sparkle-200) 1px, transparent 1px), - radial-gradient(circle at 90% 80%, var(--color-sparkle-400) 1px, transparent 1px), - radial-gradient(circle at 30% 70%, var(--color-sparkle-200) 0.5px, transparent 0.5px), - radial-gradient(circle at 70% 30%, var(--color-sparkle-400) 0.5px, transparent 0.5px), - radial-gradient(circle at 50% 10%, var(--color-sparkle-200) 1px, transparent 1px), - radial-gradient(circle at 20% 90%, var(--color-sparkle-400) 0.5px, transparent 0.5px); - background-size: 200px 200px, 300px 300px, 150px 150px, 250px 250px, 180px 180px, 220px 220px; - animation: twinkle 4s infinite ease-in-out; - pointer-events: none; - opacity: 0.6; -} - -/* Ethereal glow effects */ -.ethereal-glow { - box-shadow: - 0 0 20px rgba(232, 232, 232, 0.1), - 0 0 40px rgba(232, 232, 232, 0.05), - inset 0 1px 0 rgba(255, 255, 255, 0.1); -} - -.ethereal-text { - text-shadow: 0 0 10px rgba(232, 232, 232, 0.3); -} - -/* Dithered gradients */ -.dithered-bg { - background: - linear-gradient(45deg, var(--color-ghost-800) 25%, transparent 25%), - linear-gradient(-45deg, var(--color-ghost-800) 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, var(--color-ghost-700) 75%), - linear-gradient(-45deg, transparent 75%, var(--color-ghost-700) 75%); - background-size: 4px 4px; - background-position: 0 0, 0 2px, 2px -2px, -2px 0px; } diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 7f35a63..74928a8 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -1,31 +1,288 @@ +import { computed } from 'vue' + +const props = defineProps({ + theme: { + type: String, + default: 'purple', + validator: (value) => ['purple', 'blue', 'emerald', 'gray'].includes(value) + }, + brandName: { + type: String, + default: 'Ghost Guild' + }, + description: { + type: String, + default: 'A community for game developers exploring cooperative models and building sustainable studios together.' + }, + copyrightText: { + type: String, + default: 'All rights reserved.' + }, + customNavigationLinks: { + type: Array, + default: () => [] + }, + customCommunityLinks: { + type: Array, + default: () => [] + }, + customSocialLinks: { + type: Array, + default: () => [] + } +}) + +const currentYear = new Date().getFullYear() + +const navigationLinks = computed(() => { + if (props.customNavigationLinks.length > 0) { + return props.customNavigationLinks + } + return [ + { label: 'Home', path: '/' }, + { label: 'About', path: '/about' }, + { label: 'Events', path: '/events' }, + { label: 'Join', path: '/join' }, + { label: 'Contact', path: '/contact' } + ] +}) + +const communityLinks = computed(() => { + if (props.customCommunityLinks.length > 0) { + return props.customCommunityLinks + } + return [ + { label: 'Upcoming Events', path: '/events' }, + { label: 'Past Events', path: '/events/past' }, + { label: 'Event Calendar', path: '/events/calendar' }, + { label: 'Members Directory', path: '/members' } + ] +}) + +const socialLinks = computed(() => { + if (props.customSocialLinks.length > 0) { + return props.customSocialLinks + } + return [ + { label: 'Discord Community', href: 'https://discord.gg/ghostguild' }, + { label: 'Twitter', href: 'https://twitter.com/ghostguild' }, + { label: 'GitHub', href: 'https://github.com/ghostguild' }, + { label: 'Contact Us', href: 'mailto:hello@ghostguild.org' } + ] +}) + +const backgroundClass = computed(() => { + const themes = { + purple: 'bg-purple-50 dark:bg-purple-900/20', + blue: 'bg-blue-50 dark:bg-blue-900/20', + emerald: 'bg-emerald-50 dark:bg-emerald-900/20', + gray: 'bg-gray-50 dark:bg-gray-900' + } + return themes[props.theme] || themes.purple +}) + +const borderClass = computed(() => { + const themes = { + purple: 'border-purple-200 dark:border-purple-800', + blue: 'border-blue-200 dark:border-blue-800', + emerald: 'border-emerald-200 dark:border-emerald-800', + gray: 'border-gray-200 dark:border-gray-700' + } + return themes[props.theme] || themes.purple +}) + +const logoBackgroundClass = computed(() => { + const themes = { + purple: 'bg-purple-500', + blue: 'bg-blue-500', + emerald: 'bg-emerald-500', + gray: 'bg-gray-500' + } + return themes[props.theme] || themes.purple +}) + +const brandTextClass = computed(() => { + const themes = { + purple: 'text-purple-600 dark:text-purple-400', + blue: 'text-blue-600 dark:text-blue-400', + emerald: 'text-emerald-600 dark:text-emerald-400', + gray: 'text-gray-900 dark:text-white' + } + return themes[props.theme] || themes.purple +}) + +const headingColorClass = computed(() => { + const themes = { + purple: 'text-purple-900 dark:text-purple-100', + blue: 'text-blue-900 dark:text-blue-100', + emerald: 'text-emerald-900 dark:text-emerald-100', + gray: 'text-gray-900 dark:text-white' + } + return themes[props.theme] || themes.purple +}) + +const textColorClass = computed(() => { + const themes = { + purple: 'text-purple-600 dark:text-purple-400', + blue: 'text-blue-600 dark:text-blue-400', + emerald: 'text-emerald-600 dark:text-emerald-400', + gray: 'text-gray-600 dark:text-gray-400' + } + return themes[props.theme] || themes.purple +}) + +const linkColorClass = computed(() => { + const themes = { + purple: 'text-purple-700 dark:text-purple-300 hover:text-purple-900 dark:hover:text-purple-100', + blue: 'text-blue-700 dark:text-blue-300 hover:text-blue-900 dark:hover:text-blue-100', + emerald: 'text-emerald-700 dark:text-emerald-300 hover:text-emerald-900 dark:hover:text-emerald-100', + gray: 'text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100' + } + return themes[props.theme] || themes.purple +}) + +const decorativeBarClass = computed(() => { + const themes = { + purple: 'bg-purple-500', + blue: 'bg-blue-500', + emerald: 'bg-emerald-500', + gray: 'bg-gray-500' + } + return themes[props.theme] || themes.purple +}) + +const decorativeBarSecondaryClass = computed(() => { + const themes = { + purple: 'bg-purple-400', + blue: 'bg-blue-400', + emerald: 'bg-emerald-400', + gray: 'bg-gray-400' + } + return themes[props.theme] || themes.purple +}) + +const decorativeBarTertiaryClass = computed(() => { + const themes = { + purple: 'bg-purple-300', + blue: 'bg-blue-300', + emerald: 'bg-emerald-300', + gray: 'bg-gray-300' + } + return themes[props.theme] || themes.purple +}) + \ No newline at end of file diff --git a/app/components/AppNavigation.vue b/app/components/AppNavigation.vue index f7b1473..803f8bd 100644 --- a/app/components/AppNavigation.vue +++ b/app/components/AppNavigation.vue @@ -1,191 +1,102 @@ - - + \ No newline at end of file diff --git a/app/components/NaturalDateInput.vue b/app/components/NaturalDateInput.vue deleted file mode 100644 index c0f8983..0000000 --- a/app/components/NaturalDateInput.vue +++ /dev/null @@ -1,238 +0,0 @@ - - - \ No newline at end of file diff --git a/app/components/PageHeader.vue b/app/components/PageHeader.vue index a5646cc..b756f47 100644 --- a/app/components/PageHeader.vue +++ b/app/components/PageHeader.vue @@ -1,108 +1,65 @@ + return 'text-gray-900 dark:text-white' +}) + \ No newline at end of file diff --git a/app/components/PrivacyToggle.vue b/app/components/PrivacyToggle.vue deleted file mode 100644 index 8b0ca9a..0000000 --- a/app/components/PrivacyToggle.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - diff --git a/app/components/UpdateCard.vue b/app/components/UpdateCard.vue deleted file mode 100644 index af76bc1..0000000 --- a/app/components/UpdateCard.vue +++ /dev/null @@ -1,189 +0,0 @@ - - - - - diff --git a/app/components/UpdateForm.vue b/app/components/UpdateForm.vue deleted file mode 100644 index 286e77b..0000000 --- a/app/components/UpdateForm.vue +++ /dev/null @@ -1,182 +0,0 @@ - - - - - diff --git a/app/composables/useAuth.js b/app/composables/useAuth.js deleted file mode 100644 index 9e37046..0000000 --- a/app/composables/useAuth.js +++ /dev/null @@ -1,46 +0,0 @@ -export const useAuth = () => { - const memberData = useState('auth.member', () => null) - - const isAuthenticated = computed(() => !!memberData.value) - - const isMember = computed(() => !!memberData.value) - - const checkMemberStatus = async () => { - console.log('🔍 checkMemberStatus called') - console.log(' - Current memberData:', !!memberData.value) - - try { - console.log(' - Making API call to /api/auth/member...') - const response = await $fetch('/api/auth/member') - console.log(' - API response received:', { email: response.email, id: response.id }) - memberData.value = response - console.log(' - ✅ Member authenticated successfully') - return true - } catch (error) { - console.error(' - ❌ Failed to fetch member status:', error.statusCode, error.statusMessage) - memberData.value = null - console.log(' - Cleared memberData') - return false - } - } - - const logout = async () => { - try { - await $fetch('/api/auth/logout', { - method: 'POST' - }) - memberData.value = null - await navigateTo('/') - } catch (error) { - console.error('Logout failed:', error) - } - } - - return { - isAuthenticated: readonly(isAuthenticated), - isMember: readonly(isMember), - memberData: readonly(memberData), - checkMemberStatus, - logout - } -} \ No newline at end of file diff --git a/app/composables/useHelcim.js b/app/composables/useHelcim.js deleted file mode 100644 index efc96b1..0000000 --- a/app/composables/useHelcim.js +++ /dev/null @@ -1,90 +0,0 @@ -// Helcim API integration composable -export const useHelcim = () => { - const config = useRuntimeConfig() - const helcimToken = config.public.helcimToken - - // Base URL for Helcim API - const HELCIM_API_BASE = 'https://api.helcim.com/v2' - - // Helper function to make API requests - const makeHelcimRequest = async (endpoint, method = 'GET', body = null) => { - try { - const response = await $fetch(`${HELCIM_API_BASE}${endpoint}`, { - method, - headers: { - 'accept': 'application/json', - 'content-type': 'application/json', - 'api-token': helcimToken - }, - body: body ? JSON.stringify(body) : undefined - }) - return response - } catch (error) { - console.error('Helcim API error:', error) - throw error - } - } - - // Create a customer - const createCustomer = async (customerData) => { - return await makeHelcimRequest('/customers', 'POST', { - customerType: 'PERSON', - contactName: customerData.name, - email: customerData.email, - billingAddress: customerData.billingAddress || {} - }) - } - - // Create a subscription - const createSubscription = async (customerId, planId, cardToken) => { - return await makeHelcimRequest('/recurring/subscriptions', 'POST', { - customerId, - planId, - cardToken, - startDate: new Date().toISOString().split('T')[0] // Today's date - }) - } - - // Get customer details - const getCustomer = async (customerId) => { - return await makeHelcimRequest(`/customers/${customerId}`) - } - - // Get subscription details - const getSubscription = async (subscriptionId) => { - return await makeHelcimRequest(`/recurring/subscriptions/${subscriptionId}`) - } - - // Update subscription - const updateSubscription = async (subscriptionId, updates) => { - return await makeHelcimRequest(`/recurring/subscriptions/${subscriptionId}`, 'PATCH', updates) - } - - // Cancel subscription - const cancelSubscription = async (subscriptionId) => { - return await makeHelcimRequest(`/recurring/subscriptions/${subscriptionId}`, 'DELETE') - } - - // Get payment plans - const getPaymentPlans = async () => { - return await makeHelcimRequest('/recurring/plans') - } - - // Verify card token (for testing) - const verifyCardToken = async (cardToken) => { - return await makeHelcimRequest('/cards/verify', 'POST', { - cardToken - }) - } - - return { - createCustomer, - createSubscription, - getCustomer, - getSubscription, - updateSubscription, - cancelSubscription, - getPaymentPlans, - verifyCardToken - } -} \ No newline at end of file diff --git a/app/composables/useHelcimPay.js b/app/composables/useHelcimPay.js deleted file mode 100644 index 9f40d07..0000000 --- a/app/composables/useHelcimPay.js +++ /dev/null @@ -1,158 +0,0 @@ -// HelcimPay.js integration composable -export const useHelcimPay = () => { - let checkoutToken = null - let secretToken = null - - // Initialize HelcimPay.js session - const initializeHelcimPay = async (customerId, customerCode, amount = 0) => { - try { - const response = await $fetch('/api/helcim/initialize-payment', { - method: 'POST', - body: { - customerId, - customerCode, - amount - } - }) - - if (response.success) { - checkoutToken = response.checkoutToken - secretToken = response.secretToken - return true - } - - throw new Error('Failed to initialize payment session') - } catch (error) { - console.error('Payment initialization error:', error) - throw error - } - } - - // Show payment modal - const showPaymentModal = () => { - return new Promise((resolve, reject) => { - if (!checkoutToken) { - reject(new Error('Payment not initialized. Call initializeHelcimPay first.')) - return - } - - // Load HelcimPay.js modal script - if (!window.appendHelcimPayIframe) { - console.log('HelcimPay script not loaded, loading now...') - const script = document.createElement('script') - script.src = 'https://secure.helcim.app/helcim-pay/services/start.js' - script.async = true - script.onload = () => { - console.log('HelcimPay script loaded successfully!') - console.log('Available functions:', Object.keys(window).filter(key => key.includes('Helcim') || key.includes('helcim'))) - console.log('appendHelcimPayIframe available:', typeof window.appendHelcimPayIframe) - openModal(resolve, reject) - } - script.onerror = () => { - reject(new Error('Failed to load HelcimPay.js')) - } - document.head.appendChild(script) - } else { - console.log('HelcimPay script already loaded, calling openModal') - openModal(resolve, reject) - } - }) - } - - // Open the payment modal - const openModal = (resolve, reject) => { - try { - console.log('Trying to open modal with checkoutToken:', checkoutToken) - - if (typeof window.appendHelcimPayIframe === 'function') { - // Set up event listener for HelcimPay.js responses - const helcimPayJsIdentifierKey = 'helcim-pay-js-' + checkoutToken - - const handleHelcimPayEvent = (event) => { - console.log('Received window message:', event.data) - - if (event.data.eventName === helcimPayJsIdentifierKey) { - console.log('HelcimPay event received:', event.data) - - // Remove event listener to prevent multiple responses - window.removeEventListener('message', handleHelcimPayEvent) - - if (event.data.eventStatus === 'SUCCESS') { - console.log('Payment success:', event.data.eventMessage) - - // Parse the JSON string eventMessage - let paymentData - try { - paymentData = JSON.parse(event.data.eventMessage) - console.log('Parsed payment data:', paymentData) - } catch (parseError) { - console.error('Failed to parse eventMessage:', parseError) - reject(new Error('Invalid payment response format')) - return - } - - // Extract transaction details from nested data structure - const transactionData = paymentData.data?.data || {} - console.log('Transaction data:', transactionData) - - resolve({ - success: true, - transactionId: transactionData.transactionId, - cardToken: transactionData.cardToken, - cardLast4: transactionData.cardNumber ? transactionData.cardNumber.slice(-4) : undefined, - cardType: transactionData.cardType || 'unknown' - }) - } else if (event.data.eventStatus === 'ABORTED') { - console.log('Payment aborted:', event.data.eventMessage) - reject(new Error(event.data.eventMessage || 'Payment failed')) - } else if (event.data.eventStatus === 'HIDE') { - console.log('Modal closed without completion') - reject(new Error('Payment cancelled by user')) - } - } - } - - // Add event listener - window.addEventListener('message', handleHelcimPayEvent) - - // Open the HelcimPay iframe modal - console.log('Calling appendHelcimPayIframe with token:', checkoutToken) - window.appendHelcimPayIframe(checkoutToken, true) - console.log('appendHelcimPayIframe called, waiting for window messages...') - - // Add timeout to clean up if no response - setTimeout(() => { - console.log('60 seconds passed, cleaning up event listener...') - window.removeEventListener('message', handleHelcimPayEvent) - reject(new Error('Payment timeout - no response received')) - }, 60000) - } else { - reject(new Error('appendHelcimPayIframe function not available')) - } - } catch (error) { - console.error('Error opening modal:', error) - reject(error) - } - } - - // Process payment verification - const verifyPayment = async () => { - try { - return await showPaymentModal() - } catch (error) { - throw error - } - } - - // Cleanup tokens - const cleanup = () => { - checkoutToken = null - secretToken = null - } - - return { - initializeHelcimPay, - verifyPayment, - cleanup - } -} \ No newline at end of file diff --git a/app/layouts/admin.vue b/app/layouts/admin.vue index fda4eef..a9194f7 100644 --- a/app/layouts/admin.vue +++ b/app/layouts/admin.vue @@ -57,18 +57,18 @@ - + - Series + Analytics @@ -159,15 +159,15 @@ - Series + Analytics diff --git a/app/layouts/default.vue b/app/layouts/default.vue index 07411f7..befc6f9 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -1,35 +1,7 @@ + \ No newline at end of file diff --git a/app/middleware/auth.js b/app/middleware/auth.js deleted file mode 100644 index a29566b..0000000 --- a/app/middleware/auth.js +++ /dev/null @@ -1,27 +0,0 @@ -export default defineNuxtRouteMiddleware(async (to, from) => { - // Skip on server-side rendering - if (process.server) { - console.log('🛡️ Auth middleware - skipping on server') - return - } - - const { memberData, checkMemberStatus } = useAuth() - - console.log('🛡️ Auth middleware (CLIENT) - route:', to.path) - console.log(' - memberData exists:', !!memberData.value) - console.log(' - Running on:', process.server ? 'SERVER' : 'CLIENT') - - // If no member data, try to check authentication - if (!memberData.value) { - console.log(' - No member data, checking authentication...') - const isAuthenticated = await checkMemberStatus() - console.log(' - Authentication result:', isAuthenticated) - - if (!isAuthenticated) { - console.log(' - ❌ Authentication failed, redirecting to login') - return navigateTo('/login') - } - } - - console.log(' - ✅ Authentication successful for:', memberData.value?.email) -}) \ No newline at end of file diff --git a/app/pages/about.vue b/app/pages/about.vue index cbc7469..44ddd0e 100644 --- a/app/pages/about.vue +++ b/app/pages/about.vue @@ -1,278 +1,306 @@