Improve invitation UI and fix access removal
- Move role selector (Caretaker/Guardian) above email input in Access screen - Remove "(view only)" suffix from Caretaker role in email templates - Remove "expires in 7 days" text from invitation emails - Remove expires_at field from invitation creation (invitations never expire) - Fix deletion of accepted invitations (now also removes user_access record) - Add favicon to accept-invite.html page
This commit is contained in:
parent
d9fcdf1751
commit
f4ff281bcc
@ -324,32 +324,7 @@ export default function ShareAccessScreen() {
|
|||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Invite Someone</Text>
|
<Text style={styles.sectionTitle}>Invite Someone</Text>
|
||||||
|
|
||||||
<View style={styles.inputRow}>
|
{/* Role Toggle - above email */}
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
placeholder="Email address"
|
|
||||||
placeholderTextColor={AppColors.textMuted}
|
|
||||||
value={email}
|
|
||||||
onChangeText={setEmail}
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect={false}
|
|
||||||
keyboardType="email-address"
|
|
||||||
editable={!isLoading}
|
|
||||||
/>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.sendButton, isLoading && styles.sendButtonDisabled]}
|
|
||||||
onPress={handleSendInvite}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<ActivityIndicator size="small" color={AppColors.white} />
|
|
||||||
) : (
|
|
||||||
<Ionicons name="send" size={18} color={AppColors.white} />
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Role Toggle */}
|
|
||||||
<View style={styles.roleToggle}>
|
<View style={styles.roleToggle}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.roleButton, role === 'caretaker' && styles.roleButtonActive]}
|
style={[styles.roleButton, role === 'caretaker' && styles.roleButtonActive]}
|
||||||
@ -374,6 +349,31 @@ export default function ShareAccessScreen() {
|
|||||||
? 'Can view activity and chat with Julia'
|
? 'Can view activity and chat with Julia'
|
||||||
: 'Full access: edit info, manage subscription'}
|
: 'Full access: edit info, manage subscription'}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<View style={[styles.inputRow, { marginTop: Spacing.md }]}>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Email address"
|
||||||
|
placeholderTextColor={AppColors.textMuted}
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
keyboardType="email-address"
|
||||||
|
editable={!isLoading}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.sendButton, isLoading && styles.sendButtonDisabled]}
|
||||||
|
onPress={handleSendInvite}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<ActivityIndicator size="small" color={AppColors.white} />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="send" size={18} color={AppColors.white} />
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* People with Access */}
|
{/* People with Access */}
|
||||||
|
|||||||
@ -310,9 +310,7 @@ router.post('/', async (req, res) => {
|
|||||||
attempts++;
|
attempts++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create invitation (expires in 7 days)
|
// Create invitation (no expiry)
|
||||||
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
const { data: invitation, error } = await supabase
|
const { data: invitation, error } = await supabase
|
||||||
.from('invitations')
|
.from('invitations')
|
||||||
.insert({
|
.insert({
|
||||||
@ -321,8 +319,7 @@ router.post('/', async (req, res) => {
|
|||||||
token: inviteToken,
|
token: inviteToken,
|
||||||
role: role,
|
role: role,
|
||||||
email: email?.toLowerCase() || null,
|
email: email?.toLowerCase() || null,
|
||||||
label: label || null,
|
label: label || null
|
||||||
expires_at: expiresAt.toISOString()
|
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
@ -690,8 +687,13 @@ router.delete('/:id', async (req, res) => {
|
|||||||
return res.status(404).json({ error: 'Invitation not found' });
|
return res.status(404).json({ error: 'Invitation not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invitation.accepted_at) {
|
// If invitation was accepted, also remove user_access
|
||||||
return res.status(400).json({ error: 'Cannot revoke accepted invitation' });
|
if (invitation.accepted_by) {
|
||||||
|
await supabase
|
||||||
|
.from('user_access')
|
||||||
|
.delete()
|
||||||
|
.eq('beneficiary_id', invitation.beneficiary_id)
|
||||||
|
.eq('accessor_id', invitation.accepted_by);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete invitation
|
// Delete invitation
|
||||||
|
|||||||
@ -222,7 +222,7 @@ If you didn't request this code, you can safely ignore this email.
|
|||||||
async function sendInvitationEmail({ email, inviterName, beneficiaryName, role, inviteCode }) {
|
async function sendInvitationEmail({ email, inviterName, beneficiaryName, role, inviteCode }) {
|
||||||
const frontendUrl = process.env.FRONTEND_URL || 'https://wellnuo.smartlaunchhub.com';
|
const frontendUrl = process.env.FRONTEND_URL || 'https://wellnuo.smartlaunchhub.com';
|
||||||
const acceptLink = `${frontendUrl}/accept-invite.html?code=${inviteCode}`;
|
const acceptLink = `${frontendUrl}/accept-invite.html?code=${inviteCode}`;
|
||||||
const roleText = role === 'guardian' ? 'Guardian (full access)' : 'Caretaker (view only)';
|
const roleText = role === 'guardian' ? 'Guardian' : 'Caretaker';
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -276,9 +276,6 @@ async function sendInvitationEmail({ email, inviterName, beneficiaryName, role,
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p style="margin: 24px 0 0 0; font-size: 13px; color: #999999; text-align: center;">
|
|
||||||
This invitation expires in 7 days.
|
|
||||||
</p>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@ -310,8 +307,6 @@ Your invitation code: ${inviteCode}
|
|||||||
|
|
||||||
Accept your invitation: ${acceptLink}
|
Accept your invitation: ${acceptLink}
|
||||||
|
|
||||||
This invitation expires in 7 days.
|
|
||||||
|
|
||||||
If you didn't expect this invitation, you can safely ignore this email.
|
If you didn't expect this invitation, you can safely ignore this email.
|
||||||
|
|
||||||
WellNuo - Elderly Care Monitoring
|
WellNuo - Elderly Care Monitoring
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Accept Invitation - WellNuo</title>
|
<title>Accept Invitation - WellNuo</title>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
<style>
|
<style>
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
body {
|
body {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user