Improve information architecture of Settings (#11347)

* WIP Rename categories and make consistency card titles

* Add rest of sections to Extensions page

* icons

* Reshuffle Integrations and remove unnecessary Integrations tab in controller

* Update rspec tests

* Fix specs

Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com>
Co-authored-by: rhymes <rhymes@hey.com>
This commit is contained in:
Lisa Sy 2020-11-24 12:26:06 -08:00 committed by GitHub
parent c3b0103eb1
commit fc4f6e3b1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 393 additions and 216 deletions

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.728 9.999c-.58.39-1.116.84-1.6 1.345-.07-2.358-.637-5.408-3.762-6.901-.618-3.364-7.83-1.655-7.183-1.329 1.285.65 1.97 2.305 2.796 3.175 1.474 1.552 3.113 1.647 3.928.433 1.927 1.252 2.054 3.627 1.995 6.166-.006.28-.013.542-.013.78v7.776c0 .614 2.223.614 2.223 0v-6.383c.3-.53 1.179-1.947 2.46-2.941.881.774 2.301.527 3.59-.83.829-.871 1.275-2.525 2.56-3.176.68-.342-7.11-2.218-6.993 1.885" fill="#77B255"/>
</svg>

After

Width:  |  Height:  |  Size: 527 B

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.222 4.778A2.222 2.222 0 002 7v10a2.222 2.222 0 002.222 2.222h15.556A2.222 2.222 0 0022 17V7s0-2.222-2.222-2.222H4.222z" fill="#FFAC33"/>
<path d="M22 10.333H2V7.556h20v2.777z" fill="#292F33"/>
<path d="M19.778 15.889H4.222v-3.333h15.556v3.333z" fill="#F4F7F9"/>
<path d="M12.556 15.333c-.947 0-1.301-.672-1.372-1-.304.023-.6.168-1.002.424-.426.27-.908.576-1.515.576-.665 0-1.07-.375-1.111-1.11-.002-.032.02-.105.011-.105-1.032 0-1.779.978-1.786.989a.555.555 0 11-.895-.658c.042-.058 1.064-1.338 2.67-1.338 1.076 0 1.137.764 1.154 1.049l.003.06c.27-.018.55-.196.873-.401.533-.339 1.196-.762 2.083-.535.484.124.56.574.588.743.007.043.018.106.027.125.005 0 .082.05.321.073.456.041.957-.155 1.486-.363.549-.216 1.117-.439 1.728-.439 1.883 0 2.649.909 2.73 1.013a.556.556 0 01-.876.684c-.013-.015-.516-.585-1.854-.585-.401 0-.848.175-1.322.36-.56.22-1.351.438-1.941.438z" fill="#8899A6"/>
</svg>

After

Width:  |  Height:  |  Size: 1,015 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 16.444a4.444 4.444 0 110-8.889 4.444 4.444 0 010 8.89zm8.889-6.11H19.02a7.16 7.16 0 00-.879-2.12l1.322-1.32a1.112 1.112 0 000-1.572l-.786-.786a1.11 1.11 0 00-1.571 0l-1.321 1.322a7.167 7.167 0 00-2.12-.88V3.112A1.111 1.111 0 0012.557 2h-1.112a1.11 1.11 0 00-1.11 1.111V4.98a7.167 7.167 0 00-2.12.879l-1.32-1.322a1.111 1.111 0 00-1.572 0l-.786.786a1.112 1.112 0 000 1.571l1.322 1.321a7.172 7.172 0 00-.88 2.12H3.112A1.111 1.111 0 002 11.443v1.112a1.11 1.11 0 001.111 1.11H4.98c.18.76.48 1.473.879 2.119l-1.322 1.322a1.112 1.112 0 000 1.571l.786.786a1.113 1.113 0 001.571 0l1.321-1.322c.655.405 1.37.702 2.12.88v1.867A1.111 1.111 0 0011.443 22h1.112a1.111 1.111 0 001.11-1.111V19.02c.76-.18 1.473-.48 2.119-.879l1.322 1.322a1.108 1.108 0 001.571 0l.786-.786a1.111 1.111 0 000-1.571l-1.322-1.321a7.16 7.16 0 00.88-2.12h1.867A1.111 1.111 0 0022 12.557v-1.112a1.111 1.111 0 00-1.111-1.11z" fill="#66757F"/>
</svg>

After

Width:  |  Height:  |  Size: 1,022 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.556 22a.554.554 0 01-.494-.81l3.87-7.523h-6.71a.556.556 0 01-.363-.976L16.082 2.135a.555.555 0 01.857.675l-3.87 7.523h6.709a.556.556 0 01.363.976L7.919 21.865a.555.555 0 01-.363.135" fill="#FFAC33"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.222 20.333c0 1.228-4.444 1.228-4.444 0v-5.555a2.222 2.222 0 114.444 0v5.555z" fill="#C1694F"/>
<path d="M16.444 3.667H7.556v11.11h13.333V8.112a4.444 4.444 0 00-4.445-4.444z" fill="#99AAB5"/>
<path d="M7.556 3.667A4.444 4.444 0 003.11 8.11v6.667H12V8.11a4.445 4.445 0 00-4.444-4.444" fill="#292F33"/>
<path d="M20.889 9.222h-6.667a1.111 1.111 0 000 2.222h4.445v1.112a1.11 1.11 0 001.11 1.11h1.112A1.111 1.111 0 0022 12.557v-2.223a1.111 1.111 0 00-1.111-1.11z" fill="#DD2E44"/>
</svg>

After

Width:  |  Height:  |  Size: 608 B

View file

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.889 9.222a1.111 1.111 0 01-1.111 1.111h-3.334a1.111 1.111 0 01-1.11-1.11V8.11A1.111 1.111 0 0116.443 7h3.334a1.11 1.11 0 011.11 1.111v1.111zm-12.222 0a1.111 1.111 0 01-1.111 1.111H4.222a1.111 1.111 0 01-1.11-1.11V8.11A1.111 1.111 0 014.221 7h3.334a1.111 1.111 0 011.11 1.111v1.111z" fill="#DAC8B1"/>
<path d="M22 20.889A1.111 1.111 0 0120.889 22H3.11A1.111 1.111 0 012 20.889V9.222a1.111 1.111 0 011.111-1.11H20.89A1.111 1.111 0 0122 9.221V20.89z" fill="#F1DCC1"/>
<path d="M14.222 7V5.889c0-.41-.224-.765-.555-.957v-.154a1.111 1.111 0 00-1.111-1.111h-1.112a1.11 1.11 0 00-1.11 1.11v.155c-.331.192-.556.547-.556.957V7h-.556v15h5.556V7h-.556z" fill="#DAC8B1"/>
<path d="M10.889 7H9.778V5.889h1.11V7zm2.222 0h1.111V5.889h-1.11V7zm-.555 0h-1.112V5.889h1.112V7z" fill="#55ACEE"/>
<path d="M11.444 18.111h-1.11v-7.778h1.11v7.778zm2.223 0h-1.111v-7.778h1.11v7.778z" fill="#3B88C3"/>
<path d="M16.444 18.111h-1.11v-6.667h1.11v6.667zm2.223 0h-1.111v-6.667h1.11v6.667zm2.222 0h-1.111v-6.667h1.11v6.667zm-16.667 0h-1.11v-6.667h1.11v6.667zm2.222 0h-1.11v-6.667h1.11v6.667zm2.223 0H7.556v-6.667h1.11v6.667zm-4.445 1.667h-1.11v-1.111h1.11v1.11zm2.222 0h-1.11v-1.111h1.11v1.11zm2.223 0H7.556v-1.111h1.11v1.11z" fill="#55ACEE"/>
<path d="M11.444 19.778h-1.11v-1.111h1.11v1.11zm2.223 0h-1.111v-1.111h1.11v1.11z" fill="#3B88C3"/>
<path d="M16.444 19.778h-1.11v-1.111h1.11v1.11zm2.223 0h-1.111v-1.111h1.11v1.11zm2.222 0h-1.111v-1.111h1.11v1.11z" fill="#55ACEE"/>
<path d="M4.222 22h-1.11v-1.667h1.11V22zm2.222 0h-1.11v-1.667h1.11V22zm2.223 0H7.556v-1.667h1.11V22zm2.777 0h-1.11v-1.667h1.11V22zm2.223 0h-1.111v-1.667h1.11V22zm2.777 0h-1.11v-1.667h1.11V22zm1.112 0h1.11v-1.667h-1.11V22zm3.333 0h-1.111v-1.667h1.11V22z" fill="#66757F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10z" fill="#FFCC4D"/>
<path d="M7.842 15.123c.025.1.649 2.433 4.158 2.433 3.51 0 4.133-2.333 4.158-2.433a.277.277 0 00-.464-.265c-.011.01-1.086 1.03-3.695 1.03-2.607 0-3.683-1.02-3.692-1.03a.28.28 0 00-.452.087.278.278 0 00-.014.178zM10.056 9.5c0 1.074-.622 1.944-1.39 1.944-.767 0-1.388-.87-1.388-1.944 0-1.074.621-1.944 1.389-1.944.767 0 1.389.87 1.389 1.944zm6.666 0c0 1.074-.621 1.944-1.389 1.944-.767 0-1.389-.87-1.389-1.944 0-1.074.622-1.944 1.39-1.944.767 0 1.388.87 1.388 1.944z" fill="#664500"/>
</svg>

After

Width:  |  Height:  |  Size: 694 B

View file

@ -235,14 +235,17 @@ class UsersController < ApplicationController
return @tab = "profile" if @tab.blank?
case @tab
when "profile"
handle_integrations_tab
when "organization"
handle_organization_tab
when "integrations"
handle_integrations_tab
when "billing"
handle_billing_tab
when "response-templates"
handle_response_templates_tab
when "extensions"
handle_integrations_tab
handle_response_templates_tab
else
not_found unless @tab.in?(Constants::Settings::TAB_LIST.map { |t| t.downcase.tr(" ", "-") })
end

View file

@ -2,15 +2,12 @@ module Constants
module Settings
TAB_LIST = %w[
Profile
UX
Integrations
Customization
Notifications
Publishing\ from\ RSS
Organization
Response\ Templates
Billing
Account
Misc
Billing
Organization
Extensions
].freeze
end
end

View file

@ -47,7 +47,32 @@
</div>
<div class="crayons-card crayons-card--content-rows">
<h2 class="color-accent-danger">Danger Zone</h2>
<h2 class="crayons-subtitle-1">
Export content
</h2>
<% if @user.export_requested? %>
<div class="crayons-notice crayons-notice--warning">
<p>You have recently requested an export of your content. Please check your email.</p>
</div>
<% else %>
<p class="-mt-2">You can request an export of all your content. Currently we only support the export of your posts and comments. They will be emailed to your inbox.</p>
<%= form_for(@user, html: { id: nil }) do |f| %>
<div class="crayons-field crayons-field--checkbox mb-6">
<%= f.check_box :export_requested, class: "crayons-checkbox" %>
<%= f.label :export_requested, "Request an export of your content", class: "crayons-field__label" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Submit Data Request</button></div>
<% end %>
<% end %>
</div>
<div class="crayons-card crayons-card--content-rows">
<h2 class="crayons-subtitle-1 color-accent-danger">
Danger Zone
</h2>
<% if @user.identities.enabled.size > 1 %>
<div class="grid gap-2">
@ -80,13 +105,13 @@
<% end %>
<div class="grid gap-2">
<h3>Delete Account</h3>
<h3>Delete account</h3>
<% if current_user.email? %>
<%= form_tag user_request_destroy_path, method: :post, autocomplete: "off", id: "delete__account", class: "grid gap-2" do %>
<p>Deleting your account will:</p>
<ul class="list-disc pl-6">
<li>delete your profile, along with your authentication associations.
<li>Delete your profile, along with your authentication associations.
This does not include applications permissions. You will have to remove them yourself:
@ -94,8 +119,8 @@
</li>
<%# TODO: expand the delete messaging later %>
<li>delete any and all content you have, such as articles, comments, your reading list or chat messages.</li>
<li>allow your username to become available to anyone.</li>
<li>Delete any and all content you have, such as articles, comments, your reading list or chat messages.</li>
<li>Allow your username to become available to anyone.</li>
</ul>
<div><button class="crayons-btn crayons-btn--danger" id="delete__account__btn" type="submit">Delete Account</button></div>
<% end %>

View file

@ -1,7 +1,11 @@
<div class="crayons-card crayons-card--content-rows">
<header>
<h2 class="mb-2">Set New Password</h2>
<p>As an alternative to signing in via linked social accounts, you may create a password.</p>
<h2 class="crayons-subtitle-1">
Set new password
</h2>
<p class="color-base-70">
As an alternative to signing in via linked social accounts, you may create a password.
</p>
</header>
<%= form_for @user do |f| %>
<div class="crayons-field mb-4">

View file

@ -1,5 +1,7 @@
<div class="crayons-card crayons-card--content-rows">
<h3>Billing Details</h3>
<h2 class="crayons-subtitle-1">
Billing
</h2>
<% if current_user.stripe_id_code.blank? %>
<%= form_tag stripe_active_cards_path, id: "credit-card-form", method: :post do %>

View file

@ -2,7 +2,9 @@
<%= form_for @user, html: { id: "ux-customization-form", class: "sticky-footer-form" } do |f| %>
<div class="crayons-card crayons-card--content-rows">
<h2>Style Customization</h2>
<h2 class="crayons-subtitle-1">
Appearance
</h2>
<fieldset class="crayons-field">
<legend class="crayons-field__label">Site Theme</legend>
@ -33,7 +35,9 @@
</div>
<div class="crayons-card crayons-card--content-rows">
<h2>Writing</h2>
<h2 class="crayons-subtitle-1">
Writing
</h2>
<div class="crayons-field">
<%= f.label :editor_version, "Editor Version", class: "crayons-field__label" %>
<div class="theme-selector-field grid gap-4 grid-cols-2 l:grid-cols-3">
@ -45,7 +49,9 @@
</div>
<div class="crayons-card crayons-card--content-rows">
<h2>Content Customization</h2>
<h2 class="crayons-subtitle-1">
Content
</h2>
<div class="crayons-field">
<%= f.label :experience_level, "What is your approximate experience level (1-5)?", class: "crayons-field__label" %>
<div class="theme-selector-field grid grid-cols-5">
@ -57,8 +63,62 @@
</div>
</div>
<%= render partial: "language_settings" %>
<div class="crayons-card crayons-card--content-rows">
<header>
<h2 class="crayons-subtitle-1">
Sponsors
</h2>
<p>
You have the option to remove sponsor messaging (where it is practical
to do so). Our wonderful sponsors help sustain the platform and improve
your experience, and we strive to make their presence constructive to
the community, but feel free to use this setting if you wish.
</p>
</header>
<%= form_for(@user, html: { id: nil, class: "grid gap-4" }) do |f| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :display_sponsors, class: "crayons-checkbox" %>
<%= f.label :display_sponsors, "Display Sponsors (When browsing)", class: "crayons-field__label" %>
</div>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :permit_adjacent_sponsors, class: "crayons-checkbox" %>
<%= f.label :permit_adjacent_sponsors, "Permit Nearby Sponsors (When publishing)", class: "crayons-field__label" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Save Sponsors Settings</button></div>
<% end %>
</div>
<div class="crayons-card crayons-card--content-rows">
<header>
<h2 class="crayons-subtitle-1">
Announcements
</h2>
<p>
Announcements inform you on important site-wide
information, like updates and events.
</p>
</header>
<%= form_for(@user, html: { id: nil, class: "grid gap-4" }) do |f| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :display_announcements, class: "crayons-checkbox" %>
<%= f.label :display_announcements, "Display Announcements (When browsing)", class: "crayons-field__label" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Save Announcements Settings</button></div>
<% end %>
</div>
<div class="save-footer crayons-card crayons-card--content-rows">
<%= f.hidden_field :tab, value: @tab, id: nil %>
<button class="crayons-btn" type="submit">Save</button>
</div>
<% end %>

View file

@ -0,0 +1,58 @@
<%= render partial: "response_templates" %>
<div class="crayons-card crayons-card--content-rows">
<h2 class="crayons-subtitle-1">
Connect settings
</h2>
<%= form_for(@user, html: { id: nil, class: "grid gap-6" }) do |f| %>
<div class="crayons-field">
<%= f.label :inbox_type, "Inbox privacy", class: "crayons-field__label" %>
<p class="crayons-field__description">Open your inbox to messages from people you don't follow or keep your inbox private to mutual follows.</p>
<%= f.select :inbox_type, [%w[Open open], %w[Private private]], {}, { class: "crayons-select" } %>
</div>
<div class="crayons-field">
<%= f.label :inbox_guidelines, "Open inbox guidelines/instructions", class: "crayons-field__label" %>
<p class="crayons-field__description">
Optional
</p>
<%= f.text_area :inbox_guidelines, maxlength: 200, class: "crayons-textfield", rows: 5, placeholder: "For example:
- Only contact me about consulting opportunities.
- I'm happy to help beginners with CSS.
- Allow for a few days to respond.
- etc.
" %>
</div>
<button type="submit" class="crayons-btn w-max">
Save Connect Settings
</button>
<% end %>
</div>
<%= render partial: "publishing_from_rss" %>
<div class="crayons-card crayons-card--content-rows">
<header>
<h2 class="crayons-subtitle-1 flex gap-2 items-center">
Web monetization
<span class="crayons-indicator crayons-indicator--accent">beta</span>
</h2>
<p class="color-base-70">
Receive micropayments for time spent reading your posts directy. <a href="https://dev.to/hacksultan/web-monetization-like-i-m-5-1418">Learn more</a>
</p>
</p>
</header>
<%= form_for(@user, html: { id: nil, class: "grid gap-4" }) do |f| %>
<div class="crayons-field">
<%= f.label :payment_pointer, "Payment Pointer", class: "crayons-field__label" %>
<%= f.text_field :payment_pointer, class: "crayons-textfield", placeholder: "$pay.somethinglikethis.co/value" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Save Web Monetization Settings</button></div>
<% end %>
</div>
<%= render partial: "integrations" %>
<%= render partial: "users/integrations_github_repositories", locals: { show_integration: @github_repositories_show } %>

View file

@ -1,16 +1,23 @@
<%= render partial: "users/integrations_github_repositories", locals: { show_integration: @github_repositories_show } %>
<div class="crayons-card crayons-card--content-rows">
<h3>Generate a personal blog from your <%= community_name %> posts</h3>
<h2 class="crayons-subtitle-1">
Generate a personal blog from your <%= community_name %> posts
</h2>
<div class="single-app-integration">
<% if current_user.decorate.stackbit_integration? %>
<h3>Connected to Stackbit <sup>beta</sup><img width="20px" style="margin-left:2px;" src="<%= asset_path("checkmark-green.svg") %>" /></h3>
<p style="font-size: 1.1em"><strong><a href="https://app.stackbit.com/dashboard">Go To Your Stackbit Dashboard</a></strong></p>
<p style="font-size: 1.1em"><strong><a href="https://app.stackbit.com/create?ref=devto">Create New Stackbit Site</a></strong></p>
<% else %>
<h3>Connect to Stackbit <span class="crayons-indicator crayons-indicator--accent">beta</span></h3>
<p>Automatically generate a self-hosted static blog feed from your <%= community_name %> posts.</p>
<p style="font-size: 1.1em"><strong><a href="/connecting-with-stackbit">Get Started</a></strong></p>
<h3 class="crayons-subtitle-2 flex gap-2 items-center">
Connect to Stackbit
<span class="crayons-indicator crayons-indicator--accent">beta</span>
</h3>
<p class="mb-4">
Automatically generate a self-hosted static blog feed from your <%= community_name %> posts.
</p>
<a href="/connecting-with-stackbit" class="crayons-btn">
Get Started
</a>
<% end %>
</div>
</div>

View file

@ -1,6 +1,13 @@
<% if show_integration %>
<div class="crayons-card crayons-card--content-rows">
<h3>Pin a few of your GitHub repositories to your profile</h3>
<header>
<h2 class="crayons-subtitle-1">
GitHub
</h2>
<p class="color-base-70">
Pin your GitHub repositories to your profile
</p>
</header>
<div id="github-repos-container">
<div class="github-repos loading-repos">

View file

@ -1,14 +1,17 @@
<div class="crayons-card grid gap-6 p-6 mb-6">
<header>
<h3 class="mb-2">Languages <span class="crayons-indicator crayons-indicator--accent">beta</span></h3>
<h2 class="crayons-subtitle-1 flex gap-2 items-center">
Languages
<div class="crayons-tooltip flex items-center" data-tooltip="This feature is in beta and
will improve over time. Another way to tailor your feed is to discover
and follow members who write in your preferred language.">
<span class="crayons-indicator crayons-indicator--accent">beta</span>
<div>
</h2>
<p>Select which languages you'd prefer to see in your feed.</p>
<p>This setting controls which languages you are more likely to see throughout the site, but you may still see other languages, especially English.</p>
</header>
<div class="crayons-notice crayons-notice--warning">
<p>This feature is in beta and will improve over time. Another way to tailor your feed is to discover and follow members who write in your preferred language.</p>
</div>
<%= form_tag users_update_language_settings_path, class: "grid gap-4" do |f| %>
<% Languages::LIST.each do |code, name| %>
<label class="crayons-field crayons-field--checkbox">

View file

@ -1,103 +0,0 @@
<div class="crayons-card crayons-card--content-rows">
<h3>Connect</h3>
<%= form_for(@user, html: { id: nil, class: "grid gap-6" }) do |f| %>
<div class="crayons-field">
<%= f.label :inbox_type, "Inbox type", class: "crayons-field__label" %>
<p class="crayons-field__description">Open your inbox to messages from people you don't follow or keep your inbox private to mutual follows.</p>
<%= f.select :inbox_type, [%w[Open open], %w[Private private]], {}, { class: "crayons-select" } %>
</div>
<div class="crayons-field">
<%= f.label :inbox_guidelines, "Open inbox guidelines/instructions", class: "crayons-field__label" %>
<%= f.text_area :inbox_guidelines, maxlength: 200, class: "crayons-textfield", rows: 5, placeholder: "For example:
- Only contact me about consulting opportunities.
- I'm happy to help beginners with CSS.
- Allow for a few days to respond.
- etc.
" %>
<p class="crayons-field__description">(optional)</p>
</div>
<div><button type="submit" class="crayons-btn">Save Connect Settings</button></div>
<% end %>
</div>
<%= render "language_settings" %>
<div class="crayons-card crayons-card--content-rows">
<header>
<h3 class="mb-2">Web Monetization</h3>
<p>Receive micropayments for time spent reading your posts directly.</p>
</header>
<div class="crayons-notice crayons-notice--warning">
<p>This feature is in beta. Read more about web monetization <a href="https://dev.to/hacksultan/web-monetization-like-i-m-5-1418">here</a>.</p>
</div>
<%= form_for(@user, html: { id: nil, class: "grid gap-4" }) do |f| %>
<div class="crayons-field">
<%= f.label :payment_pointer, "Payment Pointer", class: "crayons-field__label" %>
<%= f.text_field :payment_pointer, class: "crayons-textfield", placeholder: "$pay.somethinglikethis.co/value" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Save Web Monetization Settings</button></div>
<% end %>
</div>
<div class="crayons-card crayons-card--content-rows">
<header>
<h3 class="mb-2">Sponsors</h3>
<p>You have the option to remove sponsor messaging (where it is practical to do so). Our wonderful sponsors help sustain the platform and improve your experience, and we strive to make their presence constructive to the community, but feel free to use this setting if you wish.</p>
</header>
<%= form_for(@user, html: { id: nil, class: "grid gap-4" }) do |f| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :display_sponsors, class: "crayons-checkbox" %>
<%= f.label :display_sponsors, "Display Sponsors (When browsing)", class: "crayons-field__label" %>
</div>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :permit_adjacent_sponsors, class: "crayons-checkbox" %>
<%= f.label :permit_adjacent_sponsors, "Permit Nearby Sponsors (When publishing)", class: "crayons-field__label" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Save Sponsors Settings</button></div>
<% end %>
</div>
<div class="crayons-card crayons-card--content-rows">
<header>
<h3 class="mb-2">Announcements</h3>
<p>You have the option to dismiss announcements. Announcements are used to inform you on important site-wide information, like updates and events.</p>
</header>
<%= form_for(@user, html: { id: nil, class: "grid gap-4" }) do |f| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :display_announcements, class: "crayons-checkbox" %>
<%= f.label :display_announcements, "Display Announcements (When browsing)", class: "crayons-field__label" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Save Announcements Settings</button></div>
<% end %>
</div>
<div class="crayons-card crayons-card--content-rows">
<h3>Export Content</h3>
<% if @user.export_requested? %>
<div class="crayons-notice crayons-notice--warning">
<p>You have recently requested an export of your content. Please check your email.</p>
</div>
<% else %>
<p class="-mt-2">You can request an export of all your content. Currently we only support the export of your posts and comments. They will be emailed to your inbox.</p>
<%= form_for(@user, html: { id: nil }) do |f| %>
<div class="crayons-field crayons-field--checkbox mb-6">
<%= f.check_box :export_requested, class: "crayons-checkbox" %>
<%= f.label :export_requested, "Request an export of your content", class: "crayons-field__label" %>
</div>
<%= f.hidden_field :tab, value: @tab, id: nil %>
<div><button type="submit" class="crayons-btn">Submit Data Request</button></div>
<% end %>
<% end %>
</div>

View file

@ -1,7 +1,9 @@
<%= form_for @user do |f| %>
<div class="crayons-card crayons-card--content-rows">
<header>
<h3>Email Notification Settings</h3>
<h2 class="crayons-subtitle-1">
Email notifications
</h2>
</header>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :email_newsletter, class: "crayons-checkbox" %>
@ -57,8 +59,13 @@
<div class="crayons-card crayons-card--content-rows">
<header>
<h3 class="mb-2">Mobile Notification Settings (Beta)</h3>
<p>Additional settings will be rolled out as new notification features are made available.</p>
<h2 class="crayons-subtitle-1 flex items-center gap-2">
Mobile notifications
<span class="crayons-indicator crayons-indicator--accent">beta</span>
</h2>
<p class="color-base-70">
Additional settings will be rolled out as new notification features are made available.
</p>
</header>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :mobile_comment_notifications, class: "crayons-checkbox" %>
@ -68,8 +75,12 @@
<div class="crayons-card crayons-card--content-rows">
<header>
<h3 class="mb-2">General Notification Settings</h3>
<p>Notifications that only appear on the <a href="<%= notifications_path %>">notifications page</a>.</p>
<h2 class="crayons-subtitle-1">
General notifications
</h2>
<p class="color-base-70">
Notifications that only appear on the <a href="<%= notifications_path %>">notifications page</a>.
</p>
</header>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :welcome_notifications, class: "crayons-checkbox" %>
@ -83,7 +94,9 @@
<% if current_user.trusted %>
<div class="crayons-card crayons-card--content-rows">
<h3>Mod Notification Settings</h3>
<h2 class="crayons-subtitle-1">
Moderator notifications
</h2>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :mod_roundrobin_notifications, class: "crayons-checkbox" %>
<%= f.label :mod_roundrobin_notifications, "Send me occasional community-success mod notifications", class: "crayons-field__label" %>

View file

@ -1,6 +1,8 @@
<div class="crayons-card crayons-card--content-rows">
<div class="grid gap-4">
<h3>Publishing to <%= community_name %> from RSS</h3>
<h2 class="crayons-subtitle-1">
Publishing to <%= community_name %> from RSS
</h2>
<% if @user.feed_url.present? %>
<%= form_for(@user) do |f| %>
<%= f.hidden_field :feed_url, value: @user.feed_url %>

View file

@ -1,57 +1,69 @@
<h3>Personal Response Templates</h3>
<p class="mb-6">Response templates are snippets that you can re-use for quick and accurate comments.</p>
<div class="crayons-card crayons-card--content-rows">
<header>
<h2 class="crayons-subtitle-1">
Comment templates
</h2>
<p class="color-base-70">
Create and save <strong> comment templates</strong> that you can reuse.</p>
</p>
</header>
<% if @response_templates.present? %>
<div class="crayons-card crayons-card--content-rows">
<h3>Your templates</h3>
<div class="github-repos-container -mt-4">
<div class="github-repos">
<% if @response_templates.present? %>
<section>
<h3 class="crayons-subtitle-3">
Saved templates
</h3>
<% @response_templates.each do |response_template| %>
<div class="github-repo-row <%= "github-repo-row-featured" if response_template.id == params[:id].to_i %>">
<div class="github-repo-row-name flex items-center crayons-card crayons-card--secondary p-2 pl-4 mt-2">
<div class="flex-1">
<%= response_template.title %>
</div>
<div class="flex">
<%= form_with url: response_template_path(response_template.id), method: :delete, local: true, html: { onsubmit: "return confirm('Are you sure you want to delete: #{response_template.title}?');" } do %>
<button type="submit" class="crayons-btn crayons-btn--secondary">Remove</button>
<% end %>
<a class="crayons-btn crayons-btn--secondary ml-2" href="/settings/response-templates/<%= response_template.id %>" role="button">Edit</a>
<div class="<%= "github-repo-row-featured" if response_template.id == params[:id].to_i %> mb-4">
<div class="flex items-center py-2 mt-2">
<div class="flex-1 ff-monospace">
<%= response_template.title %>
</div>
<div class="flex">
<%= form_with url: response_template_path(response_template.id), method: :delete, local: true, html: { onsubmit: "return confirm('Are you sure you want to delete: #{response_template.title}?');" } do %>
<button type="submit" class="crayons-btn crayons-btn--secondary">Remove</button>
<% end %>
<a class="crayons-btn crayons-btn--secondary ml-2" href="/settings/response-templates/<%= response_template.id %>" role="button">Edit</a>
</div>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
<% end %>
</section>
<% end %>
<%= form_with model: @response_template do |f| %>
<div class="crayons-card crayons-card--content-rows">
<header>
<%= form_with model: @response_template do |f| %>
<section class="flex flex-col gap-3">
<% if @response_template.persisted? %>
<% title = params[:previous_title] || @response_template.title %>
<% content = params[:previous_content] || @response_template.content %>
<h3>Editing a response template</h3>
<h3 class="crayons-subtitle-3">
Edit comment template
</h3>
<a href="/settings/response-templates">Create a new response template</a>
<% else %>
<% title = params[:previous_title] %>
<% content = params[:previous_content] %>
<h3>Add a new response template</h3>
<h3 class="crayons-subtitle-3">
Add new comment template
</h3>
<% end %>
</header>
<div class="crayons-field">
<%= f.label :title, class: "crayons-field__label" %>
<%= f.text_field :title, placeholder: "...", value: title, class: "crayons-textfield" %>
<p class="crayons-field__description">Memorable cue for template</p>
</div>
<div class="crayons-field">
<%= f.label :content, "Comment body (Markdown)", class: "crayons-field__label" %>
<%= f.text_area :content, value: content, class: "crayons-textfield" %>
</div>
<div class="crayons-field">
<%= f.label :title, class: "crayons-field__label" %>
<p class="crayons-field__description">
Memorable cue for template
</p>
<%= f.text_field :title, placeholder: "...", value: title, class: "crayons-textfield" %>
</div>
<div class="crayons-field">
<%= f.label :content, "Comment body (Markdown)", class: "crayons-field__label" %>
<%= f.text_area :content, value: content, class: "crayons-textfield" %>
</div>
<div><button type="submit" class="crayons-btn">Save template</button></div>
<div>
<button type="submit" class="crayons-btn">Save template</button>
</div>
</section>
<% end %>
</div>
<% end %>

View file

@ -45,6 +45,7 @@
<nav class="hidden m:block">
<% Constants::Settings::TAB_LIST.each do |possible_tab| %>
<a class="crayons-link crayons-link--block <% if @tab == possible_tab.downcase.tr(' ', '-') %>crayons-link--current<% end %>" href="/settings/<%= possible_tab.downcase.tr(" ", "-") %>">
<%= inline_svg_tag("twemoji/#{possible_tab.downcase.tr(' ', '-')}", aria: true, class: "crayons-icon crayons-icon--default", title: possible_tab) %>
<%= possible_tab %>
</a>
<% end %>

View file

@ -15,8 +15,9 @@ RSpec.describe "UserSettings", type: :request do
before { sign_in user }
it "renders various settings tabs properly" do
%w[organization misc account ux].each do |tab|
get "/settings/#{tab}"
Constants::Settings::TAB_LIST.each do |tab|
get user_settings_path(tab.downcase.tr(" ", "-"))
expect(response.body).to include("Settings for")
end
end
@ -26,32 +27,80 @@ RSpec.describe "UserSettings", type: :request do
.to raise_error(ActiveRecord::RecordNotFound)
end
it "displays content on ux tab properly" do
get "/settings/ux"
expect(response.body).to include("Style Customization")
it "displays content on Profile tab properly" do
get user_settings_path(:profile)
expect(response.body).to include("User")
end
it "displays content on misc tab properly" do
get "/settings/misc"
expect(response.body).to include("Connect", "Languages", "Sponsors", "Announcements", "Export Content")
it "displays profile groups content on Profile tab" do
profile_field = create(:profile_field)
get user_settings_path(:profile)
expect(response.body).to include(profile_field.profile_field_group.name)
end
it "displays content on RSS tab properly" do
get "/settings/publishing-from-rss"
title = "Publishing to #{SiteConfig.community_name} from RSS"
expect(response.body).to include(title)
it "displays content on Customization tab properly" do
get user_settings_path(:customization)
expect(response.body).to include("Appearance", "Writing", "Content", "Languages", "Sponsors", "Announcements")
end
it "displays content on Notifications tab properly" do
get user_settings_path(:notifications)
expect(response.body).to include("Email notifications", "Mobile notifications", "General notifications")
end
it "displays moderator notifications secons on Notifications tab if trusted" do
user.add_role(:trusted)
get user_settings_path(:notifications)
expect(response.body).to include("Moderator notifications")
end
it "displays content on Account tab properly" do
get user_settings_path(:account)
expect(response.body).to include("Set new password", "Account emails", "API Keys", "Danger Zone")
end
it "displays content on Billing tab properly" do
get user_settings_path(:billing)
expect(response.body).to include("Billing")
end
it "displays content on Organization tab properly" do
get user_settings_path(:organization)
expect(response.body).to include("Join An Organization", "Create An Organization")
end
it "displays content on Extensions tab properly" do
get user_settings_path(:extensions)
feed_section = "Publishing to #{SiteConfig.community_name} from RSS"
stackbit_section = "Generate a personal blog from your #{SiteConfig.community_name} posts"
titles = ["Comment templates", "Connect settings", feed_section, "Web monetization", stackbit_section]
expect(response.body).to include(*titles)
end
it "renders heads up dupe account message with proper param" do
get "/settings?state=previous-registration"
error_message = "There is an existing account authorized with that social account"
expect(response.body).to include error_message
expect(response.body).to include(error_message)
end
it "renders the proper response template" do
response_template = create(:response_template, user: user)
get user_settings_path(tab: "response-templates", id: response_template.id)
expect(response.body).to include "Editing a response template"
expect(response.body).to include("Edit comment template")
end
end
@ -106,7 +155,7 @@ RSpec.describe "UserSettings", type: :request do
user = create(:user, :with_identity, identities: [:github])
sign_in user
get "/settings"
get user_settings_path
expect(response.body).not_to include("Connect GitHub Account")
end
@ -117,7 +166,7 @@ RSpec.describe "UserSettings", type: :request do
user = create(:user, :with_identity, identities: [:github])
sign_in user
get "/settings"
get user_settings_path
expect(response.body).not_to include("Connect GitHub Account")
end
@ -127,25 +176,27 @@ RSpec.describe "UserSettings", type: :request do
user = create(:user, :with_identity, identities: [:twitter])
sign_in user
get "/settings"
get user_settings_path
expect(response.body).to include("Connect GitHub Account")
end
end
describe ":integrations" do
describe "GitHub repositories" do
it "renders the repositories container if the user has authenticated through GitHub" do
allow(Authentication::Providers).to receive(:enabled).and_return(Authentication::Providers.available)
user = create(:user, :with_identity, identities: [:github])
sign_in user
get user_settings_path(tab: :integrations)
sign_in user
get user_settings_path(:extensions)
expect(response.body).to include("github-repos-container")
end
it "does not render anything if the user has not authenticated through GitHub" do
sign_in user
get user_settings_path(:extensions)
get user_settings_path(tab: :integrations)
expect(response.body).not_to include("github-repos-container")
end
end
@ -213,9 +264,7 @@ RSpec.describe "UserSettings", type: :request do
context "when requesting an export of the articles" do
def send_request(export_requested: true)
put "/users/#{user.id}", params: {
user: { tab: "misc", export_requested: export_requested }
}
put user_path(user.id), params: { user: { tab: :account, export_requested: export_requested } }
end
it "updates export_requested flag" do

View file

@ -1,18 +1,19 @@
require "rails_helper"
RSpec.describe "User edits their UX settings", type: :system do
RSpec.describe "User edits their Customization settings", type: :system do
let(:user) { create(:user) }
before do
sign_in user
visit "/settings/ux"
visit user_settings_path(:customization)
end
describe "visiting /settings/ux" do
describe "visiting Customization section" do
it "makes the 'Save Button' footer sticky once a theme is selected", js: true do
expect(page).not_to have_css(".sticky")
choose "Ten X Hacker Theme"
choose("Ten X Hacker Theme")
expect(page).to have_css(".sticky")
end

View file

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe "User edits their integrations", type: :system, js: true do
RSpec.describe "User edits their extensions", type: :system, js: true do
let(:user) { create(:user) }
let(:github_response_body) do
[
@ -25,7 +25,7 @@ RSpec.describe "User edits their integrations", type: :system, js: true do
end
it "has connect-to-stackbit prompt" do
click_link "Integrations"
click_link "Extensions"
expect(page).to have_text("Connect to Stackbit")
end
@ -33,7 +33,7 @@ RSpec.describe "User edits their integrations", type: :system, js: true do
it "has connected-to-stackbit prompt if already integrated" do
create(:doorkeeper_access_token, resource_owner: user)
click_link "Integrations"
click_link "Extensions"
expect(page).to have_text("Connected to Stackbit")
end
end

View file

@ -19,10 +19,10 @@ RSpec.describe "User uses response templates settings", type: :system do
end
it "shows the proper message when deleting a reponse template", js: true do
visit "/settings/response-templates"
visit "/settings/extensions"
expect(page).to have_text(response_template.title)
expect(page).to have_css(".github-repo-row-name", text: response_template.title)
expect(page).to have_css(".flex-1", text: response_template.title)
begin
accept_confirm { click_button("Remove") }
rescue Capybara::ModalNotFound => e