From 5f578a912c2ec23803a00276a380e45587fecd8f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 26 Nov 2025 02:03:27 +0000 Subject: [PATCH 1/4] feat: Add contact page and update social links Co-authored-by: justinohallo --- app/contact/page.tsx | 211 +++++++++++++++++++++++++++ components/contact/QRCodeDisplay.tsx | 86 +++++++++++ components/layout/Footer.tsx | 23 ++- components/layout/Navbar.tsx | 1 + lib/utils/urls.ts | 2 + package-lock.json | 10 ++ package.json | 1 + 7 files changed, 321 insertions(+), 13 deletions(-) create mode 100644 app/contact/page.tsx create mode 100644 components/contact/QRCodeDisplay.tsx diff --git a/app/contact/page.tsx b/app/contact/page.tsx new file mode 100644 index 0000000..33ecd3c --- /dev/null +++ b/app/contact/page.tsx @@ -0,0 +1,211 @@ +import { headers } from "next/headers"; + +import { PageContainer } from "@/components/layout/PageContainer"; +import { QRCodeDisplay } from "@/components/contact/QRCodeDisplay"; +import { JsonLd } from "@/components/seo/JsonLd"; +import { Heading } from "@/components/ui/Heading"; + +import { + createBreadcrumbList, + createSchemaGraph, +} from "@/lib/seo"; +import { paths, urls } from "@/lib/utils/urls"; + +export async function generateMetadata() { + return { + title: "Contact | Builder Vancouver", + description: + "Connect with Builder Vancouver. Follow us on X, Nostr, and Luma. Scan our QR code to share our website.", + keywords: ["contact", "builder vancouver", "bitcoin", "connect"], + }; +} + +/** + * Contact page with real-time QR code and social links + * Optimized for mobile sharing and showcasing work + */ +export default async function ContactPage() { + const headersList = await headers(); + const host = headersList.get("host") || ""; + const protocol = headersList.get("x-forwarded-proto") || "https"; + const currentUrl = `${protocol}://${host}${paths.contact()}`; + + const breadcrumbSchema = createBreadcrumbList([ + { name: "Home", url: urls.home() }, + { name: "Contact" }, + ]); + + const structuredData = createSchemaGraph(breadcrumbSchema); + + // Social links - update these with actual URLs + const socialLinks = [ + { + name: "X (Twitter)", + href: "https://x.com/builder_van", // Update with actual URL + icon: ( + + ), + color: "hover:text-neutral-100", + }, + { + name: "Nostr", + href: "https://nostr.com/builder_van", // Update with actual Nostr profile URL or npub + icon: ( + + ), + color: "hover:text-orange-400", + }, + { + name: "Luma", + href: "https://lu.ma/builder-vancouver", // Update with actual Luma URL + icon: ( + + ), + color: "hover:text-blue-400", + }, + ]; + + return ( + <> + + + {/* Hero Section */} +
+ + Connect With Us + +

+ Join the Builder Vancouver community +

+

+ Scan the QR code or follow us on social media +

+
+ + {/* QR Code Section - Mobile Optimized */} +
+
+ +
+
+ + {/* Social Links Section */} +
+

+ Follow Us +

+ +
+ + {/* Contact Information */} +
+

+ Get In Touch +

+
+

+ Email us at{" "} + + bitcoinbuildervan@gmail.com + +

+

+ We typically respond within 48 hours +

+
+
+ + {/* Work Showcase Section */} +
+

+ What We Do +

+
+
+

+ Bitcoin Education +

+

+ Learn about Bitcoin, Lightning Network, and Layer 2 solutions + through our educational sessions and workshops. +

+
+
+

+ Community Events +

+

+ Join our regular meetups, presentations, and networking events + in Vancouver. +

+
+
+

+ Open Source +

+

+ Contribute to Bitcoin-related projects and collaborate with + builders in the ecosystem. +

+
+
+

+ Resources +

+

+ Access presentations, slides, recaps, and educational materials + from our community. +

+
+
+
+
+ + ); +} diff --git a/components/contact/QRCodeDisplay.tsx b/components/contact/QRCodeDisplay.tsx new file mode 100644 index 0000000..104a5b6 --- /dev/null +++ b/components/contact/QRCodeDisplay.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { QRCodeSVG } from "qrcode.react"; + +interface QRCodeDisplayProps { + url: string; +} + +/** + * Client component for QR code display with mobile detection + * Uses the current page URL dynamically for real-time QR code generation + */ +export function QRCodeDisplay({ url }: QRCodeDisplayProps) { + const [currentUrl, setCurrentUrl] = useState(url); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + // Use the actual current page URL + setCurrentUrl(window.location.href); + + const checkMobile = () => { + setIsMobile(window.innerWidth < 768); + }; + checkMobile(); + window.addEventListener("resize", checkMobile); + return () => window.removeEventListener("resize", checkMobile); + }, []); + + const handleShare = async () => { + if (navigator.share) { + try { + await navigator.share({ + title: "Builder Vancouver", + text: "Check out Builder Vancouver - Bitcoin Meetups & Education", + url: currentUrl, + }); + } catch (err) { + // User cancelled or error occurred + console.log("Share cancelled"); + } + } + }; + + return ( +
+

+ Share This Page +

+ + {/* QR Code Container */} +
+ +
+ + {/* URL Display */} +
+

+ Current URL: +

+
+

+ {currentUrl} +

+
+
+ + {/* Share Button for Mobile */} + {isMobile && typeof navigator !== "undefined" && "share" in navigator && ( + + )} +
+ ); +} diff --git a/components/layout/Footer.tsx b/components/layout/Footer.tsx index 2d7b758..2f83272 100644 --- a/components/layout/Footer.tsx +++ b/components/layout/Footer.tsx @@ -43,14 +43,15 @@ export function Footer() { { href: paths.cities.list(), label: "Cities" }, { href: paths.sponsors.list(), label: "Sponsors" }, { href: paths.members.list(), label: "Members" }, + { href: paths.contact(), label: "Contact" }, ], }; // Social media links - update these with actual URLs const socialLinks = [ { - name: "Twitter", - href: "#", + name: "X (Twitter)", + href: "https://x.com/builder_van", // Update with actual URL icon: ( ), }, { - name: "GitHub", - href: "#", + name: "Nostr", + href: "https://nostr.com/builder_van", // Update with actual Nostr profile URL icon: ( ), }, { - name: "Nostr", - href: "#", + name: "Luma", + href: "https://lu.ma/builder-vancouver", // Update with actual Luma URL icon: ( ), }, diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx index 146cf3e..6be02f8 100644 --- a/components/layout/Navbar.tsx +++ b/components/layout/Navbar.tsx @@ -116,6 +116,7 @@ export function Navbar() { { href: "/sponsors", label: "Sponsors" }, { href: "/members", label: "Members" }, { href: "/get-involved", label: "Get Involved" }, + { href: "/contact", label: "Contact" }, ], }, ]; diff --git a/lib/utils/urls.ts b/lib/utils/urls.ts index 2475273..e1ed557 100644 --- a/lib/utils/urls.ts +++ b/lib/utils/urls.ts @@ -94,6 +94,7 @@ export const urls = { }, faq: () => buildUrl("/faq"), + contact: () => buildUrl("/contact"), page: (slug: string) => buildUrl(`/${slug}`), } as const; @@ -169,6 +170,7 @@ export const paths = { }, faq: () => "/faq", + contact: () => "/contact", page: (slug: string) => `/${slug}`, } as const; diff --git a/package-lock.json b/package-lock.json index f620fef..2b4af1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "leaflet": "^1.9.4", "next": "16.0.3", + "qrcode.react": "^4.2.0", "react": "19.2.0", "react-dom": "19.2.0", "react-leaflet": "^5.0.0", @@ -6831,6 +6832,15 @@ "node": ">=6" } }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", diff --git a/package.json b/package.json index 333eb0f..c765023 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "leaflet": "^1.9.4", "next": "16.0.3", + "qrcode.react": "^4.2.0", "react": "19.2.0", "react-dom": "19.2.0", "react-leaflet": "^5.0.0", From 942fa4126606d03dafa409f2c58cc8f9bb8689d7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 26 Nov 2025 02:04:21 +0000 Subject: [PATCH 2/4] Add contact link to footer and navbar Co-authored-by: justinohallo --- components/layout/Footer.tsx | 8 ++++++++ components/layout/Navbar.tsx | 1 + 2 files changed, 9 insertions(+) diff --git a/components/layout/Footer.tsx b/components/layout/Footer.tsx index 2f83272..64bdd00 100644 --- a/components/layout/Footer.tsx +++ b/components/layout/Footer.tsx @@ -211,6 +211,14 @@ export function Footer() { ))} +
+ + Contact Us + +

