Skip to content

Commit

Permalink
feat: create member invites (still missing the user part)
Browse files Browse the repository at this point in the history
  • Loading branch information
kjellberg committed Apr 7, 2024
1 parent b559d13 commit ed3289c
Show file tree
Hide file tree
Showing 17 changed files with 288 additions and 15 deletions.
22 changes: 13 additions & 9 deletions app/assets/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@

@layer components {
.box {
@apply bg-surface text-text w-full rounded-lg p-8 shadow;
@apply bg-surface text-text w-full rounded-lg p-8 shadow relative;
}

.button {
@apply border-border bg-button inline-block cursor-pointer rounded px-5 py-2 font-bold text-white no-underline;
@apply border-border bg-button inline-flex items-center gap-x-2 cursor-pointer rounded px-5 py-2 font-bold text-white no-underline;
}

.button.xs {
Expand All @@ -54,25 +54,29 @@
@apply bg-rose-500;
}

.kiqr-table {
.button.alt {
@apply bg-light;
}

.box table {
width: calc(100% + 4rem);
@apply -mx-8;
@apply -mx-8 -mb-8;
}

.kiqr-table thead {
.box table thead {
@apply border-border;
}

.kiqr-table thead th {
.box table thead th {
@apply text-text;
}

.kiqr-table tbody tr {
.box table tbody tr {
@apply text-text px-5 border-border odd:bg-gray-100 even:bg-gray-50 dark:odd:bg-stone-900 dark:even:bg-stone-800;
}

.kiqr-table th,
.kiqr-table td {
.box table th,
.box table td {
@apply p-2 px-4 first:pl-8 last:pr-8;
}
}
32 changes: 32 additions & 0 deletions app/controllers/accounts/invitations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Accounts::InvitationsController < ApplicationController
def index
@invitations = current_account.account_invitations
@account = current_account
end

def new
@invitation = current_account.account_invitations.new
end

def create
@invitation = current_account.account_invitations.new(invitation_params)
if @invitation.save
# AccountMailer.invitation_email(@invitation).deliver_later
redirect_to invitations_path(account_id: current_account), notice: t(".invitation_sent", email: @invitation.email)
else
render :new, status: :unprocessable_entity
end
end

def destroy
@invitation = current_account.account_invitations.find_puid!(params[:id])
@invitation.destroy
redirect_to invitations_path, notice: t(".deleted")
end

private

def invitation_params
params.require(:account_invitation).permit(:email)
end
end
1 change: 1 addition & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ class Account < ApplicationRecord
validates :name, presence: true, length: {minimum: 3, maximum: 255}

has_many :account_users, dependent: :destroy
has_many :account_invitations, dependent: :destroy
has_many :users, through: :account_users
end
13 changes: 13 additions & 0 deletions app/models/account_invitation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class AccountInvitation < ApplicationRecord
# This will generate a public_uid for the model when it is created.
# Use this public_uidas a unique public identifier for the model.
# To find a model by its public_uid, use the following method:
# Account.find_puid('xxxxxxxx')
# Learn more: https://github.com/equivalent/public_uid
include PublicUid::ModelConcern

belongs_to :account, inverse_of: :account_invitations, counter_cache: true

validates :email, presence: true, format: {with: URI::MailTo::EMAIL_REGEXP}
validates :email, uniqueness: {scope: :account_id, message: I18n.t("accounts.invitations.new.form.errors.email.taken")}
end
7 changes: 7 additions & 0 deletions app/views/accounts/invitations/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= simple_form_for(account, url: form_submit_path, method: form_method) do |f| %>
<%= f.input :name, placeholder: (account.personal? ? t(".name.personal") : t(".name.team")), required: true, autofocus: true %>
<%#= f.input :custom_field %>
<div class="mt-4 flex justify-between items-center">
<%= f.button :submit, account.persisted? ? t(".button.save") : t(".button.create") %>
</div>
<% end %>
44 changes: 44 additions & 0 deletions app/views/accounts/invitations/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<% title t(".title") %>
<%= render(PageLayouts::Settings::Component.new(
title: t(".title"),
description: t(".description")
)) do %>
<div class="box">
<header class="border-b pb-4 mb-4">
<h3 class="font-bold text-primary uppercase"><%= t(".box_title", team_name: @account.name) %></h3>
</header>

<% if @invitations.any? %>
<div class="prose dark:prose-invert">
<table>
<thead>
<tr>
<th><%= t(".table.email") %></th>
<th><%= t(".table.sent_at") %></th>
<th class="text-right"><%= t(".table.actions") %></th>
</tr>
</thead>
<tbody>
<% @invitations.each do |invitation| %>
<tr>
<td><%= invitation.email %></td>
<td><%= invitation.created_at %></td>
<td class="text-right">
<%= button_to t(".table.remove"), invitation_path(id: invitation), class: "button xs danger", method: :delete, data: { confirm: t(".confirm_delete", email: invitation.email), turbo_confirm: t(".confirm_delete", email: invitation.email) } %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<p><%= t(".no_invitations") %></p>
<% end %>
</div>
<div class="mt-6 flex justify-start gap-x-6 items-center">
<%= link_to members_path, class: "button alt" do %>
<i class="fa fa-arrow-left"></i> <%= t(".buttons.back") %>
<% end %>
<%= link_to t(".buttons.invite"), new_invitation_path, class: "button" %>
</div>
<% end %>
27 changes: 27 additions & 0 deletions app/views/accounts/invitations/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<% title t(".title") %>
<%= render(PageLayouts::Settings::Component.new(
title: t(".title"),
description: t(".description")
)) do %>
<div class="box">
<header class="border-b pb-4 mb-4">
<h3 class="font-bold text-primary uppercase"><%= t(".box_title") %></h3>
</header>

<div class="prose dark:prose-invert">
<p><%= t(".instructions") %></p>

<%= simple_form_for(@invitation, url: invitations_path) do |f| %>
<%= f.input :email, placeholder: t(".form.email.placeholder"), required: true, autofocus: true %>
<div class="mt-4 flex justify-between items-center">
<%= f.button :submit, t(".form.submit") %>
</div>
<% end %>
</div>
</div>
<div class="mt-6 flex justify-start gap-x-6 items-center">
<%= link_to members_path, class: "button alt" do %>
<i class="fa fa-arrow-left"></i> <%= t(".back_to_members") %>
<% end %>
</div>
<% end %>
5 changes: 5 additions & 0 deletions app/views/accounts/members/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@
<%= button_to t(".delete.button"), member_path(@account_user), class: "button danger", method: :delete, data: { confirm: t(".confirmation_message", name: @account_user.name), turbo_confirm: t(".confirmation_message", name: @account_user.name) } %>
</div>
</div>
<div class="mt-6 flex justify-start gap-x-6 items-center">
<%= link_to members_path, class: "button alt" do %>
<i class="fa fa-arrow-left"></i> <%= t(".back_to_members") %>
<% end %>
</div>
<% end %>
10 changes: 6 additions & 4 deletions app/views/accounts/members/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
title: t(".title"),
description: t(".description")
)) do %>

<div class="box">
<header class="border-b pb-4 mb-4">
<h3 class="font-bold text-primary uppercase"><%= t(".box_title", team_name: @account.name) %></h3>
</header>

<div class="prose">
<table class="kiqr-table">
<div class="prose dark:prose-invert">
<table>
<thead>
<tr>
<th><%= t(".table.name") %></th>
Expand All @@ -31,7 +32,8 @@
</table>
</div>
</div>
<div class="mt-6 flex justify-between items-center">
<%= link_to "Invite a new member", "#", class: "button" %>
<div class="mt-6 flex justify-start gap-x-6 items-center">
<%= link_to t(".buttons.invite"), new_invitation_path, class: "button" %>
<%= link_to t(".buttons.view_pending"), invitations_path, class: "text-primary" %>
</div>
<% end %>
2 changes: 1 addition & 1 deletion app/views/partials/navigations/_settings.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
description: t('.items.members.description'),
icon: "fa fa-users",
path: members_path,
active: current_base_path?(members_path)
active: current_base_path?(members_path) || current_base_path?(invitations_path)
)) %>
<% end %>

Expand Down
37 changes: 37 additions & 0 deletions config/locales/kiqr.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,54 @@ en:
email: "Email"
actions: "Actions"
edit: "Edit"
buttons:
invite: "Invite a user"
view_pending: "View pending invitations"
edit:
title: "Edit team member"
description: "Change settings for a team member."
box_title: "Edit team member: %{name}"
confirmation_message: "Are you sure you want to REMOVE %{name} from the team?"
back_to_members: "Back to team members"
delete:
box_title: "Remove team member"
description: "Remove %{name} from the team. They will no longer have access to the team account, but you can always invite them back later."
button: "Remove team member"
form:
submit: "Save changes"
invitations:
new:
title: "Invite a user"
description: "Invite a user to your team"
box_title: "Invite a user to your team"
instructions: "Enter the email address of the user you want to invite to your team."
back_to_members: "Back to team members"
form:
email:
placeholder: "Enter the email address"
submit: "Send invitation"
errors:
email:
taken: "has already been invited."
index:
title: "Pending invitations"
description: "Manage invitations to your team"
box_title: "Pending invitations to %{team_name}"
no_invitations: "No invitations have been sent yet."
confirm_delete: "Are you sure you want to cancel the invitation to %{email}?"
table:
email: "Email"
status: "Status"
actions: "Actions"
resend: "Resend"
cancel: "Cancel"
buttons:
invite: "Invite a new user"
back: "Back to team members"
create:
invitation_sent: "Invitation has been sent to %{email}."
destroy:
deleted: "Invitation has been successfully deleted."
account_users:
destroy:
success: "Member has successfully been removed from the team."
Expand Down
1 change: 1 addition & 0 deletions config/routes/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
scope "(/team/:account_id)", account_id: %r{[^/]+} do
resource :account, only: [:edit, :update], path: "profile"
resources :members, controller: "accounts/members", only: [:index, :edit, :update, :destroy]
resources :invitations, controller: "accounts/invitations", only: [:index, :new, :create, :destroy]
end
14 changes: 14 additions & 0 deletions db/migrate/20240407174725_create_account_invitations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateAccountInvitations < ActiveRecord::Migration[7.1]
def change
create_table :account_invitations do |t|
t.string :public_uid, index: {unique: true}
t.references :account, null: false, foreign_key: true
t.string :email, null: false
t.timestamps
end

add_column :accounts, :account_invitations_count, :integer, default: 0
add_index :account_invitations, :email
add_index :account_invitations, [:account_id, :email], unique: true
end
end
16 changes: 15 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ed3289c

Please sign in to comment.