mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Fix leaving and deleting family confirmation dialogs
This commit is contained in:
parent
1f67e889e3
commit
5252388b8c
10 changed files with 228 additions and 226 deletions
File diff suppressed because one or more lines are too long
|
|
@ -19,9 +19,9 @@ class FamilyMembershipsController < ApplicationController
|
|||
def destroy
|
||||
authorize @membership
|
||||
|
||||
if @membership.owner? && @family.members.count > 1
|
||||
if @membership.owner?
|
||||
redirect_to family_path(@family),
|
||||
alert: 'Cannot remove family owner while other members exist. Transfer ownership first.'
|
||||
alert: 'Family owners cannot remove their own membership. To leave the family, delete it instead.'
|
||||
else
|
||||
member_email = @membership.user.email
|
||||
@membership.destroy!
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ class FamilyMailer < ApplicationMailer
|
|||
@family = invitation.family
|
||||
@invited_by = invitation.invited_by
|
||||
@accept_url = family_invitation_url(@invitation.token)
|
||||
|
||||
pp @accept_url
|
||||
mail(
|
||||
to: @invitation.email,
|
||||
subject: "You've been invited to join #{@family.name} on Dawarich"
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
<%= t('families.edit.title', default: 'Edit Family') %>
|
||||
</h1>
|
||||
<%= link_to family_path(@family),
|
||||
class: "text-gray-600 hover:text-gray-800 font-medium" do %>
|
||||
class: "text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 font-medium" do %>
|
||||
<%= t('families.edit.back', default: '← Back to Family') %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= form_with model: @family, local: true, class: "space-y-6" do |form| %>
|
||||
<% if @family.errors.any? %>
|
||||
<div class="bg-red-50 border border-red-200 rounded-md p-4">
|
||||
<div class="bg-red-50 dark:bg-red-900/50 border border-red-200 dark:border-red-700 rounded-md p-4">
|
||||
<div class="flex">
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-800">
|
||||
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">
|
||||
<%= t('families.edit.error_title', default: 'There were problems with your submission:') %>
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-red-700">
|
||||
<div class="mt-2 text-sm text-red-700 dark:text-red-300">
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<% @family.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
|
|
@ -32,45 +32,45 @@
|
|||
<% end %>
|
||||
|
||||
<div>
|
||||
<%= form.label :name, t('families.form.name', default: 'Family Name'), class: "block text-sm font-medium text-gray-700 mb-2" %>
|
||||
<%= form.label :name, t('families.form.name', default: 'Family Name'), class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" %>
|
||||
<%= form.text_field :name,
|
||||
class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
||||
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
||||
placeholder: t('families.form.name_placeholder', default: 'Enter your family name') %>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.edit.name_help', default: 'Choose a name that all family members will recognize.') %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-md">
|
||||
<h3 class="text-sm font-medium text-gray-900 mb-2">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-md">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
<%= t('families.edit.family_info', default: 'Family Information') %>
|
||||
</h3>
|
||||
<dl class="grid grid-cols-1 gap-x-4 gap-y-2 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.edit.creator', default: 'Created by') %>
|
||||
</dt>
|
||||
<dd class="text-sm text-gray-900"><%= @family.creator.email %></dd>
|
||||
<dd class="text-sm text-gray-900 dark:text-gray-100"><%= @family.creator.email %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.edit.created_on', default: 'Created on') %>
|
||||
</dt>
|
||||
<dd class="text-sm text-gray-900"><%= @family.created_at.strftime('%B %d, %Y') %></dd>
|
||||
<dd class="text-sm text-gray-900 dark:text-gray-100"><%= @family.created_at.strftime('%B %d, %Y') %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.edit.members_count', default: 'Members') %>
|
||||
</dt>
|
||||
<dd class="text-sm text-gray-900">
|
||||
<dd class="text-sm text-gray-900 dark:text-gray-100">
|
||||
<%= pluralize(@family.members.count, 'member') %>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.edit.last_updated', default: 'Last updated') %>
|
||||
</dt>
|
||||
<dd class="text-sm text-gray-900"><%= @family.updated_at.strftime('%B %d, %Y') %></dd>
|
||||
<dd class="text-sm text-gray-900 dark:text-gray-100"><%= @family.updated_at.strftime('%B %d, %Y') %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
|
@ -78,9 +78,9 @@
|
|||
<div class="flex items-center justify-between pt-4">
|
||||
<div class="flex space-x-3">
|
||||
<%= form.submit t('families.edit.save_changes', default: 'Save Changes'),
|
||||
class: "bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
class: "bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-6 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
<%= link_to family_path(@family),
|
||||
class: "bg-gray-300 hover:bg-gray-400 text-gray-700 px-6 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
class: "bg-gray-300 hover:bg-gray-400 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-200 px-6 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
<%= t('families.edit.cancel', default: 'Cancel') %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -88,9 +88,9 @@
|
|||
<% if policy(@family).destroy? %>
|
||||
<%= link_to family_path(@family),
|
||||
method: :delete,
|
||||
confirm: t('families.edit.delete_confirm', default: 'Are you sure you want to delete this family? This action cannot be undone and will remove all members.'),
|
||||
class: "text-red-600 hover:text-red-800 font-medium" do %>
|
||||
<%= t('families.edit.delete_family', default: 'Delete Family') %>
|
||||
data: { turbo_confirm: 'Are you sure you want to delete this family? This action cannot be undone and will remove all members.' },
|
||||
class: "text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 font-medium" do %>
|
||||
Delete Family
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,44 @@
|
|||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-4">
|
||||
<%= t('families.index.title', default: 'Family Management') %>
|
||||
</h1>
|
||||
<p class="text-gray-600">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
<%= t('families.index.description', default: 'Create or join a family to share your location data with loved ones.') %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<h2 class="text-xl font-semibold mb-4">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
|
||||
<%= t('families.index.create_family', default: 'Create Your Family') %>
|
||||
</h2>
|
||||
|
||||
<%= form_with model: Family.new, local: true, class: "space-y-4" do |form| %>
|
||||
<div>
|
||||
<%= form.label :name, t('families.form.name', default: 'Family Name'), class: "block text-sm font-medium text-gray-700 mb-1" %>
|
||||
<%= form.label :name, t('families.form.name', default: 'Family Name'), class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
||||
<%= form.text_field :name,
|
||||
placeholder: t('families.form.name_placeholder', default: 'Enter your family name'),
|
||||
class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" %>
|
||||
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" %>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<%= form.submit t('families.form.create', default: 'Create Family'),
|
||||
class: "bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
class: "bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-6 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 text-center">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
<%= t('families.index.have_invitation', default: 'Have an invitation?') %>
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||
<%= t('families.index.invitation_instructions', default: 'If someone has invited you to join their family, you should have received an email with an invitation link.') %>
|
||||
</p>
|
||||
<div class="text-sm text-gray-500">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.index.invitation_help', default: 'Check your email for an invitation link that looks like: ') %>
|
||||
<code class="bg-gray-100 px-2 py-1 rounded text-xs">
|
||||
<code class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-xs">
|
||||
<%= "#{request.base_url}/invitations/..." %>
|
||||
</code>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-4">
|
||||
<%= t('families.new.title', default: 'Create Your Family') %>
|
||||
</h1>
|
||||
<p class="text-gray-600">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
<%= t('families.new.description', default: 'Create a family to share your location data with your loved ones.') %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<%= form_with model: @family, local: true, class: "space-y-6" do |form| %>
|
||||
<% if @family.errors.any? %>
|
||||
<div class="bg-red-50 border border-red-200 rounded-md p-4">
|
||||
<div class="bg-red-50 dark:bg-red-900/50 border border-red-200 dark:border-red-700 rounded-md p-4">
|
||||
<div class="flex">
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-800">
|
||||
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">
|
||||
<%= t('families.new.error_title', default: 'There were problems with your submission:') %>
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-red-700">
|
||||
<div class="mt-2 text-sm text-red-700 dark:text-red-300">
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<% @family.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
|
|
@ -31,20 +31,20 @@
|
|||
<% end %>
|
||||
|
||||
<div>
|
||||
<%= form.label :name, t('families.form.name', default: 'Family Name'), class: "block text-sm font-medium text-gray-700 mb-2" %>
|
||||
<%= form.label :name, t('families.form.name', default: 'Family Name'), class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" %>
|
||||
<%= form.text_field :name,
|
||||
class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
||||
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
||||
placeholder: t('families.form.name_placeholder', default: 'Enter your family name') %>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.new.name_help', default: 'Choose a name that all family members will recognize, like "The Smith Family" or "Our Travel Group".') %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-50 p-4 rounded-md">
|
||||
<h3 class="text-sm font-medium text-blue-900 mb-2">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/30 p-4 rounded-md">
|
||||
<h3 class="text-sm font-medium text-blue-900 dark:text-blue-200 mb-2">
|
||||
<%= t('families.new.what_happens_title', default: 'What happens next?') %>
|
||||
</h3>
|
||||
<ul class="text-sm text-blue-800 space-y-1">
|
||||
<ul class="text-sm text-blue-800 dark:text-blue-300 space-y-1">
|
||||
<li>• <%= t('families.new.what_happens_1', default: 'You will become the family owner') %></li>
|
||||
<li>• <%= t('families.new.what_happens_2', default: 'You can invite others to join your family') %></li>
|
||||
<li>• <%= t('families.new.what_happens_3', default: 'Family members can view shared location data') %></li>
|
||||
|
|
@ -54,9 +54,9 @@
|
|||
|
||||
<div class="flex items-center justify-between">
|
||||
<%= form.submit t('families.new.create_family', default: 'Create Family'),
|
||||
class: "bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
class: "bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-6 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
<%= link_to families_path,
|
||||
class: "text-gray-600 hover:text-gray-800 font-medium" do %>
|
||||
class: "text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 font-medium" do %>
|
||||
<%= t('families.new.back', default: '← Back') %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- Family Header -->
|
||||
<div class="bg-white shadow rounded-lg p-6 mb-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6 mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900"><%= @family.name %></h1>
|
||||
<p class="text-gray-600 mt-1">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100"><%= @family.name %></h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
||||
<%= t('families.show.created_by', default: 'Created by') %>
|
||||
<%= @family.creator.email %>
|
||||
<%= t('families.show.on_date', default: 'on') %>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<div class="flex space-x-3">
|
||||
<% if policy(@family).update? %>
|
||||
<%= link_to edit_family_path(@family),
|
||||
class: "bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
class: "bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
<%= t('families.show.edit', default: 'Edit Family') %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
@ -24,18 +24,18 @@
|
|||
<% if policy(@family).leave? %>
|
||||
<%= link_to leave_family_path(@family),
|
||||
method: :delete,
|
||||
confirm: t('families.show.leave_confirm', default: 'Are you sure you want to leave this family?'),
|
||||
class: "bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
<%= t('families.show.leave', default: 'Leave Family') %>
|
||||
data: { confirm: 'Are you sure you want to leave this family?', turbo_confirm: 'Are you sure you want to leave this family?' },
|
||||
class: "bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
Leave Family
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if policy(@family).destroy? %>
|
||||
<%= link_to family_path(@family),
|
||||
method: :delete,
|
||||
confirm: t('families.show.delete_confirm', default: 'Are you sure you want to delete this family? This action cannot be undone.'),
|
||||
class: "bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
<%= t('families.show.delete', default: 'Delete Family') %>
|
||||
data: { confirm: 'Are you sure you want to delete this family? This action cannot be undone.', turbo_confirm: 'Are you sure you want to delete this family? This action cannot be undone.' },
|
||||
class: "bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
Delete Family
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -44,33 +44,33 @@
|
|||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Family Members -->
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-900">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
<%= t('families.show.members_title', default: 'Family Members') %>
|
||||
<span class="text-sm font-normal text-gray-500">(<%= @members.count %>)</span>
|
||||
<span class="text-sm font-normal text-gray-500 dark:text-gray-400">(<%= @members.count %>)</span>
|
||||
</h2>
|
||||
<%= link_to family_members_path(@family),
|
||||
class: "text-blue-600 hover:text-blue-800 text-sm font-medium" do %>
|
||||
class: "text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium" do %>
|
||||
<%= t('families.show.manage_members', default: 'Manage') %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<% @members.each do |member| %>
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<div>
|
||||
<div class="font-medium text-gray-900"><%= member.email %></div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100"><%= member.email %></div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<%= member.family_membership.role.humanize %>
|
||||
<% if member.family_membership.role == 'owner' %>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800 ml-2">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 ml-2">
|
||||
<%= t('families.show.owner_badge', default: 'Owner') %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.show.joined_on', default: 'Joined') %>
|
||||
<%= member.family_membership.created_at.strftime('%b %d, %Y') %>
|
||||
</div>
|
||||
|
|
@ -80,25 +80,25 @@
|
|||
</div>
|
||||
|
||||
<!-- Pending Invitations -->
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-900">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
<%= t('families.show.invitations_title', default: 'Pending Invitations') %>
|
||||
<span class="text-sm font-normal text-gray-500">(<%= @pending_invitations.count %>)</span>
|
||||
<span class="text-sm font-normal text-gray-500 dark:text-gray-400">(<%= @pending_invitations.count %>)</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<% if @pending_invitations.any? %>
|
||||
<div class="space-y-3 mb-4">
|
||||
<% @pending_invitations.each do |invitation| %>
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<div>
|
||||
<div class="font-medium text-gray-900"><%= invitation.email %></div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<div class="font-medium text-gray-900 dark:text-gray-100"><%= invitation.email %></div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<%= t('families.show.invited_on', default: 'Invited') %>
|
||||
<%= invitation.created_at.strftime('%b %d, %Y') %>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400">
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">
|
||||
<%= t('families.show.expires_on', default: 'Expires') %>
|
||||
<%= invitation.expires_at.strftime('%b %d, %Y at %I:%M %p') %>
|
||||
</div>
|
||||
|
|
@ -106,16 +106,16 @@
|
|||
<% if policy(@family).manage_invitations? %>
|
||||
<%= link_to family_invitation_path(@family, invitation),
|
||||
method: :delete,
|
||||
confirm: t('families.show.cancel_invitation_confirm', default: 'Are you sure you want to cancel this invitation?'),
|
||||
class: "text-red-600 hover:text-red-800 text-sm font-medium" do %>
|
||||
<%= t('families.show.cancel', default: 'Cancel') %>
|
||||
confirm: 'Are you sure you want to cancel this invitation?',
|
||||
class: "text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 text-sm font-medium" do %>
|
||||
Cancel
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-gray-500 text-center py-4">
|
||||
<p class="text-gray-500 dark:text-gray-400 text-center py-4">
|
||||
<%= t('families.show.no_pending_invitations', default: 'No pending invitations') %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
|
@ -123,21 +123,21 @@
|
|||
<!-- Invite New Member -->
|
||||
<% if policy(@family).invite? %>
|
||||
<div class="border-t pt-4">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-3">
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">
|
||||
<%= t('families.show.invite_member', default: 'Invite New Member') %>
|
||||
</h3>
|
||||
|
||||
<%= form_with model: [@family, FamilyInvitation.new], url: family_invitations_path(@family), local: true, class: "space-y-3" do |form| %>
|
||||
<div>
|
||||
<%= form.label :email, t('families.show.email_label', default: 'Email Address'), class: "block text-sm font-medium text-gray-700 mb-1" %>
|
||||
<%= form.label :email, t('families.show.email_label', default: 'Email Address'), class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
|
||||
<%= form.email_field :email,
|
||||
placeholder: t('families.show.email_placeholder', default: 'Enter email address'),
|
||||
class: "w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" %>
|
||||
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" %>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<%= form.submit t('families.show.send_invitation', default: 'Send Invitation'),
|
||||
class: "bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
class: "bg-green-600 hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -145,4 +145,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="bg-white shadow rounded-lg p-6 mb-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6 mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">
|
||||
<%= t('family_memberships.index.title', default: 'Family Members') %>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
Family Members
|
||||
</h1>
|
||||
<p class="text-gray-600 mt-1">
|
||||
<%= t('family_memberships.index.subtitle', default: 'Manage members of %{family_name}', family_name: @family.name) %>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Manage members of <%= @family.name %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<%= link_to family_path(@family),
|
||||
class: "bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
<%= t('family_memberships.index.back_to_family', default: '← Back to Family') %>
|
||||
class: "bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-200 px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
← Back to Family
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Members List -->
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
<%= t('family_memberships.index.members_count', default: 'All Members (%{count})', count: @members.count) %>
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
All Members (<%= @members.count %>)
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="divide-y divide-gray-200">
|
||||
<div class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<% @members.each do |member| %>
|
||||
<div class="px-6 py-4 hover:bg-gray-50">
|
||||
<div class="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- Member Info -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
|
||||
<span class="text-sm font-medium text-gray-700">
|
||||
<div class="h-10 w-10 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
<%= member.email.first.upcase %>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -43,35 +43,35 @@
|
|||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center space-x-2">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
<%= member.email %>
|
||||
</p>
|
||||
|
||||
<% if member.family_membership.role == 'owner' %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
<%= t('family_memberships.index.owner', default: 'Owner') %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200">
|
||||
Owner
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">
|
||||
<%= member.family_membership.role.humanize %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% if member == current_user %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<%= t('family_memberships.index.you', default: 'You') %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200">
|
||||
You
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4 mt-1">
|
||||
<p class="text-sm text-gray-500">
|
||||
<%= t('family_memberships.index.joined', default: 'Joined %{date}', date: member.family_membership.created_at.strftime('%B %d, %Y')) %>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Joined <%= member.family_membership.created_at.strftime('%B %d, %Y') %>
|
||||
</p>
|
||||
|
||||
<% if member.family_membership.role == 'owner' %>
|
||||
<p class="text-sm text-gray-500">
|
||||
<%= t('family_memberships.index.created_family', default: 'Created this family') %>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Created this family
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -81,24 +81,18 @@
|
|||
<!-- Actions -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<%= link_to family_member_path(@family, member.family_membership),
|
||||
class: "text-blue-600 hover:text-blue-800 text-sm font-medium" do %>
|
||||
<%= t('family_memberships.index.view', default: 'View') %>
|
||||
class: "text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium" do %>
|
||||
View
|
||||
<% end %>
|
||||
|
||||
<% if policy(member.family_membership).destroy? %>
|
||||
<% unless member.family_membership.owner? && @family.members.count > 1 %>
|
||||
<% if !member.family_membership.owner? %>
|
||||
<%= link_to family_member_path(@family, member.family_membership),
|
||||
method: :delete,
|
||||
confirm: t('family_memberships.index.remove_confirm',
|
||||
default: 'Are you sure you want to remove %{email} from the family?',
|
||||
email: member.email),
|
||||
class: "text-red-600 hover:text-red-800 text-sm font-medium" do %>
|
||||
<%= t('family_memberships.index.remove', default: 'Remove') %>
|
||||
confirm: "Are you sure you want to remove #{member.email} from the family?",
|
||||
class: "text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 text-sm font-medium" do %>
|
||||
Remove
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="text-gray-400 text-sm">
|
||||
<%= t('family_memberships.index.cannot_remove_owner', default: 'Cannot remove owner') %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -110,19 +104,19 @@
|
|||
|
||||
<!-- Family Statistics -->
|
||||
<div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg class="h-8 w-8 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
||||
<%= t('family_memberships.index.total_members', default: 'Total Members') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||
Total Members
|
||||
</dt>
|
||||
<dd class="text-lg font-medium text-gray-900">
|
||||
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
<%= @members.count %>
|
||||
</dd>
|
||||
</dl>
|
||||
|
|
@ -130,19 +124,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg class="h-8 w-8 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
||||
<%= t('family_memberships.index.active_members', default: 'Active Members') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||
Active Members
|
||||
</dt>
|
||||
<dd class="text-lg font-medium text-gray-900">
|
||||
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
<%= @members.count %>
|
||||
</dd>
|
||||
</dl>
|
||||
|
|
@ -150,19 +144,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-8 w-8 text-yellow-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg class="h-8 w-8 text-yellow-600 dark:text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
||||
<%= t('family_memberships.index.family_age', default: 'Family Age') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||
Family Age
|
||||
</dt>
|
||||
<dd class="text-lg font-medium text-gray-900">
|
||||
<dd class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
<%= time_ago_in_words(@family.created_at) %>
|
||||
</dd>
|
||||
</dl>
|
||||
|
|
@ -171,4 +165,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="bg-white shadow rounded-lg p-6 mb-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6 mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="h-16 w-16 rounded-full bg-gray-300 flex items-center justify-center">
|
||||
<span class="text-xl font-medium text-gray-700">
|
||||
<div class="h-16 w-16 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center">
|
||||
<span class="text-xl font-medium text-gray-700 dark:text-gray-200">
|
||||
<%= @membership.user.email.first.upcase %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||
<%= @membership.user.email %>
|
||||
</h1>
|
||||
<div class="flex items-center space-x-2 mt-1">
|
||||
<% if @membership.role == 'owner' %>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
|
||||
<%= t('family_memberships.show.owner', default: 'Family Owner') %>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200">
|
||||
Family Owner
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">
|
||||
<%= @membership.role.humanize %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% if @membership.user == current_user %>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
|
||||
<%= t('family_memberships.show.you', default: 'You') %>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200">
|
||||
You
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -36,20 +36,16 @@
|
|||
|
||||
<div class="flex space-x-3">
|
||||
<%= link_to family_members_path(@family),
|
||||
class: "bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
<%= t('family_memberships.show.back_to_members', default: '← All Members') %>
|
||||
class: "bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-200 px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
← All Members
|
||||
<% end %>
|
||||
|
||||
<% if policy(@membership).destroy? %>
|
||||
<% unless @membership.owner? && @family.members.count > 1 %>
|
||||
<%= link_to family_member_path(@family, @membership),
|
||||
method: :delete,
|
||||
confirm: t('family_memberships.show.remove_confirm',
|
||||
default: 'Are you sure you want to remove %{email} from the family?',
|
||||
email: @membership.user.email),
|
||||
class: "bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
<%= t('family_memberships.show.remove_member', default: 'Remove Member') %>
|
||||
<% end %>
|
||||
<% if policy(@membership).destroy? && !@membership.owner? %>
|
||||
<%= link_to family_member_path(@family, @membership),
|
||||
method: :delete,
|
||||
confirm: "Are you sure you want to remove #{@membership.user.email} from the family?",
|
||||
class: "bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 text-white px-4 py-2 rounded-md font-medium transition-colors duration-200" do %>
|
||||
Remove Member
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -59,47 +55,47 @@
|
|||
<!-- Member Details -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Basic Information -->
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<h2 class="text-lg font-medium text-gray-900 mb-4">
|
||||
<%= t('family_memberships.show.basic_info', default: 'Basic Information') %>
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
Basic Information
|
||||
</h2>
|
||||
|
||||
<dl class="space-y-4">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.email', default: 'Email Address') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Email Address
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @membership.user.email %></dd>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100"><%= @membership.user.email %></dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.role', default: 'Family Role') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Family Role
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
<%= @membership.role.humanize %>
|
||||
<% if @membership.role == 'owner' %>
|
||||
<span class="text-gray-500">
|
||||
- <%= t('family_memberships.show.owner_description', default: 'Can manage family settings and members') %>
|
||||
<span class="text-gray-500 dark:text-gray-400">
|
||||
- Can manage family settings and members
|
||||
</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.joined_date', default: 'Joined Date') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Joined Date
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
<%= @membership.created_at.strftime('%B %d, %Y at %I:%M %p') %>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.time_in_family', default: 'Time in Family') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Time in Family
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
<%= time_ago_in_words(@membership.created_at) %>
|
||||
</dd>
|
||||
</div>
|
||||
|
|
@ -107,40 +103,40 @@
|
|||
</div>
|
||||
|
||||
<!-- Family Information -->
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<h2 class="text-lg font-medium text-gray-900 mb-4">
|
||||
<%= t('family_memberships.show.family_info', default: 'Family Information') %>
|
||||
<div class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-700 rounded-lg p-6">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">
|
||||
Family Information
|
||||
</h2>
|
||||
|
||||
<dl class="space-y-4">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.family_name', default: 'Family Name') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Family Name
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @family.name %></dd>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100"><%= @family.name %></dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.family_creator', default: 'Family Creator') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Family Creator
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @family.creator.email %></dd>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100"><%= @family.creator.email %></dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.family_created', default: 'Family Created') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Family Created
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
<%= @family.created_at.strftime('%B %d, %Y') %>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= t('family_memberships.show.total_members', default: 'Total Members') %>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
Total Members
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||
<%= pluralize(@family.members.count, 'member') %>
|
||||
</dd>
|
||||
</div>
|
||||
|
|
@ -151,8 +147,8 @@
|
|||
<!-- Actions and Warnings -->
|
||||
<div class="mt-6 space-y-4">
|
||||
<!-- Owner-specific Warning -->
|
||||
<% if @membership.owner? && @family.members.count > 1 %>
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
||||
<% if @membership.owner? %>
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 rounded-md p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
|
|
@ -160,13 +156,12 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-800">
|
||||
<%= t('family_memberships.show.owner_warning_title', default: 'Family Owner Protection') %>
|
||||
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
||||
Family Owner Protection
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-yellow-700">
|
||||
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
|
||||
<p>
|
||||
<%= t('family_memberships.show.owner_warning_message',
|
||||
default: 'This member is the family owner and cannot be removed while other members exist. To remove the owner, first remove all other members or transfer ownership.') %>
|
||||
Family owners cannot remove their own membership. To leave the family, the owner must delete the entire family instead.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -176,7 +171,7 @@
|
|||
|
||||
<!-- Self-removal Info -->
|
||||
<% if @membership.user == current_user %>
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-md p-4">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-700 rounded-md p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
|
|
@ -184,19 +179,18 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-blue-800">
|
||||
<%= t('family_memberships.show.self_info_title', default: 'Your Membership') %>
|
||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-200">
|
||||
Your Membership
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-blue-700">
|
||||
<div class="mt-2 text-sm text-blue-700 dark:text-blue-300">
|
||||
<p>
|
||||
<%= t('family_memberships.show.self_info_message',
|
||||
default: 'This is your own membership. You can leave the family at any time from the family page, unless you are the owner with other members present.') %>
|
||||
This is your own membership. <% if @membership.owner? %>As the owner, you can delete the family to leave it.<% else %>You can leave the family at any time from the family page.<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<%= link_to family_path(@family),
|
||||
class: "text-blue-800 hover:text-blue-900 font-medium" do %>
|
||||
<%= t('family_memberships.show.go_to_family', default: 'Go to Family Page →') %>
|
||||
class: "text-blue-800 hover:text-blue-900 dark:text-blue-200 dark:hover:text-blue-100 font-medium" do %>
|
||||
Go to Family Page →
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -101,35 +101,30 @@ RSpec.describe 'Family Memberships', type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when trying to remove the owner while other members exist' do
|
||||
context 'when trying to remove the owner' do
|
||||
it 'does not remove the owner' do
|
||||
expect do
|
||||
delete "/families/#{family.id}/members/#{owner_membership.id}"
|
||||
end.not_to change(FamilyMembership, :count)
|
||||
end
|
||||
|
||||
it 'redirects with error message' do
|
||||
it 'redirects with error message explaining owners must delete family' do
|
||||
delete "/families/#{family.id}/members/#{owner_membership.id}"
|
||||
expect(response).to redirect_to(family_path(family))
|
||||
follow_redirect!
|
||||
expect(response.body).to include('Cannot remove family owner while other members exist')
|
||||
expect(response.body).to include('Family owners cannot remove their own membership. To leave the family, delete it instead.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when owner is the only member' do
|
||||
before { member_membership.destroy! }
|
||||
it 'prevents owner removal even when they are the only member' do
|
||||
member_membership.destroy!
|
||||
|
||||
it 'allows removing the owner' do
|
||||
expect do
|
||||
delete "/families/#{family.id}/members/#{owner_membership.id}"
|
||||
end.to change(FamilyMembership, :count).by(-1)
|
||||
end
|
||||
end.not_to change(FamilyMembership, :count)
|
||||
|
||||
it 'redirects with success message' do
|
||||
user_email = user.email
|
||||
delete "/families/#{family.id}/members/#{owner_membership.id}"
|
||||
expect(response).to redirect_to(family_path(family))
|
||||
expect(flash[:notice]).to include("#{user_email} has been removed from the family")
|
||||
follow_redirect!
|
||||
expect(response.body).to include('Family owners cannot remove their own membership')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -210,7 +205,7 @@ RSpec.describe 'Family Memberships', type: :request do
|
|||
expect(member_user.reload.family).to be_nil
|
||||
end
|
||||
|
||||
it 'prevents removing owner when family has members' do
|
||||
it 'prevents removing owner regardless of member count' do
|
||||
# Verify initial state
|
||||
expect(family.members.count).to eq(2)
|
||||
expect(user.family_owner?).to be true
|
||||
|
|
@ -224,7 +219,7 @@ RSpec.describe 'Family Memberships', type: :request do
|
|||
expect(user.reload.family).to eq(family)
|
||||
end
|
||||
|
||||
it 'allows removing owner when they are the only member' do
|
||||
it 'prevents removing owner even when they are the only member' do
|
||||
# Remove other member first
|
||||
member_membership.destroy!
|
||||
|
||||
|
|
@ -232,12 +227,31 @@ RSpec.describe 'Family Memberships', type: :request do
|
|||
expect(family.reload.members.count).to eq(1)
|
||||
expect(family.members).to include(user)
|
||||
|
||||
# Remove owner
|
||||
# Try to remove owner - should be prevented
|
||||
expect do
|
||||
delete "/families/#{family.id}/members/#{owner_membership.id}"
|
||||
end.to change(FamilyMembership, :count).by(-1)
|
||||
end.not_to change(FamilyMembership, :count)
|
||||
|
||||
expect(response).to redirect_to(family_path(family))
|
||||
expect(user.reload.family).to eq(family)
|
||||
expect(family.reload).to be_present
|
||||
end
|
||||
|
||||
it 'requires owners to use family deletion to leave the family' do
|
||||
# This test documents that owners must delete the family to leave
|
||||
# rather than removing their membership
|
||||
|
||||
# Remove other member first
|
||||
member_membership.destroy!
|
||||
|
||||
# Try to remove owner membership - should fail
|
||||
delete "/families/#{family.id}/members/#{owner_membership.id}"
|
||||
expect(response).to redirect_to(family_path(family))
|
||||
expect(flash[:alert]).to include('Family owners cannot remove their own membership')
|
||||
|
||||
# Owner must delete the family instead
|
||||
delete "/families/#{family.id}"
|
||||
expect(response).to redirect_to(families_path)
|
||||
expect(user.reload.family).to be_nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue