[deploy] /profile/settings page rendering (Ridhwana/Michael) (#10733)
* chore: update the profile to be more dynamic. Maps out the group + field name + value for field * feat: add the correct input type to the form tying it to the correct method to update * feat: update the value and the label * feat: update the profile with a submit * refactor: ensure we dont show empty groups on the settings/profile * fix: this shoudl be dribbble * fix: use attributes! to refresh the cache to ensure that we font run into the case where Profile.attributes is [] * feat: show group description only when it exists * chore: add some classes * feat: use the profile update service to update the relevant user information * chore: rename variable * chore: use a scope to not return empty groups * fix: only update user if the profile sync is update is successful. Also capture errors * feat: style the crayons field checkbox * refactor: rename the attribute to user instead of profile * refactor: service object * Fix CodeClimate warning and rearrange code * Fix service object, add missing attribute * Fix flash and add redirect * Fix service object + spec * Fix user edits profile spec * Disable spec * Fix more specs * Undo vim fail * Add data update scripts * Update where clause in data update script * Rename method * Make sync_to_user conditional * Add after update actions and Honeycomb * feat: handle checkbox allow chcekbox to check * Don't set experience cookie in ProfilesController * Clean up Profiles::Update object * Fix spec * Remove Twitch integration (#10732) * Remove Twitch integration * Ignore columns * Change admin password in docs (#10735) Update default admin password in documentation * Do not require meta_keywords to be set (#10721) * Remove extra character from meta_keywords in /listings/index.html.erb * Remove meta_keywords from MANDATORY_CONFIGS * Add and use meta_keywords_tag helper * Use modern tag syntax instead of deprecated syntax * Add and use meta_keywords_default helper * Add and use meta_keywords_article helper * Remove * from meta_keywords_field.label * Update meta_keyword specs to account for no keywords being set * Generalizes Connect welcome message and settings nav tab and refactors tests (#10741) [deploy] * [deploy] fix: Clear chat input after Enter key submit (#10487) * fix: Clear chat input after Enter key submit * fix: Rename Composer component * test: Add integration tests for input value * After pressing Enter * After clicking Send * After clicking Save edit * After clicking Close edit * Remove tabindex of -1 on upload image input in the write a post editor (#10543) * Remove tabindex of -1 on upload image input in the write a post editor * Remove negative tabindex on image uploader's outer button * Give tabindex of -1 to outer button on image uploader * Update buttons.scss * [deploy] Add recaptcha keys to as site-configurable keys (#10736) * Add recaptcha keys to as site-configurable keys * Add recaptcha to site config view * Use site config key * Make ReCAPTCHA tag optional if keys are blank * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Refactor logic for readability * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Add Section for Series/Collections (#10719) * Add Section for Series/Collections I added a small section in this area of the docs to share some information about collections/ series. There is not much information in the documentation about collections or series and I felt like this would be beneficial to others looking to help with the Series feature of the website. * Updated wording of collections section. * Articles API: filtering by tags (#3654) (#10614) * Articles API: filtering by tags (#3654) * Address CR comments (#3654) * Add test case * Move tests * Update docs * Use _any_ option for querying by tags (#3654) * Move Imgproxy endpoint config to Images::Optimizer (#10742) * Link Email Account creation UI to existing Create Account UI (#10746) [deploy] * Conditional button to link to email accout creation * Rename email signup state param * Test for email account creation button scenarios * Add forem meta tags (#10747) * Add forem meta tags * Add docs * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com> * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com> * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com> * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com> * Remove Twitch-related columns from users table (#10750) * [deploy] Long text goes outside cards (#10740) * Add word-break to crayons-card Add word-break to crayons-card this will allow words to be broken if the word is too long but will wrap normally otherwise. * Use overflow-wrap: anywhere; `break-word` is deprecated though `anywhere` does not work in IE * Removes superfluous sign_in from users_onboarding_spec.rb (#10757) [deploy] * [deploy] Allow glitch id starting with ~ (#10544) * [deploy] crayons prep (#10737) * Add DEV special case for following hiring tag * Move image validation to Profiles::Update object * spec: move over the tests for validating images * Move follow_hiring_tag into the service object Co-authored-by: Ridhwana <ridhwana.khan16@gmail.com> Co-authored-by: Andrew Bone <AndrewB05@gmail.com> Co-authored-by: Vaidehi Joshi <vaidehi.sj@gmail.com> Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> Co-authored-by: Robin Gagnon <me@reobin.dev> Co-authored-by: Manaswini <58681405+Manaswini1832@users.noreply.github.com> Co-authored-by: Andy Zhao <17884966+Zhao-Andy@users.noreply.github.com> Co-authored-by: Christopher Wray <53663762+cwray-tech@users.noreply.github.com> Co-authored-by: Rafal Trojanowski <rt.trojanowski@gmail.com> Co-authored-by: Mac Siri <mac@dev.to> Co-authored-by: Josh Puetz <josh@dev.to> Co-authored-by: Ben Halpern <bendhalpern@gmail.com> Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com> Co-authored-by: irmela <irmela@berlin-coding.de> Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com>
This commit is contained in:
parent
14f0f03aba
commit
9377eee4f5
14 changed files with 214 additions and 269 deletions
|
|
@ -1,13 +1,20 @@
|
|||
class ProfilesController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
ALLOWED_USER_PARAMS = %i[email username profile_image].freeze
|
||||
|
||||
def update
|
||||
Profiles::Update.call(current_user.profile, update_params)
|
||||
update_result = Profiles::Update.call(current_user, update_params)
|
||||
if update_result.success?
|
||||
flash[:settings_notice] = "Your profile has been updated"
|
||||
else
|
||||
flash[:error] = "Error: #{update_result.error_message}"
|
||||
end
|
||||
redirect_to user_settings_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_params
|
||||
params.require(:profile).permit(*Profile.attributes)
|
||||
params.permit(profile: Profile.attributes!, user: ALLOWED_USER_PARAMS)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,11 +39,6 @@ class UsersController < ApplicationController
|
|||
def update
|
||||
set_current_tab(params["user"]["tab"])
|
||||
|
||||
unless valid_image?
|
||||
render :edit, status: :bad_request
|
||||
return
|
||||
end
|
||||
|
||||
if @user.update(permitted_attributes(@user))
|
||||
RssReaderFetchUserWorker.perform_async(@user.id) if @user.feed_url.present?
|
||||
notice = "Your profile was successfully updated."
|
||||
|
|
@ -55,7 +50,6 @@ class UsersController < ApplicationController
|
|||
ExportContentWorker.perform_async(@user.id)
|
||||
end
|
||||
cookies.permanent[:user_experience_level] = @user.experience_level.to_s if @user.experience_level.present?
|
||||
follow_hiring_tag(@user)
|
||||
flash[:settings_notice] = notice
|
||||
@user.touch(:profile_updated_at)
|
||||
redirect_to "/settings/#{@tab}"
|
||||
|
|
@ -234,15 +228,6 @@ class UsersController < ApplicationController
|
|||
|
||||
def signout_confirm; end
|
||||
|
||||
def follow_hiring_tag(user)
|
||||
return unless user.looking_for_work?
|
||||
|
||||
hiring_tag = Tag.find_by(name: "hiring")
|
||||
return if !hiring_tag || user.following?(hiring_tag)
|
||||
|
||||
Users::FollowWorker.perform_async(user.id, hiring_tag.id, "Tag")
|
||||
end
|
||||
|
||||
def handle_settings_tab
|
||||
return @tab = "profile" if @tab.blank?
|
||||
|
||||
|
|
@ -355,32 +340,6 @@ class UsersController < ApplicationController
|
|||
range.cover?(user_identity_age)
|
||||
end
|
||||
|
||||
def valid_image?
|
||||
image = params.dig("user", "profile_image")
|
||||
return true unless image
|
||||
|
||||
return true if valid_image_file?(image) && valid_filename?(image)
|
||||
|
||||
Honeycomb.add_field("error", @user.errors.messages)
|
||||
Honeycomb.add_field("errored", true)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def valid_image_file?(image)
|
||||
return true if file?(image)
|
||||
|
||||
@user.errors.add(:profile_image, IS_NOT_FILE_MESSAGE)
|
||||
false
|
||||
end
|
||||
|
||||
def valid_filename?(image)
|
||||
return true unless long_filename?(image)
|
||||
|
||||
@user.errors.add(:profile_image, FILENAME_TOO_LONG_MESSAGE)
|
||||
false
|
||||
end
|
||||
|
||||
def destroy_request_in_progress?
|
||||
Rails.cache.exist?("user-destroy-token-#{@user.id}")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class Profile < ApplicationRecord
|
|||
git_lab_url: :gitlab_url,
|
||||
linked_in_url: :linkedin_url,
|
||||
recruiters_can_contact_me_about_job_opportunities: :contact_consent,
|
||||
skills_languages: :mostly_work_with,
|
||||
stack_overflow_url: :stackoverflow_url
|
||||
}.with_indifferent_access.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,8 @@ class ProfileFieldGroup < ApplicationRecord
|
|||
scope :onboarding, lambda {
|
||||
includes(:profile_fields).where(profile_fields: { show_in_onboarding: true })
|
||||
}
|
||||
|
||||
scope :non_empty_groups, lambda {
|
||||
joins(:profile_fields).includes(:profile_fields).order(:name).uniq
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module ProfileFields
|
|||
field "StackOverflow URL", :text_field, placeholder: "https://stackoverflow.com/users/..."
|
||||
field "LinkedIn URL", :text_field, placeholder: "https://www.linkedin.com/in/..."
|
||||
field "Behance URL", :text_field, placeholder: "https://www.behance.net/..."
|
||||
field "Dribble URL", :text_field, placeholder: "https://dribble.com/..."
|
||||
field "Dribbble URL", :text_field, placeholder: "https://dribbble.com/..."
|
||||
field "Medium URL", :text_field, placeholder: "https://medium.com/@..."
|
||||
field "GitLab URL", :text_field, placeholder: "https://gitlab.com/..."
|
||||
field "Instagram URL", :text_field, placeholder: "https://www.instagram.com/..."
|
||||
|
|
|
|||
|
|
@ -1,36 +1,29 @@
|
|||
module Profiles
|
||||
class Update
|
||||
def self.call(profile, updated_attributes = {})
|
||||
new(profile, updated_attributes).call
|
||||
include ImageUploads
|
||||
|
||||
def self.call(user, updated_attributes = {})
|
||||
new(user, updated_attributes).call
|
||||
end
|
||||
|
||||
attr_reader
|
||||
attr_reader :error_message
|
||||
|
||||
def initialize(profile, updated_attributes)
|
||||
@profile = profile
|
||||
@updated_attributes = updated_attributes
|
||||
def initialize(user, updated_attributes)
|
||||
@user = user
|
||||
@profile = user.profile
|
||||
@updated_profile_attributes = updated_attributes[:profile] || {}
|
||||
@updated_user_attributes = updated_attributes[:user].to_h || {}
|
||||
@success = false
|
||||
end
|
||||
|
||||
def call
|
||||
# Ensure we have up to date attributes
|
||||
Profile.refresh_attributes!
|
||||
|
||||
# Handle user specific custom profile fields
|
||||
custom_attributes = @updated_attributes.extract!(*@profile.custom_profile_attributes)
|
||||
@updated_attributes[:custom_attributes] = custom_attributes
|
||||
|
||||
# We don't update `data` directly. This uses the defined store_attributes
|
||||
# so we can make use of their typecasting.
|
||||
@profile.assign_attributes(@updated_attributes)
|
||||
|
||||
# Before saving, filter out obsolete profile fields
|
||||
@profile.data.slice!(*Profile.attributes)
|
||||
|
||||
return unless @profile.save
|
||||
|
||||
# Propagate changes back to the `users` table
|
||||
sync_to_user
|
||||
if verify_profile_image && update_profile && sync_to_user
|
||||
@user.touch(:profile_updated_at)
|
||||
follow_hiring_tag
|
||||
else
|
||||
Honeycomb.add_field("error", @error_message)
|
||||
Honeycomb.add_field("errored", true)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
|
|
@ -40,13 +33,81 @@ module Profiles
|
|||
|
||||
private
|
||||
|
||||
def verify_profile_image
|
||||
image = @updated_user_attributes[:profile_image]
|
||||
return true unless image
|
||||
return true if valid_image_file?(image) && valid_filename?(image)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def valid_image_file?(image)
|
||||
return true if file?(image)
|
||||
|
||||
@error_message = IS_NOT_FILE_MESSAGE
|
||||
false
|
||||
end
|
||||
|
||||
def valid_filename?(image)
|
||||
return true unless long_filename?(image)
|
||||
|
||||
@error_message = FILENAME_TOO_LONG_MESSAGE
|
||||
false
|
||||
end
|
||||
|
||||
def update_profile
|
||||
# Ensure we have up to date attributes
|
||||
Profile.refresh_attributes!
|
||||
|
||||
# Handle user specific custom profile fields
|
||||
if (custom_profile_attributes = @profile.custom_profile_attributes).any?
|
||||
custom_attributes = @updated_profile_attributes.extract!(*custom_profile_attributes)
|
||||
@updated_profile_attributes[:custom_attributes] = custom_attributes
|
||||
end
|
||||
|
||||
# We don't update `data` directly. This uses the defined store_attributes
|
||||
# so we can make use of their typecasting.
|
||||
@profile.assign_attributes(@updated_profile_attributes)
|
||||
|
||||
# Before saving, filter out obsolete profile fields
|
||||
@profile.data.slice!(*Profile.attributes)
|
||||
|
||||
@profile.save
|
||||
end
|
||||
|
||||
# Propagate changes back to the `users` table
|
||||
def sync_to_user
|
||||
user_attributes = @profile.data.transform_keys do |key|
|
||||
# These are the profile attributes that still exist as columns on User.
|
||||
profile_attributes = @profile.data.transform_keys do |key|
|
||||
Profile::MAPPED_ATTRIBUTES.fetch(key, key).to_s
|
||||
end
|
||||
@profile.user._skip_profile_sync = true
|
||||
@success = true if @profile.user.update(user_attributes.except("custom_attributes"))
|
||||
if @profile.user.update(profile_attributes.except("custom_attributes"))
|
||||
update_user_attributes
|
||||
else
|
||||
@error_message = @user.errors_as_sentence
|
||||
end
|
||||
|
||||
@success
|
||||
ensure
|
||||
@profile.user._skip_profile_sync = false
|
||||
end
|
||||
|
||||
def update_user_attributes
|
||||
if @user.update(@updated_user_attributes)
|
||||
@success = true
|
||||
else
|
||||
@error_message = @user.errors_as_sentence
|
||||
end
|
||||
end
|
||||
|
||||
def follow_hiring_tag
|
||||
return unless SiteConfig.dev_to? && @user.looking_for_work?
|
||||
|
||||
hiring_tag = Tag.find_by(name: "hiring")
|
||||
return unless hiring_tag && @user.following?(hiring_tag)
|
||||
|
||||
Users::FollowWorker.perform_async(@user.id, hiring_tag.id, "Tag")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,26 +15,18 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for @user, html: { class: "sticky-footer-form" } do |f| %>
|
||||
<%= form_with(url: profile_path(@user.profile_id), method: "put", html: { class: "sticky-footer-form" }) do |f| %>
|
||||
|
||||
<div class="crayons-card mb-6 grid grid-flow-row gap-6 p-6">
|
||||
<h2>User</h2>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :email, class: "crayons-field__label" %>
|
||||
<%= f.text_field :email, class: "crayons-textfield", placeholder: "john.doe@example.com" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field crayons-field--checkbox">
|
||||
<%= f.check_box :email_public, class: "crayons-checkbox" %>
|
||||
<%= f.label :email_public, "Display email on profile", class: "crayons-field__label" %>
|
||||
<%= f.text_field "user[email]", class: "crayons-textfield", placeholder: "john.doe@example.com", value: @user.email %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :username, class: "crayons-field__label" %>
|
||||
<%= f.text_field :username, maxlength: 30, class: "crayons-textfield", placeholder: "johndoe" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :name, class: "crayons-field__label" %>
|
||||
<%= f.text_field :name, maxlength: 100, class: "crayons-textfield", placeholder: "John Doe" %>
|
||||
<%= f.text_field "user[username]", maxlength: 30, class: "crayons-textfield", placeholder: "johndoe", value: @user.username %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
|
|
@ -43,158 +35,32 @@
|
|||
<div class="flex items-center">
|
||||
<% if @user.profile_image_url.present? %>
|
||||
<span class="crayons-avatar crayons-avatar--xl mr-2"><img alt="<%= @user.username %> profile image" src="<%= Images::Profile.call(@user.profile_image_url, length: 50) %>" class="crayons-avatar__image" /></span>
|
||||
<%= f.file_field :profile_image, accept: "image/*", class: "crayons-card crayons-card--secondary p-3 flex items-center flex-1 w-100" %>
|
||||
<%= f.file_field "user[profile_image]", accept: "image/*", class: "crayons-card crayons-card--secondary p-3 flex items-center flex-1 w-100" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :website_url, class: "crayons-field__label" %>
|
||||
<%= f.text_field :website_url, maxlength: 100, class: "crayons-textfield", placeholder: "https://yoursite.com" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :summary, class: "crayons-field__label" %>
|
||||
<%= f.text_area :summary, maxlength: 200, class: "crayons-textfield", placeholder: "A short bio...", rows: 3 %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :location, class: "crayons-field__label" %>
|
||||
<%= f.text_field :location, maxlength: 100, class: "crayons-textfield", placeholder: "Halifax, Nova Scotia" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :education, class: "crayons-field__label" %>
|
||||
<%= f.text_field :education, maxlength: 100, class: "crayons-textfield" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :employer_name, class: "crayons-field__label" %>
|
||||
<%= f.text_field :employer_name, maxlength: 100, class: "crayons-textfield", placeholder: "Acme Inc." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :employer_url, class: "crayons-field__label" %>
|
||||
<%= f.text_field :employer_url, maxlength: 100, class: "crayons-textfield", placeholder: "https://dev.com" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :employment_title, class: "crayons-field__label" %>
|
||||
<%= f.text_field :employment_title, maxlength: 100, class: "crayons-textfield", placeholder: "Junior Frontend Engineer" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :mostly_work_with, "Skills/Languages", class: "crayons-field__label" %>
|
||||
<%= f.text_area :mostly_work_with, maxlength: 500, class: "crayons-textfield", rows: 5 %>
|
||||
<p class="crayons-field__description">What tools and languages are you most experienced with? Are you specialized or more of a generalist?</p>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :currently_learning, "I'm getting into", class: "crayons-field__label" %>
|
||||
<%= f.text_area :currently_learning, maxlength: 500, class: "crayons-textfield", rows: 5 %>
|
||||
<p class="crayons-field__description">What are you learning right now? What are the new tools and languages you're picking up in <%= Time.zone.now.year %>?</p>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :currently_hacking_on, "My projects and hacks", class: "crayons-field__label" %>
|
||||
<%= f.text_area :currently_hacking_on, maxlength: 500, class: "crayons-textfield", rows: 5 %>
|
||||
<p class="crayons-field__description">What projects are currently occupying most of your time?</p>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :available_for, class: "crayons-field__label" %>
|
||||
<%= f.text_area :available_for, maxlength: 500, class: "crayons-textfield", rows: 5 %>
|
||||
<p class="crayons-field__description">What kinds of collaborations or discussions are you available for? What's a good reason to say <em>Hey!</em> to you these days?</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="crayons-card mb-6 grid gap-4 p-6">
|
||||
<h2>Branding</h2>
|
||||
<%= render partial: "shared/logo_design", locals: { f: f, account: @user } %>
|
||||
<% ProfileFieldGroup.non_empty_groups.each do |group| %>
|
||||
<div class="crayons-card mb-6 grid grid-flow-row gap-6 p-6">
|
||||
<h2><%= group.name %></h2>
|
||||
<% if group.description.present? %>
|
||||
<div class="color-base-40"><%= group.description %></div>
|
||||
<% end %>
|
||||
<% group.profile_fields.each do |field| %>
|
||||
<div class="crayons-field <%= "crayons-field--check_box" if field.input_type == "check_box" %>">
|
||||
<div><%= field.label %></div>
|
||||
<% if field["input_type"] == "check_box" %>
|
||||
<%= f.public_send(field["input_type"], "profile[#{field.attribute_name}]", checked: @user.profile.data[field.attribute_name]) %>
|
||||
<% else %>
|
||||
<%= f.public_send(field["input_type"], "profile[#{field.attribute_name}]", value: @user.profile.data[field.attribute_name]) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="save-footer crayons-card mb-6 grid p-6">
|
||||
<button type="submit" class="crayons-btn">Save Profile Information</button>
|
||||
</div>
|
||||
|
||||
<div class="crayons-card mb-6 grid gap-4 p-6">
|
||||
<h2>Work opportunities settings</h2>
|
||||
<div class="crayons-field crayons-field--checkbox">
|
||||
<%= f.check_box :looking_for_work, class: "crayons-checkbox" %>
|
||||
<%= f.label :looking_for_work, class: "crayons-field__label" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field crayons-field--checkbox">
|
||||
<%= f.check_box :looking_for_work_publicly, class: "crayons-checkbox" %>
|
||||
<%= f.label :looking_for_work_publicly, "Display \"looking for work\" on profile", class: "crayons-field__label" %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field crayons-field--checkbox">
|
||||
<%= f.check_box :contact_consent, class: "crayons-checkbox" %>
|
||||
<%= f.label :contact_consent, "Recruiters can contact me about job opportunities", class: "crayons-field__label" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="crayons-card mb-6 grid gap-6 p-6">
|
||||
<h2>Links</h2>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :facebook_url, "Facebook profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :facebook_url, class: "crayons-textfield", placeholder: "https://facebook.com/..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :youtube_url, "Youtube URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :youtube_url, class: "crayons-textfield", placeholder: "https://www.youtube.com/channel/..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :stackoverflow_url, "Stack Overflow profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :stackoverflow_url, class: "crayons-textfield", placeholder: "https://stackoverflow.com/users/..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :linkedin_url, "LinkedIn profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :linkedin_url, class: "crayons-textfield", placeholder: "https://..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :behance_url, "Behance profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :behance_url, class: "crayons-textfield", placeholder: "https://..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :dribbble_url, "Dribbble profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :dribbble_url, class: "crayons-textfield", placeholder: "https://dribbble.com/..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :medium_url, "Medium profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :medium_url, class: "crayons-textfield", placeholder: "https://..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :gitlab_url, "GitLab profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :gitlab_url, class: "crayons-textfield", placeholder: "https://..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :instagram_url, "Instagram profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :instagram_url, class: "crayons-textfield", placeholder: "https://..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :mastodon_url, "Mastodon profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :mastodon_url, class: "crayons-textfield", placeholder: "https://..." %>
|
||||
</div>
|
||||
|
||||
<div class="crayons-field">
|
||||
<%= f.label :twitch_url, "Twitch profile URL", class: "crayons-field__label" %>
|
||||
<%= f.url_field :twitch_url, class: "crayons-textfield", placeholder: "https://..." %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="save-footer crayons-card mb-6 grid gap-6 p-6">
|
||||
<%= f.hidden_field :tab, value: @tab %>
|
||||
<div>
|
||||
<button type="submit" class="crayons-btn">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Youtube URL,text_field,https://www.youtube.com/channel/...,,Links
|
|||
StackOverflow URL,text_field,https://stackoverflow.com/users/...,,Links
|
||||
LinkedIn URL,text_field,https://www.linkedin.com/in/...,,Links
|
||||
Behance URL,text_field,https://www.behance.net/...,,Links
|
||||
Dribble URL,text_field,https://dribble.com/...,,Links
|
||||
Dribbble URL,text_field,https://dribbble.com/...,,Links
|
||||
Medium URL,text_field,https://medium.com/@...,,Links
|
||||
GitLab URL,text_field,https://gitlab.com/...,,Links
|
||||
Instagram URL,text_field,https://www.instagram.com/...,,Links
|
||||
|
|
|
|||
|
|
|
@ -0,0 +1,18 @@
|
|||
module DataUpdateScripts
|
||||
class FixDribbbleUrlProfileField
|
||||
def run
|
||||
old_field = ProfileField.find_by(attribute_name: "dribble_url")
|
||||
return unless old_field
|
||||
|
||||
old_field.update(label: "Dribbble URL",
|
||||
attribute_name: "dribbble_url",
|
||||
placeholder_text: "https://dribbble.com/...")
|
||||
|
||||
Profile.refresh_attributes!
|
||||
|
||||
User.where.not(dribbble_url: [nil, ""]).find_each do |user|
|
||||
user.profile.update(dribbble_url: user.dribbble_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
module DataUpdateScripts
|
||||
class BackfillProfileSkillsLanguages
|
||||
def run
|
||||
User.where.not(mostly_work_with: [nil, ""]).find_each do |user|
|
||||
user.profile.update(skills_languages: user.mostly_work_with)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -199,28 +199,6 @@ RSpec.describe "UserSettings", type: :request do
|
|||
expect(response.body).to include("Username is too short")
|
||||
end
|
||||
|
||||
it "returns error if Profile image is too large" do
|
||||
profile_image = fixture_file_upload("files/large_profile_img.jpg", "image/jpeg")
|
||||
put "/users/#{user.id}", params: { user: { tab: "profile", profile_image: profile_image } }
|
||||
expect(response.body).to include("Profile image File size should be less than 2 MB")
|
||||
end
|
||||
|
||||
it "returns error if Profile image file name is too long" do
|
||||
profile_image = fixture_file_upload("files/800x600.png", "image/png")
|
||||
allow(profile_image).to receive(:original_filename).and_return("#{'a_very_long_filename' * 15}.png")
|
||||
|
||||
put "/users/#{user.id}", params: { user: { tab: "profile", profile_image: profile_image } }
|
||||
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it "returns error if Profile image is not a file" do
|
||||
profile_image = "A String"
|
||||
put "/users/#{user.id}", params: { user: { tab: "profile", profile_image: profile_image } }
|
||||
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it "returns error message if user can't be saved" do
|
||||
put "/users/#{user.id}", params: { user: { password: "1", password_confirmation: "1" } }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ RSpec.describe Profiles::Update, type: :service do
|
|||
let(:profile) do
|
||||
create(:profile, data: { name: "Sloan Doe", looking_for_work: true, removed: "Bla" })
|
||||
end
|
||||
let(:user) { profile.user }
|
||||
|
||||
before do
|
||||
create(:profile_field, label: "Name", input_type: :text_field)
|
||||
|
|
@ -11,21 +12,29 @@ RSpec.describe Profiles::Update, type: :service do
|
|||
Profile.refresh_attributes!
|
||||
end
|
||||
|
||||
it "only tries to sync changes to User if the profile update succeeds" do
|
||||
service = described_class.new(user, profile: {}, user: {})
|
||||
allow(service).to receive(:update_profile).and_return(false)
|
||||
|
||||
expect(service).not_to receive(:sync_to_user) # rubocop:disable RSpec/MessageSpies
|
||||
service.call
|
||||
end
|
||||
|
||||
it "correctly typecasts new attributes", :aggregate_failures do
|
||||
described_class.call(profile, name: 123, looking_for_work: "false")
|
||||
described_class.call(user, profile: { name: 123, looking_for_work: "false" })
|
||||
expect(profile.name).to eq "123"
|
||||
expect(profile.looking_for_work).to be false
|
||||
end
|
||||
|
||||
it "removes old attributes from the profile" do
|
||||
expect do
|
||||
described_class.call(profile, {})
|
||||
described_class.call(user, profile: {})
|
||||
end.to change { profile.data.key?("removed") }.to(false)
|
||||
end
|
||||
|
||||
it "propagates changes to user", :agregate_failures do
|
||||
new_name = "Sloan Doe"
|
||||
described_class.call(profile, name: new_name)
|
||||
described_class.call(user, profile: {}, user: { name: new_name })
|
||||
expect(profile.name).to eq new_name
|
||||
expect(profile.user[:name]).to eq new_name
|
||||
end
|
||||
|
|
@ -34,8 +43,38 @@ RSpec.describe Profiles::Update, type: :service do
|
|||
custom_profile_field = create(:custom_profile_field, profile: profile)
|
||||
custom_attribute = custom_profile_field.attribute_name
|
||||
|
||||
described_class.call(profile, custom_attribute => "Test")
|
||||
|
||||
described_class.call(user, profile: { custom_attribute => "Test" }, user: {})
|
||||
expect(profile.custom_attributes[custom_attribute]).to eq "Test"
|
||||
end
|
||||
|
||||
it "updates the profile_updated_at column" do
|
||||
expect do
|
||||
described_class.call(user, profile: { name: 123, looking_for_work: "false" })
|
||||
end.to change { user.reload.profile_updated_at }
|
||||
end
|
||||
|
||||
it "returns an error if Profile image is too large" do
|
||||
profile_image = fixture_file_upload("files/large_profile_img.jpg", "image/jpeg")
|
||||
service = described_class.call(user, profile: {}, user: { profile_image: profile_image })
|
||||
|
||||
expect(service.success?).to be false
|
||||
expect(service.error_message).to eq "Profile image File size should be less than 2 MB"
|
||||
end
|
||||
|
||||
it "returns an error if Profile image is not a file" do
|
||||
profile_image = "A String"
|
||||
service = described_class.call(user, profile: {}, user: { profile_image: profile_image })
|
||||
|
||||
expect(service.success?).to be false
|
||||
expect(service.error_message).to eq "invalid file type. Please upload a valid image."
|
||||
end
|
||||
|
||||
it "returns an error if Profile image file name is too long" do
|
||||
profile_image = fixture_file_upload("files/800x600.png", "image/png")
|
||||
allow(profile_image).to receive(:original_filename).and_return("#{'a_very_long_filename' * 15}.png")
|
||||
service = described_class.call(user, profile: {}, user: { profile_image: profile_image })
|
||||
|
||||
expect(service.success?).to be false
|
||||
expect(service.error_message).to eq "filename too long - the max is 250 characters."
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ RSpec.describe "User edits their profile", type: :system do
|
|||
it "makes the 'Save Button' footer sticky once a field is filled in", js: true do
|
||||
expect(page).not_to have_css(".sticky")
|
||||
|
||||
fill_in "user[website_url]", with: "example.com"
|
||||
fill_in "user[username]", with: "sloan"
|
||||
|
||||
find("#user_website_url").native.send_keys :tab # this un-focuses the filled-in field
|
||||
find_field(id: "user[username]").native.send_keys :tab # this un-focuses the filled-in field
|
||||
|
||||
expect(page).to have_css(".sticky")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,17 +4,20 @@ RSpec.describe "Looking For Work", type: :system do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
create(:tag, name: "hiring")
|
||||
sign_in(user)
|
||||
visit "/settings"
|
||||
user.follow(create(:tag, name: "hiring"))
|
||||
create(:profile_field, label: "Looking for work", input_type: :check_box)
|
||||
Profile.refresh_attributes!
|
||||
end
|
||||
|
||||
it "user selects looking for work and autofollows hiring tag", js: true do
|
||||
page.check "Looking for work"
|
||||
sidekiq_perform_enqueued_jobs do
|
||||
click_button("Save")
|
||||
end
|
||||
expect(page).to have_text("Your profile was successfully updated")
|
||||
allow(SiteConfig).to receive(:dev_to?).and_return(true)
|
||||
|
||||
sign_in(user)
|
||||
visit "/settings"
|
||||
page.check("profile[looking_for_work]")
|
||||
sidekiq_perform_enqueued_jobs { click_button("Save") }
|
||||
|
||||
expect(page).to have_text("Your profile has been updated")
|
||||
expect(user.follows.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue