diff --git a/app/assets/stylesheets/avatars.css b/app/assets/stylesheets/avatars.css index e80737055..34a75c0bc 100644 --- a/app/assets/stylesheets/avatars.css +++ b/app/assets/stylesheets/avatars.css @@ -54,3 +54,8 @@ } } } + +.avatar__form { + display: grid; + grid-template-columns: 1fr auto 1fr; +} diff --git a/app/assets/stylesheets/inputs.css b/app/assets/stylesheets/inputs.css index 602ff7976..eb4fbc821 100644 --- a/app/assets/stylesheets/inputs.css +++ b/app/assets/stylesheets/inputs.css @@ -69,6 +69,13 @@ --input-border-size: 0.15rem; } } + + &:is(.avatar) { + img, + input[type="file"] { + border-radius: 50%; + } + } } .input--select { diff --git a/app/assets/stylesheets/utilities.css b/app/assets/stylesheets/utilities.css index 6cf59bfed..761a24b5d 100644 --- a/app/assets/stylesheets/utilities.css +++ b/app/assets/stylesheets/utilities.css @@ -251,6 +251,12 @@ } } +/* QR Codes */ +.qr-code { + aspect-ratio: 1; + block-size: 50dvh; +} + /* Accessibility */ .for-screen-reader { block-size: 1px; diff --git a/app/controllers/qr_codes_controller.rb b/app/controllers/qr_codes_controller.rb new file mode 100644 index 000000000..707166930 --- /dev/null +++ b/app/controllers/qr_codes_controller.rb @@ -0,0 +1,11 @@ +class QrCodesController < ApplicationController + allow_unauthenticated_access + + def show + qr_code_link = QrCodeLink.from_signed(params[:id]) + svg = RQRCode::QRCode.new(qr_code_link.url).as_svg(viewbox: true, fill: :white, color: :black) + + expires_in 1.year, public: true + render plain: svg, content_type: "image/svg+xml" + end +end diff --git a/app/controllers/sessions/transfers_controller.rb b/app/controllers/sessions/transfers_controller.rb new file mode 100644 index 000000000..d30c263b6 --- /dev/null +++ b/app/controllers/sessions/transfers_controller.rb @@ -0,0 +1,15 @@ +class Sessions::TransfersController < ApplicationController + require_unauthenticated_access + + def show + end + + def update + if user = User.active.find_by_transfer_id(params[:id]) + start_new_session_for user + redirect_to root_path + else + head :bad_request + end + end +end diff --git a/app/controllers/users/avatars_controller.rb b/app/controllers/users/avatars_controller.rb index aec5e1eae..29b4c5996 100644 --- a/app/controllers/users/avatars_controller.rb +++ b/app/controllers/users/avatars_controller.rb @@ -13,6 +13,11 @@ class Users::AvatarsController < ApplicationController end end + def destroy + @user.avatar.destroy + redirect_to user_path(@user) + end + private def set_user @user = Current.account.users.find(params[:user_id]) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c0c9dbc9a..32e5c340c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,8 @@ class UsersController < ApplicationController - require_unauthenticated_access + require_unauthenticated_access only: %i[ new create ] - before_action :set_account_from_join_code + before_action :set_user, only: %i[ show edit update ] + before_action :set_account_from_join_code, only: %i[ new create ] def new @user = @account.users.build @@ -13,12 +14,27 @@ class UsersController < ApplicationController redirect_to root_path end + def show + end + + def edit + end + + def update + @user.update user_params + redirect_to user_path(@user) + end + private def set_account_from_join_code @account = Account.find_by_join_code!(params[:join_code]) end + def set_user + @user = Current.account.users.active.find(params[:id]) + end + def user_params - params.expect(user: [ :name, :email_address, :password ]) + params.expect(user: [ :name, :email_address, :password, :avatar ]) end end diff --git a/app/helpers/forms_helper.rb b/app/helpers/forms_helper.rb new file mode 100644 index 000000000..3220d2d78 --- /dev/null +++ b/app/helpers/forms_helper.rb @@ -0,0 +1,8 @@ +module FormsHelper + def auto_submit_form_with(**attributes, &) + data = attributes.delete(:data) || {} + data[:controller] = "auto-submit #{data[:controller]}".strip + + form_with **attributes, data: data, & + end +end diff --git a/app/helpers/qr_codes_helper.rb b/app/helpers/qr_codes_helper.rb new file mode 100644 index 000000000..64bf243a2 --- /dev/null +++ b/app/helpers/qr_codes_helper.rb @@ -0,0 +1,6 @@ +module QrCodesHelper + def qr_code_image(url) + qr_code_link = QrCodeLink.new(url) + image_tag qr_code_path(qr_code_link.signed), class: "qr-code center", alt: "QR Code" + end +end diff --git a/app/models/qr_code_link.rb b/app/models/qr_code_link.rb new file mode 100644 index 000000000..81a65fa25 --- /dev/null +++ b/app/models/qr_code_link.rb @@ -0,0 +1,26 @@ +class QrCodeLink + attr_reader :url + + def initialize(url) + @url = url + end + + def signed + self.class.verifier.generate(@url, purpose: :qr_code) + end + + def self.from_signed(signed) + new verifier.verify(signed, purpose: :qr_code) + end + + private + class << self + def verifier + ActiveSupport::MessageVerifier.new(secret, url_safe: true) + end + + def secret + Rails.application.key_generator.generate_key("qr_codes") + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index bfa562008..907881cc5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,6 @@ class User < ApplicationRecord + include Avatar, Transferable + belongs_to :account has_many :sessions, dependent: :destroy diff --git a/app/models/user/avatar.rb b/app/models/user/avatar.rb new file mode 100644 index 000000000..a9d2ad12e --- /dev/null +++ b/app/models/user/avatar.rb @@ -0,0 +1,17 @@ +module User::Avatar + extend ActiveSupport::Concern + + included do + has_one_attached :avatar + end + + class_methods do + def from_avatar_token(sid) + find_signed!(sid, purpose: :avatar) + end + end + + def avatar_token + signed_id(purpose: :avatar) + end +end diff --git a/app/models/user/transferable.rb b/app/models/user/transferable.rb new file mode 100644 index 000000000..096913aab --- /dev/null +++ b/app/models/user/transferable.rb @@ -0,0 +1,15 @@ +module User::Transferable + extend ActiveSupport::Concern + + TRANSFER_LINK_EXPIRY_DURATION = 4.hours + + class_methods do + def find_by_transfer_id(id) + find_signed(id, purpose: :transfer) + end + end + + def transfer_id + signed_id(purpose: :transfer, expires_in: TRANSFER_LINK_EXPIRY_DURATION) + end +end diff --git a/app/views/accounts/users/_invite.html.erb b/app/views/accounts/users/_invite.html.erb index 48d7b6ba9..f18982dd4 100644 --- a/app/views/accounts/users/_invite.html.erb +++ b/app/views/accounts/users/_invite.html.erb @@ -9,7 +9,7 @@
<%= @user.name %>
+