diff --git a/backend/src/routes/invitations.js b/backend/src/routes/invitations.js index 26be3a4..1a1b940 100644 --- a/backend/src/routes/invitations.js +++ b/backend/src/routes/invitations.js @@ -23,6 +23,56 @@ function authMiddleware(req, res, next) { } } +/** + * PUBLIC: Get invitation info without auth + * GET /api/invitations/info/:code + * Used to show role info before accepting + */ +router.get('/info/:code', async (req, res) => { + try { + const code = req.params.code; + + if (!code) { + return res.status(400).json({ error: 'Code is required' }); + } + + // Find invitation by code + const formattedCode = code.toUpperCase().replace(/-/g, '').replace(/(.{3})/g, '$1-').slice(0, 11); + + let { data: invitation } = await supabase + .from('invitations') + .select('role, accepted_at') + .eq('token', formattedCode) + .single(); + + // Try without formatting + if (!invitation) { + const { data: inv2 } = await supabase + .from('invitations') + .select('role, accepted_at') + .eq('token', code.toUpperCase()) + .single(); + invitation = inv2; + } + + if (!invitation) { + return res.status(404).json({ error: 'Invitation not found' }); + } + + if (invitation.accepted_at) { + return res.status(400).json({ error: 'This invitation has already been accepted' }); + } + + res.json({ + role: invitation.role + }); + + } catch (error) { + console.error('[INVITE] Get info error:', error); + res.status(500).json({ error: error.message }); + } +}); + /** * PUBLIC: Accept invitation without auth * POST /api/invitations/accept-public diff --git a/web/accept-invite.html b/web/accept-invite.html index ad1ff2e..faddbfc 100644 --- a/web/accept-invite.html +++ b/web/accept-invite.html @@ -153,8 +153,24 @@
+ +
+

Loading invitation...

+
+ -
+

You're all set!

-
-

- You have been granted access to monitor a family member through WellNuo. + Your invitation has been accepted. Download the WellNuo app to get started.

-
-
With your role you can:
-
    -
    + + Go to WellNuo.com + -
    -
    What's next?
    -
    - Download the WellNuo app and sign in with the email address this invitation was sent to. You'll see the beneficiary in your dashboard. -
    -
    +

    + Sign in with the email address this invitation was sent to. +

    @@ -199,38 +209,29 @@ const params = new URLSearchParams(window.location.search); const inviteCode = params.get('code') || ''; - // Show error if no code - if (!inviteCode) { - document.getElementById('errorMessage').textContent = 'Invalid invitation link.'; - document.getElementById('errorMessage').style.display = 'block'; - document.getElementById('acceptBtn').disabled = true; - } + // Load invitation info on page load + async function loadInvitationInfo() { + const loadingSection = document.getElementById('loadingSection'); + const acceptSection = document.getElementById('acceptSection'); + const errorMessage = document.getElementById('errorMessage'); - async function acceptInvitation() { - const btn = document.getElementById('acceptBtn'); - const errorEl = document.getElementById('errorMessage'); - - btn.disabled = true; - btn.innerHTML = 'Accepting...'; - errorEl.style.display = 'none'; + if (!inviteCode) { + loadingSection.innerHTML = '

    Invalid invitation link.

    '; + return; + } try { - const response = await fetch(`${API_BASE}/invitations/accept-public`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ code: inviteCode }) - }); - + const response = await fetch(`${API_BASE}/invitations/info/${inviteCode}`); const data = await response.json(); if (!response.ok) { - throw new Error(data.error || 'Failed to accept invitation'); + throw new Error(data.error || 'Invalid invitation'); } - // Show success with role-specific permissions + // Show role info before accepting const isGuardian = data.role === 'guardian'; - const roleBadge = document.getElementById('roleBadge'); - const permissionsList = document.getElementById('permissionsList'); + const roleBadge = document.getElementById('roleBadgeAccept'); + const permissionsList = document.getElementById('permissionsListAccept'); if (isGuardian) { roleBadge.textContent = 'Guardian'; @@ -254,6 +255,40 @@ `; } + // Show accept section + loadingSection.style.display = 'none'; + acceptSection.style.display = 'block'; + + } catch (error) { + loadingSection.innerHTML = `

    ${error.message}

    `; + } + } + + // Load info on page load + loadInvitationInfo(); + + async function acceptInvitation() { + const btn = document.getElementById('acceptBtn'); + const errorEl = document.getElementById('errorMessage'); + + btn.disabled = true; + btn.innerHTML = 'Accepting...'; + errorEl.style.display = 'none'; + + try { + const response = await fetch(`${API_BASE}/invitations/accept-public`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ code: inviteCode }) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to accept invitation'); + } + + // Show success document.getElementById('acceptSection').style.display = 'none'; document.getElementById('successSection').style.display = 'block';