Various improvements (#332)

* Fix locale fallbacks
* Make header links relative
* Improve like button style
* Other small improvements...
This commit is contained in:
Riccardo Graziosi
2024-05-09 19:23:45 +02:00
committed by GitHub
parent a292b133b0
commit 747483cfa3
32 changed files with 115 additions and 61 deletions

View File

@@ -4,27 +4,15 @@
.flex-column, .flex-column,
.mr-3; .mr-3;
$like_button_size: 11px; .likeButton:hover {
.likeButton {
@extend .mb-2;
border-left: $like_button_size solid transparent;
border-right: $like_button_size solid transparent;
border-bottom: $like_button_size solid rgba(35,35,35,.2);
&:hover {
border-bottom-color: var(--primary-color);
cursor: pointer; cursor: pointer;
} transform: scale(1.15);
}
.likeButton.liked {
border-bottom-color: var(--primary-color);
} }
.likeCountLabel { .likeCountLabel {
@extend .mt-2;
text-align: center; text-align: center;
font-size: 17px; font-size: 20px;
} }
} }

View File

@@ -19,6 +19,9 @@ class ApplicationController < ActionController::Base
end end
def load_tenant_data def load_tenant_data
# Set default locale
I18n.locale = I18n.default_locale
current_tenant = get_tenant_from_request(request) current_tenant = get_tenant_from_request(request)
return unless current_tenant return unless current_tenant
@@ -38,7 +41,7 @@ class ApplicationController < ActionController::Base
@tenant_billing = TenantBilling.first_or_create @tenant_billing = TenantBilling.first_or_create
@boards = Board.select(:id, :name, :slug).order(order: :asc) @boards = Board.select(:id, :name, :slug).order(order: :asc)
# Setup locale # Set tenant locale
I18n.locale = @tenant.locale I18n.locale = @tenant.locale
end end

View File

@@ -172,6 +172,7 @@ class BillingController < ApplicationController
@tenant_setting = @tenant.tenant_setting @tenant_setting = @tenant.tenant_setting
@tenant_billing = @tenant.tenant_billing @tenant_billing = @tenant.tenant_billing
@boards = Board.select(:id, :name, :slug).order(order: :asc) @boards = Board.select(:id, :name, :slug).order(order: :asc)
@header_full_urls = true
I18n.locale = @tenant.locale I18n.locale = @tenant.locale
# needed because signing out from 'billing' subdomain cause authenticity token error # needed because signing out from 'billing' subdomain cause authenticity token error

View File

@@ -7,7 +7,7 @@ class TenantsController < ApplicationController
def new def new
@page_title = "Create your feedback space" @page_title = "Create your feedback space"
@o_auths = OAuth.unscoped.where(tenant_id: nil, is_enabled: true) @o_auths = OAuth.unscoped.where(tenant_id: nil, is_enabled: true).order(created_at: :asc)
end end
def show def show
@@ -102,7 +102,7 @@ class TenantsController < ApplicationController
# Given a new_subdomain # Given a new_subdomain
# Returns true if it is available, false otherwise # Returns true if it is available, false otherwise
def is_available def is_available
subdomain = params[:new_subdomain] subdomain = params[:new_subdomain].downcase
return unless subdomain.present? return unless subdomain.present?
return if RESERVED_SUBDOMAINS.include?(subdomain) return if RESERVED_SUBDOMAINS.include?(subdomain)

View File

