mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Fix remembering family members layer state and refreshing locations
This commit is contained in:
parent
07216e00dd
commit
05237995cf
7 changed files with 173 additions and 68 deletions
File diff suppressed because one or more lines are too long
|
|
@ -13,7 +13,8 @@ class Family::InvitationsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@invitation = Family::Invitation.find_by!(token: params[:token])
|
token = params[:token] || params[:id]
|
||||||
|
@invitation = Family::Invitation.find_by!(token: token)
|
||||||
|
|
||||||
if @invitation.expired?
|
if @invitation.expired?
|
||||||
redirect_to root_path, alert: 'This invitation has expired.' and return
|
redirect_to root_path, alert: 'This invitation has expired.' and return
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,11 @@ export default class extends Controller {
|
||||||
mapsController.updateLayerControl({
|
mapsController.updateLayerControl({
|
||||||
"Family Members": this.familyMarkersLayer
|
"Family Members": this.familyMarkersLayer
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Dispatch event to notify that Family Members layer is now available
|
||||||
|
document.dispatchEvent(new CustomEvent('family:layer:ready', {
|
||||||
|
detail: { layer: this.familyMarkersLayer }
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ export default class extends BaseController {
|
||||||
this.speedColoredPolylines = this.userSettings.speed_colored_routes || false;
|
this.speedColoredPolylines = this.userSettings.speed_colored_routes || false;
|
||||||
this.speedColorScale = this.userSettings.speed_color_scale || colorFormatEncode(colorStopsFallback);
|
this.speedColorScale = this.userSettings.speed_color_scale || colorFormatEncode(colorStopsFallback);
|
||||||
|
|
||||||
|
// Flag to prevent saving layers during initialization/restoration
|
||||||
|
this.isRestoringLayers = false;
|
||||||
|
|
||||||
// Ensure we have valid markers array
|
// Ensure we have valid markers array
|
||||||
if (!Array.isArray(this.markers)) {
|
if (!Array.isArray(this.markers)) {
|
||||||
console.warn('Markers is not an array, setting to empty array');
|
console.warn('Markers is not an array, setting to empty array');
|
||||||
|
|
@ -229,6 +232,9 @@ export default class extends BaseController {
|
||||||
// Initialize layers based on settings
|
// Initialize layers based on settings
|
||||||
this.initializeLayersFromSettings();
|
this.initializeLayersFromSettings();
|
||||||
|
|
||||||
|
// Listen for Family Members layer becoming ready
|
||||||
|
this.setupFamilyLayerListener();
|
||||||
|
|
||||||
// Initialize tracks layer
|
// Initialize tracks layer
|
||||||
this.initializeTracksLayer();
|
this.initializeTracksLayer();
|
||||||
|
|
||||||
|
|
@ -465,8 +471,10 @@ export default class extends BaseController {
|
||||||
|
|
||||||
// Add event listeners for overlay layer changes to keep routes/tracks selector in sync
|
// Add event listeners for overlay layer changes to keep routes/tracks selector in sync
|
||||||
this.map.on('overlayadd', (event) => {
|
this.map.on('overlayadd', (event) => {
|
||||||
// Save enabled layers whenever a layer is added
|
// Save enabled layers whenever a layer is added (unless we're restoring from settings)
|
||||||
this.saveEnabledLayers();
|
if (!this.isRestoringLayers) {
|
||||||
|
this.saveEnabledLayers();
|
||||||
|
}
|
||||||
|
|
||||||
if (event.name === 'Routes') {
|
if (event.name === 'Routes') {
|
||||||
this.handleRouteLayerToggle('routes');
|
this.handleRouteLayerToggle('routes');
|
||||||
|
|
@ -523,8 +531,10 @@ export default class extends BaseController {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.map.on('overlayremove', (event) => {
|
this.map.on('overlayremove', (event) => {
|
||||||
// Save enabled layers whenever a layer is removed
|
// Save enabled layers whenever a layer is removed (unless we're restoring from settings)
|
||||||
this.saveEnabledLayers();
|
if (!this.isRestoringLayers) {
|
||||||
|
this.saveEnabledLayers();
|
||||||
|
}
|
||||||
|
|
||||||
if (event.name === 'Routes' || event.name === 'Tracks') {
|
if (event.name === 'Routes' || event.name === 'Tracks') {
|
||||||
// Don't auto-switch when layers are manually turned off
|
// Don't auto-switch when layers are manually turned off
|
||||||
|
|
@ -585,7 +595,8 @@ export default class extends BaseController {
|
||||||
const enabledLayers = [];
|
const enabledLayers = [];
|
||||||
const layerNames = [
|
const layerNames = [
|
||||||
'Points', 'Routes', 'Tracks', 'Heatmap', 'Fog of War',
|
'Points', 'Routes', 'Tracks', 'Heatmap', 'Fog of War',
|
||||||
'Scratch map', 'Areas', 'Photos', 'Suggested Visits', 'Confirmed Visits'
|
'Scratch map', 'Areas', 'Photos', 'Suggested Visits', 'Confirmed Visits',
|
||||||
|
'Family Members'
|
||||||
];
|
];
|
||||||
|
|
||||||
const controlsLayer = {
|
const controlsLayer = {
|
||||||
|
|
@ -598,7 +609,8 @@ export default class extends BaseController {
|
||||||
'Areas': this.areasLayer,
|
'Areas': this.areasLayer,
|
||||||
'Photos': this.photoMarkers,
|
'Photos': this.photoMarkers,
|
||||||
'Suggested Visits': this.visitsManager?.getVisitCirclesLayer(),
|
'Suggested Visits': this.visitsManager?.getVisitCirclesLayer(),
|
||||||
'Confirmed Visits': this.visitsManager?.getConfirmedVisitCirclesLayer()
|
'Confirmed Visits': this.visitsManager?.getConfirmedVisitCirclesLayer(),
|
||||||
|
'Family Members': window.familyMembersController?.familyMarkersLayer
|
||||||
};
|
};
|
||||||
|
|
||||||
layerNames.forEach(name => {
|
layerNames.forEach(name => {
|
||||||
|
|
@ -608,16 +620,6 @@ export default class extends BaseController {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add family member layers
|
|
||||||
if (window.familyController && window.familyController.familyLayers) {
|
|
||||||
Object.keys(window.familyController.familyLayers).forEach(memberName => {
|
|
||||||
const layer = window.familyController.familyLayers[memberName];
|
|
||||||
if (layer && this.map.hasLayer(layer)) {
|
|
||||||
enabledLayers.push(memberName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/api/v1/settings', {
|
fetch('/api/v1/settings', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -1481,23 +1483,31 @@ export default class extends BaseController {
|
||||||
'Areas': this.areasLayer,
|
'Areas': this.areasLayer,
|
||||||
'Photos': this.photoMarkers,
|
'Photos': this.photoMarkers,
|
||||||
'Suggested Visits': this.visitsManager?.getVisitCirclesLayer(),
|
'Suggested Visits': this.visitsManager?.getVisitCirclesLayer(),
|
||||||
'Confirmed Visits': this.visitsManager?.getConfirmedVisitCirclesLayer()
|
'Confirmed Visits': this.visitsManager?.getConfirmedVisitCirclesLayer(),
|
||||||
|
'Family Members': window.familyMembersController?.familyMarkersLayer
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add family member layers if available
|
|
||||||
if (window.familyController && window.familyController.familyLayers) {
|
|
||||||
Object.entries(window.familyController.familyLayers).forEach(([memberName, layer]) => {
|
|
||||||
controlsLayer[memberName] = layer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saved layer preferences
|
// Apply saved layer preferences
|
||||||
Object.entries(controlsLayer).forEach(([name, layer]) => {
|
Object.entries(controlsLayer).forEach(([name, layer]) => {
|
||||||
if (!layer) return;
|
if (!layer) {
|
||||||
|
if (enabledLayers.includes(name)) {
|
||||||
|
console.log(`Layer ${name} is in enabled layers but layer object is null/undefined`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const shouldBeEnabled = enabledLayers.includes(name);
|
const shouldBeEnabled = enabledLayers.includes(name);
|
||||||
const isCurrentlyEnabled = this.map.hasLayer(layer);
|
const isCurrentlyEnabled = this.map.hasLayer(layer);
|
||||||
|
|
||||||
|
if (name === 'Family Members') {
|
||||||
|
console.log('Family Members layer check:', {
|
||||||
|
shouldBeEnabled,
|
||||||
|
isCurrentlyEnabled,
|
||||||
|
layerExists: !!layer,
|
||||||
|
controllerExists: !!window.familyMembersController
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldBeEnabled && !isCurrentlyEnabled) {
|
if (shouldBeEnabled && !isCurrentlyEnabled) {
|
||||||
// Add layer to map
|
// Add layer to map
|
||||||
layer.addTo(this.map);
|
layer.addTo(this.map);
|
||||||
|
|
@ -1534,6 +1544,11 @@ export default class extends BaseController {
|
||||||
if (this.drawControl && !this.map._controlCorners.topleft.querySelector('.leaflet-draw')) {
|
if (this.drawControl && !this.map._controlCorners.topleft.querySelector('.leaflet-draw')) {
|
||||||
this.map.addControl(this.drawControl);
|
this.map.addControl(this.drawControl);
|
||||||
}
|
}
|
||||||
|
} else if (name === 'Family Members') {
|
||||||
|
// Refresh family locations when layer is restored
|
||||||
|
if (window.familyMembersController && typeof window.familyMembersController.refreshFamilyLocations === 'function') {
|
||||||
|
window.familyMembersController.refreshFamilyLocations();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (!shouldBeEnabled && isCurrentlyEnabled) {
|
} else if (!shouldBeEnabled && isCurrentlyEnabled) {
|
||||||
// Remove layer from map
|
// Remove layer from map
|
||||||
|
|
@ -1543,6 +1558,36 @@ export default class extends BaseController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupFamilyLayerListener() {
|
||||||
|
// Listen for when the Family Members layer becomes available
|
||||||
|
document.addEventListener('family:layer:ready', (event) => {
|
||||||
|
console.log('Family layer ready event received');
|
||||||
|
const enabledLayers = this.userSettings.enabled_map_layers || [];
|
||||||
|
|
||||||
|
// Check if Family Members should be enabled based on saved settings
|
||||||
|
if (enabledLayers.includes('Family Members')) {
|
||||||
|
const layer = event.detail.layer;
|
||||||
|
if (layer && !this.map.hasLayer(layer)) {
|
||||||
|
// Set flag to prevent saving during restoration
|
||||||
|
this.isRestoringLayers = true;
|
||||||
|
|
||||||
|
layer.addTo(this.map);
|
||||||
|
console.log('Enabled layer: Family Members (from ready event)');
|
||||||
|
|
||||||
|
// Refresh family locations
|
||||||
|
if (window.familyMembersController && typeof window.familyMembersController.refreshFamilyLocations === 'function') {
|
||||||
|
window.familyMembersController.refreshFamilyLocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset flag after a short delay to allow all events to complete
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isRestoringLayers = false;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { once: true }); // Only listen once
|
||||||
|
}
|
||||||
|
|
||||||
toggleRightPanel() {
|
toggleRightPanel() {
|
||||||
if (this.rightPanel) {
|
if (this.rightPanel) {
|
||||||
const panel = document.querySelector('.leaflet-right-panel');
|
const panel = document.querySelector('.leaflet-right-panel');
|
||||||
|
|
|
||||||
|
|
@ -125,32 +125,41 @@ export function showFlashMessage(type, message) {
|
||||||
if (!flashContainer) {
|
if (!flashContainer) {
|
||||||
flashContainer = document.createElement('div');
|
flashContainer = document.createElement('div');
|
||||||
flashContainer.id = 'flash-messages';
|
flashContainer.id = 'flash-messages';
|
||||||
// Use z-[9999] to ensure flash messages appear above navbar (z-50)
|
flashContainer.className = 'fixed top-5 right-5 flex flex-col gap-2 z-50';
|
||||||
flashContainer.className = 'fixed top-20 right-5 flex flex-col-reverse gap-2';
|
|
||||||
flashContainer.style.zIndex = '9999';
|
|
||||||
document.body.appendChild(flashContainer);
|
document.body.appendChild(flashContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the flash message div
|
// Create the flash message div with DaisyUI alert classes
|
||||||
const flashDiv = document.createElement('div');
|
const flashDiv = document.createElement('div');
|
||||||
flashDiv.setAttribute('data-controller', 'removals');
|
flashDiv.setAttribute('data-controller', 'removals');
|
||||||
flashDiv.className = `flex items-center justify-between ${classesForFlash(type)} py-3 px-5 rounded-lg shadow-lg`;
|
flashDiv.setAttribute('data-removals-timeout-value', type === 'notice' || type === 'success' ? '5000' : '0');
|
||||||
|
flashDiv.setAttribute('role', 'alert');
|
||||||
|
flashDiv.className = `alert ${getAlertClass(type)} shadow-lg z-[6000]`;
|
||||||
|
|
||||||
// Create the message div
|
// Create the content wrapper
|
||||||
const messageDiv = document.createElement('div');
|
const contentDiv = document.createElement('div');
|
||||||
messageDiv.className = 'mr-4';
|
contentDiv.className = 'flex items-center gap-2';
|
||||||
messageDiv.innerText = message;
|
|
||||||
|
// Add the icon
|
||||||
|
const icon = getFlashIcon(type);
|
||||||
|
contentDiv.appendChild(icon);
|
||||||
|
|
||||||
|
// Create the message span
|
||||||
|
const messageSpan = document.createElement('span');
|
||||||
|
messageSpan.innerText = message;
|
||||||
|
contentDiv.appendChild(messageSpan);
|
||||||
|
|
||||||
// Create the close button
|
// Create the close button
|
||||||
const closeButton = document.createElement('button');
|
const closeButton = document.createElement('button');
|
||||||
closeButton.setAttribute('type', 'button');
|
closeButton.setAttribute('type', 'button');
|
||||||
closeButton.setAttribute('data-action', 'click->removals#remove');
|
closeButton.setAttribute('data-action', 'click->removals#remove');
|
||||||
closeButton.className = 'ml-auto'; // Ensures button stays on the right
|
closeButton.setAttribute('aria-label', 'Close');
|
||||||
|
closeButton.className = 'btn btn-sm btn-circle btn-ghost';
|
||||||
|
|
||||||
// Create the SVG icon for the close button
|
// Create the SVG icon for the close button
|
||||||
const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
closeIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
closeIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||||
closeIcon.setAttribute('class', 'h-6 w-6');
|
closeIcon.setAttribute('class', 'h-5 w-5');
|
||||||
closeIcon.setAttribute('fill', 'none');
|
closeIcon.setAttribute('fill', 'none');
|
||||||
closeIcon.setAttribute('viewBox', '0 0 24 24');
|
closeIcon.setAttribute('viewBox', '0 0 24 24');
|
||||||
closeIcon.setAttribute('stroke', 'currentColor');
|
closeIcon.setAttribute('stroke', 'currentColor');
|
||||||
|
|
@ -164,33 +173,75 @@ export function showFlashMessage(type, message) {
|
||||||
// Append all elements
|
// Append all elements
|
||||||
closeIcon.appendChild(closeIconPath);
|
closeIcon.appendChild(closeIconPath);
|
||||||
closeButton.appendChild(closeIcon);
|
closeButton.appendChild(closeIcon);
|
||||||
flashDiv.appendChild(messageDiv);
|
flashDiv.appendChild(contentDiv);
|
||||||
flashDiv.appendChild(closeButton);
|
flashDiv.appendChild(closeButton);
|
||||||
flashContainer.appendChild(flashDiv);
|
flashContainer.appendChild(flashDiv);
|
||||||
|
|
||||||
// Automatically remove after 5 seconds
|
// Automatically remove after 5 seconds for notice/success
|
||||||
setTimeout(() => {
|
if (type === 'notice' || type === 'success') {
|
||||||
if (flashDiv && flashDiv.parentNode) {
|
setTimeout(() => {
|
||||||
flashDiv.remove();
|
if (flashDiv && flashDiv.parentNode) {
|
||||||
// Remove container if empty
|
flashDiv.remove();
|
||||||
if (flashContainer && !flashContainer.hasChildNodes()) {
|
// Remove container if empty
|
||||||
flashContainer.remove();
|
if (flashContainer && !flashContainer.hasChildNodes()) {
|
||||||
|
flashContainer.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, 5000);
|
||||||
}, 5000);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function classesForFlash(type) {
|
function getAlertClass(type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'error':
|
case 'error':
|
||||||
return 'bg-red-100 text-red-700 border-red-300';
|
case 'alert':
|
||||||
|
return 'alert-error';
|
||||||
case 'notice':
|
case 'notice':
|
||||||
return 'bg-blue-100 text-blue-700 border-blue-300';
|
case 'info':
|
||||||
|
return 'alert-info';
|
||||||
|
case 'success':
|
||||||
|
return 'alert-success';
|
||||||
|
case 'warning':
|
||||||
|
return 'alert-warning';
|
||||||
default:
|
default:
|
||||||
return 'bg-blue-100 text-blue-700 border-blue-300';
|
return 'alert-info';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFlashIcon(type) {
|
||||||
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
|
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||||
|
svg.setAttribute('class', 'h-6 w-6 shrink-0 stroke-current');
|
||||||
|
svg.setAttribute('fill', 'none');
|
||||||
|
svg.setAttribute('viewBox', '0 0 24 24');
|
||||||
|
|
||||||
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||||
|
path.setAttribute('stroke-linecap', 'round');
|
||||||
|
path.setAttribute('stroke-linejoin', 'round');
|
||||||
|
path.setAttribute('stroke-width', '2');
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'error':
|
||||||
|
case 'alert':
|
||||||
|
path.setAttribute('d', 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z');
|
||||||
|
break;
|
||||||
|
case 'success':
|
||||||
|
path.setAttribute('d', 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z');
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
path.setAttribute('d', 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z');
|
||||||
|
break;
|
||||||
|
case 'notice':
|
||||||
|
case 'info':
|
||||||
|
default:
|
||||||
|
path.setAttribute('d', 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.appendChild(path);
|
||||||
|
return svg;
|
||||||
|
}
|
||||||
|
|
||||||
export function debounce(func, wait) {
|
export function debounce(func, wait) {
|
||||||
let timeout;
|
let timeout;
|
||||||
return function executedFunction(...args) {
|
return function executedFunction(...args) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ class Users::ImportDataJob < ApplicationJob
|
||||||
|
|
||||||
import_stats = Users::ImportData.new(user, archive_path).import
|
import_stats = Users::ImportData.new(user, archive_path).import
|
||||||
|
|
||||||
|
# Reset counter caches after import completes
|
||||||
|
User.reset_counters(user.id, :points)
|
||||||
|
|
||||||
Rails.logger.info "Import completed successfully for user #{user.email}: #{import_stats}"
|
Rails.logger.info "Import completed successfully for user #{user.email}: #{import_stats}"
|
||||||
rescue ActiveRecord::RecordNotFound => e
|
rescue ActiveRecord::RecordNotFound => e
|
||||||
ExceptionReporter.call(e, "Import job failed for import_id #{import_id} - import not found")
|
ExceptionReporter.call(e, "Import job failed for import_id #{import_id} - import not found")
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
You've been invited by <strong class="text-base-content"><%= @invitation.invited_by.email %></strong> to join their family. Create your account to accept the invitation and start sharing location data.
|
You've been invited by <strong class="text-base-content"><%= @invitation.invited_by.email %></strong> to join their family. Create your account to accept the invitation and start sharing location data.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="alert alert-info inline-flex rounded-lg px-4 py-2 mt-4">
|
<div class="alert alert-info inline-flex items-center rounded-lg px-4 py-3 mt-4 gap-3">
|
||||||
<%= icon 'info', class: "h-5 w-5 mr-2" %>
|
<%= icon 'info', class: "h-5 w-5 shrink-0" %>
|
||||||
<span class="text-sm font-medium">
|
<span class="text-sm font-medium">
|
||||||
Your email (<%= @invitation.email %>) will be used for this account
|
Your email (<%= @invitation.email %>) will be used for this account
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -96,23 +96,23 @@
|
||||||
|
|
||||||
<!-- Invitation Details -->
|
<!-- Invitation Details -->
|
||||||
<div class="bg-base-300 rounded-lg p-6 mb-6">
|
<div class="bg-base-300 rounded-lg p-6 mb-6">
|
||||||
<h3 class="text-sm font-medium text-base-content opacity-70 mb-3">Invitation Details</h3>
|
<h3 class="text-lg font-semibold text-base-content mb-6">Invitation Details</h3>
|
||||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div class="space-y-2">
|
||||||
<span class="text-base-content opacity-60">Family:</span>
|
<div class="text-sm text-base-content opacity-60">Family:</div>
|
||||||
<span class="ml-2 font-semibold text-base-content"><%= @invitation.family.name %></span>
|
<div class="text-base font-semibold text-base-content"><%= @invitation.family.name %></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="space-y-2">
|
||||||
<span class="text-base-content opacity-60">Invited by:</span>
|
<div class="text-sm text-base-content opacity-60">Invited by:</div>
|
||||||
<span class="ml-2 font-semibold text-base-content"><%= @invitation.invited_by.email %></span>
|
<div class="text-base font-semibold text-base-content"><%= @invitation.invited_by.email %></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="space-y-2">
|
||||||
<span class="text-base-content opacity-60">Your email:</span>
|
<div class="text-sm text-base-content opacity-60">Your email:</div>
|
||||||
<span class="ml-2 font-semibold text-base-content"><%= @invitation.email %></span>
|
<div class="text-base font-semibold text-base-content"><%= @invitation.email %></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="space-y-2">
|
||||||
<span class="text-base-content opacity-60">Expires:</span>
|
<div class="text-sm text-base-content opacity-60">Expires:</div>
|
||||||
<span class="ml-2 font-semibold text-base-content"><%= @invitation.expires_at.strftime('%b %d, %Y') %></span>
|
<div class="text-base font-semibold text-base-content"><%= @invitation.expires_at.strftime('%b %d, %Y') %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue