diff --git a/src/app/api/me/route.ts b/src/app/api/me/route.ts new file mode 100644 index 0000000..7ec020e --- /dev/null +++ b/src/app/api/me/route.ts @@ -0,0 +1,52 @@ +/** + * /api/me β€” Returns current user's auth status. + * + * Checks for EncryptID token in Authorization header or cookie, + * then verifies it against the EncryptID server. + */ + +import { NextRequest, NextResponse } from 'next/server'; + +const ENCRYPTID_URL = process.env.ENCRYPTID_URL || 'https://auth.ridentity.online'; + +export async function GET(req: NextRequest) { + // Extract token from Authorization header or cookie + const auth = req.headers.get('Authorization'); + let token: string | null = null; + + if (auth?.startsWith('Bearer ')) { + token = auth.slice(7); + } else { + const tokenCookie = req.cookies.get('encryptid_token'); + if (tokenCookie) token = tokenCookie.value; + } + + if (!token) { + return NextResponse.json({ authenticated: false }); + } + + try { + const res = await fetch(`${ENCRYPTID_URL}/api/session/verify`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!res.ok) { + return NextResponse.json({ authenticated: false }); + } + + const data = await res.json(); + if (data.valid) { + return NextResponse.json({ + authenticated: true, + user: { + username: data.username || null, + did: data.did || data.userId || null, + }, + }); + } + + return NextResponse.json({ authenticated: false }); + } catch { + return NextResponse.json({ authenticated: false }); + } +} diff --git a/src/app/api/spaces/route.ts b/src/app/api/spaces/route.ts new file mode 100644 index 0000000..f5cf371 --- /dev/null +++ b/src/app/api/spaces/route.ts @@ -0,0 +1,45 @@ +/** + * Spaces API proxy β€” forwards to rSpace (the canonical spaces authority). + * + * Every r*App proxies /api/spaces to rSpace so the SpaceSwitcher dropdown + * shows the same spaces everywhere. The EncryptID token is forwarded so + * rSpace can return user-specific spaces (owned/member). + */ + +import { NextRequest, NextResponse } from 'next/server'; + +const RSPACE_API = process.env.RSPACE_API_URL || 'https://rspace.online'; + +export async function GET(req: NextRequest) { + const headers: Record = {}; + + // Forward the EncryptID token (from Authorization header or cookie) + const auth = req.headers.get('Authorization'); + if (auth) { + headers['Authorization'] = auth; + } else { + // Fallback: check for encryptid_token cookie + const tokenCookie = req.cookies.get('encryptid_token'); + if (tokenCookie) { + headers['Authorization'] = `Bearer ${tokenCookie.value}`; + } + } + + try { + const res = await fetch(`${RSPACE_API}/api/spaces`, { + headers, + next: { revalidate: 30 }, // cache for 30s to avoid hammering rSpace + }); + + if (!res.ok) { + // If rSpace is down, return empty spaces (graceful degradation) + return NextResponse.json({ spaces: [] }); + } + + const data = await res.json(); + return NextResponse.json(data); + } catch { + // rSpace unreachable β€” return empty list + return NextResponse.json({ spaces: [] }); + } +} diff --git a/src/components/AppSwitcher.tsx b/src/components/AppSwitcher.tsx index 65a115d..24f2dcc 100644 --- a/src/components/AppSwitcher.tsx +++ b/src/components/AppSwitcher.tsx @@ -17,6 +17,8 @@ const MODULES: AppModule[] = [ { id: 'space', name: 'rSpace', badge: 'rS', color: 'bg-teal-300', emoji: '🎨', description: 'Real-time collaborative canvas', domain: 'rspace.online' }, { id: 'notes', name: 'rNotes', badge: 'rN', color: 'bg-amber-300', emoji: 'πŸ“', description: 'Group note-taking & knowledge capture', domain: 'rnotes.online' }, { id: 'pubs', name: 'rPubs', badge: 'rP', color: 'bg-rose-300', emoji: 'πŸ“–', description: 'Collaborative publishing platform', domain: 'rpubs.online' }, + { id: 'tube', name: 'rTube', badge: 'rTu', color: 'bg-pink-300', emoji: '🎬', description: 'Community video platform', domain: 'rtube.online' }, + { id: 'swag', name: 'rSwag', badge: 'rSw', color: 'bg-red-200', emoji: 'πŸ‘•', description: 'Community merch & swag store', domain: 'rswag.online' }, // Planning { id: 'cal', name: 'rCal', badge: 'rC', color: 'bg-sky-300', emoji: 'πŸ“…', description: 'Collaborative scheduling & events', domain: 'rcal.online' }, { id: 'trips', name: 'rTrips', badge: 'rT', color: 'bg-emerald-300', emoji: '✈️', description: 'Group travel planning in real time', domain: 'rtrips.online' }, @@ -34,13 +36,12 @@ const MODULES: AppModule[] = [ { id: 'wallet', name: 'rWallet', badge: 'rW', color: 'bg-yellow-300', emoji: 'πŸ’°', description: 'Multi-chain crypto wallet', domain: 'rwallet.online' }, { id: 'cart', name: 'rCart', badge: 'rCt', color: 'bg-orange-300', emoji: 'πŸ›’', description: 'Group commerce & shared shopping', domain: 'rcart.online' }, { id: 'auctions', name: 'rAuctions', badge: 'rA', color: 'bg-red-300', emoji: 'πŸ”¨', description: 'Live auction platform', domain: 'rauctions.online' }, - { id: 'swag', name: 'rSwag', badge: 'rSw', color: 'bg-red-200', emoji: 'πŸ‘•', description: 'Community merch & swag store', domain: 'rswag.online' }, - // Social & Media - { id: 'photos', name: 'rPhotos', badge: 'rPh', color: 'bg-pink-200', emoji: 'πŸ“Έ', description: 'Shared community photo albums', domain: 'rphotos.online' }, - { id: 'tube', name: 'rTube', badge: 'rTu', color: 'bg-pink-300', emoji: '🎬', description: 'Group video platform', domain: 'rtube.online' }, + // Sharing + { id: 'photos', name: 'rPhotos', badge: 'rPh', color: 'bg-pink-200', emoji: 'πŸ“Έ', description: 'Community photo commons', domain: 'rphotos.online' }, { id: 'network', name: 'rNetwork', badge: 'rNe', color: 'bg-blue-300', emoji: 'πŸ•ΈοΈ', description: 'Community network & social graph', domain: 'rnetwork.online' }, - { id: 'socials', name: 'rSocials', badge: 'rSo', color: 'bg-sky-200', emoji: 'πŸ“’', description: 'Social media management', domain: 'rsocials.online' }, { id: 'files', name: 'rFiles', badge: 'rFi', color: 'bg-cyan-300', emoji: 'πŸ“', description: 'Collaborative file storage', domain: 'rfiles.online' }, + { id: 'socials', name: 'rSocials', badge: 'rSo', color: 'bg-sky-200', emoji: 'πŸ“’', description: 'Social media management', domain: 'rsocials.online' }, + // Observing { id: 'data', name: 'rData', badge: 'rD', color: 'bg-purple-300', emoji: 'πŸ“Š', description: 'Analytics & insights dashboard', domain: 'rdata.online' }, // Work & Productivity { id: 'work', name: 'rWork', badge: 'rWo', color: 'bg-slate-300', emoji: 'πŸ“‹', description: 'Project & task management', domain: 'rwork.online' }, @@ -53,6 +54,8 @@ const MODULE_CATEGORIES: Record = { space: 'Creating', notes: 'Creating', pubs: 'Creating', + tube: 'Creating', + swag: 'Creating', cal: 'Planning', trips: 'Planning', maps: 'Planning', @@ -66,13 +69,11 @@ const MODULE_CATEGORIES: Record = { wallet: 'Funding & Commerce', cart: 'Funding & Commerce', auctions: 'Funding & Commerce', - swag: 'Funding & Commerce', - photos: 'Social & Media', - tube: 'Social & Media', - network: 'Social & Media', - socials: 'Social & Media', - files: 'Social & Media', - data: 'Social & Media', + photos: 'Sharing', + network: 'Sharing', + files: 'Sharing', + socials: 'Sharing', + data: 'Observing', work: 'Work & Productivity', ids: 'Identity & Infrastructure', stack: 'Identity & Infrastructure', @@ -84,7 +85,8 @@ const CATEGORY_ORDER = [ 'Communicating', 'Deciding', 'Funding & Commerce', - 'Social & Media', + 'Sharing', + 'Observing', 'Work & Productivity', 'Identity & Infrastructure', ]; diff --git a/src/components/EcosystemFooter.tsx b/src/components/EcosystemFooter.tsx index ac513c6..4228ed1 100644 --- a/src/components/EcosystemFooter.tsx +++ b/src/components/EcosystemFooter.tsx @@ -1,30 +1,39 @@ 'use client'; const FOOTER_LINKS = [ + // Creating { name: 'rSpace', href: 'https://rspace.online' }, { name: 'rNotes', href: 'https://rnotes.online' }, { name: 'rPubs', href: 'https://rpubs.online' }, + { name: 'rTube', href: 'https://rtube.online' }, + { name: 'rSwag', href: 'https://rswag.online' }, + // Planning { name: 'rCal', href: 'https://rcal.online' }, { name: 'rTrips', href: 'https://rtrips.online' }, { name: 'rMaps', href: 'https://rmaps.online' }, + // Communicating { name: 'rChats', href: 'https://rchats.online' }, { name: 'rInbox', href: 'https://rinbox.online' }, { name: 'rMail', href: 'https://rmail.online' }, { name: 'rForum', href: 'https://rforum.online' }, + // Deciding { name: 'rChoices', href: 'https://rchoices.online' }, { name: 'rVote', href: 'https://rvote.online' }, + // Funding & Commerce { name: 'rFunds', href: 'https://rfunds.online' }, { name: 'rWallet', href: 'https://rwallet.online' }, { name: 'rCart', href: 'https://rcart.online' }, { name: 'rAuctions', href: 'https://rauctions.online' }, - { name: 'rSwag', href: 'https://rswag.online' }, + // Sharing { name: 'rPhotos', href: 'https://rphotos.online' }, - { name: 'rTube', href: 'https://rtube.online' }, { name: 'rNetwork', href: 'https://rnetwork.online' }, - { name: 'rSocials', href: 'https://rsocials.online' }, { name: 'rFiles', href: 'https://rfiles.online' }, + { name: 'rSocials', href: 'https://rsocials.online' }, + // Observing { name: 'rData', href: 'https://rdata.online' }, + // Work & Productivity { name: 'rWork', href: 'https://rwork.online' }, + // Identity & Infrastructure { name: 'rIDs', href: 'https://ridentity.online' }, { name: 'rStack', href: 'https://rstack.online' }, ]; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 0d4599d..cdade23 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -22,7 +22,7 @@ interface HeaderProps { export function Header({ current = 'notes', breadcrumbs, actions, maxWidth = 'max-w-6xl' }: HeaderProps) { return ( -