@@ -44,7 +44,7 @@ const Return = ({ tenantBilling, homeUrl, billingUrl }: Props) => {
if (status === 'complete') { if (status === 'complete') {
return ( return (
<Box customClass="billingContainer"> <Box customClass="billingContainer">
<h2>Success</h2> <h2>Success!</h2>
<p>Thank you for choosing Astuto! Your subscription will be activated shortly.</p> <p>Thank you for choosing Astuto! Your subscription will be activated shortly.</p>
<ActionLink onClick={() => window.location.href = homeUrl} icon={<BackIcon />}> <ActionLink onClick={() => window.location.href = homeUrl} icon={<BackIcon />}>

View File

@@ -1,4 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { LikeIcon, SolidLikeIcon } from '../common/Icons';
interface Props { interface Props {
postId: number; postId: number;
@@ -35,6 +36,7 @@ const LikeButtonP = ({
className={`likeButton${liked ? ' liked' : ''}`} className={`likeButton${liked ? ' liked' : ''}`}
hidden={!showLikeButton} hidden={!showLikeButton}
> >
{ liked ? <SolidLikeIcon /> : <LikeIcon />}
</div> </div>
{ showLikeCount && <span className="likeCountLabel">{likeCount}</span> } { showLikeCount && <span className="likeCountLabel">{likeCount}</span> }
</div> </div>

View File

@@ -119,6 +119,7 @@ const OAuthForm = ({
<label htmlFor="logo">{ getLabel('o_auth', 'logo') }</label> <label htmlFor="logo">{ getLabel('o_auth', 'logo') }</label>
<input <input
{...register('logo')} {...register('logo')}
placeholder='https://example.com/logo.png'
id="logo" id="logo"
className="formControl" className="formControl"
/> />

View File

@@ -133,6 +133,7 @@ const GeneralSiteSettingsP = ({
<label htmlFor="siteLogo">{ getLabel('tenant', 'site_logo') }</label> <label htmlFor="siteLogo">{ getLabel('tenant', 'site_logo') }</label>
<input <input
{...register('siteLogo')} {...register('siteLogo')}
placeholder='https://example.com/logo.png'
id="siteLogo" id="siteLogo"
className="formControl" className="formControl"
/> />

View File

@@ -45,6 +45,9 @@ const TenantSignUpForm = ({
<div className="formRow"> <div className="formRow">
<div className="input-group"> <div className="input-group">
<div className="input-group-prepend">
<div className="input-group-text">https://</div>
</div>
<input <input
{...register('subdomain', { {...register('subdomain', {
required: true, required: true,
@@ -57,7 +60,7 @@ const TenantSignUpForm = ({
}, },
}, },
})} })}
placeholder={getLabel('tenant', 'subdomain')} placeholder={getLabel('tenant', 'subdomain').toLowerCase()}
id="tenantSubdomain" id="tenantSubdomain"
className="formControl" className="formControl"
/> />

View File

@@ -9,6 +9,7 @@ import { MdContentCopy, MdDone, MdOutlineArrowBack } from 'react-icons/md';
import { GrTest, GrClearOption } from 'react-icons/gr'; import { GrTest, GrClearOption } from 'react-icons/gr';
import { MdOutlineLibraryBooks } from "react-icons/md"; import { MdOutlineLibraryBooks } from "react-icons/md";
import { MdVerified } from "react-icons/md"; import { MdVerified } from "react-icons/md";
import { BiLike, BiSolidLike } from "react-icons/bi";
export const EditIcon = () => <FiEdit />; export const EditIcon = () => <FiEdit />;
@@ -39,3 +40,7 @@ export const StaffIcon = () => (
); );
export const ClearIcon = () => <GrClearOption />; export const ClearIcon = () => <GrClearOption />;
export const LikeIcon = ({size = 32}) => <BiLike size={size} />;
export const SolidLikeIcon = ({size = 32}) => <BiSolidLike size={size} />;

View File

@@ -14,7 +14,7 @@ export interface IOAuth {
callbackUrl?: string; callbackUrl?: string;
tenantId?: number; tenantId?: number;
defaultOAuthIsEnabled: boolean; defaultOAuthIsEnabled?: boolean;
} }
export interface IOAuthJSON { export interface IOAuthJSON {
@@ -33,7 +33,7 @@ export interface IOAuthJSON {
callback_url?: string; callback_url?: string;
tenant_id?: string; tenant_id?: string;
default_o_auth_is_enabled: boolean; default_o_auth_is_enabled?: boolean;
} }
export const oAuthJSON2JS = (oAuthJSON: IOAuthJSON): IOAuth => ({ export const oAuthJSON2JS = (oAuthJSON: IOAuthJSON): IOAuth => ({

View File

@@ -2,6 +2,8 @@ import I18n from "i18n-js"
I18n.translations = <%= I18n::JS.filtered_translations.to_json %> I18n.translations = <%= I18n::JS.filtered_translations.to_json %>
I18n.locale = LOCALE I18n.locale = LOCALE
I18n.defaultLocale = "en"
I18n.fallbacks = <%= Rails.env.production? ? true : false %>
I18n.pluralization["zh-CN"] = function(count) { return ["other"] } I18n.pluralization["zh-CN"] = function(count) { return ["other"] }
I18n.pluralization["vi"] = function(count) { return ["other"] } I18n.pluralization["vi"] = function(count) { return ["other"] }

View File

@@ -6,7 +6,13 @@ class ApplicationMailer < ActionMailer::Base
def set_mail_from_for_multitenancy def set_mail_from_for_multitenancy
if Rails.application.multi_tenancy? if Rails.application.multi_tenancy?
from = "#{Current.tenant_or_raise!.site_name} <#{ENV.fetch('EMAIL_MAIL_FROM', 'notifications@astuto.io')}>" from = "#{Current.tenant.site_name} <#{ENV.fetch('EMAIL_MAIL_FROM', 'notifications@astuto.io')}>"
# Set a specific 'from' for the Devise::Mailer#confirmation_instructions method on tenant signup
if self.class.name == "Devise::Mailer" && action_name == "confirmation_instructions" && Current.tenant.users.count == 1
from = "Astuto <#{ENV.fetch('EMAIL_MAIL_FROM', 'notifications@astuto.io')}>"
end
mail.from = from mail.from = from
end end
end end

View File

@@ -1,6 +1,7 @@
class TenantMailer < ApplicationMailer class TenantMailer < ApplicationMailer
layout :choose_layout layout :choose_layout
skip_after_action :set_mail_from_for_multitenancy skip_after_action :set_mail_from_for_multitenancy
before_action :set_locale_to_english
def trial_start(tenant:) def trial_start(tenant:)
@tenant = tenant @tenant = tenant
@@ -20,7 +21,7 @@ class TenantMailer < ApplicationMailer
@tenant = tenant @tenant = tenant
Current.tenant = tenant Current.tenant = tenant
@trial_ends_at = tenant.tenant_billing.trial_ends_at @trial_ends_at = tenant.tenant_billing.trial_ends_at.to_date
mail( mail(
from: email_from_riccardo, from: email_from_riccardo,
@@ -62,7 +63,7 @@ class TenantMailer < ApplicationMailer
@tenant = tenant @tenant = tenant
Current.tenant = tenant Current.tenant = tenant
@subscription_ends_at = Current.tenant.tenant_billing.subscription_ends_at @subscription_ends_at = Current.tenant.tenant_billing.subscription_ends_at.to_date
mail( mail(
from: email_from_astuto, from: email_from_astuto,
@@ -97,4 +98,8 @@ class TenantMailer < ApplicationMailer
def choose_layout def choose_layout
action_name == 'trial_mid' || action_name == 'trial_end' ? 'mailer_no_style' : 'mailer' action_name == 'trial_mid' || action_name == 'trial_end' ? 'mailer_no_style' : 'mailer'
end end
def set_locale_to_english
I18n.locale = :en
end
end end

View File

@@ -65,6 +65,14 @@ class User < ApplicationRecord
"https://secure.gravatar.com/avatar/#{gravatar_id}" "https://secure.gravatar.com/avatar/#{gravatar_id}"
end end
def full_name_or_email
if full_name.present? && full_name != I18n.t('defaults.user_full_name') && full_name != I18n.t('defaults.user_full_name', locale: 'en')
full_name
else
email
end
end
def owner? def owner?
role == 'owner' role == 'owner'
end end

View File

@@ -1,6 +1,10 @@
<p><%= t('mailers.devise.welcome_greeting', email: @email, site_name: Current.tenant_or_raise!.site_name) %></p> <p>
<%= t('mailers.devise.welcome_greeting', email: @email, site_name: Current.tenant.users.count == 1 ? "Astuto" : Current.tenant.site_name) %>
</p>
<p><%= t('mailers.devise.confirmation_instructions.body') %></p> <p>
<%= t('mailers.devise.confirmation_instructions.body') %>
</p>
<p> <p>
<%= link_to t('mailers.devise.confirmation_instructions.action'), <%= link_to t('mailers.devise.confirmation_instructions.action'),

View File

@@ -66,3 +66,20 @@
method: :delete, method: :delete,
class: "btn btn-danger btn-block" %> class: "btn btn-danger btn-block" %>
</div> </div>
<br />
<% if Rails.application.multi_tenancy? && user_signed_in? && current_user.owner? %>
<div class="edit_user">
<h3>Delete feedback space</h3>
<p>
Deleting the feedback space will also delete all the feedback and users associated with it. This action cannot be undone.
</p>
<p>
If you want to delete the feedback space, please send an email to <a href="mailto:info@astuto.io">info@astuto.io</a> from the email address associated with this feedback space.
</p>
</div>
<% end %>
<br />

View File

@@ -6,14 +6,14 @@
<ul class="dropdown-menu boards-dropdown" aria-labelledby="navbarDropdown"> <ul class="dropdown-menu boards-dropdown" aria-labelledby="navbarDropdown">
<% boards.each do |board| %> <% boards.each do |board| %>
<li> <li>
<%= link_to board.name, get_url_for(method(:board_url), resource: board), class: 'dropdown-item' %> <%= link_to board.name, @header_full_urls ? get_url_for(method(:board_url), resource: board) : board_path(board), class: 'dropdown-item' %>
</li> </li>
<% end %> <% end %>
</ul> </ul>
<% else %> <% else %>
<% boards.each do |board| %> <% boards.each do |board| %>
<li class="nav-item<%= board.id == @board.id ? ' active' : '' unless @board.nil? %>"> <li class="nav-item<%= board.id == @board.id ? ' active' : '' unless @board.nil? %>">
<%= link_to board.name, get_url_for(method(:board_url), resource: board), class: 'nav-link' %> <%= link_to board.name, @header_full_urls ? get_url_for(method(:board_url), resource: board) : board_path(board), class: 'nav-link' %>
</li> </li>
<% end %> <% end %>
<% end %> <% end %>

View File

@@ -1,7 +1,7 @@
<nav class="header"> <nav class="header">
<div class="container"> <div class="container">
<%= <%=
link_to get_url_for(method(:root_url)), class: 'brand' do link_to @header_full_urls ? get_url_for(method(:root_url)) : root_path, class: 'brand' do
app_name = content_tag :span, @tenant.site_name app_name = content_tag :span, @tenant.site_name
logo = image_tag(@tenant.site_logo ? @tenant.site_logo : "", class: 'logo', skip_pipeline: true) logo = image_tag(@tenant.site_logo ? @tenant.site_logo : "", class: 'logo', skip_pipeline: true)
@@ -31,7 +31,7 @@
<ul class="boardsNav"> <ul class="boardsNav">
<% if @tenant_setting.show_roadmap_in_header %> <% if @tenant_setting.show_roadmap_in_header %>
<li class="nav-item<%= (current_page?(roadmap_path) or (@tenant_setting.root_board_id == 0 and current_page?(root_path))) ? ' active' : '' %>"> <li class="nav-item<%= (current_page?(roadmap_path) or (@tenant_setting.root_board_id == 0 and current_page?(root_path))) ? ' active' : '' %>">
<%= link_to t('roadmap.title'), get_url_for(method(:roadmap_url)), class: 'nav-link' %> <%= link_to t('roadmap.title'), @header_full_urls ? get_url_for(method(:roadmap_url)) : roadmap_path, class: 'nav-link' %>
</li> </li>
<% end %> <% end %>
@@ -47,14 +47,21 @@
</a> </a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
<% if current_user.moderator? %> <% if current_user.moderator? %>
<% if current_user.admin? or current_user.owner? %>
<%= <%=
link_to t('header.menu.site_settings'), link_to t('header.menu.site_settings'),
current_user.admin? ? get_url_for(method(:site_settings_general_url)) : get_url_for(method(:site_settings_users_url)), @header_full_urls ? get_url_for(method(:site_settings_general_url)) : site_settings_general_path,
class: 'dropdown-item' class: 'dropdown-item'
%> %>
<% else %>
<%=
link_to t('header.menu.site_settings'),
@header_full_urls ? get_url_for(method(:site_settings_users_url)) : site_settings_users_path,
class: 'dropdown-item'
%>
<% end %>
<% if current_user.owner? and Rails.application.multi_tenancy? %> <% if current_user.owner? and Rails.application.multi_tenancy? %>
<%= link_to get_url_for(method(:request_billing_page_url)), class: 'dropdown-item', data: {turbolinks: false} do %> <%= link_to @header_full_urls ? get_url_for(method(:request_billing_page_url)) : request_billing_page_path, class: 'dropdown-item', data: {turbolinks: false} do %>
<%= t('billing.title') %> <%= t('billing.title') %>
<% if not Current.tenant.tenant_billing.has_active_subscription? %> <% if not Current.tenant.tenant_billing.has_active_subscription? %>
<span style="color: red; font-size: 18px;">⚠️</span> <span style="color: red; font-size: 18px;">⚠️</span>
@@ -65,11 +72,11 @@
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<% end %> <% end %>
<%= link_to t('header.menu.profile_settings'), get_url_for(method(:edit_user_registration_url)), class: 'dropdown-item' %> <%= link_to t('header.menu.profile_settings'), @header_full_urls ? get_url_for(method(:edit_user_registration_url)) : edit_user_registration_path, class: 'dropdown-item' %>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<% unless @disable_sign_out %> <% unless @disable_sign_out %>
<%= button_to t('header.menu.sign_out'), get_url_for(method(:destroy_user_session_url)), method: :delete, class: 'dropdown-item' %> <%= button_to t('header.menu.sign_out'), destroy_user_session_path, method: :delete, class: 'dropdown-item' %>
<% else %> <% else %>
<small style="font-size: 12px; color: grey; padding: 0.25rem 1.5rem;"> <small style="font-size: 12px; color: grey; padding: 0.25rem 1.5rem;">
Sign out disabled Sign out disabled
@@ -79,7 +86,7 @@
</li> </li>
<% else %> <% else %>
<li class="nav-item"> <li class="nav-item">
<%= link_to t('header.log_in'), get_url_for(method(:new_user_session_url)), class: 'nav-link' %> <%= link_to t('header.log_in'), @header_full_urls ? get_url_for(method(:new_user_session_url)) : new_user_session_path, class: 'nav-link' %>
</li> </li>
<% end %> <% end %>
</ul> </ul>

View File

@@ -1,5 +1,5 @@
<p> <p>
Hello <%= @tenant.owner.full_name %>! Hello <%= @tenant.owner.full_name_or_email %>!
</p> </p>
<p> <p>
@@ -7,7 +7,7 @@ We're sorry to see you go. <b>Your subscription has been cancelled and won't be
</p> </p>
<p> <p>
Your current subscription will remain active until <%= @subscription_ends_at.strftime('%B %d, %Y') %>. Your current subscription will remain active until <%= I18n.localize @subscription_ends_at, format: :long %>.
</p> </p>
<p> <p>

View File

@@ -1,5 +1,5 @@
<p> <p>
Hello <%= @tenant.owner.full_name %>! Hello <%= @tenant.owner.full_name_or_email %>!
</p> </p>
<p> <p>

View File

@@ -1,5 +1,5 @@
<p> <p>
Hello <%= @tenant.owner.full_name %>! Hello <%= @tenant.owner.full_name_or_email %>!
</p> </p>
<p> <p>

View File

@@ -1,5 +1,5 @@
<p> <p>
Hi <%= @tenant.owner.full_name %>, Hi <%= @tenant.owner.full_name_or_email %>,
</p> </p>
<p> <p>

View File

@@ -1,5 +1,5 @@
<p> <p>
Hi <%= @tenant.owner.full_name %>, Hi <%= @tenant.owner.full_name_or_email %>,
</p> </p>
<p> <p>
@@ -15,7 +15,7 @@ How's your Astuto trial going? If you have any questions or encounter any issues
</p> </p>
<p> <p>
Also remember that your trial period will expire on <%= @trial_ends_at.strftime('%B %d, %Y') %>. Also remember that your trial period will expire on <%= I18n.localize @trial_ends_at, format: :long %>.
</p> </p>
<p> <p>

View File

@@ -1,5 +1,5 @@
<p> <p>
Hello <%= @tenant.owner.full_name %>, Hello <%= @tenant.owner.full_name_or_email %>,
</p> </p>
<p> <p>

View File

@@ -1,4 +1,4 @@
<%= render 'user_mailer/opening', user_name: @user.full_name %> <%= render 'user_mailer/opening', user_name: @user.full_name_or_email %>
<p> <p>
<%= t('mailers.user.notify_comment_owner.body_html', user: @comment.user.full_name, post: @comment.post.title) %>: <%= t('mailers.user.notify_comment_owner.body_html', user: @comment.user.full_name, post: @comment.post.title) %>:

View File

@@ -1,4 +1,4 @@
<%= render 'user_mailer/opening', user_name: @user.full_name %> <%= render 'user_mailer/opening', user_name: @user.full_name_or_email %>
<p> <p>
<%= t('mailers.user.notify_follower_of_post_status_change.body_html', post: @post.title) %>: <%= t('mailers.user.notify_follower_of_post_status_change.body_html', post: @post.title) %>:

View File

@@ -1,4 +1,4 @@
<%= render 'user_mailer/opening', user_name: @user.full_name %> <%= render 'user_mailer/opening', user_name: @user.full_name_or_email %>
<p> <p>
<%= t('mailers.user.notify_follower_of_post_update.body_html', user: @comment.user.full_name, post: @comment.post.title) %>: <%= t('mailers.user.notify_follower_of_post_update.body_html', user: @comment.user.full_name, post: @comment.post.title) %>:

View File

@@ -1,4 +1,4 @@
<%= render 'user_mailer/opening', user_name: @user.full_name %> <%= render 'user_mailer/opening', user_name: @user.full_name_or_email %>
<p> <p>
<%= t('mailers.user.notify_post_owner.body_html', user: @comment.user.full_name, post: @comment.post.title) %>: <%= t('mailers.user.notify_post_owner.body_html', user: @comment.user.full_name, post: @comment.post.title) %>:

View File

@@ -11,6 +11,9 @@ module App
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.0 config.load_defaults 6.0
# Set default locale
config.i18n.default_locale = :en
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers # Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading # -- all .rb files in that directory are automatically loaded after loading

View File

@@ -3,8 +3,6 @@ I18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml')]
I18n.available_locales = [:en, :it, :de, :fr, :es, 'zh-CN', :ru, :vi] I18n.available_locales = [:en, :it, :de, :fr, :es, 'zh-CN', :ru, :vi]
I18n.default_locale = :en
# Custom pluralization rules # Custom pluralization rules
# Those must be mirrored in app/javascript/translations/index.js.erb # Those must be mirrored in app/javascript/translations/index.js.erb
I18n::Backend::Simple.include(I18n::Backend::Pluralization) I18n::Backend::Simple.include(I18n::Backend::Pluralization)

View File

@@ -19,7 +19,7 @@ task notify_tenants_trial_period: [:environment] do
tenants_mid_trial.each do |tenant| tenants_mid_trial.each do |tenant|
puts "Delivering trial_mid email for #{tenant.site_name}..." puts "Delivering trial_mid email for #{tenant.site_name}..."
TenantMailer.trial_mid(tenant: tenant).deliver_later TenantMailer.trial_mid(tenant: tenant).deliver_now
end end
# Notify tenants end of trial # Notify tenants end of trial
@@ -28,7 +28,7 @@ task notify_tenants_trial_period: [:environment] do
tenants_end_trial.each do |tenant| tenants_end_trial.each do |tenant|
puts "Delivering trial_end email for #{tenant.site_name}..." puts "Delivering trial_end email for #{tenant.site_name}..."
TenantMailer.trial_end(tenant: tenant).deliver_later TenantMailer.trial_end(tenant: tenant).deliver_now
end end
rescue Exception => e rescue Exception => e
error_subject = "Scheduled Task 'notify_tenants_trial_period.rake' Failed" error_subject = "Scheduled Task 'notify_tenants_trial_period.rake' Failed"