Add consumable invites (#5814)
* Add consumable invites * Add UI for generating invite codes * Add tests * Display max uses and expiration in invites table, delete invite * Remove unused column and redundant validator - Default follows not used, probably bad idea - InviteCodeValidator is redundant because RegistrationsController checks invite code validity * Add admin setting to disable invites * Add admin UI for invites, configurable role for invite creation - Admin UI that lists everyone's invites, always available - Admin setting min_invite_role to control who can invite people - Non-admin invite UI only visible if users are allowed to * Do not remove invites from database, expire them instantly
This commit is contained in:
		| @ -1,7 +1,7 @@ | ||||
| %li.log-entry | ||||
|   .log-entry__header | ||||
|     .log-entry__avatar | ||||
|       = image_tag action_log.account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar' | ||||
|       = image_tag action_log.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' | ||||
|     .log-entry__content | ||||
|       .log-entry__title | ||||
|         = t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target')).html_safe | ||||
|  | ||||
							
								
								
									
										15
									
								
								app/views/admin/invites/_invite.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/views/admin/invites/_invite.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| %tr | ||||
|   %td | ||||
|     .name-tag | ||||
|       = image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar' | ||||
|       %span.username= invite.user.account.username | ||||
|   %td | ||||
|     = invite.uses | ||||
|     = " / #{invite.max_uses}" unless invite.max_uses.nil? | ||||
|   %td | ||||
|     - if invite.expires_at.nil? | ||||
|       ∞ | ||||
|     - else | ||||
|       = l invite.expires_at | ||||
|   %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) | ||||
|   %td= table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete if policy(invite).destroy? | ||||
							
								
								
									
										22
									
								
								app/views/admin/invites/index.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/views/admin/invites/index.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| - content_for :page_title do | ||||
|   = t('admin.invites.title') | ||||
|  | ||||
| - if policy(:invite).create? | ||||
|   %p= t('invites.prompt') | ||||
|  | ||||
|   = render 'invites/form' | ||||
|  | ||||
|   %hr/ | ||||
|  | ||||
| %table.table | ||||
|   %thead | ||||
|     %tr | ||||
|       %th | ||||
|       %th= t('invites.table.uses') | ||||
|       %th= t('invites.table.expires_at') | ||||
|       %th | ||||
|       %th | ||||
|   %tbody | ||||
|     = render @invites | ||||
|  | ||||
| = paginate @invites | ||||
| @ -32,6 +32,11 @@ | ||||
|  | ||||
|   %hr/ | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, as: :radio_buttons, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' | ||||
|  | ||||
|   %hr/ | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } | ||||
|     = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 } | ||||
|  | ||||
| @ -16,6 +16,7 @@ | ||||
|   = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' } | ||||
|   = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' } | ||||
|   = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' } | ||||
|   = f.input :invite_code, as: :hidden | ||||
|  | ||||
|   .actions | ||||
|     = f.button :button, t('auth.register'), type: :submit | ||||
|  | ||||
							
								
								
									
										9
									
								
								app/views/invites/_form.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/views/invites/_form.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| = simple_form_for(@invite) do |f| | ||||
|   = render 'shared/error_messages', object: @invite | ||||
|  | ||||
|   .fields-group | ||||
|     = f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: lambda { |num| I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt') | ||||
|     = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') | ||||
|  | ||||
|   .actions | ||||
|     = f.button :button, t('invites.generate'), type: :submit | ||||
							
								
								
									
										11
									
								
								app/views/invites/_invite.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/views/invites/_invite.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| %tr | ||||
|   %td | ||||
|     = invite.uses | ||||
|     = " / #{invite.max_uses}" unless invite.max_uses.nil? | ||||
|   %td | ||||
|     - if invite.expires_at.nil? | ||||
|       ∞ | ||||
|     - else | ||||
|       = l invite.expires_at | ||||
|   %td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) | ||||
|   %td= table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete if policy(invite).destroy? | ||||
							
								
								
									
										19
									
								
								app/views/invites/index.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/views/invites/index.html.haml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| - content_for :page_title do | ||||
|   = t('invites.title') | ||||
|  | ||||
| - if policy(:invite).create? | ||||
|   %p= t('invites.prompt') | ||||
|  | ||||
|   = render 'form' | ||||
|  | ||||
|   %hr/ | ||||
|  | ||||
| %table.table | ||||
|   %thead | ||||
|     %tr | ||||
|       %th= t('invites.table.uses') | ||||
|       %th= t('invites.table.expires_at') | ||||
|       %th | ||||
|       %th | ||||
|   %tbody | ||||
|     = render @invites | ||||
		Reference in New Issue
	
	Block a user