diff --git a/app/controllers/admin/faq_categories/faqs_controller.rb b/app/controllers/admin/faq_categories/faqs_controller.rb new file mode 100644 index 00000000000..fa6af629f96 --- /dev/null +++ b/app/controllers/admin/faq_categories/faqs_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Admin::FAQCategories::FaqsController < AdminController + before_action :set_faq_category, only: %i[index update] + + def index + @faqs = @faq_category.faqs + end + + def update + faq = @faq_category.faqs.find(params[:id]) + faq.insert_at(params[:faq][:insert_at].to_i) + head :ok + end + + private + + def set_faq_category + @faq_category = FAQCategory.find(params[:faq_category_id]) + end +end diff --git a/app/controllers/admin/faq_categories_controller.rb b/app/controllers/admin/faq_categories_controller.rb new file mode 100644 index 00000000000..21bdb37eaaf --- /dev/null +++ b/app/controllers/admin/faq_categories_controller.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +class Admin::FAQCategoriesController < AdminController + before_action :set_faq_category, only: %i[edit update destroy] + + def index + @faq_categories = FAQCategory.order(:created_at) + end + + def new + @faq_category = FAQCategory.new + end + + def create + @faq_category = FAQCategory.new(faq_category_params) + + if @faq_category.save + redirect_to admin_faq_categories_path, notice: 'FAQカテゴリーを作成しました。' + else + render 'new' + end + end + + def edit; end + + def update + if params[:faq_category][:insert_at] + @faq_category.insert_at(params[:faq_category][:insert_at].to_i) + return head :ok + end + + if @faq_category.update(faq_category_params) + redirect_to admin_faq_categories_path, notice: 'FAQカテゴリーを更新しました。' + else + render 'edit' + end + end + + def destroy + @faq_category.destroy + redirect_to admin_faq_categories_path, notice: 'FAQカテゴリーを削除しました。' + end + + private + + def faq_category_params + params.require(:faq_category).permit(:name, :insert_at) + end + + def set_faq_category + @faq_category = FAQCategory.find(params[:id]) + end +end diff --git a/app/controllers/admin/faqs_controller.rb b/app/controllers/admin/faqs_controller.rb new file mode 100644 index 00000000000..88895971fcf --- /dev/null +++ b/app/controllers/admin/faqs_controller.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class Admin::FaqsController < AdminController + before_action :set_faq, only: %i[show edit update destroy] + before_action :set_faq_category, only: %i[index edit new update create] + + def index; end + + def new + @faq = FAQ.new + end + + def create + @faq = FAQ.new(faq_params) + + if @faq.save + redirect_to admin_faqs_path, notice: 'FAQを作成しました。' + else + render 'new' + end + end + + def show; end + def edit; end + + def update + if @faq.update(faq_params) + redirect_to admin_faqs_path, notice: 'FAQを更新しました。' + else + render 'edit' + end + end + + def destroy + @faq.destroy + redirect_to admin_faqs_path, notice: 'FAQを削除しました。' + end + + private + + def faq_params + params.require(:faq).permit(:answer, :question, :faq_category_id) + end + + def set_faq + @faq = FAQ.find(params[:id]) + end + + def set_faq_category + @faq_categories = FAQCategory.all + end +end diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb index a74746a3361..524c96cd2fb 100644 --- a/app/controllers/welcome_controller.rb +++ b/app/controllers/welcome_controller.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true +require_dependency 'faq_category' class WelcomeController < ApplicationController skip_before_action :require_active_user_login, raise: false layout 'lp' DEFAULT_COURSE = 'Railsエンジニア' + FAQ_CATEGORY_NAME = '法人利用について' def index @mentors = current_user ? User.mentors_sorted_by_created_at : User.visible_sorted_mentors @@ -15,9 +17,22 @@ def job_support; end def pricing; end - def faq; end + def faq + @faq_categories = FAQCategory.order(:position).select do |faq_category| + faq_category.faqs.present? + end + + if params[:category].present? + faq_category = FAQCategory.find_by(name: params[:category]) + @faqs = faq_category.faqs + else + @faqs = FAQ.order(:position) + end + end - def training; end + def training + @faqs = FAQCategory.find_by(name: FAQ_CATEGORY_NAME).faqs + end def practices; end diff --git a/app/helpers/faq_helper.rb b/app/helpers/faq_helper.rb new file mode 100644 index 00000000000..076a11362b8 --- /dev/null +++ b/app/helpers/faq_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module FAQHelper + def format_question(question) + "#{question.delete('??')}?" + end +end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 852818e886c..4fbed6ad80c 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -57,6 +57,8 @@ import '../invitation-url-updater.js' import '../payment-methods-check-boxes.js' import '../product-checker.js' import '../user-follow.js' +import '../sort-faq.js' +import '../sort-faq-category.js' import VueMounter from '../VueMounter.js' import Questions from '../components/questions.vue' diff --git a/app/javascript/sort-faq-category.js b/app/javascript/sort-faq-category.js new file mode 100644 index 00000000000..be38b30a51b --- /dev/null +++ b/app/javascript/sort-faq-category.js @@ -0,0 +1,24 @@ +import Sortable from 'sortablejs' +import Bootcamp from 'bootcamp' + +document.addEventListener('DOMContentLoaded', () => { + const element = document.querySelector('#js-faq-category-sortable') + if (!element) return + + Sortable.create(element, { + handle: '.js-grab', + onEnd(event) { + const id = event.item.id + const url = `/admin/faq_categories/${id}` + const params = { + faq_category: { + insert_at: event.newIndex + 1 + } + } + + Bootcamp.patch(url, params).catch((error) => { + console.error('Error while updating an order:', error) + }) + } + }) +}) diff --git a/app/javascript/sort-faq.js b/app/javascript/sort-faq.js new file mode 100644 index 00000000000..c18f72f3a6e --- /dev/null +++ b/app/javascript/sort-faq.js @@ -0,0 +1,25 @@ +import Sortable from 'sortablejs' +import Bootcamp from 'bootcamp' + +document.addEventListener('DOMContentLoaded', () => { + const element = document.querySelector('#js-faq-sortable') + if (!element) return + + Sortable.create(element, { + handle: '.js-grab', + onEnd(event) { + const id = event.item.id + const categoryId = event.item.dataset.faqCategoryId + const url = `/admin/faq_categories/${categoryId}/faqs/${id}` + const params = { + faq: { + insert_at: event.newIndex + 1 + } + } + + Bootcamp.patch(url, params).catch((error) => { + console.error('Error while updating an order:', error) + }) + } + }) +}) diff --git a/app/javascript/stylesheets/_common-imports.sass b/app/javascript/stylesheets/_common-imports.sass index d3dc822c4f5..e416f535afc 100644 --- a/app/javascript/stylesheets/_common-imports.sass +++ b/app/javascript/stylesheets/_common-imports.sass @@ -173,4 +173,30 @@ @import "shared/blocks/card-list/card-list" @import "shared/blocks/card-list/products" +@import shared/blocks/form/block-checks +@import shared/blocks/form/books-form-item +@import shared/blocks/form/books-form +@import shared/blocks/form/checkboxes +@import shared/blocks/form/form-actions +@import shared/blocks/form/form-added-choice +@import shared/blocks/form/form-item-actions +@import shared/blocks/form/form-item-block +@import shared/blocks/form/form-item-group +@import shared/blocks/form/form-item +@import shared/blocks/form/form-link-block +@import shared/blocks/form/form-notice +@import shared/blocks/form/form-selects +@import shared/blocks/form/form-tabs-item +@import shared/blocks/form/form-tabs +@import shared/blocks/form/form-times +@import shared/blocks/form/form +@import shared/blocks/form/hidden-form-item +@import shared/blocks/form/important-message +@import shared/blocks/form/linear-scale +@import shared/blocks/form/many-check-boxes +@import shared/blocks/form/markdown-form +@import shared/blocks/form/radios +@import shared/blocks/form/vue-tags-input +@import shared/blocks/form/form-textarea + @import "shared/helpers/state" diff --git a/app/javascript/stylesheets/application.sass b/app/javascript/stylesheets/application.sass index a50a26c4b39..e0590a1cef2 100644 --- a/app/javascript/stylesheets/application.sass +++ b/app/javascript/stylesheets/application.sass @@ -29,32 +29,6 @@ @import application/blocks/footer/footer-nav @import application/blocks/footer/footer -@import application/blocks/form/block-checks -@import application/blocks/form/books-form-item -@import application/blocks/form/books-form -@import application/blocks/form/checkboxes -@import application/blocks/form/form-actions -@import application/blocks/form/form-added-choice -@import application/blocks/form/form-item-actions -@import application/blocks/form/form-item-block -@import application/blocks/form/form-item-group -@import application/blocks/form/form-item -@import application/blocks/form/form-link-block -@import application/blocks/form/form-notice -@import application/blocks/form/form-selects -@import application/blocks/form/form-tabs-item -@import application/blocks/form/form-tabs -@import application/blocks/form/form-times -@import application/blocks/form/form -@import application/blocks/form/hidden-form-item -@import application/blocks/form/important-message -@import application/blocks/form/linear-scale -@import application/blocks/form/many-check-boxes -@import application/blocks/form/markdown-form -@import application/blocks/form/radios -@import application/blocks/form/vue-tags-input -@import application/blocks/form/form-textarea - @import application/blocks/header/header-current-user @import application/blocks/header/header-dropdown @import application/blocks/header/header-links diff --git a/app/javascript/stylesheets/atoms/_a-text-input.sass b/app/javascript/stylesheets/atoms/_a-text-input.sass index 4865e2f9648..dfd97e4f45a 100644 --- a/app/javascript/stylesheets/atoms/_a-text-input.sass +++ b/app/javascript/stylesheets/atoms/_a-text-input.sass @@ -53,6 +53,8 @@ textarea.a-text-input line-height: 1.5 min-height: 10rem overflow: hidden + &.is-xs + min-height: 6rem &.is-sm min-height: 10rem &.is-md diff --git a/app/javascript/stylesheets/config/mixins/_grid.sass b/app/javascript/stylesheets/config/mixins/_grid.sass index 5c435867655..8104ffc60cc 100644 --- a/app/javascript/stylesheets/config/mixins/_grid.sass +++ b/app/javascript/stylesheets/config/mixins/_grid.sass @@ -11,7 +11,6 @@ .row @for $i from 0 through 16 $gutter-size: $i * 4 - +media-breakpoint-up(xl) - &.is-gutter-width-#{$gutter-size} - gap: #{math.div($gutter-size, 16)}rem - +make-grid-columns($grid-columns, #{math.div($gutter-size, 16)}rem) + &.is-gutter-width-#{$gutter-size} + gap: #{math.div($gutter-size, 16)}rem + +make-grid-columns($grid-columns, #{math.div($gutter-size, 16)}rem) diff --git a/app/javascript/stylesheets/config/variables/_colors.sass b/app/javascript/stylesheets/config/variables/_colors.sass index 97fe85f7a2b..563e567bf89 100644 --- a/app/javascript/stylesheets/config/variables/_colors.sass +++ b/app/javascript/stylesheets/config/variables/_colors.sass @@ -3,7 +3,7 @@ $accent: hsl(44, 96%, 54%) $base: white // completion -$completion: hsl(197deg 83% 49%) +$completion: hsl(197, 83%, 49%) $completion-dark: rgb(0 0 0 / 20%) // background diff --git a/app/javascript/stylesheets/lp.sass b/app/javascript/stylesheets/lp.sass index face95ec51d..feb2a4d5325 100644 --- a/app/javascript/stylesheets/lp.sass +++ b/app/javascript/stylesheets/lp.sass @@ -16,6 +16,8 @@ @import lp/blocks/lp/lp-header @import lp/blocks/lp/lp-header-nav +@import lp/blocks/lp/welcome-top-info + // top @import lp/blocks/lp/lp-top-cover @@ -60,6 +62,7 @@ // lp-faqs @import lp/blocks/lp/lp-faqs @import lp/blocks/lp/lp-faq +@import lp/blocks/lp/side-filter // lp-course-selection @import lp/blocks/lp/lp-course-selection @@ -79,11 +82,6 @@ @import lp/blocks/articles/article-author ///////////////////// -@import lp/welcome/welcome-top-info -@import lp/welcome/welcome-trial-period-table - -@import lp/corporate-training/corporate-training-images -@import lp/corporate-training/corporate-training-overview @import lp/corporate-training/corporate-training-example @import lp/corporate-training/corporate-training-testimonial diff --git a/app/javascript/stylesheets/lp/base/_base.sass b/app/javascript/stylesheets/lp/base/_base.sass index 7f5e3d1c670..3b9dc7f024e 100644 --- a/app/javascript/stylesheets/lp/base/_base.sass +++ b/app/javascript/stylesheets/lp/base/_base.sass @@ -5,7 +5,6 @@ html.is-lp body.is-lp background-color: var(--background) - .not-logged-in-footer color: var(--default-text) diff --git a/app/javascript/stylesheets/lp/blocks/lp/_lp-content-sub-title.sass b/app/javascript/stylesheets/lp/blocks/lp/_lp-content-sub-title.sass index c33c9b0cd59..d7b8f2947f8 100644 --- a/app/javascript/stylesheets/lp/blocks/lp/_lp-content-sub-title.sass +++ b/app/javascript/stylesheets/lp/blocks/lp/_lp-content-sub-title.sass @@ -12,3 +12,11 @@ +text-block(1.375rem 1.4, 800) &.is-course-name font-size: .875rem + &.is-sm + color: var(--main) + +media-breakpoint-up(lg) + +text-block(1.5rem 1.4, 800) + +media-breakpoint-only(md) + +text-block(1.25rem 1.4, 800) + +media-breakpoint-down(sm) + +text-block(1.125rem 1.4, 800) diff --git a/app/javascript/stylesheets/lp/blocks/lp/_lp-faq.sass b/app/javascript/stylesheets/lp/blocks/lp/_lp-faq.sass index caa203004ef..56807b58ed2 100644 --- a/app/javascript/stylesheets/lp/blocks/lp/_lp-faq.sass +++ b/app/javascript/stylesheets/lp/blocks/lp/_lp-faq.sass @@ -29,3 +29,12 @@ font-size: 1rem +media-breakpoint-down(sm) font-size: .875rem + +.lp-faq__edit + margin-top: .75rem + +.lp-faq__edit-link + +text-block(.875rem 1.4) + color: var(--muted-text) + &:hover + color: var(--main) diff --git a/app/javascript/stylesheets/lp/blocks/lp/_lp-faqs.sass b/app/javascript/stylesheets/lp/blocks/lp/_lp-faqs.sass index 012ff017b5f..3ad02bcc819 100644 --- a/app/javascript/stylesheets/lp/blocks/lp/_lp-faqs.sass +++ b/app/javascript/stylesheets/lp/blocks/lp/_lp-faqs.sass @@ -3,4 +3,4 @@ flex-direction: column gap: 2rem max-width: 40rem - float: right + margin: 0 diff --git a/app/javascript/stylesheets/lp/blocks/lp/_side-filter.sass b/app/javascript/stylesheets/lp/blocks/lp/_side-filter.sass new file mode 100644 index 00000000000..959a1cc5689 --- /dev/null +++ b/app/javascript/stylesheets/lp/blocks/lp/_side-filter.sass @@ -0,0 +1,26 @@ +.side-filter + position: sticky + top: 4rem + max-width: 14rem + margin: 0 + .a-card + overflow: hidden + +media-breakpoint-down(md) + display: none + +.side-filter__item-link + display: flex + padding: .75rem + +text-block(.875rem 1.4) + overflow: hidden + text-decoration: none + transition: all .2s ease-out + .side-filter__item:not(:last-child) & + border-bottom: solid 1px var(--border-tint) + &:hover + background-color: var(--primary-tint) + &.is-active + background-color: var(--main) + pointer-events: none + text-decoration: none + color: var(--reversal-text) diff --git a/app/javascript/stylesheets/lp/welcome/_welcome-top-info.sass b/app/javascript/stylesheets/lp/blocks/lp/_welcome-top-info.sass similarity index 100% rename from app/javascript/stylesheets/lp/welcome/_welcome-top-info.sass rename to app/javascript/stylesheets/lp/blocks/lp/_welcome-top-info.sass diff --git a/app/javascript/stylesheets/lp/corporate-training/_corporate-training-images.sass b/app/javascript/stylesheets/lp/corporate-training/_corporate-training-images.sass deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/javascript/stylesheets/lp/corporate-training/_corporate-training-overview.sass b/app/javascript/stylesheets/lp/corporate-training/_corporate-training-overview.sass deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/app/javascript/stylesheets/lp/welcome/_welcome-trial-period-table.sass b/app/javascript/stylesheets/lp/welcome/_welcome-trial-period-table.sass deleted file mode 100644 index 3da44f4da82..00000000000 --- a/app/javascript/stylesheets/lp/welcome/_welcome-trial-period-table.sass +++ /dev/null @@ -1,21 +0,0 @@ -.welcome-trial-period-table - width: 100% - background-color: var(--base) - border: solid 1px var(--border) - border-radius: .25rem - tr - &:not(:last-child) - border-bottom: solid 1px var(--border) - th, - td - +text-block(.8125rem 1.5) - padding: .5rem 1rem - th - text-align: center - white-space: nowrap - font-weight: 800 - td - text-align: center - .trial_end - color: $danger - background-color: rgba($danger, .1) diff --git a/app/javascript/stylesheets/application/blocks/form/_block-checks.sass b/app/javascript/stylesheets/shared/blocks/form/_block-checks.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_block-checks.sass rename to app/javascript/stylesheets/shared/blocks/form/_block-checks.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_books-form-item.sass b/app/javascript/stylesheets/shared/blocks/form/_books-form-item.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_books-form-item.sass rename to app/javascript/stylesheets/shared/blocks/form/_books-form-item.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_books-form.sass b/app/javascript/stylesheets/shared/blocks/form/_books-form.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_books-form.sass rename to app/javascript/stylesheets/shared/blocks/form/_books-form.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_checkboxes.sass b/app/javascript/stylesheets/shared/blocks/form/_checkboxes.sass similarity index 87% rename from app/javascript/stylesheets/application/blocks/form/_checkboxes.sass rename to app/javascript/stylesheets/shared/blocks/form/_checkboxes.sass index 6c64a4abac2..02d95991e4c 100644 --- a/app/javascript/stylesheets/application/blocks/form/_checkboxes.sass +++ b/app/javascript/stylesheets/shared/blocks/form/_checkboxes.sass @@ -51,3 +51,10 @@ $checkbox-size: .875rem input:checked + label::after, .is-dammy label::after opacity: 1 + + &.is-radio label::before + border-radius: 50% + &.is-radio input:checked + label::before, + &.is-radio .is-dammy label::before + content: '' + box-shadow: 0 0 0 2px inset var(--base) diff --git a/app/javascript/stylesheets/application/blocks/form/_form-actions.sass b/app/javascript/stylesheets/shared/blocks/form/_form-actions.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-actions.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-actions.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-added-choice.sass b/app/javascript/stylesheets/shared/blocks/form/_form-added-choice.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-added-choice.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-added-choice.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-item-actions.sass b/app/javascript/stylesheets/shared/blocks/form/_form-item-actions.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-item-actions.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-item-actions.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-item-block.sass b/app/javascript/stylesheets/shared/blocks/form/_form-item-block.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-item-block.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-item-block.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-item-group.sass b/app/javascript/stylesheets/shared/blocks/form/_form-item-group.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-item-group.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-item-group.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-item.sass b/app/javascript/stylesheets/shared/blocks/form/_form-item.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-item.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-item.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-link-block.sass b/app/javascript/stylesheets/shared/blocks/form/_form-link-block.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-link-block.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-link-block.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-notice.sass b/app/javascript/stylesheets/shared/blocks/form/_form-notice.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-notice.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-notice.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-selects.sass b/app/javascript/stylesheets/shared/blocks/form/_form-selects.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-selects.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-selects.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-tabs-item.sass b/app/javascript/stylesheets/shared/blocks/form/_form-tabs-item.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-tabs-item.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-tabs-item.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-tabs.sass b/app/javascript/stylesheets/shared/blocks/form/_form-tabs.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-tabs.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-tabs.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-textarea.sass b/app/javascript/stylesheets/shared/blocks/form/_form-textarea.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-textarea.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-textarea.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form-times.sass b/app/javascript/stylesheets/shared/blocks/form/_form-times.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form-times.sass rename to app/javascript/stylesheets/shared/blocks/form/_form-times.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_form.sass b/app/javascript/stylesheets/shared/blocks/form/_form.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_form.sass rename to app/javascript/stylesheets/shared/blocks/form/_form.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_hidden-form-item.sass b/app/javascript/stylesheets/shared/blocks/form/_hidden-form-item.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_hidden-form-item.sass rename to app/javascript/stylesheets/shared/blocks/form/_hidden-form-item.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_important-message.sass b/app/javascript/stylesheets/shared/blocks/form/_important-message.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_important-message.sass rename to app/javascript/stylesheets/shared/blocks/form/_important-message.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_linear-scale.sass b/app/javascript/stylesheets/shared/blocks/form/_linear-scale.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_linear-scale.sass rename to app/javascript/stylesheets/shared/blocks/form/_linear-scale.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_many-check-boxes.sass b/app/javascript/stylesheets/shared/blocks/form/_many-check-boxes.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_many-check-boxes.sass rename to app/javascript/stylesheets/shared/blocks/form/_many-check-boxes.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_markdown-form.sass b/app/javascript/stylesheets/shared/blocks/form/_markdown-form.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_markdown-form.sass rename to app/javascript/stylesheets/shared/blocks/form/_markdown-form.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_radios.sass b/app/javascript/stylesheets/shared/blocks/form/_radios.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_radios.sass rename to app/javascript/stylesheets/shared/blocks/form/_radios.sass diff --git a/app/javascript/stylesheets/application/blocks/form/_vue-tags-input.sass b/app/javascript/stylesheets/shared/blocks/form/_vue-tags-input.sass similarity index 100% rename from app/javascript/stylesheets/application/blocks/form/_vue-tags-input.sass rename to app/javascript/stylesheets/shared/blocks/form/_vue-tags-input.sass diff --git a/app/javascript/stylesheets/shared/layouts/_container.sass b/app/javascript/stylesheets/shared/layouts/_container.sass index 7837a0f6a64..40d4027a56e 100644 --- a/app/javascript/stylesheets/shared/layouts/_container.sass +++ b/app/javascript/stylesheets/shared/layouts/_container.sass @@ -1,6 +1,6 @@ .container width: 100% - max-width: 100vw + max-width: 100% margin-inline: auto +media-breakpoint-up(md) padding-inline: 1.5rem diff --git a/app/models/faq.rb b/app/models/faq.rb new file mode 100644 index 00000000000..fd1a5cde3a2 --- /dev/null +++ b/app/models/faq.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class FAQ < ApplicationRecord + validates :answer, presence: true, uniqueness: { scope: :question } + validates :question, presence: true, uniqueness: true + default_scope -> { order(:position) } + belongs_to :faq_category + acts_as_list scope: :faq_category +end diff --git a/app/models/faq_category.rb b/app/models/faq_category.rb new file mode 100644 index 00000000000..b8dee45920a --- /dev/null +++ b/app/models/faq_category.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class FAQCategory < ApplicationRecord + validates :name, presence: true, uniqueness: true + has_many :faqs, -> { order(:position) }, dependent: :destroy, inverse_of: :faq_category + default_scope -> { order(:position) } + acts_as_list +end diff --git a/app/views/admin/_admin_page_tabs.html.slim b/app/views/admin/_admin_page_tabs.html.slim index 89d84d3b08a..9615a908b47 100644 --- a/app/views/admin/_admin_page_tabs.html.slim +++ b/app/views/admin/_admin_page_tabs.html.slim @@ -8,7 +8,7 @@ li.page-tabs__item = link_to 'ユーザー', admin_users_path, - class: "page-tabs__item-link #{current_link(/^admin-users/)}" + class: "page-tabs__item-link #{current_link(/^admin-users|^admin-invitation_url/)}" li.page-tabs__item = link_to '企業', admin_companies_path, @@ -22,6 +22,6 @@ 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/)}" + = link_to 'FAQ', + admin_faq_categories_path, + class: "page-tabs__item-link #{current_link(/^admin-faq_categories|^admin-faqs/)}" diff --git a/app/views/admin/faq_categories/_form.html.slim b/app/views/admin/faq_categories/_form.html.slim new file mode 100644 index 00000000000..1fd8a578f77 --- /dev/null +++ b/app/views/admin/faq_categories/_form.html.slim @@ -0,0 +1,14 @@ += form_with model: [:admin, faq_category], local: true, html: { name: 'faq_category' } do |f| + .form__items + .form-item + = f.label :name, class: 'a-form-label' + = f.text_field :name, class: 'a-text-input is-xs', placeholder: '学習内容について' + .form-actions + ul.form-actions__items + li.form-actions__item.is-main + = f.submit nil, class: 'a-button is-lg is-block is-primary' + li.form-actions__item.is-sub + = link_to 'キャンセル', :back, class: 'a-button is-sm is-text' + - if faq_category.id.present? + li.form-actions__item.is-muted + = link_to '削除', admin_faq_category_path(faq_category), data: { confirm: '本当によろしいですか?この操作はデータを削除するため元に戻すことができません。' }, method: :delete, class: 'a-button is-sm is-muted-text' diff --git a/app/views/admin/faq_categories/edit.html.slim b/app/views/admin/faq_categories/edit.html.slim new file mode 100644 index 00000000000..4611eee6ff5 --- /dev/null +++ b/app/views/admin/faq_categories/edit.html.slim @@ -0,0 +1,28 @@ +- title 'FAQカテゴリー編集' + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | FAQカテゴリー編集 + .page-main-header__end + .page-header-actions + .page-header-actions__items + .page-header-actions__item + = link_to admin_faqs_path, class: 'a-button is-md is-secondary is-block is-back' do + | FAQカテゴリー一覧 + hr.a-border + .page-body + .container.is-sm + = render 'form', faq_category: @faq_category diff --git a/app/views/admin/faq_categories/faqs/index.html.slim b/app/views/admin/faq_categories/faqs/index.html.slim new file mode 100644 index 00000000000..882a97c2db4 --- /dev/null +++ b/app/views/admin/faq_categories/faqs/index.html.slim @@ -0,0 +1,56 @@ +- title "「#{@faq_category.name}」FAQ一覧" + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | 「#{@faq_category.name}」 + | FAQ一覧 + | (#{@faqs.size}) + .page-main-header__end + .page-header-actions + .page-header-actions__items + .page-header-actions__item + = link_to admin_faq_categories_path, class: 'a-button is-md is-secondary is-back is-block' do + | カテゴリー一覧 + .page-header-actions__item + = link_to new_admin_faq_path, class: 'a-button is-md is-secondary is-block' do + i.fa-regular.fa-plus + span + | FAQ追加 + hr.a-border + .page-body + .container.is-lg + - if @faq_category.faqs.size.positive? + .admin-table + table.admin-table__table + thead.admin-table__header + tr.admin-table__labels + th.admin-table__label 質問 + th.admin-table__label 並び順 + tbody.admin-table__items#js-faq-sortable + - @faqs.each do |faq| + tr.admin-table__item(id="#{faq.id}" data-faq-category-id="#{faq.faq_category_id}") + td.admin-table__item-value + = link_to admin_faq_path(faq), class: 'a-hover-link' do + = format_question(faq.question) + td.admin-table__item-value.is-text-align-center.is-grab + span.js-grab.a-grab + i.fa-solid.fa-align-justify + - else + .o-empty-message + .o-empty-message__icon + i.fa-regular.fa-sad-tear + .o-empty-message__text + | FAQはまだありません。 diff --git a/app/views/admin/faq_categories/index.html.slim b/app/views/admin/faq_categories/index.html.slim new file mode 100644 index 00000000000..b9c6f7d2425 --- /dev/null +++ b/app/views/admin/faq_categories/index.html.slim @@ -0,0 +1,51 @@ +- title 'FAQカテゴリー一覧' + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | FAQカテゴリー一覧(#{@faq_categories.size}) + .page-main-header__end + .page-header-actions + .page-header-actions__items + .page-header-actions__item + = link_to new_admin_faq_category_path, class: 'a-button is-md is-secondary is-block' do + i.fa-regular.fa-plus + span + | カテゴリー追加 + hr.a-border + .page-body + .container.is-md + .admin-table + table.admin-table__table + thead.admin-table__header + tr.admin-table__labels + th.admin-table__label 名前 (FAQ数) + th.admin-table__label 操作 + th.admin-table__label 並び順 + tbody.admin-table__items#js-faq-category-sortable + - @faq_categories.each do |faq_category| + tr.admin-table__item(id="#{faq_category.id}") + td.admin-table__item-value + = link_to admin_faq_category_faqs_path(faq_category), class: 'a-hover-link' do + | #{faq_category.name} (#{faq_category.faqs.size}) + td.admin-table__item-value.is-text-align-center + ul.is-inline-buttons + li + = link_to edit_admin_faq_category_path(faq_category), + class: 'a-button is-sm is-secondary is-icon' do + i.fa-solid.fa-pen + td.admin-table__item-value.is-text-align-center.is-grab + span.js-grab.a-grab + i.fa-solid.fa-align-justify diff --git a/app/views/admin/faq_categories/new.html.slim b/app/views/admin/faq_categories/new.html.slim new file mode 100644 index 00000000000..bb828e97dbc --- /dev/null +++ b/app/views/admin/faq_categories/new.html.slim @@ -0,0 +1,27 @@ +- title 'FAQカテゴリー追加' + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | FAQカテゴリー追加 + .page-main-header__end + .page-main-header-actions + ul.page-main-header-actions__items + li.page-main-header-actions__item + = link_to 'カテゴリー一覧', admin_faq_categories_path, class: 'a-button is-sm is-block is-secondary is-back' + hr.a-border + .page-body + .container.is-md + = render 'form', faq_category: @faq_category diff --git a/app/views/admin/faqs/_faq_tabs.html.slim b/app/views/admin/faqs/_faq_tabs.html.slim new file mode 100644 index 00000000000..14e33cc9e3b --- /dev/null +++ b/app/views/admin/faqs/_faq_tabs.html.slim @@ -0,0 +1,11 @@ +nav.tab-nav + .container + ul.tab-nav__items + li.tab-nav__item + = link_to 'カテゴリー', + admin_faq_categories_path, + class: "tab-nav__item-link #{current_link(/^admin-faq_categories/)}" + li.tab-nav__item + = link_to 'FAQ', + admin_faqs_path, + class: "tab-nav__item-link #{current_link(/^admin-faqs/)}" diff --git a/app/views/admin/faqs/_form.html.slim b/app/views/admin/faqs/_form.html.slim new file mode 100644 index 00000000000..f3216b14fc8 --- /dev/null +++ b/app/views/admin/faqs/_form.html.slim @@ -0,0 +1,35 @@ += form_with model: [:admin, faq], local: true, html: { name: 'faq' } do |f| + .form__items + .form-item + = f.label :question, class: 'a-form-label' + = f.text_area :question, class: 'a-text-input is-xs' + .form-item + .row.js-markdown-parent + .col-md-6.col-xs-12 + = f.label :answer, class: 'a-form-label' + = f.text_area :answer, + class: 'a-text-input js-warning-form js-markdown markdown-form__text-area', + data: { 'preview': '.js-preview' } + .col-md-6.col-xs-12 + .a-form-label + | プレビュー + .js-preview.a-long-text.is-md.markdown-form__preview + .form-item + = render 'errors', object: faq + label.a-form-label + | カテゴリー + .checkboxes + .checkboxes__items + - @faq_categories.each do |faq_category| + .checkboxes__item.is-radio + = f.radio_button :faq_category_id, faq_category.id, class: 'a-toggle-checkbox' + = f.label :faq_category_id, faq_category.name, value: faq_category.id + .form-actions + ul.form-actions__items + li.form-actions__item.is-main + = f.submit nil, class: 'a-button is-lg is-block is-primary' + li.form-actions__item.is-sub + = link_to 'キャンセル', :back, class: 'a-button is-sm is-text' + - if faq.id.present? + li.form-actions__item.is-muted + = link_to '削除', admin_faq_path(faq), data: { confirm: '本当によろしいですか?この操作はデータを削除するため元に戻すことができません。' }, method: :delete, class: 'a-button is-sm is-muted-text' diff --git a/app/views/admin/faqs/edit.html.slim b/app/views/admin/faqs/edit.html.slim new file mode 100644 index 00000000000..6d3290aeecc --- /dev/null +++ b/app/views/admin/faqs/edit.html.slim @@ -0,0 +1,28 @@ +- title 'FAQ編集' + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | FAQ編集 + .page-main-header__end + .page-header-actions + .page-header-actions__items + .page-header-actions__item + = link_to admin_faqs_path, class: 'a-button is-md is-secondary is-block is-back' do + | FAQ一覧 + hr.a-border + .page-body + .container.is-xl + = render 'form', faq: @faq diff --git a/app/views/admin/faqs/index.html.slim b/app/views/admin/faqs/index.html.slim new file mode 100644 index 00000000000..e4d62320ba0 --- /dev/null +++ b/app/views/admin/faqs/index.html.slim @@ -0,0 +1,58 @@ +- title 'FAQ一覧' + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | FAQ一覧 + .page-main-header__end + .page-header-actions + .page-header-actions__items + .page-header-actions__item + = link_to new_admin_faq_path, class: 'a-button is-md is-secondary is-block' do + i.fa-regular.fa-plus + span + | FAQ追加 + hr.a-border + .page-body + .container.is-lg + - @faq_categories.each do |faq_category| + .categories-item + header.categories-item__header + h2.categories-item__title + = link_to admin_faq_category_faqs_path(faq_category), class: 'a-hover-link' do + | #{faq_category.name}(#{faq_category.faqs.size}) + + - if faq_category.faqs.size.positive? + .categories-item__body + .admin-table + table.admin-table__table + thead.admin-table__header + tr.admin-table__labels + th.admin-table__label 質問 + th.admin-table__label 編集 + tbody.admin-table__items + - faq_category.faqs.each do |faq| + tr.admin-table__item + td.admin-table__item-value + = link_to format_question(faq.question), admin_faq_path(faq) + td.admin-table__item-value.is-text-align-center + = link_to edit_admin_faq_path(faq), class: 'a-button is-sm is-secondary is-icon' do + i.fa-solid.fa-pen + - else + .o-empty-message + .o-empty-message__icon + i.fa-regular.fa-sad-tear + .o-empty-message__text + | FAQはまだありません。 diff --git a/app/views/admin/faqs/new.html.slim b/app/views/admin/faqs/new.html.slim new file mode 100644 index 00000000000..3d325b743a3 --- /dev/null +++ b/app/views/admin/faqs/new.html.slim @@ -0,0 +1,27 @@ +- title '管理ページ' + +header.page-header + .container + .page-header__inner + h1.page-header__title = title + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | FAQ追加 + .page-main-header__end + .page-main-header-actions + ul.page-main-header-actions__items + li.page-main-header-actions__item + = link_to admin_faqs_path, class: 'a-button is-sm is-block is-secondary is-back' do + | FAQ一覧 + hr.a-border + .page-body + .container.is-xl + = render 'form', faq: @faq diff --git a/app/views/admin/faqs/show.html.slim b/app/views/admin/faqs/show.html.slim new file mode 100644 index 00000000000..142bab1b034 --- /dev/null +++ b/app/views/admin/faqs/show.html.slim @@ -0,0 +1,77 @@ +- title 'FAQ詳細' + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/faqs/faq_tabs' + +main.page-main + header.page-main-header + .container + .page-main-header__inner + .page-main-header__start + h1.page-main-header__title + | FAQ詳細 + .page-main-header__end + .page-main-header-actions + ul.page-main-header-actions__items + li.page-main-header-actions__item + = link_to admin_faqs_path, class: 'a-button is-sm is-block is-secondary is-back is-back' do + | FAQ一覧 + hr.a-border + .page-body + .page-body__inner.has-side-nav + .container.is-md + .page-content + header.page-content-header + .page-content-header__end + .page-content-header__row + h1.page-content-header__title + = format_question(@faq.question) + + .page-content-header__row + .page-content-header-metas + .page-content-header-metas__start + .page-content-header-metas__meta + .a-meta + | #{l @faq.created_at} 公開 + + .page-content-header__row + .page-content-header-metas + .page-content-header-metas__start + .page-content-header-metas__meta + .a-meta + = link_to admin_faq_category_faqs_path(@faq.faq_category) do + = t("faq.#{FAQCategory.find_by(id: @faq.faq_category_id).name}") + + .a-card + .card-body + .card-body__description + .js-markdown-view.a-long-text.is-md + = @faq.answer + hr.a-border-tint + .card-footer + .card-main-actions + .card-main-actions__items + .card-main-actions__item + = link_to edit_admin_faq_path(@faq), class: 'card-main-actions__action a-button is-sm is-secondary is-block' do + i.fa-solid.fa-pen + | 内容修正 + + nav.a-side-nav + .a-side-nav__inner + header.a-side-nav__header + h2.a-side-nav__title + = link_to admin_faq_category_faqs_path(@faq.faq_category), class: 'a-hover-link' do + = t("faq.#{FAQCategory.find_by(id: @faq.faq_category_id).name}") + hr.a-border + .a-side-nav__body + ul.a-side-nav__items + - @faq.faq_category.faqs.each do |faq| + li.a-side-nav__item + = link_to admin_faq_path(faq), class: 'a-side-nav__item-link' do + = format_question(faq.question) diff --git a/app/views/admin/invitation_url/index.html.slim b/app/views/admin/invitation_url/index.html.slim index 2655a742d1d..e98843ee525 100644 --- a/app/views/admin/invitation_url/index.html.slim +++ b/app/views/admin/invitation_url/index.html.slim @@ -6,6 +6,7 @@ header.page-header h1.page-header__title = title = render 'admin/admin_page_tabs' += render 'admin/users/user_tabs' .page-main header.page-main-header diff --git a/app/views/admin/users/_user_tabs.html.slim b/app/views/admin/users/_user_tabs.html.slim new file mode 100644 index 00000000000..124c40ef245 --- /dev/null +++ b/app/views/admin/users/_user_tabs.html.slim @@ -0,0 +1,11 @@ +nav.tab-nav + .container + ul.tab-nav__items + li.tab-nav__item + = link_to 'ユーザー', + admin_users_path, + class: "tab-nav__item-link #{current_link(/^admin-users/)}" + li.tab-nav__item + = link_to '招待URL', + admin_invitation_url_index_path, + class: "tab-nav__item-link #{current_link(/^admin-invitation_url/)}" diff --git a/app/views/admin/users/edit.html.slim b/app/views/admin/users/edit.html.slim index 91e69dd5320..6adaa5147ea 100644 --- a/app/views/admin/users/edit.html.slim +++ b/app/views/admin/users/edit.html.slim @@ -1,5 +1,12 @@ - title 'ユーザー登録情報変更' -- content_for(:extra_body_classes, 'no-header no-footer') + +header.page-header + .container + .page-header__inner + h1.page-header__title = title + += render 'admin/admin_page_tabs' += render 'admin/users/user_tabs' .auth-form.is-lg.a-card header.auth-form__header diff --git a/app/views/admin/users/index.html.slim b/app/views/admin/users/index.html.slim index b47473c93cb..ef7e651b0a9 100644 --- a/app/views/admin/users/index.html.slim +++ b/app/views/admin/users/index.html.slim @@ -7,6 +7,8 @@ header.page-header = title = render 'admin/admin_page_tabs' += render 'admin/users/user_tabs' + main.page-main header.page-main-header .container diff --git a/app/views/admin/users/password/edit.html.slim b/app/views/admin/users/password/edit.html.slim index de9817723f1..8845f8b316d 100644 --- a/app/views/admin/users/password/edit.html.slim +++ b/app/views/admin/users/password/edit.html.slim @@ -1 +1,12 @@ +- title 'ユーザー登録情報変更' + +header.page-header + .container + .page-header__inner + h1.page-header__title + | 管理ページ + += render 'admin/admin_page_tabs' += render 'admin/users/user_tabs' + = render 'users/password_change', url: admin_user_password_path, user: @user diff --git a/app/views/current_user/password/edit.html.slim b/app/views/current_user/password/edit.html.slim index 138ef167e97..7f7bd27a285 100644 --- a/app/views/current_user/password/edit.html.slim +++ b/app/views/current_user/password/edit.html.slim @@ -1,4 +1,11 @@ - title 'パスワード変更' - set_meta_tags description: 'パスワード変更ページです。' +header.page-header + .container + .page-header__inner + .page-header__start + .page-header__title + = title +hr.a-border = render 'users/password_change', url: current_user_password_path, user: @user diff --git a/app/views/reports/_report_body.html.slim b/app/views/reports/_report_body.html.slim index 34a124f7557..9ff8c49b48f 100644 --- a/app/views/reports/_report_body.html.slim +++ b/app/views/reports/_report_body.html.slim @@ -10,8 +10,8 @@ = render 'reactions/reactions', reactionable: report - if report.user_id == current_user.id || mentor_login? - hr.a-border-tint - .card-footer(class="#{mentor_login? ? 'is-only-mentor' : ''}") + hr.a-border-tint(class="#{mentor_login? ? 'is-only-mentor' : ''}") + footer.card-footer(class="#{mentor_login? ? 'is-only-mentor' : ''}") .card-main-actions ul.card-main-actions__items li.card-main-actions__item diff --git a/app/views/users/_password_change.html.slim b/app/views/users/_password_change.html.slim index 5c59e8cd6c3..6068362a314 100644 --- a/app/views/users/_password_change.html.slim +++ b/app/views/users/_password_change.html.slim @@ -1,13 +1,6 @@ - title 'パスワード変更' - content_for(:extra_body_classes, 'no-footer') -header.page-header - .container - .page-header__inner - .page-header__start - .page-header__title - = title -hr.a-border .auth-form.is-lg .a-card header.auth-form__header diff --git a/app/views/welcome/_faq.html.slim b/app/views/welcome/_faq.html.slim deleted file mode 100644 index b63830b6046..00000000000 --- a/app/views/welcome/_faq.html.slim +++ /dev/null @@ -1,309 +0,0 @@ -.lp - section.lp-content.is-lp-bg-1.is-top-title - .l-container.is-xl - .lp-content__inner - .lp-content__start - header.lp-content__header - h2.lp-content-title.text-center.is-border-bottom - | よくある質問 - .lp-content__end - .row.is-gutter-width-32.justify-center - // - .col-xl-4.col-lg-4.col-xs-12 - .a-card - | それは黄金の昼下がり気ままにただようぼくらオールは二本ともあぶなげに小さな腕で漕がれ小さな手がぼくらのただよいを導こうとかっこうだけ申し訳につけてああ残酷な三人!こんな時間にこんな夢見る天気のもとで - - .a-card - .welcome-section__description - p - | わからないこと、気になることがありましたら、 - = link_to new_inquiry_path, class: 'welcome-section__description-link' do - | こちら - | から質問してください。 - - .col-xl-8.col-lg-8.col-xs-12 - section.welcome-sections.is-faq#faq - .welcome-section__faqs - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 学習を終えるのにどれくらいの時間がかかりますか? - .faqs-item__body - .a-long-text.is-md - p - | かかる時間は人それぞれですが、プログラム経験が全くない人の場合、 - | 1,200時間くらいかかる方が多いです。 - p - | 覚えが早い、遅いも関係しますが、 - | 短時間でも毎日必ず学習する場合と間が空いてしまう場合など、時間のかけかたにも影響があったりもします。 - p - | 知的好奇心が求めるままに気になったこともきっちり調べる人は、時間がかかっても、 - | 就職後に仕事やプログラマーコミュニティで大活躍をしているので、卒業までの時間が短ければいいとは考えていません。 - p - | フィヨルドブートキャンプとしては、卒業までの時間が長くなり過ぎないように、 - | 泣く泣くカリキュラムを削ったりなどもしているのですが、 - | 年々就職希望者に求めるものが増えているので、削っても削っても増えていってしまい、 - | なかなか卒業までの時間を短縮させるのが難しいです。 - p - | とはいえ、就職した卒業生は、フィヨルドブートキャンプで学んだことで就職後に役に立たなかったことは - | 何一つなかった、と言ってくれてます(自慢)。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 途中で辞めることは可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | いつでも退会ができます。退会をすると翌月からの請求は止まります。 - | 退会をしない限りは請求は止まらず、オンライン学習アプリにログインをしていなくても請求されてしまうので、 - | 学習をやめる場合は必ず退会を行ってください。 - p - | フィヨルドブートキャンプでは、プログラマーになることを考えたけど、 - | 自分には向いていなかったことに気づくことも大きな価値だと考えています。 - p - | プログラマーに向いてない、プログラミングが楽しめないと思ったときのリスクを - | 最小限にするため、一度に大金を支払うのではなく、月額課金制にしています。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 学習はオンラインだけですか? - .faqs-item__body - .a-long-text.is-md - p - | オンラインだけです。 - p - | オンラインだけですが、オンラインの強みを活かして、住んでいる場所を問わずに参加できる - | 飲み会、卒業式、受講生が主催の勉強会、受講生が主催の発表会、質問タイム、雑談会などの - | 何かしらのオンラインイベントが毎日のように開催されています。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 休会をすることは可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | 最大3ヶ月まで休会をすることが可能です。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | わからないことがあった場合、質問はできますか? - .faqs-item__body - .a-long-text.is-md - p - | できます。 - p - | フィヨルドブートキャンプの前身であるフィヨルドインターンから数えると、 - | とても長くプログラミング教育をやってきたのですが、 - | その歴史は受講生が抱えているわからないことを、どうやって拾い上げるかの歴史でもありました。 - p - | 人によっては質問をすることが恥ずかしいと感じてしまう人もいまずが、 - | 質問をすることはプログラマーにとってとても重要なスキルの一つと考えているため、 - | それを鍛えるためにたくさんの質問をする方法を用意しています。 - p - | 学習をする度に提出する日報に質問を書く、 - | フィヨルドブートキャンプの学習アプリのQ&A機能を使って質問、 - | 質問ようのチャットのチャンネルで質問、質問タイムでビデオチャットで質問、 - | メンターにペアプログラミングを依頼...など、あらゆる質問をする手段を用意しています。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 退会した月は料金が日割りで請求されますか? - .faqs-item__body - .a-long-text.is-md - p - | 退会した月も、日割りでの請求はなく、その月のご利用料金1ヶ月分をお支払い頂きますことをご了承ください。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 仕事をしながら学習をしたいのであまり時間が取れないのですがそれでも参加できますか? - .faqs-item__body - .a-long-text.is-md - p - | 学習時間の制限はありませんので、自分のできるペースで学習を進めることができます。 - | ただ、卒業までに時間がかかってしまうと、それだけ合計の料金はかかってしまいます。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 就職先が決まっているのですが、就職までの間学習をしたいです。その場合、参加は可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | はい、そのような場合も参加可能です。実際、フィヨルドブートキャンプは企業の新人研修でも利用されています。 - | もし、企業の研修として利用する場合は法人用のプランがございますのでご連絡ください。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 転職は考えていないのですが、スキルアップのためプログラミングの勉強がしたいです。その場合、参加は可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | 可能です。フィヨルドブートキャンプは就職できるためのスキルを身につけることを目標にしていますが、 - | 就職を必ずしなくてはいけないという縛りはありません。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 現在、大学に在学中なのですが、参加できますか? - .faqs-item__body - .a-long-text.is-md - p - | 学生でも参加可能です。年齢に制限はありません。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | Linuxで学習をしたいのですが可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | Linuxでも可能ですが条件があります。 - | 用意しているカリキュラムは Mac を想定して作られています。 - | そのため、Linux でカリキュラムを行う場合は自分自身でMac用のカリキュラムを - | Linuxに合わせて操作方法などを変える必要があります。 - | それができるだけのLinuxのスキルがある方のみ、Linuxで学習をすることをOKとしています。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | Windowsで学習をしたいのですが可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | Windowsでも可能ですが、WSL2を使うのでWSL2対応のパソコンであることが必須です。 - | WSL2についてとWSL2に対応しているかの調べ方は下記にかいてあります。 - | そちらを読んで理解できないようであればMacを購入してから受講することをおすすめしております。 - p = link_to 'WSL2が使えるマシンとは?', 'https://bootcamp.fjord.jp/articles/32' - p = link_to 'WSL2でLinuxを使おう', 'https://bootcamp.fjord.jp/articles/6' - - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 古い OS のままで学習をしたいのですが可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | カリキュラムが、最新の Mac の OS を想定して作られています。 - | 多少古い場合はどうにかなるかもしれませんが、OS のせいで学習に詰まることが出てくるかもしれません。 - | 何かしら事情があってお手持ちの Mac のバージョンが上げられない場合は、 - | それとは別の Mac を用意してから参加されることをおすすめします。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 転職エージェントを使って就職活動をすることは可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | はい、就職活動について制限はありません。希望があれば弊社でもエージェントを紹介することも可能です。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 卒業生が Ruby 界隈で有名な企業に就職していますが、私もそういった企業に就職できますか? - .faqs-item__body - .a-long-text.is-md - p - | 個人によります。弊社と仲良くしてくれてる Ruby 界隈で有名な企業に推薦をする場合もありますが、 - | 推薦をする側も責任があるため、誰でも推薦をすることができるとは言えません。 - | ただし、そもそもフィヨルドブートキャンプを卒業することはとても難しく、 - | それを達成できた時点で十分推薦できる人物だと判断することがほとんどです。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | プログラマーは男性が多いと聞いたことがありますが、女性でも大丈夫ですか? - .faqs-item__body - .a-long-text.is-md - p - | 全く問題ありません。 - p - | フィヨルドブートキャンプでは、入会時に性別を聞くことはしていないので、 - | 正確な数字は出せないのですが、女性比率は男性 : 女性で半々に近いくらい高いです。 - | イベントによっては女性の参加者の方が多いこともあります。 - p - | たくさんの女性が卒業しプログラマーとして就職し活躍しています。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 30代なのですが、エンジニアとして就職することは可能ですか? - .faqs-item__body - .a-long-text.is-md - p - | 今までの観測範囲から言いますと、30代の方はそれほど不利な感じはないです。 - | 特別に若手20代を中心に考えています、という会社がたまにあるぐらいです。 - p - | 40代で転職した方もいますが、40代以上の方の場合はかなり難しいです。 - p - | 年齢に関わらず、一定のプログラミングスキルがあることは最低条件で、 - | 就職をするためにはそれ以外の要素も必要ですが、 - | 30代以下に比べてより高いレベルでの、今までのキャリアで培った特別な経験や実績、 - | 高いコミュニケーション能力を求められる傾向にあります。 - p - | 30代半ばを超えていたら、今持っているスキル + プログラミングスキルを使って、 - | どの会社でどんな仕事をすれば自分の価値を出せるか? - | そのためには、どのような就活を行うべきかの戦略とプランも考えておく必要があります。 - p - | また、40代以上に強そうな転職エージェントを探し、 - | 予め相談してからフィヨルドブートキャンプを始めるかを検討するのもいいかもしれません。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 会社の新人研修など法人での利用はできますか? - .faqs-item__body - .a-long-text.is-md - p - | 可能です。 - p - | 法人でのご利用については#{link_to 'こちら', training_path}に詳細を記載しております。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 何をしたら卒業になりますか? - .faqs-item__body - .a-long-text.is-md - p - | 全てのプラクティスを修了したら卒業になります。 - p - | 最後のプラクティスが自作サービスをリリースする、というものになっています。 - | 月に一回行っている卒業式で、自作サービスの発表をしてもらい、 - | その後に卒業証書授与を行っています。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | 利用料の領収書は発行してもらえますか? - .faqs-item__body - .a-long-text.is-md - p - | 発行します。 - p - | Stripe という決済サービスを利用しているのですが、 - | それが発行する領収書をダウンロードすることができます。 - section.faqs-item.a-card - .card-body__inner - header.faqs-item__header - h2.faqs-item__title - | フィヨルドブートキャンプを利用していることを誰にも知られたくないのですが、匿名で参加はできますか? - .faqs-item__body - .a-long-text.is-md - p - | 可能です。 - p - | フィヨルドブートキャンプは、年齢、性別、本名を伝えることなく利用することができます。 - | フィヨルドブートキャンプ内では、お互いをニックネームで呼び合っています。 - p - | オンラインイベントの中では、ビデオチャットで顔出しをするイベントもありますが、 - | カメラをオンにすることは強制していません。 diff --git a/app/views/welcome/faq.html.slim b/app/views/welcome/faq.html.slim index 19f06bf3e38..81fa401f574 100644 --- a/app/views/welcome/faq.html.slim +++ b/app/views/welcome/faq.html.slim @@ -3,12 +3,62 @@ - set_meta_tags(site: 'FJORD BOOT CAMP(フィヨルドブートキャンプ)', description: 'フィヨルドブートキャンプに寄せられたよくあるお問い合わせとその回答の一覧です。') -header.lp-page-header - .l-container - .lp-page-header__inner - .lp-page-header__start - h1.lp-page-header__title - | FAQ - .lp-page-header__end -hr.a-border-tint -= render 'faq' +.lp + header.lp-page-header + .l-container + .lp-page-header__inner + .lp-page-header__start + h1.lp-page-header__title + = title + .lp-page-header__end + - if current_user&.admin? + nav.lp-page-header-nav + ul.lp-page-header-nav__items + li.lp-page-header-nav__item + = link_to new_admin_faq_path, class: 'a-button is-md is-secondary-border is-block' do + i.fas.fa-plus + span + | FAQ追加 + section.lp-content.is-lp-bg-1.is-top-title + .l-container.is-lg + .lp-content__inner + .lp-content__start + header.lp-content__header + h2.lp-content-title.text-center.is-border-bottom + | よくある質問 + .lp-content__end + .lp-content-stack + .lp-content-stack__item + .lp-content__description + #faqs + .l-inner-container.is-md + .a-short-text + p.text-center + | わからないこと、気になることがありましたら、 + br.is-hidden-md-up + = link_to new_inquiry_path, class: 'welcome-section__description-link' do + | こちらからお問い合わせください + | 。 + .lp-content-stack__item + .lp-faqs + .row.is-gutter-width-32 + .col-lg-4.col-xs-12 + = render partial: '/welcome/faqs/faqs_mobile_filter', + locals: { faq_categories: @faq_categories, faqs: @faqs } + = render partial: '/welcome/faqs/faqs_pc_filter', + locals: { faq_categories: @faq_categories, faqs: @faqs } + .col-xl-8.col-lg-8.col-xs-12 + .lp-content-stack + .lp-content-stack__item.is-hidden-md-down + - if params[:category].present? + h2.lp-content-sub-title.text-center.is-sm + | FAQ + span.ml-4 + | 「#{t("faq.#{params[:category]}")}」 + - else + h2.lp-content-sub-title.text-center.is-sm + | 全てのFAQ + .lp-content-stack__item + .lp-faqs__items.mx-auto + - @faqs.each do |faq| + = render '/welcome/faqs/faqs_item', faq: faq diff --git a/app/views/welcome/faqs/_faqs_item.html.slim b/app/views/welcome/faqs/_faqs_item.html.slim new file mode 100644 index 00000000000..b4c0a0e8ed2 --- /dev/null +++ b/app/views/welcome/faqs/_faqs_item.html.slim @@ -0,0 +1,13 @@ +section.lp-faq + .lp-faq__inner + header.lp-faq__header + h3.lp-faq__title + = faq.question + .lp-faq__body + .a-short-text.js-markdown-view + = faq.answer + - if admin_login? + .lp-faq__edit + = link_to edit_admin_faq_path(faq), class: 'lp-faq__edit-link' do + | 編集 +hr.a-border diff --git a/app/views/welcome/faqs/_faqs_mobile_filter.html.slim b/app/views/welcome/faqs/_faqs_mobile_filter.html.slim new file mode 100644 index 00000000000..3a7e97fbd9f --- /dev/null +++ b/app/views/welcome/faqs/_faqs_mobile_filter.html.slim @@ -0,0 +1,19 @@ +.faqs__filter.is-hidden-lg-up + .container.is-xs + .form-item.is-inline + label.a-form-label + | 絞り込む + = form_with url: faq_path, + method: :get, + local: true, + html: { class: 'a-button is-md is-secondary is-select is-block' } do + select(name="category" onchange="this.form.submit()") + option(value="") + | 全て + - faq_categories.each do |faq_category| + - if params[:category] == faq_category.name + option(value="#{faq_category.name}" selected=true) + = faq_category.name + - else + option(value="#{faq_category.name}") + = faq_category.name diff --git a/app/views/welcome/faqs/_faqs_pc_filter.html.slim b/app/views/welcome/faqs/_faqs_pc_filter.html.slim new file mode 100644 index 00000000000..6fa083915b6 --- /dev/null +++ b/app/views/welcome/faqs/_faqs_pc_filter.html.slim @@ -0,0 +1,12 @@ +.side-filter + .a-card + ul.side-filter__items + li.side-filter__item + = link_to faq_path(anchor: 'faqs'), + class: "side-filter__item-link #{params[:category] ? '' : 'is-active'}" do + | 全て + - faq_categories.each do |faq_category| + li.side-filter__item + = link_to faq_path(category: faq_category.name, anchor: 'faqs'), + class: "side-filter__item-link #{params[:category] == faq_category.name ? 'is-active' : ''}" do + = faq_category.name diff --git a/app/views/welcome/training.html.slim b/app/views/welcome/training.html.slim index f0a4d5e6998..7bba7aef36c 100644 --- a/app/views/welcome/training.html.slim +++ b/app/views/welcome/training.html.slim @@ -740,69 +740,13 @@ article.lp | よくある質問 .lp-content__end .lp-faqs__items - section.lp-faq - .lp-faq__inner - header.lp-faq__header - h4.lp-faq__title - | 研修をより効果的にするための何かアドバイスはありますか? - .lp-faq__body - .a-short-text - p - | まずは、複数人で研修を始めることをおすすめします。 - p - | これまでの経験上、貴社にとっても研修を受ける本人にとっても、 - | 一人より二人以上で研修を始める方がメリットが多いです。 - | 例えば、進み具合を競ったり、お互いつまずいたところを教え合ったり、お互い励まし合ったりなど、 - | お互いにモチベーションが高め合いながらスムーズに研修を進められます。 - p - | また、同じ企業の研修生同士だけでなく、フィヨルドブートキャンプ内のイベントに参加するなどして、 - | 一般の受講生やメンターや卒業生と積極的に関わりを持ち、 - | コミュニティとしてのフィヨルドブートキャンプを活用することも、 - | モチベーションの維持に大きく役立ちますので、 - | おすすめします。 - hr.a-border - section.lp-faq - .lp-faq__inner - header.lp-faq__header - h4.lp-faq__title - | 研修ではなく福利厚生としての利用の場合、一般受講生として利用ができますか? - .lp-faq__body - .a-short-text - p - | 以前はできたのですが、2024年7月24日に規約の変更を行い、現在は研修目的でなくても - | 企業での利用の場合は法人利用としてご利用いただくことになりました。 - p - | 理由は、FBCが月額の利用料と提携企業への卒業生紹介の際に発生する紹介料によって成り立っているため、 - | 法人利用の場合、後者の収益が立たないためです。 - p - | 研修目的ではなくても、法人利用限定の機能(社内メンターの招待、請求書払いなど)は - | ご利用いただけます。 - hr.a-border - section.lp-faq - .lp-faq__inner - header.lp-faq__header - h4.lp-faq__title - | 法人利用の料金が高いのはなぜですか? - .lp-faq__body - .a-short-text - p - | 法人利用の料金が高い理由は2つあります。 - p - | まず、法人利用では業務時間内にFBCでの学習を行うため、一般利用者よりも多くのアウトプットが求められます。 - | これにより、メンターのリソースがより多く必要とされるためです。 - p - | 二つ目の理由は、FBCが月額の利用料と提携企業への卒業生紹介の際に発生する紹介料によって - | 成り立っている点です。 - | 法人利用の場合、後者の収益が立たないため、一般利用よりも高額が設定されています。 - hr.a-border - section.lp-faq - .lp-faq__inner - header.lp-faq__header - h4.lp-faq__title - | 研修はオンラインのみでしょうか? - .lp-faq__body - .a-short-text - p - | オンラインのみになります。 - | 現在、FBCには教室や学習会場のなどの用意はございません。 - hr.a-border + - @faqs.each do |faq| + section.lp-faq + .lp-faq__inner + header.lp-faq__header + h4.lp-faq__title + = faq.question + .lp-faq__body + .a-short-text + = faq.answer + hr.a-border diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 1ba32c1ed76..b0593d4557b 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -14,5 +14,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym "API" inflect.acronym "AI" + inflect.acronym "FAQ" inflect.irregular "buzz", "buzzes" end diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 28a465d09de..e0aa5b76cd4 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -47,6 +47,8 @@ ja: book: 参考書籍 regular_event: 定期イベント request_retirement: 退会申請 + faq: FAQ + faq_category: FAQカテゴリ attributes: user: login_name: アカウント @@ -303,6 +305,12 @@ ja: request_retirement/keep_data/option: keep: 残す delete: 削除する + faq: + question: 質問 + answer: 回答 + faq_category: カテゴリー + faq_category: + name: カテゴリー名 enums: user: job: diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 84738149d17..94656c6230c 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -11,5 +11,9 @@ resources :campaigns, only: %i(new create index edit update) resources :inquiries, only: %i(index show) resources :invitation_url, only: %i(index) + resources :faqs, only: %i(index new create show edit update destroy) + resources :faq_categories, only: %i(index new create edit update destroy) do + resources :faqs, only: %i(index update), controller: 'faq_categories/faqs' + end end end diff --git a/db/data/20230716015559_add_multi_faq.rb b/db/data/20230716015559_add_multi_faq.rb new file mode 100644 index 00000000000..63142c29749 --- /dev/null +++ b/db/data/20230716015559_add_multi_faq.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +class AddMultiFAQ < ActiveRecord::Migration[6.1] # rubocop:disable Metrics/ClassLength + MULTI_FAQ = [ + { + position: 1, + question: '学習を終えるのにどれくらいの時間がかかりますか?', + answer: "かかる時間は人それぞれですが、プログラム経験が全くない人の場合、1,200時間くらいかかる方が多いです。\n\n覚えが早い、遅いも関係しますが、短時間でも毎日必ず学習する場合と間が空いてしまう場合など、時間のかけかたにも影響があったりもします。\n\n知的好奇心が求めるままに気になったこともきっちり調べる人は、時間がかかっても、就職後に仕事やプログラマーコミュニティで大活躍をしているので、卒業までの時間が短ければいいとは考えていません。\n\nフィヨルドブートキャンプとしては、卒業までの時間が長くなり過ぎないように、泣く泣くカリキュラムを削ったりなどもしているのですが、年々就職希望者に求めるものが増えているので、削っても削っても増えていってしまい、なかなか卒業までの時間を短縮させるのが難しいです。\n\nとはいえ、就職した卒業生は、フィヨルドブートキャンプで学んだことで就職後に役に立たなかったことは何一つなかった、と言ってくれてます(自慢)。" # rubocop:disable Layout/LineLength + }, + { + position: 2, + question: '途中で辞めることは可能ですか?', + answer: "いつでも退会ができます。退会をすると翌月からの請求は止まります。退会をしない限りは請求は止まらず、オンライン学習アプリにログインをしていなくても請求されてしまうので、学習をやめる場合は必ず退会を行ってください。\n\nフィヨルドブートキャンプでは、プログラマーになることを考えたけど、自分には向いていなかったことに気づくことも大きな価値だと考えています。\n\nプログラマーに向いてない、プログラミングが楽しめないと思ったときのリスクを最小限にするため、一度に大金を支払うのではなく、月額課金制にしています。" # rubocop:disable Layout/LineLength + }, + { + position: 3, + question: '学習はオンラインだけですか?', + answer: "オンラインだけです。\n\nオンラインだけですが、オンラインの強みを活かして、住んでいる場所を問わずに参加できる飲み会、卒業式、受講生が主催の勉強会、受講生が主催の発表会、質問タイム、雑談会などの何かしらのオンラインイベントが毎日のように開催されています。" + }, + { + position: 4, + question: '休会をすることは可能ですか?', + answer: '最大3ヶ月まで休会をすることが可能です。' + }, + { + position: 5, + question: 'わからないことがあった場合、質問はできますか?', + answer: "できます。\n\nフィヨルドブートキャンプの前身であるフィヨルドインターンから数えると、とても長くプログラミング教育をやってきたのですが、その歴史は受講生が抱えているわからないことを、どうやって拾い上げるかの歴史でもありました。\n\n人によっては質問をすることが恥ずかしいと感じてしまう人もいまずが、質問をすることはプログラマーにとってとても重要なスキルの一つと考えているため、それを鍛えるためにたくさんの質問をする方法を用意しています。\n\n学習をする度に提出する日報に質問を書く、フィヨルドブートキャンプの学習アプリのQ&A機能を使って質問、質問ようのチャットのチャンネルで質問、質問タイムでビデオチャットで質問、メンターにペアプログラミングを依頼...など、あらゆる質問をする手段を用意しています。" # rubocop:disable Layout/LineLength + }, + { + position: 6, + question: '退会した月は料金が日割りで請求されますか?', + answer: '退会した月も、日割りでの請求はなく、その月のご利用料金1ヶ月分をお支払い頂きますことをご了承ください。' + }, + { + position: 7, + question: '仕事をしながら学習をしたいのであまり時間が取れないのですがそれでも参加できますか?', + answer: '学習時間の制限はありませんので、自分のできるペースで学習を進めることができます。ただ、卒業までに時間がかかってしまうと、それだけ合計の料金はかかってしまいます。' + }, + { + position: 8, + question: '就職先が決まっているのですが、就職までの間学習をしたいです。その場合、参加は可能ですか?', + answer: 'はい、そのような場合も参加可能です。実際、フィヨルドブートキャンプは企業の新人研修でも利用されています。もし、企業の研修として利用する場合は法人用のプランがございますのでご連絡ください。' + }, + { + position: 9, + question: '転職は考えていないのですが、スキルアップのためプログラミングの勉強がしたいです。その場合、参加は可能ですか?', + answer: '可能です。フィヨルドブートキャンプは就職できるためのスキルを身につけることを目標にしていますが、就職を必ずしなくてはいけないという縛りはありません。' + }, + { + position: 10, + question: '現在、大学に在学中なのですが、参加できますか?', + answer: '学生でも参加可能です。年齢に制限はありません。' + }, + { + position: 11, + question: 'Linuxで学習をしたいのですが可能ですか?', + answer: 'Linuxでも可能ですが条件があります。用意しているカリキュラムは Mac を想定して作られています。そのため、Linux でカリキュラムを行う場合は自分自身でMac用のカリキュラムをLinuxに合わせて操作方法などを変える必要があります。それができるだけのLinuxのスキルがある方のみ、Linuxで学習をすることをOKとしています。' # rubocop:disable Layout/LineLength + }, + { + position: 12, + question: 'Windowsで学習をしたいのですが可能ですか?', + answer: "Windowsでも可能ですが、WSL2を使うのでWSL2対応のパソコンであることが必須です。WSL2についてとWSL2に対応しているかの調べ方は下記にかいてあります。そちらを読んで理解できないようであればMacを購入してから受講することをおすすめしております。\n\n[WSL2が使えるマシンとは?](https://bootcamp.fjord.jp/articles/32)\n[WSL2でLinuxを使おう](https://bootcamp.fjord.jp/articles/6)" # rubocop:disable Layout/LineLength + }, + { + position: 13, + question: '古い OS のままで学習をしたいのですが可能ですか?', + answer: 'カリキュラムが、最新の Mac の OS を想定して作られています。多少古い場合はどうにかなるかもしれませんが、OS のせいで学習に詰まることが出てくるかもしれません。何かしら事情があってお手持ちの Mac のバージョンが上げられない場合は、それとは別の Mac を用意してから参加されることをおすすめします。' # rubocop:disable Layout/LineLength + }, + { + position: 14, + question: '転職エージェントを使って就職活動をすることは可能ですか?', + answer: 'はい、就職活動について制限はありません。希望があれば弊社でもエージェントを紹介することも可能です。' + }, + { + position: 15, + question: '卒業生が Ruby 界隈で有名な企業に就職していますが、私もそういった企業に就職できますか?', + answer: '個人によります。弊社と仲良くしてくれてる Ruby 界隈で有名な企業に推薦をする場合もありますが、推薦をする側も責任があるため、誰でも推薦をすることができるとは言えません。ただし、そもそもフィヨルドブートキャンプを卒業することはとても難しく、それを達成できた時点で十分推薦できる人物だと判断することがほとんどです。' # rubocop:disable Layout/LineLength + }, + { + position: 16, + question: 'プログラマーは男性が多いと聞いたことがありますが、女性でも大丈夫ですか?', + answer: "全く問題ありません。\n\nフィヨルドブートキャンプでは、入会時に性別を聞くことはしていないので、正確な数字は出せないのですが、女性比率は男性 : 女性で半々に近いくらい高いです。イベントによっては女性の参加者の方が多いこともあります。\n\nたくさんの女性が卒業しプログラマーとして就職し活躍しています。" # rubocop:disable Layout/LineLength + }, + { + position: 17, + question: '30代なのですが、エンジニアとして就職することは可能ですか?', + answer: "今までの観測範囲から言いますと、30代の方はそれほど不利な感じはないです。特別に若手20代を中心に考えています、という会社がたまにあるぐらいです。\n\n40代で転職した方もいますが、40代以上の方の場合はかなり難しいです。\n\n年齢に関わらず、一定のプログラミングスキルがあることは最低条件で、就職をするためにはそれ以外の要素も必要ですが、30代以下に比べてより高いレベルでの、今までのキャリアで培った特別な経験や実績、高いコミュニケーション能力を求められる傾向にあります。\n\n30代半ばを超えていたら、今持っているスキル + プログラミングスキルを使って、どの会社でどんな仕事をすれば自分の価値を出せるか?そのためには、どのような就活を行うべきかの戦略とプランも考えておく必要があります。\n\nまた、40代以上に強そうな転職エージェントを探し、予め相談してからフィヨルドブートキャンプを始めるかを検討するのもいいかもしれません。" # rubocop:disable Layout/LineLength + }, + { + position: 18, + question: '会社の新人研修など法人での利用はできますか?', + answer: "可能です。\n\n法人でのご利用については[こちら](https://bootcamp.fjord.jp/training)に詳細を記載しております。" + }, + { + position: 19, + question: '何をしたら卒業になりますか?', + answer: "全てのプラクティスを修了したら卒業になります。\n\n最後のプラクティスが自作サービスをリリースする、というものになっています。月に一回行っている卒業式で、自作サービスの発表をしてもらい、その後に卒業証書授与を行っています。" + }, + { + position: 20, + question: '利用料の領収書は発行してもらえますか?', + answer: "発行します。\n\nStripe という決済サービスを利用しているのですが、それが発行する領収書をダウンロードすることができます。" + }, + { + position: 21, + question: 'フィヨルドブートキャンプを利用していることを誰にも知られたくないのですが、匿名で参加はできますか?', + answer: "可能です。\n\nフィヨルドブートキャンプは、年齢、性別、本名を伝えることなく利用することができます。フィヨルドブートキャンプ内では、お互いをニックネームで呼び合っています。\n\nオンラインイベントの中では、ビデオチャットで顔出しをするイベントもありますが、カメラをオンにすることは強制していません。" # rubocop:disable Layout/LineLength + } + ].freeze + + def up + MULTI_FAQ.each do |faq| + position, question, answer = faq.values_at(:position, :faq, :answer) + FAQ.create(position:, question:, answer:) + end + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/fixtures/faq_categories.yml b/db/fixtures/faq_categories.yml new file mode 100644 index 00000000000..a10c3ad1995 --- /dev/null +++ b/db/fixtures/faq_categories.yml @@ -0,0 +1,41 @@ +faq_categories1: + name: 学習内容について + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + position: 1 + +faq_categories2: + name: 学習環境について + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + position: 2 + +faq_categories3: + name: 利用料について + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + position: 3 + +faq_categories4: + name: 就職について + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + position: 4 + +faq_categories5: + name: 入会について + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + position: 5 + +faq_categories6: + name: 退会、休会、卒業について + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + position: 6 + +faq_categories7: + name: 法人利用について + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + position: 7 diff --git a/db/fixtures/faqs.yml b/db/fixtures/faqs.yml new file mode 100644 index 00000000000..9091355f50c --- /dev/null +++ b/db/fixtures/faqs.yml @@ -0,0 +1,217 @@ +faq1: + position: 1 + question: 学習を終えるのにどれくらいの時間がかかりますか? + answer: "\ + かかる時間は人それぞれですが、プログラム経験が全くない人の場合、1,200時間くらいかかる方が多いです。\n\n\ + 覚えが早い、遅いも関係しますが、短時間でも毎日必ず学習する場合と間が空いてしまう場合など、時間のかけかたにも影響があったりもします。\n\n\ + 知的好奇心が求めるままに気になったこともきっちり調べる人は、時間がかかっても、就職後に仕事やプログラマーコミュニティで大活躍をしているので、卒業までの時間が短ければいいとは考えていません。\n\n\ + フィヨルドブートキャンプとしては、卒業までの時間が長くなり過ぎないように、泣く泣くカリキュラムを削ったりなどもしているのですが、年々就職希望者に求めるものが増えているので、削っても削っても増えていってしまい、なかなか卒業までの時間を短縮させるのが難しいです。\n\n\ + とはいえ、就職した卒業生は、フィヨルドブートキャンプで学んだことで就職後に役に立たなかったことは何一つなかった、と言ってくれてます(自慢)。\ + " + faq_category: faq_categories1 + +faq2: + position: 2 + question: 途中で辞めることは可能ですか? + answer: "\ + いつでも退会ができます。退会をすると翌月からの請求は止まります。退会をしない限りは請求は止まらず、オンライン学習アプリにログインをしていなくても請求されてしまうので、学習をやめる場合は必ず退会を行ってください。\n\n\ + フィヨルドブートキャンプでは、プログラマーになることを考えたけど、自分には向いていなかったことに気づくことも大きな価値だと考えています。\n\n\ + プログラマーに向いてない、プログラミングが楽しめないと思ったときのリスクを最小限にするため、一度に大金を支払うのではなく、月額課金制にしています。\ + " + faq_category: faq_categories6 + +faq3: + position: 3 + question: 学習はオンラインだけですか? + answer: "\ + オンラインだけです。\n\n\ + オンラインだけですが、オンラインの強みを活かして、住んでいる場所を問わずに参加できる飲み会、卒業式、受講生が主催の勉強会、受講生が主催の発表会、質問タイム、雑談会などの何かしらのオンラインイベントが毎日のように開催されています。\ + " + faq_category: faq_categories2 + +faq4: + position: 4 + question: 休会をすることは可能ですか? + answer: 最大3ヶ月まで休会をすることが可能です。 + faq_category: faq_categories6 + +faq5: + position: 5 + question: わからないことがあった場合、質問はできますか? + answer: "\ + できます。\n\n\ + フィヨルドブートキャンプの前身であるフィヨルドインターンから数えると、とても長くプログラミング教育をやってきたのですが、その歴史は受講生が抱えているわからないことを、どうやって拾い上げるかの歴史でもありました。\n\n\ + 人によっては質問をすることが恥ずかしいと感じてしまう人もいまずが、質問をすることはプログラマーにとってとても重要なスキルの一つと考えているため、それを鍛えるためにたくさんの質問をする方法を用意しています。\n\n\ + 学習をする度に提出する日報に質問を書く、フィヨルドブートキャンプの学習アプリのQ&A機能を使って質問、質問ようのチャットのチャンネルで質問、質問タイムでビデオチャットで質問、メンターにペアプログラミングを依頼...など、あらゆる質問をする手段を用意しています。\ + " + faq_category: faq_categories2 + +faq6: + position: 6 + question: 退会した月は料金が日割りで請求されますか? + answer: 退会した月も、日割りでの請求はなく、その月のご利用料金1ヶ月分をお支払い頂きますことをご了承ください。 + faq_category: faq_categories3 + +faq7: + position: 7 + question: 仕事をしながら学習をしたいのであまり時間が取れないのですがそれでも参加できますか? + answer: 学習時間の制限はありませんので、自分のできるペースで学習を進めることができます。ただ、卒業までに時間がかかってしまうと、それだけ合計の料金はかかってしまいます。 + faq_category: faq_categories5 + +faq8: + position: 8 + question: 就職先が決まっているのですが、就職までの間学習をしたいです。その場合、参加は可能ですか? + answer: はい、そのような場合も参加可能です。実際、フィヨルドブートキャンプは企業の新人研修でも利用されています。もし、企業の研修として利用する場合は法人用のプランがございますのでご連絡ください。 + faq_category: faq_categories5 + +faq9: + position: 9 + question: 転職は考えていないのですが、スキルアップのためプログラミングの勉強がしたいです。その場合、参加は可能ですか? + answer: 可能です。フィヨルドブートキャンプは就職できるためのスキルを身につけることを目標にしていますが、就職を必ずしなくてはいけないという縛りはありません。 + faq_category: faq_categories4 + +faq10: + position: 10 + question: 現在、大学に在学中なのですが、参加できますか? + answer: 学生でも参加可能です。年齢に制限はありません。 + faq_category: faq_categories5 + +faq11: + position: 11 + question: Linuxで学習をしたいのですが可能ですか? + answer: Linuxでも可能ですが条件があります。用意しているカリキュラムは Mac を想定して作られています。そのため、Linux でカリキュラムを行う場合は自分自身でMac用のカリキュラムをLinuxに合わせて操作方法などを変える必要があります。それができるだけのLinuxのスキルがある方のみ、Linuxで学習をすることをOKとしています。 + faq_category: faq_categories2 + +faq12: + position: 12 + question: Windowsで学習をしたいのですが可能ですか? + answer: "\ + Windowsでも可能ですが、WSL2を使うのでWSL2対応のパソコンであることが必須です。WSL2についてとWSL2に対応しているかの調べ方は下記にかいてあります。そちらを読んで理解できないようであればMacを購入してから受講することをおすすめしております。\n\n\ + [WSL2が使えるマシンとは?](https://bootcamp.fjord.jp/articles/32)\n\ + [WSL2でLinuxを使おう](https://bootcamp.fjord.jp/articles/6)\ + " + faq_category: faq_categories2 + +faq13: + position: 13 + question: 古い OS のままで学習をしたいのですが可能ですか? + answer: カリキュラムが、最新の Mac の OS を想定して作られています。多少古い場合はどうにかなるかもしれませんが、OS のせいで学習に詰まることが出てくるかもしれません。何かしら事情があってお手持ちの Mac のバージョンが上げられない場合は、それとは別の Mac を用意してから参加されることをおすすめします。 + faq_category: faq_categories2 + +faq14: + position: 14 + question: 転職エージェントを使って就職活動をすることは可能ですか? + answer: はい、就職活動について制限はありません。希望があれば弊社でもエージェントを紹介することも可能です。 + faq_category: faq_categories4 + +faq15: + position: 15 + question: 卒業生が Ruby 界隈で有名な企業に就職していますが、私もそういった企業に就職できますか? + answer: 個人によります。弊社と仲良くしてくれてる Ruby 界隈で有名な企業に推薦をする場合もありますが、推薦をする側も責任があるため、誰でも推薦をすることができるとは言えません。ただし、そもそもフィヨルドブートキャンプを卒業することはとても難しく、それを達成できた時点で十分推薦できる人物だと判断することがほとんどです。 + faq_category: faq_categories4 + +faq16: + position: 16 + question: プログラマーは男性が多いと聞いたことがありますが、女性でも大丈夫ですか? + answer: "\ + 全く問題ありません。\n\n\ + フィヨルドブートキャンプでは、入会時に性別を聞くことはしていないので、正確な数字は出せないのですが、女性比率は男性 : 女性で半々に近いくらい高いです。イベントによっては女性の参加者の方が多いこともあります。\n\n\ + たくさんの女性が卒業しプログラマーとして就職し活躍しています。\ + " + faq_category: faq_categories4 + +faq17: + position: 17 + question: 30代なのですが、エンジニアとして就職することは可能ですか? + answer: "\ + 今までの観測範囲から言いますと、30代の方はそれほど不利な感じはないです。特別に若手20代を中心に考えています、という会社がたまにあるぐらいです。\n\n\ + 40代で転職した方もいますが、40代以上の方の場合はかなり難しいです。\n\n\ + 年齢に関わらず、一定のプログラミングスキルがあることは最低条件で、就職をするためにはそれ以外の要素も必要ですが、30代以下に比べてより高いレベルでの、今までのキャリアで培った特別な経験や実績、高いコミュニケーション能力を求められる傾向にあります。\n\n\ + 30代半ばを超えていたら、今持っているスキル + プログラミングスキルを使って、どの会社でどんな仕事をすれば自分の価値を出せるか?そのためには、どのような就活を行うべきかの戦略とプランも考えておく必要があります。\n\n\ + また、40代以上に強そうな転職エージェントを探し、予め相談してからフィヨルドブートキャンプを始めるかを検討するのもいいかもしれません。\ + " + faq_category: faq_categories4 + +faq18: + position: 18 + question: 会社の新人研修など法人での利用はできますか? + answer: "\ + 可能です。\n\n\ + 法人でのご利用については[こちら](https://bootcamp.fjord.jp/training)に詳細を記載しております。\ + " + faq_category: faq_categories7 + +faq19: + position: 19 + question: 何をしたら卒業になりますか? + answer: "\ + 全てのプラクティスを修了したら卒業になります。\n\n\ + 最後のプラクティスが自作サービスをリリースする、というものになっています。月に一回行っている卒業式で、自作サービスの発表をしてもらい、その後に卒業証書授与を行っています。\ + " + faq_category: faq_categories6 + +faq20: + position: 20 + question: 利用料の領収書は発行してもらえますか? + answer: "\ + 発行します。\n\n\ + Stripe という決済サービスを利用しているのですが、それが発行する領収書をダウンロードすることができます。\ + " + faq_category: faq_categories3 + +faq21: + position: 21 + question: フィヨルドブートキャンプを利用していることを誰にも知られたくないのですが、匿名で参加はできますか? + answer: "\ + 可能です。\n\n\ + フィヨルドブートキャンプは、年齢、性別、本名を伝えることなく利用することができます。フィヨルドブートキャンプ内では、お互いをニックネームで呼び合っています。\n\n\ + オンラインイベントの中では、ビデオチャットで顔出しをするイベントもありますが、カメラをオンにすることは強制していません。\ + " + faq_category: faq_categories5 + +faq22: + position: 22 + question: 研修をより効果的にするための何かアドバイスはありますか? + answer: |- + まずは、複数人で研修を始めることをおすすめします。 + + これまでの経験上、貴社にとっても研修を受ける本人にとっても、一人より二人以上で研修を始める方がメリットが多いです。例えば、進み具合を競ったり、お互いつまずいたところを教え合ったり、お互い励まし合ったりなど、お互いにモチベーションが高め合いながらスムーズに研修を進められます。 + + また、同じ企業の研修生同士だけでなく、フィヨルドブートキャンプ内のイベントに参加するなどして、一般の受講生やメンターや卒業生と積極的に関わりを持ち、コミュニティとしてのフィヨルドブートキャンプを活用することも、モチベーションの維持に大きく役立ちますので、おすすめします。 + faq_category: faq_categories7 + +faq23: + position: 23 + question: 研修ではなく福利厚生としての利用の場合、一般受講生として利用ができますか? + answer: |- + 以前はできたのですが、2024年7月24日に規約の変更を行い、現在は研修目的でなくても企業での利用の場合は法人利用としてご利用いただくことになりました。 + + 理由は、FBCが月額の利用料と提携企業への卒業生紹介の際に発生する紹介料によって成り立っているため、法人利用の場合、後者の収益が立たないためです。 + + 研修目的ではなくても、法人利用限定の機能(社内メンターの招待、請求書払いなど)はご利用いただけます。 + faq_category: faq_categories7 + +faq24: + position: 24 + question: 法人利用の料金が高いのはなぜですか? + answer: |- + 法人利用の料金が高い理由は2つあります。 + + まず、法人利用では業務時間内にFBCでの学習を行うため、一般利用者よりも多くのアウトプットが求められます。これにより、メンターのリソースがより多く必要とされるためです。 + + 二つ目の理由は、FBCが月額の利用料と提携企業への卒業生紹介の際に発生する紹介料によって成り立っている点です。法人利用の場合、後者の収益が立たないため、一般利用よりも高額が設定されています。 + faq_category: faq_categories7 + +faq25: + position: 25 + question: 研修はオンラインのみでしょうか? + answer: |- + オンラインのみになります。現在、FBCには教室や学習会場のなどの用意はございません。 + faq_category: faq_categories7 + +faq26: + position: 26 + question: 途中でコースの変更はできますか? + answer: |- + 「Railsエンジニアコース」、「フロントエンドエンジニアコース」間のコース変更は可能です。メンターからレビューを受けている途中の課題がないキリのいいタイミングであれば、好きなタイミングで変更をすることができます。変更の回数制限もありません。 + faq_category: faq_categories1 diff --git a/db/migrate/20230712223805_create_faqs.rb b/db/migrate/20230712223805_create_faqs.rb new file mode 100644 index 00000000000..7f29a844aea --- /dev/null +++ b/db/migrate/20230712223805_create_faqs.rb @@ -0,0 +1,12 @@ +class CreateFaqs < ActiveRecord::Migration[6.1] + def change + create_table :faqs do |t| + t.string :answer, null: false, unique: true + t.string :question, null: false, unique: true + + t.timestamps + end + add_index :faqs, :question, unique: true + add_index :faqs, [:answer, :question], unique: true + end +end diff --git a/db/migrate/20230721033152_add_position_to_faq.rb b/db/migrate/20230721033152_add_position_to_faq.rb new file mode 100644 index 00000000000..47bcd60cdcd --- /dev/null +++ b/db/migrate/20230721033152_add_position_to_faq.rb @@ -0,0 +1,5 @@ +class AddPositionToFAQ < ActiveRecord::Migration[6.1] + def change + add_column :faqs, :position, :integer + end +end diff --git a/db/migrate/20230802001904_change_datatype_answer_of_faq.rb b/db/migrate/20230802001904_change_datatype_answer_of_faq.rb new file mode 100644 index 00000000000..589b328a72d --- /dev/null +++ b/db/migrate/20230802001904_change_datatype_answer_of_faq.rb @@ -0,0 +1,5 @@ +class ChangeDatatypeAnswerOfFAQ < ActiveRecord::Migration[6.1] + def change + change_column :faqs, :answer, :text, null: false, unique: true + end +end diff --git a/db/migrate/20240313020054_create_faq_categories.rb b/db/migrate/20240313020054_create_faq_categories.rb new file mode 100644 index 00000000000..255bd0a5337 --- /dev/null +++ b/db/migrate/20240313020054_create_faq_categories.rb @@ -0,0 +1,10 @@ +class CreateFAQCategories < ActiveRecord::Migration[6.1] + def change + create_table :faq_categories do |t| + t.references :faq, null: false, foreign_key: true + t.string :name, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20240313025400_add_foreign_key_to_faqs.rb b/db/migrate/20240313025400_add_foreign_key_to_faqs.rb new file mode 100644 index 00000000000..f651fb30674 --- /dev/null +++ b/db/migrate/20240313025400_add_foreign_key_to_faqs.rb @@ -0,0 +1,5 @@ +class AddForeignKeyToFaqs < ActiveRecord::Migration[6.1] + def change + add_reference :faqs, :faq_categories, foreign_key: true + end +end diff --git a/db/migrate/20240327122855_add_not_null_constraint_to_faq_categories_id.rb b/db/migrate/20240327122855_add_not_null_constraint_to_faq_categories_id.rb new file mode 100644 index 00000000000..700f6bf6bae --- /dev/null +++ b/db/migrate/20240327122855_add_not_null_constraint_to_faq_categories_id.rb @@ -0,0 +1,5 @@ +class AddNotNullConstraintToFAQCategoriesId < ActiveRecord::Migration[6.1] + def change + change_column_null :faqs, :faq_categories_id, false + end +end diff --git a/db/migrate/20240327123100_add_not_null_constraint_to_name_in_faq_categories.rb b/db/migrate/20240327123100_add_not_null_constraint_to_name_in_faq_categories.rb new file mode 100644 index 00000000000..d22ca90e843 --- /dev/null +++ b/db/migrate/20240327123100_add_not_null_constraint_to_name_in_faq_categories.rb @@ -0,0 +1,5 @@ +class AddNotNullConstraintToNameInFAQCategories < ActiveRecord::Migration[6.1] + def change + change_column_null :faq_categories, :name, false + end +end diff --git a/db/migrate/20240506064858_rename_faq_categories_id_to_faq_category_id.rb b/db/migrate/20240506064858_rename_faq_categories_id_to_faq_category_id.rb new file mode 100644 index 00000000000..c4d6fd0dcb7 --- /dev/null +++ b/db/migrate/20240506064858_rename_faq_categories_id_to_faq_category_id.rb @@ -0,0 +1,5 @@ +class RenameFAQCategoriesIdToFAQCategoryId < ActiveRecord::Migration[6.1] + def change + rename_column :faqs, :faq_categories_id, :faq_category_id + end +end diff --git a/db/migrate/20240506101642_add_unique_index_to_faq_categories_name.rb b/db/migrate/20240506101642_add_unique_index_to_faq_categories_name.rb new file mode 100644 index 00000000000..348c087b7b0 --- /dev/null +++ b/db/migrate/20240506101642_add_unique_index_to_faq_categories_name.rb @@ -0,0 +1,5 @@ +class AddUniqueIndexToFAQCategoriesName < ActiveRecord::Migration[6.1] + def change + add_index :faq_categories, :name, unique: true + end +end diff --git a/db/migrate/20241010132610_remove_faq_id_from_faq_categories.rb b/db/migrate/20241010132610_remove_faq_id_from_faq_categories.rb new file mode 100644 index 00000000000..c35068bcd82 --- /dev/null +++ b/db/migrate/20241010132610_remove_faq_id_from_faq_categories.rb @@ -0,0 +1,6 @@ +class RemoveFAQIdFromFAQCategories < ActiveRecord::Migration[6.1] + def change + remove_foreign_key :faq_categories, :faqs + remove_column :faq_categories, :faq_id + end +end diff --git a/db/migrate/20241103082456_add_position_to_faq_categories.rb b/db/migrate/20241103082456_add_position_to_faq_categories.rb new file mode 100644 index 00000000000..feed59a14d3 --- /dev/null +++ b/db/migrate/20241103082456_add_position_to_faq_categories.rb @@ -0,0 +1,5 @@ +class AddPositionToFAQCategories < ActiveRecord::Migration[6.1] + def change + add_column :faq_categories, :position, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 0c91e92aa40..1feea419347 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_08_21_190009) do +ActiveRecord::Schema.define(version: 2024_11_03_082456) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" @@ -77,8 +77,8 @@ t.datetime "published_at" t.text "summary" t.integer "thumbnail_type", default: 0, null: false - t.boolean "display_thumbnail_in_body", default: true, null: false t.string "token" + t.boolean "display_thumbnail_in_body", default: true, null: false t.index ["user_id"], name: "index_articles_on_user_id" end @@ -267,6 +267,26 @@ t.index ["user_id"], name: "index_external_entries_on_user_id" end + create_table "faq_categories", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.integer "position" + t.index ["name"], name: "index_faq_categories_on_name", unique: true + end + + create_table "faqs", force: :cascade do |t| + t.text "answer", null: false + t.string "question", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.integer "position" + t.bigint "faq_category_id", null: false + t.index ["answer", "question"], name: "index_faqs_on_answer_and_question", unique: true + t.index ["faq_category_id"], name: "index_faqs_on_faq_category_id" + t.index ["question"], name: "index_faqs_on_question", unique: true + end + create_table "followings", force: :cascade do |t| t.integer "follower_id" t.integer "followed_id" @@ -762,9 +782,9 @@ t.string "country_code" t.string "subdivision_code" t.boolean "auto_retire", default: true - t.boolean "invoice_payment", default: false, null: false t.integer "editor" t.string "other_editor" + t.boolean "invoice_payment", default: false, null: false t.boolean "hide_mentor_profile", default: false, null: false t.integer "experiences", default: 0, null: false t.index ["course_id"], name: "index_users_on_course_id" @@ -809,6 +829,7 @@ add_foreign_key "check_boxes", "survey_questions" add_foreign_key "discord_profiles", "users" add_foreign_key "external_entries", "users" + add_foreign_key "faqs", "faq_categories" add_foreign_key "hibernations", "users" add_foreign_key "images", "users" add_foreign_key "learning_minute_statistics", "practices" diff --git a/db/seeds.rb b/db/seeds.rb index acc291f14b4..8680ef27d2b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -25,6 +25,8 @@ discord_profiles events external_entries + faqs + faq_categories followings reports learning_times diff --git a/test/fixtures/faq_categories.yml b/test/fixtures/faq_categories.yml new file mode 100644 index 00000000000..1862c0060fd --- /dev/null +++ b/test/fixtures/faq_categories.yml @@ -0,0 +1,41 @@ +faq_category1: + name: 学習内容について + position: 1 + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + +faq_category2: + name: 学習環境について + position: 2 + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + +faq_category3: + name: 利用料について + position: 3 + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + +faq_category4: + name: 就職について + position: 4 + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + +faq_category5: + name: 入会について + position: 5 + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + +faq_category6: + name: 退会、休会、卒業について + position: 6 + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + +faq_category7: + name: 法人利用について + position: 7 + created_at: <%= Time.current %> + updated_at: <%= Time.current %> diff --git a/test/fixtures/faqs.yml b/test/fixtures/faqs.yml new file mode 100644 index 00000000000..03dea829725 --- /dev/null +++ b/test/fixtures/faqs.yml @@ -0,0 +1,170 @@ +faq1: + position: 1 + question: 学習を終えるのにどれくらいの時間がかかりますか? + answer: "\ + かかる時間は人それぞれですが、プログラム経験が全くない人の場合、1,200時間くらいかかる方が多いです。\n\n\ + 覚えが早い、遅いも関係しますが、短時間でも毎日必ず学習する場合と間が空いてしまう場合など、時間のかけかたにも影響があったりもします。\n\n\ + 知的好奇心が求めるままに気になったこともきっちり調べる人は、時間がかかっても、就職後に仕事やプログラマーコミュニティで大活躍をしているので、卒業までの時間が短ければいいとは考えていません。\n\n\ + フィヨルドブートキャンプとしては、卒業までの時間が長くなり過ぎないように、泣く泣くカリキュラムを削ったりなどもしているのですが、年々就職希望者に求めるものが増えているので、削っても削っても増えていってしまい、なかなか卒業までの時間を短縮させるのが難しいです。\n\n\ + とはいえ、就職した卒業生は、フィヨルドブートキャンプで学んだことで就職後に役に立たなかったことは何一つなかった、と言ってくれてます(自慢)。\ + " + faq_category: faq_categories1 + +faq2: + position: 2 + question: 途中で辞めることは可能ですか? + answer: "\ + いつでも退会ができます。退会をすると翌月からの請求は止まります。退会をしない限りは請求は止まらず、オンライン学習アプリにログインをしていなくても請求されてしまうので、学習をやめる場合は必ず退会を行ってください。\n\n\ + フィヨルドブートキャンプでは、プログラマーになることを考えたけど、自分には向いていなかったことに気づくことも大きな価値だと考えています。\n\n\ + プログラマーに向いてない、プログラミングが楽しめないと思ったときのリスクを最小限にするため、一度に大金を支払うのではなく、月額課金制にしています。\ + " + faq_category: faq_categories6 + +faq3: + position: 3 + question: 学習はオンラインだけですか? + answer: "\ + オンラインだけです。\n\n\ + オンラインだけですが、オンラインの強みを活かして、住んでいる場所を問わずに参加できる飲み会、卒業式、受講生が主催の勉強会、受講生が主催の発表会、質問タイム、雑談会などの何かしらのオンラインイベントが毎日のように開催されています。\ + " + faq_category: faq_categories2 + +faq4: + position: 4 + question: 休会をすることは可能ですか? + answer: 最大3ヶ月まで休会をすることが可能です。 + faq_category: faq_categories6 + +faq5: + position: 5 + question: わからないことがあった場合、質問はできますか? + answer: "\ + できます。\n\n\ + フィヨルドブートキャンプの前身であるフィヨルドインターンから数えると、とても長くプログラミング教育をやってきたのですが、その歴史は受講生が抱えているわからないことを、どうやって拾い上げるかの歴史でもありました。\n\n\ + 人によっては質問をすることが恥ずかしいと感じてしまう人もいまずが、質問をすることはプログラマーにとってとても重要なスキルの一つと考えているため、それを鍛えるためにたくさんの質問をする方法を用意しています。\n\n\ + 学習をする度に提出する日報に質問を書く、フィヨルドブートキャンプの学習アプリのQ&A機能を使って質問、質問ようのチャットのチャンネルで質問、質問タイムでビデオチャットで質問、メンターにペアプログラミングを依頼...など、あらゆる質問をする手段を用意しています。\ + " + faq_category: faq_categories2 + +faq6: + position: 6 + question: 退会した月は料金が日割りで請求されますか? + answer: 退会した月も、日割りでの請求はなく、その月のご利用料金1ヶ月分をお支払い頂きますことをご了承ください。 + faq_category: faq_categories3 + +faq7: + position: 7 + question: 仕事をしながら学習をしたいのであまり時間が取れないのですがそれでも参加できますか? + answer: 学習時間の制限はありませんので、自分のできるペースで学習を進めることができます。ただ、卒業までに時間がかかってしまうと、それだけ合計の料金はかかってしまいます。 + faq_category: faq_categories5 + +faq8: + position: 8 + question: 就職先が決まっているのですが、就職までの間学習をしたいです。その場合、参加は可能ですか? + answer: はい、そのような場合も参加可能です。実際、フィヨルドブートキャンプは企業の新人研修でも利用されています。もし、企業の研修として利用する場合は法人用のプランがございますのでご連絡ください。 + faq_category: faq_categories5 + +faq9: + position: 9 + question: 転職は考えていないのですが、スキルアップのためプログラミングの勉強がしたいです。その場合、参加は可能ですか? + answer: 可能です。フィヨルドブートキャンプは就職できるためのスキルを身につけることを目標にしていますが、就職を必ずしなくてはいけないという縛りはありません。 + faq_category: faq_categories5 + +faq10: + position: 10 + question: 現在、大学に在学中なのですが、参加できますか? + answer: 学生でも参加可能です。年齢に制限はありません。 + faq_category: faq_categories5 + +faq11: + position: 11 + question: Linuxで学習をしたいのですが可能ですか? + answer: Linuxでも可能ですが条件があります。用意しているカリキュラムは Mac を想定して作られています。そのため、Linux でカリキュラムを行う場合は自分自身でMac用のカリキュラムをLinuxに合わせて操作方法などを変える必要があります。それができるだけのLinuxのスキルがある方のみ、Linuxで学習をすることをOKとしています。 + faq_category: faq_categories2 + +faq12: + position: 12 + question: Windowsで学習をしたいのですが可能ですか? + answer: "\ + Windowsでも可能ですが、WSL2を使うのでWSL2対応のパソコンであることが必須です。WSL2についてとWSL2に対応しているかの調べ方は下記にかいてあります。そちらを読んで理解できないようであればMacを購入してから受講することをおすすめしております。\n\n\ + [WSL2が使えるマシンとは?](https://bootcamp.fjord.jp/articles/32)\n\ + [WSL2でLinuxを使おう](https://bootcamp.fjord.jp/articles/6)\ + " + faq_category: faq_categories2 + +faq13: + position: 13 + question: 古い OS のままで学習をしたいのですが可能ですか? + answer: カリキュラムが、最新の Mac の OS を想定して作られています。多少古い場合はどうにかなるかもしれませんが、OS のせいで学習に詰まることが出てくるかもしれません。何かしら事情があってお手持ちの Mac のバージョンが上げられない場合は、それとは別の Mac を用意してから参加されることをおすすめします。 + faq_category: faq_categories2 + +faq14: + position: 14 + question: 転職エージェントを使って就職活動をすることは可能ですか? + answer: はい、就職活動について制限はありません。希望があれば弊社でもエージェントを紹介することも可能です。 + faq_category: faq_categories4 + +faq15: + position: 15 + question: 卒業生が Ruby 界隈で有名な企業に就職していますが、私もそういった企業に就職できますか? + answer: 個人によります。弊社と仲良くしてくれてる Ruby 界隈で有名な企業に推薦をする場合もありますが、推薦をする側も責任があるため、誰でも推薦をすることができるとは言えません。ただし、そもそもフィヨルドブートキャンプを卒業することはとても難しく、それを達成できた時点で十分推薦できる人物だと判断することがほとんどです。 + faq_category: faq_categories4 + +faq16: + position: 16 + question: プログラマーは男性が多いと聞いたことがありますが、女性でも大丈夫ですか? + answer: "\ + 全く問題ありません。\n\n\ + フィヨルドブートキャンプでは、入会時に性別を聞くことはしていないので、正確な数字は出せないのですが、女性比率は男性 : 女性で半々に近いくらい高いです。イベントによっては女性の参加者の方が多いこともあります。\n\n\ + たくさんの女性が卒業しプログラマーとして就職し活躍しています。\ + " + faq_category: faq_categories5 + +faq17: + position: 17 + question: 30代なのですが、エンジニアとして就職することは可能ですか? + answer: "\ + 今までの観測範囲から言いますと、30代の方はそれほど不利な感じはないです。特別に若手20代を中心に考えています、という会社がたまにあるぐらいです。\n\n\ + 40代で転職した方もいますが、40代以上の方の場合はかなり難しいです。\n\n\ + 年齢に関わらず、一定のプログラミングスキルがあることは最低条件で、就職をするためにはそれ以外の要素も必要ですが、30代以下に比べてより高いレベルでの、今までのキャリアで培った特別な経験や実績、高いコミュニケーション能力を求められる傾向にあります。\n\n\ + 30代半ばを超えていたら、今持っているスキル + プログラミングスキルを使って、どの会社でどんな仕事をすれば自分の価値を出せるか?そのためには、どのような就活を行うべきかの戦略とプランも考えておく必要があります。\n\n\ + また、40代以上に強そうな転職エージェントを探し、予め相談してからフィヨルドブートキャンプを始めるかを検討するのもいいかもしれません。\ + " + faq_category: faq_categories4 + +faq18: + position: 18 + question: 会社の新人研修など法人での利用はできますか? + answer: "\ + 可能です。\n\n\ + 法人でのご利用については[こちら](https://bootcamp.fjord.jp/training)に詳細を記載しております。\ + " + faq_category: faq_categories7 + +faq19: + position: 19 + question: 何をしたら卒業になりますか? + answer: "\ + 全てのプラクティスを修了したら卒業になります。\n\n\ + 最後のプラクティスが自作サービスをリリースする、というものになっています。月に一回行っている卒業式で、自作サービスの発表をしてもらい、その後に卒業証書授与を行っています。\ + " + faq_category: faq_categories6 + +faq20: + position: 20 + question: 利用料の領収書は発行してもらえますか? + answer: "\ + 発行します。\n\n\ + Stripe という決済サービスを利用しているのですが、それが発行する領収書をダウンロードすることができます。\ + " + faq_category: faq_categories3 + +faq21: + position: 21 + question: フィヨルドブートキャンプを利用していることを誰にも知られたくないのですが、匿名で参加はできますか? + answer: "\ + 可能です。\n\n\ + フィヨルドブートキャンプは、年齢、性別、本名を伝えることなく利用することができます。フィヨルドブートキャンプ内では、お互いをニックネームで呼び合っています。\n\n\ + オンラインイベントの中では、ビデオチャットで顔出しをするイベントもありますが、カメラをオンにすることは強制していません。\ + " + faq_category: faq_categories24 diff --git a/test/helpers/faq_helper.rb b/test/helpers/faq_helper.rb new file mode 100644 index 00000000000..0dda03245c4 --- /dev/null +++ b/test/helpers/faq_helper.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'test_helper' + +class FAQHelperTest < ActionView::TestCase + test '#format_question' do + faq = faqs(:faq1) + assert_equal format_question(faq.question), "#{faq.question}?" + end + + test '#format_question returns a ? mark' do + faq = faqs(:faq1) + faq.question += '?' + assert_equal format_question(faq.question).count('?'), 1 + end +end diff --git a/test/models/user_test.rb b/test/models/user_test.rb index ee84a2db751..b01a1970b9f 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -745,7 +745,7 @@ class UserTest < ActiveSupport::TestCase no_area_user = users(:komagata) assert_equal tokyo_user.area, '東京都' assert_equal america_user.area, '米国' - assert_equal no_area_user.area, nil + assert_nil no_area_user.area end test '.by_area' do diff --git a/test/system/admin/faq_category_test.rb b/test/system/admin/faq_category_test.rb new file mode 100644 index 00000000000..90d4e89923f --- /dev/null +++ b/test/system/admin/faq_category_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'application_system_test_case' + +class Admin::FaqCategoryest < ApplicationSystemTestCase + test 'display listing FAQCategory' do + visit_with_auth '/admin/faq_categories', 'komagata' + has_css? 'h1.page-main-header__title', text: 'FAQカテゴリー一覧' + end + + test 'create FAQCategory' do + visit_with_auth '/admin/faq_categories/new', 'komagata' + within 'form[name=faq_category]' do + fill_in 'faq_category[name]', with: 'test FAQCategory' + click_button '登録する' + end + assert_text 'FAQカテゴリーを作成しました。' + end + + test 'update FAQ' do + visit_with_auth "/admin/faq_categories/#{faq_categories(:faq_category1).id}/edit", 'komagata' + within 'form[name=faq_category]' do + fill_in 'faq_category[name]', with: 'updated FAQCategory' + click_button '更新する' + end + assert_text 'FAQカテゴリーを更新しました。' + end + + test 'delete FAQ' do + visit_with_auth "/admin/faq_categories/#{faq_categories(:faq_category1).id}/edit", 'komagata' + click_on '削除' + page.driver.browser.switch_to.alert.accept + assert_text 'FAQカテゴリーを削除しました。' + end +end diff --git a/test/system/admin/faq_test.rb b/test/system/admin/faq_test.rb new file mode 100644 index 00000000000..85b25da51d4 --- /dev/null +++ b/test/system/admin/faq_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'application_system_test_case' + +class Admin::FAQTest < ApplicationSystemTestCase + test 'display listing FAQs' do + visit_with_auth '/admin/faqs', 'komagata' + assert_text 'FAQ一覧' + end + + test 'create FAQ' do + visit_with_auth '/admin/faqs/new', 'komagata' + within 'form[name=faq]' do + fill_in 'faq[question]', with: 'test FAQ' + fill_in 'faq[answer]', with: 'test FAQ' + find('label', text: '学習内容について').click + click_button '登録する' + end + assert_text 'FAQを作成しました。' + end + + test 'update FAQ' do + visit_with_auth "/admin/faqs/#{faqs(:faq1).id}/edit", 'komagata' + within 'form[name=faq]' do + fill_in 'faq[question]', with: 'updated FAQ' + fill_in 'faq[answer]', with: 'updated FAQ' + find('label', text: '学習内容について').click + click_button '更新する' + end + assert_text 'FAQを更新しました。' + end + + test "display answer's preview" do + visit_with_auth '/admin/faqs/new', 'komagata' + within 'form[name=faq]' do + fill_in 'faq[answer]', with: 'updated FAQ' + end + assert_selector '.markdown-form__preview', text: 'updated FAQ' + end + + test 'display link tag when answer has links' do + visit_with_auth '/admin/faqs/new', 'komagata' + within 'form[name=faq]' do + fill_in 'faq[answer]', with: '[test](https://example.com)' + end + assert_selector '.markdown-form__preview a', text: 'test' + end + + test 'delete FAQ' do + visit_with_auth "/admin/faqs/#{faqs(:faq1).id}/edit", 'komagata' + click_on '削除' + page.driver.browser.switch_to.alert.accept + assert_text 'FAQを削除しました。' + end +end diff --git a/test/system/admin/home_test.rb b/test/system/admin/home_test.rb index c1700ef4aeb..71a3ae5e711 100644 --- a/test/system/admin/home_test.rb +++ b/test/system/admin/home_test.rb @@ -7,15 +7,9 @@ class Admin::HomeTest < ApplicationSystemTestCase visit_with_auth '/admin', 'komagata' assert_equal '管理ページ | FBC', title - 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' + tabs = %w[FAQ お問い合わせ お試し延長 ユーザー 企業 管理ページ].sort + assert_equal tabs, all('a.page-tabs__item-link').map(&:text).sort assert_no_selector '.page-tabs__item-link', text: 'プラクティス' - assert_no_selector '.page-tabs__item-link', text: 'カテゴリー' assert_no_selector '.page-tabs__item-link', text: 'コース' end end diff --git a/test/system/faq_test.rb b/test/system/faq_test.rb new file mode 100644 index 00000000000..ce11dfdea6c --- /dev/null +++ b/test/system/faq_test.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'application_system_test_case' + +class FAQTest < ApplicationSystemTestCase + test 'show listing FAQs' do + visit faq_path + assert_selector '.lp-faq', count: FAQ.all.size + end + + test 'show listing FAQs by category' do + visit '/faq?category=学習環境について' + category = FAQCategory.find_by(name: '学習環境について').id + assert_selector '.lp-faq', count: FAQ.where(faq_category: category).size + end +end