diff --git a/app/controllers/admin/invitation_url_controller.rb b/app/controllers/admin/invitation_url_controller.rb new file mode 100644 index 00000000000..c9def209d74 --- /dev/null +++ b/app/controllers/admin/invitation_url_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Admin::InvitationUrlController < AdminController + def index + @invitation_url_template = new_user_url( + company_id: 'dummy_company_id', + role: 'dummy_role', + course_id: 'dummy_course_id', + token: ENV['TOKEN'] || 'token' + ) + end +end diff --git a/app/javascript/invitation-url-updater.js b/app/javascript/invitation-url-updater.js new file mode 100644 index 00000000000..cd400adb6e0 --- /dev/null +++ b/app/javascript/invitation-url-updater.js @@ -0,0 +1,40 @@ +document.addEventListener('DOMContentLoaded', async () => { + const invitationElements = Array.from( + document.querySelectorAll('.invitation__element select') + ) + const invitationUrl = document.querySelector('.js-invitation-url') + const invitationUrlText = document.querySelector('.js-invitation-url-text') + + if (invitationElements.length === 0 || !invitationUrlText || !invitationUrl) { + return + } + + const updateInvitationURL = async () => { + const invitationCompany = document.querySelector('.js-invitation-company') + const invitationRole = document.querySelector('.js-invitation-role') + const invitationCourse = document.querySelector('.js-invitation-course') + + const selectedCompanyId = + invitationCompany.options[invitationCompany.selectedIndex].value + const selectedRole = + invitationRole.options[invitationRole.selectedIndex].value + const selectedCourseId = + invitationCourse.options[invitationCourse.selectedIndex].value + + const invitationUrlTemplate = + document.querySelector('.invitation__url').dataset.invitationUrlTemplate + const targetUrl = invitationUrlTemplate + .replace(/company_id=[^&]*/, `company_id=${selectedCompanyId}`) + .replace(/role=[^&]*/, `role=${selectedRole}`) + .replace(/course_id=[^&]*/, `course_id=${selectedCourseId}`) + + invitationUrl.href = targetUrl + invitationUrlText.value = targetUrl + } + + invitationElements.forEach((invitationElement) => { + invitationElement.addEventListener('change', updateInvitationURL) + }) + + window.addEventListener('pageshow', updateInvitationURL) +}) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 9f15579527f..461978aaad2 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -56,6 +56,7 @@ import '../upload-image-to-article.js' import '../header-dropdown.js' import '../editor-selection-form.js' import '../user_mentor_memo.js' +import '../invitation-url-updater.js' import VueMounter from '../VueMounter.js' import Books from '../components/books.vue' diff --git a/app/models/user.rb b/app/models/user.rb index 89b5b82eb59..bf5c9629b88 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,6 +24,12 @@ class User < ApplicationRecord DEFAULT_REGULAR_EVENT_ORGANIZER = 'komagata' HIBERNATION_LIMIT = 6.months + INVITATION_ROLES = [ + [I18n.t('invitation_role.adviser'), :adviser], + [I18n.t('invitation_role.trainee'), :trainee], + [I18n.t('invitation_role.mentor'), :mentor] + ].freeze + enum job: { student: 0, office_worker: 2, diff --git a/app/views/admin/_admin_page_tabs.html.slim b/app/views/admin/_admin_page_tabs.html.slim index ac36b852a18..89d84d3b08a 100644 --- a/app/views/admin/_admin_page_tabs.html.slim +++ b/app/views/admin/_admin_page_tabs.html.slim @@ -21,3 +21,7 @@ = link_to 'お問い合わせ', admin_inquiries_path, class: "page-tabs__item-link #{current_link(/^admin-inquiries/)}" + li.page-tabs__item + = link_to '招待URL', + admin_invitation_url_index_path, + class: "page-tabs__item-link #{current_link(/^admin-invitation_url/)}" diff --git a/app/views/admin/invitation_url/index.html.slim b/app/views/admin/invitation_url/index.html.slim new file mode 100644 index 00000000000..2655a742d1d --- /dev/null +++ b/app/views/admin/invitation_url/index.html.slim @@ -0,0 +1,39 @@ +- title '管理ページ' + +header.page-header + .container + .page-header__inner + h1.page-header__title = title + += render 'admin/admin_page_tabs' + +.page-main + header.page-main-header + .container + .page-main-header__inner + h1.page-main-header__title + | 招待URL + hr.a-border + .page-body + .container.is-lg + .invitation.flex.flex-col.gap-4 + .invitation__elements.flex.gap-4.flex-col(class='md:flex-row') + .invitation__element.flex-1 + = label_tag :company, '企業', class: 'a-form-label' + .a-button.is-md.is-secondary.is-select.is-block + = select_tag :company, options_from_collection_for_select(Company.order(created_at: :desc), :id, :name), class: 'js-invitation-company' + .invitation__element.flex-1 + = label_tag :role, 'ロール', class: 'a-form-label' + .a-button.is-md.is-secondary.is-select.is-block + = select_tag :role, options_for_select(User::INVITATION_ROLES), class: 'js-invitation-role' + .invitation__element.flex-1 + = label_tag :course, 'コース', class: 'a-form-label' + .a-button.is-md.is-secondary.is-select.is-block + = select_tag :course, options_from_collection_for_select(Course.order(:created_at), :id, :title), class: 'js-invitation-course' + .invitation__url data-invitation-url-template=@invitation_url_template + .a-form-label + | URL + .invitation__url-inner.flex.gap-1 + input.js-invitation-url-text.a-text-input(type="text") + a.js-invitation-url.a-button.is-md.is-secondary.is-icon(class='h-[2.375rem]') + i.fa-solid.fa-arrow-up-right-from-square diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 8a6f8daa2ee..085256709eb 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -371,6 +371,10 @@ ja: unchecked_no_replied: 未返信 unchecked_all: 全て campaign: お試し延長 + invitation_role: + adviser: アドバイザー + trainee: 研修生 + mentor: メンター watch: all: すべて true: コメントあり diff --git a/config/routes/admin.rb b/config/routes/admin.rb index a8aaa99d881..84738149d17 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -10,5 +10,6 @@ end resources :campaigns, only: %i(new create index edit update) resources :inquiries, only: %i(index show) + resources :invitation_url, only: %i(index) end end diff --git a/test/system/admin/home_test.rb b/test/system/admin/home_test.rb index 9d444345b2e..c1700ef4aeb 100644 --- a/test/system/admin/home_test.rb +++ b/test/system/admin/home_test.rb @@ -7,12 +7,13 @@ class Admin::HomeTest < ApplicationSystemTestCase visit_with_auth '/admin', 'komagata' assert_equal '管理ページ | FBC', title - assert_selector 'a.page-tabs__item-link', count: 5 + assert_selector 'a.page-tabs__item-link', count: 6 assert_selector '.page-tabs__item-link', text: '管理ページ' assert_selector '.page-tabs__item-link', text: 'ユーザー' assert_selector '.page-tabs__item-link', text: '企業' assert_selector '.page-tabs__item-link', text: 'お試し延長' assert_selector '.page-tabs__item-link', text: 'お問い合わせ' + assert_selector '.page-tabs__item-link', text: '招待URL' assert_no_selector '.page-tabs__item-link', text: 'プラクティス' assert_no_selector '.page-tabs__item-link', text: 'カテゴリー' assert_no_selector '.page-tabs__item-link', text: 'コース' diff --git a/test/system/admin/invitation_url_test.rb b/test/system/admin/invitation_url_test.rb new file mode 100644 index 00000000000..2a65457ed21 --- /dev/null +++ b/test/system/admin/invitation_url_test.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'application_system_test_case' + +class Admin::InvitationUrlTest < ApplicationSystemTestCase + setup do + @new_user_url = "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}/users/new" + end + + test 'non-admin cannot be visited invitation-url page' do + visit_with_auth '/admin/invitation_url', 'kimura' + assert_text '管理者としてログインしてください' + end + + test 'admin can be visited invitation-url page' do + visit_with_auth '/admin/invitation_url', 'komagata' + assert_equal '管理ページ | FBC', title + end + + def wait_for_invitation_url_with_timeout(expected) + Timeout.timeout(Capybara.default_max_wait_time, StandardError) do + loop until find('.js-invitation-url-text').value == expected && find('.js-invitation-url')[:href] == expected + end + end + + test 'show invitation-url page' do + visit_with_auth '/admin/invitation_url', 'komagata' + company = Company.order(created_at: :desc).first + role = User::INVITATION_ROLES.first[1] + course = Course.order(:created_at).first + expected = "#{@new_user_url}?company_id=#{company.id}&course_id=#{course.id}&role=#{role}&token=token" + + wait_for_invitation_url_with_timeout(expected) + end + + test 'change selected company' do + visit_with_auth '/admin/invitation_url', 'komagata' + company = companies(:company3) + role = User::INVITATION_ROLES.first[1] + course = Course.order(:created_at).first + expected = "#{@new_user_url}?company_id=#{company.id}&course_id=#{course.id}&role=#{role}&token=token" + + find('.js-invitation-company').click + select(company.name) + wait_for_invitation_url_with_timeout(expected) + end + + test 'change selected role' do + visit_with_auth '/admin/invitation_url', 'komagata' + company = Company.order(created_at: :desc).first + role_text = User::INVITATION_ROLES.second[0] + role = User::INVITATION_ROLES.second[1] + course = Course.order(:created_at).first + expected = "#{@new_user_url}?company_id=#{company.id}&course_id=#{course.id}&role=#{role}&token=token" + + find('.js-invitation-role').click + select(role_text) + wait_for_invitation_url_with_timeout(expected) + end + + test 'change selected course' do + visit_with_auth '/admin/invitation_url', 'komagata' + company = Company.order(created_at: :desc).first + role = User::INVITATION_ROLES.first[1] + course = courses(:course2) + expected = "#{@new_user_url}?company_id=#{company.id}&course_id=#{course.id}&role=#{role}&token=token" + + find('.js-invitation-course') + select(course.title) + wait_for_invitation_url_with_timeout(expected) + end +end