Builder Vancouver

Bitcoin Meetups & Education

diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx index 6be02f8..e727b18 100644 --- a/components/layout/Navbar.tsx +++ b/components/layout/Navbar.tsx @@ -60,6 +60,7 @@ export function Navbar() { const navItems: NavItem[] = [ { href: "/", label: "Home" }, + { href: "/contact", label: "Contact" }, { label: "About", children: [ From 425cff4c8f45852ef728899bd776e9a5b8a67609 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 26 Nov 2025 02:06:49 +0000 Subject: [PATCH 3/4] Refactor: Use SITE_URL for contact page QR code Co-authored-by: justinohallo --- app/contact/page.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 33ecd3c..5c748ff 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -1,10 +1,9 @@ -import { headers } from "next/headers"; - import { PageContainer } from "@/components/layout/PageContainer"; import { QRCodeDisplay } from "@/components/contact/QRCodeDisplay"; import { JsonLd } from "@/components/seo/JsonLd"; import { Heading } from "@/components/ui/Heading"; +import { SITE_URL } from "@/lib/constants"; import { createBreadcrumbList, createSchemaGraph, @@ -24,11 +23,9 @@ export async function generateMetadata() { * Contact page with real-time QR code and social links * Optimized for mobile sharing and showcasing work */ -export default async function ContactPage() { - const headersList = await headers(); - const host = headersList.get("host") || ""; - const protocol = headersList.get("x-forwarded-proto") || "https"; - const currentUrl = `${protocol}://${host}${paths.contact()}`; +export default function ContactPage() { + // Use SITE_URL as fallback, QRCodeDisplay will update with actual URL on client + const fallbackUrl = `${SITE_URL}${paths.contact()}`; const breadcrumbSchema = createBreadcrumbList([ { name: "Home", url: urls.home() }, @@ -106,7 +103,7 @@ export default async function ContactPage() { {/* QR Code Section - Mobile Optimized */}
- +
From 9b951eca8488154e6aacf5a17baab62dbe926540 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 26 Nov 2025 02:10:18 +0000 Subject: [PATCH 4/4] feat: Add qrcode.react dependency Co-authored-by: justinohallo --- pnpm-lock.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9318211..3d2452e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: next: specifier: 16.0.3 version: 16.0.3(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + qrcode.react: + specifier: ^4.2.0 + version: 4.2.0(react@19.2.0) react: specifier: 19.2.0 version: 19.2.0 @@ -2017,6 +2020,11 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qrcode.react@4.2.0: + resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4375,6 +4383,10 @@ snapshots: punycode@2.3.1: {} + qrcode.react@4.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + queue-microtask@1.2.3: {} react-dom@19.2.0(react@19.2.0):