Improve invitation acceptance flow
- Add GET /api/invitations/info/:code endpoint to fetch role before accepting - Show role and permissions on accept page BEFORE clicking Accept - Simplify success page: remove permissions list, add link to wellnuo.com - Minimalist design: light header background, logo only
This commit is contained in:
parent
d0c4930d38
commit
d9fcdf1751
@ -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
|
* PUBLIC: Accept invitation without auth
|
||||||
* POST /api/invitations/accept-public
|
* POST /api/invitations/accept-public
|
||||||
|
|||||||
@ -153,8 +153,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<!-- Loading Section -->
|
||||||
|
<div id="loadingSection">
|
||||||
|
<p style="color: #666;">Loading invitation...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Accept Section -->
|
<!-- Accept Section -->
|
||||||
<div id="acceptSection">
|
<div id="acceptSection" style="display: none;">
|
||||||
|
<div id="roleBadgeAccept" class="role-badge"></div>
|
||||||
|
|
||||||
|
<p class="success-text">
|
||||||
|
You've been invited to monitor a family member through WellNuo.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="permissionsBlockAccept">
|
||||||
|
<div class="permissions-title">With this role you will be able to:</div>
|
||||||
|
<ul class="permissions-list" id="permissionsListAccept"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="errorMessage" class="error-message"></div>
|
<div id="errorMessage" class="error-message"></div>
|
||||||
|
|
||||||
<button class="btn btn-primary" id="acceptBtn" onclick="acceptInvitation()">
|
<button class="btn btn-primary" id="acceptBtn" onclick="acceptInvitation()">
|
||||||
@ -171,23 +187,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<h2 class="success-title">You're all set!</h2>
|
<h2 class="success-title">You're all set!</h2>
|
||||||
|
|
||||||
<div id="roleBadge" class="role-badge"></div>
|
|
||||||
|
|
||||||
<p class="success-text">
|
<p class="success-text">
|
||||||
You have been granted access to monitor a family member through WellNuo.
|
Your invitation has been accepted. Download the WellNuo app to get started.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div id="permissionsBlock">
|
<a href="https://wellnuo.com" target="_blank" class="btn btn-primary" style="display: block; text-decoration: none; margin-top: 20px;">
|
||||||
<div class="permissions-title">With your role you can:</div>
|
Go to WellNuo.com
|
||||||
<ul class="permissions-list" id="permissionsList"></ul>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="next-step">
|
<p style="margin-top: 16px; font-size: 13px; color: #666;">
|
||||||
<div class="next-step-title">What's next?</div>
|
Sign in with the email address this invitation was sent to.
|
||||||
<div class="next-step-text">
|
</p>
|
||||||
Download the WellNuo app and sign in with the email address this invitation was sent to. You'll see the beneficiary in your dashboard.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -199,38 +209,29 @@
|
|||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const inviteCode = params.get('code') || '';
|
const inviteCode = params.get('code') || '';
|
||||||
|
|
||||||
// Show error if no code
|
// Load invitation info on page load
|
||||||
if (!inviteCode) {
|
async function loadInvitationInfo() {
|
||||||
document.getElementById('errorMessage').textContent = 'Invalid invitation link.';
|
const loadingSection = document.getElementById('loadingSection');
|
||||||
document.getElementById('errorMessage').style.display = 'block';
|
const acceptSection = document.getElementById('acceptSection');
|
||||||
document.getElementById('acceptBtn').disabled = true;
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
}
|
|
||||||
|
|
||||||
async function acceptInvitation() {
|
if (!inviteCode) {
|
||||||
const btn = document.getElementById('acceptBtn');
|
loadingSection.innerHTML = '<p style="color: #c00;">Invalid invitation link.</p>';
|
||||||
const errorEl = document.getElementById('errorMessage');
|
return;
|
||||||
|
}
|
||||||
btn.disabled = true;
|
|
||||||
btn.innerHTML = '<span class="loading"></span>Accepting...';
|
|
||||||
errorEl.style.display = 'none';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/invitations/accept-public`, {
|
const response = await fetch(`${API_BASE}/invitations/info/${inviteCode}`);
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ code: inviteCode })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
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 isGuardian = data.role === 'guardian';
|
||||||
const roleBadge = document.getElementById('roleBadge');
|
const roleBadge = document.getElementById('roleBadgeAccept');
|
||||||
const permissionsList = document.getElementById('permissionsList');
|
const permissionsList = document.getElementById('permissionsListAccept');
|
||||||
|
|
||||||
if (isGuardian) {
|
if (isGuardian) {
|
||||||
roleBadge.textContent = 'Guardian';
|
roleBadge.textContent = 'Guardian';
|
||||||
@ -254,6 +255,40 @@
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show accept section
|
||||||
|
loadingSection.style.display = 'none';
|
||||||
|
acceptSection.style.display = 'block';
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
loadingSection.innerHTML = `<p style="color: #c00;">${error.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load info on page load
|
||||||
|
loadInvitationInfo();
|
||||||
|
|
||||||
|
async function acceptInvitation() {
|
||||||
|
const btn = document.getElementById('acceptBtn');
|
||||||
|
const errorEl = document.getElementById('errorMessage');
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<span class="loading"></span>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('acceptSection').style.display = 'none';
|
||||||
document.getElementById('successSection').style.display = 'block';
|
document.getElementById('successSection').style.display = 'block';
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user