mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 05:09:38 +02:00
Sync notifications and troubleshooting guides (#998)
* Add help articles * Broadcast sync messages as notifications * Lint fixes * more lint fixes * Remove redundant code
This commit is contained in:
parent
b200b71284
commit
fa08f027c7
33 changed files with 256 additions and 97 deletions
1
Gemfile
1
Gemfile
|
@ -44,6 +44,7 @@ gem "pagy"
|
||||||
gem "rails-settings-cached"
|
gem "rails-settings-cached"
|
||||||
gem "tzinfo-data", platforms: %i[ windows jruby ]
|
gem "tzinfo-data", platforms: %i[ windows jruby ]
|
||||||
gem "csv"
|
gem "csv"
|
||||||
|
gem "redcarpet"
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem "debug", platforms: %i[ mri windows ]
|
gem "debug", platforms: %i[ mri windows ]
|
||||||
|
|
|
@ -336,6 +336,7 @@ GEM
|
||||||
logger
|
logger
|
||||||
rdoc (6.7.0)
|
rdoc (6.7.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
|
redcarpet (3.6.0)
|
||||||
regexp_parser (2.9.2)
|
regexp_parser (2.9.2)
|
||||||
reline (0.5.9)
|
reline (0.5.9)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
|
@ -492,6 +493,7 @@ DEPENDENCIES
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
rails!
|
rails!
|
||||||
rails-settings-cached
|
rails-settings-cached
|
||||||
|
redcarpet
|
||||||
rubocop-rails-omakase
|
rubocop-rails-omakase
|
||||||
ruby-lsp-rails
|
ruby-lsp-rails
|
||||||
selenium-webdriver
|
selenium-webdriver
|
||||||
|
|
|
@ -31,7 +31,7 @@ class Account::EntriesController < ApplicationController
|
||||||
else
|
else
|
||||||
# TODO: this is not an ideal way to handle errors and should eventually be improved.
|
# TODO: this is not an ideal way to handle errors and should eventually be improved.
|
||||||
# See: https://github.com/hotwired/turbo-rails/pull/367
|
# See: https://github.com/hotwired/turbo-rails/pull/367
|
||||||
flash[:error] = @entry.errors.full_messages.to_sentence
|
flash[:alert] = @entry.errors.full_messages.to_sentence
|
||||||
redirect_to account_path(@account)
|
redirect_to account_path(@account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ class Account::TransfersController < ApplicationController
|
||||||
else
|
else
|
||||||
# TODO: this is not an ideal way to handle errors and should eventually be improved.
|
# TODO: this is not an ideal way to handle errors and should eventually be improved.
|
||||||
# See: https://github.com/hotwired/turbo-rails/pull/367
|
# See: https://github.com/hotwired/turbo-rails/pull/367
|
||||||
flash[:error] = @transfer.errors.full_messages.to_sentence
|
flash[:alert] = @transfer.errors.full_messages.to_sentence
|
||||||
redirect_to transactions_path
|
redirect_to transactions_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,8 +68,6 @@ class AccountsController < ApplicationController
|
||||||
unless @account.syncing?
|
unless @account.syncing?
|
||||||
@account.sync_later
|
@account.sync_later
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to account_path(@account), notice: t(".success")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def sync_all
|
def sync_all
|
||||||
|
|
11
app/controllers/help/articles_controller.rb
Normal file
11
app/controllers/help/articles_controller.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
class Help::ArticlesController < ApplicationController
|
||||||
|
layout "with_sidebar"
|
||||||
|
|
||||||
|
def show
|
||||||
|
@article = Help::Article.find(params[:id])
|
||||||
|
|
||||||
|
unless @article
|
||||||
|
head :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -42,13 +42,13 @@ class ImportsController < ApplicationController
|
||||||
begin
|
begin
|
||||||
@import.raw_csv_str = import_params[:raw_csv_str].read
|
@import.raw_csv_str = import_params[:raw_csv_str].read
|
||||||
rescue NoMethodError
|
rescue NoMethodError
|
||||||
flash.now[:error] = "Please select a file to upload"
|
flash.now[:alert] = "Please select a file to upload"
|
||||||
render :load, status: :unprocessable_entity and return
|
render :load, status: :unprocessable_entity and return
|
||||||
end
|
end
|
||||||
if @import.save
|
if @import.save
|
||||||
redirect_to configure_import_path(@import), notice: t(".import_loaded")
|
redirect_to configure_import_path(@import), notice: t(".import_loaded")
|
||||||
else
|
else
|
||||||
flash.now[:error] = @import.errors.full_messages.to_sentence
|
flash.now[:alert] = @import.errors.full_messages.to_sentence
|
||||||
render :load, status: :unprocessable_entity
|
render :load, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -57,7 +57,7 @@ class ImportsController < ApplicationController
|
||||||
if @import.update(import_params)
|
if @import.update(import_params)
|
||||||
redirect_to configure_import_path(@import), notice: t(".import_loaded")
|
redirect_to configure_import_path(@import), notice: t(".import_loaded")
|
||||||
else
|
else
|
||||||
flash.now[:error] = @import.errors.full_messages.to_sentence
|
flash.now[:alert] = @import.errors.full_messages.to_sentence
|
||||||
render :load, status: :unprocessable_entity
|
render :load, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Settings::HostingsController < SettingsController
|
||||||
|
|
||||||
def send_test_email
|
def send_test_email
|
||||||
unless Setting.smtp_settings_populated?
|
unless Setting.smtp_settings_populated?
|
||||||
flash[:error] = t(".missing_smtp_setting_error")
|
flash[:alert] = t(".missing_smtp_setting_error")
|
||||||
render(:show, status: :unprocessable_entity)
|
render(:show, status: :unprocessable_entity)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -27,7 +27,7 @@ class Settings::HostingsController < SettingsController
|
||||||
begin
|
begin
|
||||||
NotificationMailer.with(user: Current.user).test_email.deliver_now
|
NotificationMailer.with(user: Current.user).test_email.deliver_now
|
||||||
rescue => _e
|
rescue => _e
|
||||||
flash[:error] = t(".error")
|
flash[:alert] = t(".error")
|
||||||
render :show, status: :unprocessable_entity
|
render :show, status: :unprocessable_entity
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,11 +13,18 @@ module ApplicationHelper
|
||||||
name.underscore
|
name.underscore
|
||||||
end
|
end
|
||||||
|
|
||||||
def notification(text, **options, &block)
|
def family_notifications_stream
|
||||||
content = tag.p(text)
|
turbo_stream_from [ Current.family, :notifications ] if Current.family
|
||||||
content = capture &block if block_given?
|
end
|
||||||
|
|
||||||
render partial: "shared/notification", locals: { type: options[:type], content: { body: content } }
|
def render_flash_notifications
|
||||||
|
notifications = flash.flat_map do |type, message_or_messages|
|
||||||
|
Array(message_or_messages).map do |message|
|
||||||
|
render partial: "shared/notification", locals: { type: type, message: message }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
safe_join(notifications)
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -2,8 +2,6 @@ class Account < ApplicationRecord
|
||||||
include Syncable
|
include Syncable
|
||||||
include Monetizable
|
include Monetizable
|
||||||
|
|
||||||
broadcasts_refreshes
|
|
||||||
|
|
||||||
validates :name, :balance, :currency, presence: true
|
validates :name, :balance, :currency, presence: true
|
||||||
|
|
||||||
belongs_to :family
|
belongs_to :family
|
||||||
|
|
|
@ -48,13 +48,35 @@ class Account::Sync < ApplicationRecord
|
||||||
|
|
||||||
def start!
|
def start!
|
||||||
update! status: "syncing", last_ran_at: Time.now
|
update! status: "syncing", last_ran_at: Time.now
|
||||||
|
broadcast_start
|
||||||
end
|
end
|
||||||
|
|
||||||
def complete!
|
def complete!
|
||||||
update! status: "completed"
|
update! status: "completed"
|
||||||
|
broadcast_result type: "notice", message: "Sync complete"
|
||||||
end
|
end
|
||||||
|
|
||||||
def fail!(error)
|
def fail!(error)
|
||||||
update! status: "failed", error: error.message
|
update! status: "failed", error: error.message
|
||||||
|
broadcast_result type: "alert", message: error.message
|
||||||
|
end
|
||||||
|
|
||||||
|
def broadcast_start
|
||||||
|
broadcast_append_to(
|
||||||
|
[ account.family, :notifications ],
|
||||||
|
target: "notification-tray",
|
||||||
|
partial: "shared/notification",
|
||||||
|
locals: { id: id, type: "processing", message: "Syncing account balances" }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def broadcast_result(type:, message:)
|
||||||
|
broadcast_remove_to account.family, :notifications, target: id # Remove persistent syncing notification
|
||||||
|
broadcast_append_to(
|
||||||
|
[ account.family, :notifications ],
|
||||||
|
target: "notification-tray",
|
||||||
|
partial: "shared/notification",
|
||||||
|
locals: { type: type, message: message }
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Current < ActiveSupport::CurrentAttributes
|
class Current < ActiveSupport::CurrentAttributes
|
||||||
attribute :user
|
attribute :user
|
||||||
|
|
||||||
delegate :family, to: :user
|
delegate :family, to: :user, allow_nil: true
|
||||||
end
|
end
|
||||||
|
|
54
app/models/help/article.rb
Normal file
54
app/models/help/article.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
class Help::Article
|
||||||
|
attr_reader :frontmatter, :content
|
||||||
|
|
||||||
|
def initialize(frontmatter:, content:)
|
||||||
|
@frontmatter = frontmatter
|
||||||
|
@content = content
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
frontmatter["title"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def html
|
||||||
|
render_markdown(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def root_path
|
||||||
|
Rails.root.join("docs", "help")
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(slug)
|
||||||
|
Dir.glob(File.join(root_path, "*.md")).each do |file_path|
|
||||||
|
file_content = File.read(file_path)
|
||||||
|
frontmatter, markdown_content = parse_frontmatter(file_content)
|
||||||
|
|
||||||
|
return new(frontmatter:, content: markdown_content) if frontmatter["slug"] == slug
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_frontmatter(content)
|
||||||
|
if content =~ /\A---(.+?)---/m
|
||||||
|
frontmatter = YAML.safe_load($1)
|
||||||
|
markdown_content = content[($~.end(0))..-1].strip
|
||||||
|
else
|
||||||
|
frontmatter = {}
|
||||||
|
markdown_content = content
|
||||||
|
end
|
||||||
|
|
||||||
|
[ frontmatter, markdown_content ]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def render_markdown(content)
|
||||||
|
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML)
|
||||||
|
markdown.render(content)
|
||||||
|
end
|
||||||
|
end
|
19
app/views/accounts/_alert.html.erb
Normal file
19
app/views/accounts/_alert.html.erb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<%# locals: (message:, help_path: nil) -%>
|
||||||
|
<%= tag.div class: "flex gap-6 items-center rounded-xl px-4 py-3 bg-error/5",
|
||||||
|
data: { controller: "element-removal" },
|
||||||
|
role: "alert" do %>
|
||||||
|
<div class="flex gap-3 items-center text-red-500 grow overflow-x-scroll">
|
||||||
|
<%= lucide_icon("alert-octagon", class: "w-5 h-5 shrink-0") %>
|
||||||
|
<p class="text-sm whitespace-nowrap"><%= message %></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4 ml-auto">
|
||||||
|
<% if help_path %>
|
||||||
|
<%= link_to "Troubleshoot", help_path, class: "text-red-500 font-medium hover:underline", data: { turbo_frame: :drawer } %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= tag.button data: { action: "click->element-removal#remove" } do %>
|
||||||
|
<%= lucide_icon("x", class: "w-5 h-5 shrink-0 text-red-500") %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -1,8 +0,0 @@
|
||||||
<%# locals: (is_syncing:) %>
|
|
||||||
<% if is_syncing %>
|
|
||||||
<div class="my-4 px-8 py-4 rounded-lg bg-yellow-500/10 flex items-center justify-between">
|
|
||||||
<p class="text-gray-900 text-sm">
|
|
||||||
Syncing your account balances.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
|
@ -1,5 +1,3 @@
|
||||||
<%= turbo_stream_from @account %>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
@ -45,12 +43,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= turbo_frame_tag "sync_message" do %>
|
|
||||||
<%= render partial: "accounts/sync_message", locals: { is_syncing: @account.syncing? } %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if @account.alert %>
|
<% if @account.alert %>
|
||||||
<%= render partial: "shared/alert", locals: { type: "error", content: t("." + @account.alert) } %>
|
<%= render "alert", message: @account.alert, help_path: help_article_path("troubleshooting") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="bg-white shadow-xs rounded-xl border border-alpha-black-25 rounded-lg">
|
<div class="bg-white shadow-xs rounded-xl border border-alpha-black-25 rounded-lg">
|
||||||
|
|
7
app/views/help/articles/show.html.erb
Normal file
7
app/views/help/articles/show.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<%= drawer do %>
|
||||||
|
<div class="prose">
|
||||||
|
<%= tag.h1 @article.title %>
|
||||||
|
|
||||||
|
<%= sanitize(@article.html).html_safe %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -25,8 +25,13 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="h-full">
|
<body class="h-full">
|
||||||
<div id="notification-tray" class="fixed z-50 space-y-1 top-6 right-10"></div>
|
<div class="fixed z-50 space-y-1 top-6 right-10">
|
||||||
<%= safe_join(flash.map { |type, message| notification(message, type: type) }) %>
|
<div id="notification-tray">
|
||||||
|
<%= render_flash_notifications %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= family_notifications_stream %>
|
||||||
|
|
||||||
<%= content_for?(:content) ? yield(:content) : yield %>
|
<%= content_for?(:content) ? yield(:content) : yield %>
|
||||||
|
|
||||||
|
@ -39,5 +44,4 @@
|
||||||
<%= render "shared/app_version" %>
|
<%= render "shared/app_version" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
<%# locals: (type: "error", content: "") -%>
|
|
||||||
<%= content_tag :div,
|
|
||||||
class: "flex justify-between rounded-xl p-3 #{type == "error" ? "bg-red-50" : "bg-yellow-50"}",
|
|
||||||
data: {controller: "element-removal" },
|
|
||||||
role: type == "error" ? "alert" : "status" do %>
|
|
||||||
<div class="flex gap-3 items-center <%= type == "error" ? "text-red-500" : "text-yellow-500" %>">
|
|
||||||
<%= lucide_icon("info", class: "w-5 h-5 shrink-0") %>
|
|
||||||
<p class="text-sm"><%= content %></p>
|
|
||||||
</div>
|
|
||||||
<%= content_tag :a, lucide_icon("x", class: "w-5 h-5 shrink-0 #{type == "error" ? "text-red-500" : "text-yellow-500"}"), data: { action: "click->element-removal#remove" }, class:"flex gap-1 font-medium items-center text-gray-900 px-3 py-1.5 rounded-lg cursor-pointer" %>
|
|
||||||
<% end %>
|
|
|
@ -1,5 +1,5 @@
|
||||||
<%= turbo_frame_tag "drawer" do %>
|
<%= turbo_frame_tag "drawer" do %>
|
||||||
<dialog class="bg-white border border-alpha-black-25 rounded-2xl max-h-[calc(100vh-32px)] max-w-[480px] w-full shadow-xs h-full mt-4 mr-4" data-controller="modal" data-action="click->modal#clickOutside">
|
<dialog class="bg-white border border-alpha-black-25 rounded-2xl max-h-[calc(100vh-32px)] max-w-[480px] w-full shadow-xs h-full mt-4 mr-4 focus-visible:outline-none" data-controller="modal" data-action="click->modal#clickOutside">
|
||||||
<div class="flex flex-col h-full p-4">
|
<div class="flex flex-col h-full p-4">
|
||||||
<div class="flex justify-end items-center h-9">
|
<div class="flex justify-end items-center h-9">
|
||||||
<div data-action="click->modal#close" class="cursor-pointer">
|
<div data-action="click->modal#close" class="cursor-pointer">
|
||||||
|
|
|
@ -1,45 +1,45 @@
|
||||||
<%# locals: (type: "success", content: { title: '', body: ''}, action: { label:'' , url:'' }, options: { auto_dismiss: true }) -%>
|
<%# locals: (message:, type: "notice", id: nil, **_opts) %>
|
||||||
|
|
||||||
<turbo-stream action="append" target="notification-tray">
|
<% type = type.to_sym %>
|
||||||
<template>
|
<% action = "animationend->element-removal#remove" if type == :notice %>
|
||||||
<% actions = options[:auto_dismiss] ? "animationend->element-removal#remove" : "" %>
|
|
||||||
<% animation = options[:auto_dismiss] ? "animate-[appear-then-fades_5s_300ms_both]" : "animate-[appear_5s_300ms_both]" %>
|
|
||||||
<%= content_tag :div,
|
|
||||||
class: "max-w-80 bg-white shadow-xs border border-alpha-black-50 border-solid py-4 px-4 rounded-[10px] text-sm font-medium flex gap-4 #{animation} group",
|
|
||||||
data: {controller: "element-removal", action: actions },
|
|
||||||
role: type == "error" ? "alert" : "status" do -%>
|
|
||||||
<% base_class = "w-5 h-5 p-1 text-white flex shrink-0 items-center justify-center rounded-full" %>
|
|
||||||
<%= type.in?(["error", "alert"]) ? lucide_icon("x", class: "#{base_class} bg-error") : lucide_icon("check", class: "#{base_class} bg-success") %>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<% if content[:title].present? %>
|
|
||||||
<h1 class="text-sm text-gray-900 font-medium"><%= content[:title] %></h1>
|
|
||||||
<% end %>
|
|
||||||
<p class="text-sm text-gray-500 font-normal"><%= content[:body] %></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row justify-end gap-2">
|
<%= tag.div class: "flex gap-3 rounded-lg border bg-white p-4 group max-w-80 shadow-xs border-alpha-black-25",
|
||||||
<% if !options[:auto_dismiss] %>
|
id: id,
|
||||||
<%= content_tag :a, t(".dismiss"), data: { action: "click->element-removal#remove" }, class:"flex gap-1 font-medium items-center text-gray-900 px-3 py-1.5 rounded-lg cursor-pointer" %>
|
data: {
|
||||||
<% end %>
|
controller: "element-removal",
|
||||||
<% if action[:label].present? && action[:url].present? %>
|
action: action
|
||||||
<%= link_to action[:label], action[:url], class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 px-3 py-1.5 rounded-lg" %>
|
} do %>
|
||||||
<% end %>
|
|
||||||
|
<div class="h-5 w-5 shrink-0 p-px text-white">
|
||||||
|
<% case type %>
|
||||||
|
<% when :notice %>
|
||||||
|
<div class="flex h-full items-center justify-center rounded-full bg-success">
|
||||||
|
<%= lucide_icon "check", class: "w-3 h-3" %>
|
||||||
|
</div>
|
||||||
|
<% when :alert %>
|
||||||
|
<div class="flex h-full items-center justify-center rounded-full bg-error">
|
||||||
|
<%= lucide_icon "x", class: "w-3 h-3" %>
|
||||||
|
</div>
|
||||||
|
<% when :processing %>
|
||||||
|
<%= lucide_icon "loader", class: "w-5 h-5 text-gray-500 animate-pulse" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= tag.p message, class: "text-gray-900 text-sm font-medium" %>
|
||||||
|
|
||||||
|
<div class="ml-auto">
|
||||||
|
<% if type.to_sym == :notice %>
|
||||||
|
<div class="h-5 shrink-0">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="shrink-0">
|
||||||
|
<path d="M18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10ZM3.6 10C3.6 13.5346 6.46538 16.4 10 16.4C13.5346 16.4 16.4 13.5346 16.4 10C16.4 6.46538 13.5346 3.6 10 3.6C6.46538 3.6 3.6 6.46538 3.6 10Z" fill="#E5E5E5" />
|
||||||
|
<circle class="origin-center -rotate-90 animate-[stroke-fill_5s_300ms_forwards]" stroke="#141414" stroke-opacity="0.4" r="7.2" cx="10" cy="10" stroke-dasharray="43.9822971503" stroke-dashoffset="43.9822971503" />
|
||||||
|
</svg>
|
||||||
|
<div class="absolute -top-2 -right-2">
|
||||||
|
<%= lucide_icon "x", class: "w-5 h-5 p-0.5 hidden group-hover:inline-block border border-alpha-black-50 border-solid rounded-lg bg-white text-gray-400 cursor-pointer", data: { action: "click->element-removal#remove" } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<% elsif type.to_sym == :alert %>
|
||||||
<% if options[:auto_dismiss] %>
|
<%= lucide_icon "x", data: { action: "click->element-removal#remove" }, class: "w-5 h-5 text-gray-500 hover:text-gray-600 cursor-pointer" %>
|
||||||
<div class="shrink-0 h-5">
|
<% end %>
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="shrink-0">
|
|
||||||
<path d="M18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10ZM3.6 10C3.6 13.5346 6.46538 16.4 10 16.4C13.5346 16.4 16.4 13.5346 16.4 10C16.4 6.46538 13.5346 3.6 10 3.6C6.46538 3.6 3.6 6.46538 3.6 10Z" fill="#E5E5E5" />
|
|
||||||
<circle class="origin-center -rotate-90 animate-[stroke-fill_5s_300ms_forwards]" stroke="#141414" stroke-opacity="0.4" r="7.2" cx="10" cy="10" stroke-dasharray="43.9822971503" stroke-dashoffset="43.9822971503" />
|
|
||||||
</svg>
|
|
||||||
<div class="absolute -top-2 -right-2">
|
|
||||||
<%= lucide_icon "x", class: "w-5 h-5 p-0.5 hidden group-hover:inline-block border border-alpha-black-50 border-solid rounded-lg bg-white text-gray-400 cursor-pointer", data: { action: "click->element-removal#remove" } %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end -%>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<% end %>
|
||||||
</turbo-stream>
|
|
||||||
|
|
|
@ -66,8 +66,6 @@ en:
|
||||||
value: Value
|
value: Value
|
||||||
summary:
|
summary:
|
||||||
new: New account
|
new: New account
|
||||||
sync:
|
|
||||||
success: Account sync started
|
|
||||||
sync_all:
|
sync_all:
|
||||||
success: Successfully queued accounts for syncing.
|
success: Successfully queued accounts for syncing.
|
||||||
update:
|
update:
|
||||||
|
|
|
@ -13,8 +13,6 @@ en:
|
||||||
no_account_subtitle: Since no accounts have been added, there's no data to display.
|
no_account_subtitle: Since no accounts have been added, there's no data to display.
|
||||||
Add your first accounts to start viewing dashboard data.
|
Add your first accounts to start viewing dashboard data.
|
||||||
no_account_title: No accounts yet
|
no_account_title: No accounts yet
|
||||||
notification:
|
|
||||||
dismiss: Dismiss
|
|
||||||
upgrade_notification:
|
upgrade_notification:
|
||||||
app_upgraded: The app has been upgraded to %{version}.
|
app_upgraded: The app has been upgraded to %{version}.
|
||||||
dismiss: Dismiss
|
dismiss: Dismiss
|
||||||
|
|
|
@ -10,6 +10,10 @@ Rails.application.routes.draw do
|
||||||
resource :password_reset
|
resource :password_reset
|
||||||
resource :password
|
resource :password
|
||||||
|
|
||||||
|
namespace :help do
|
||||||
|
resources :articles, only: :show
|
||||||
|
end
|
||||||
|
|
||||||
namespace :settings do
|
namespace :settings do
|
||||||
resource :profile, only: %i[show update destroy]
|
resource :profile, only: %i[show update destroy]
|
||||||
resource :preferences, only: %i[show update]
|
resource :preferences, only: %i[show update]
|
||||||
|
|
10
docs/help/placeholder.md
Normal file
10
docs/help/placeholder.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
title: Troubleshooting
|
||||||
|
slug: troubleshooting
|
||||||
|
---
|
||||||
|
|
||||||
|
Coming soon...
|
||||||
|
|
||||||
|
We're working on new guides to help troubleshoot various issues within the app.
|
||||||
|
|
||||||
|
Help us out by reporting [issues on Github](https://github.com/maybe-finance/maybe/issues).
|
|
@ -69,7 +69,7 @@ class Account::EntriesControllerTest < ActionDispatch::IntegrationTest
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal "Date has already been taken", flash[:error]
|
assert_equal "Date has already been taken", flash[:alert]
|
||||||
assert_redirected_to account_path(@valuation.account)
|
assert_redirected_to account_path(@valuation.account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ class AccountsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
|
||||||
test "can sync an account" do
|
test "can sync an account" do
|
||||||
post sync_account_path(@account)
|
post sync_account_path(@account)
|
||||||
assert_redirected_to account_url(@account)
|
assert_response :no_content
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can sync all accounts" do
|
test "can sync all accounts" do
|
||||||
|
|
18
test/controllers/help/articles_controller_test.rb
Normal file
18
test/controllers/help/articles_controller_test.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class Help::ArticlesControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
setup do
|
||||||
|
sign_in @user = users(:family_admin)
|
||||||
|
|
||||||
|
@article = Help::Article.new(frontmatter: { title: "Test Article", slug: "test-article" }, content: "")
|
||||||
|
|
||||||
|
Help::Article.stubs(:find).returns(@article)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can view help article" do
|
||||||
|
get help_article_path(@article)
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_dom "h1", text: @article.title, count: 1
|
||||||
|
end
|
||||||
|
end
|
|
@ -81,7 +81,7 @@ class ImportsControllerTest < ActionDispatch::IntegrationTest
|
||||||
patch load_import_url(@empty_import), params: { import: { raw_csv_str: malformed_csv_str } }
|
patch load_import_url(@empty_import), params: { import: { raw_csv_str: malformed_csv_str } }
|
||||||
|
|
||||||
assert_response :unprocessable_entity
|
assert_response :unprocessable_entity
|
||||||
assert_equal "Raw csv str is not a valid CSV format", flash[:error]
|
assert_equal "Raw csv str is not a valid CSV format", flash[:alert]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should flash error message if invalid CSV file upload" do
|
test "should flash error message if invalid CSV file upload" do
|
||||||
|
@ -91,14 +91,14 @@ class ImportsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
|
||||||
patch upload_import_url(@empty_import), params: { import: { raw_csv_str: Rack::Test::UploadedFile.new(temp, ".csv") } }
|
patch upload_import_url(@empty_import), params: { import: { raw_csv_str: Rack::Test::UploadedFile.new(temp, ".csv") } }
|
||||||
assert_response :unprocessable_entity
|
assert_response :unprocessable_entity
|
||||||
assert_equal "Raw csv str is not a valid CSV format", flash[:error]
|
assert_equal "Raw csv str is not a valid CSV format", flash[:alert]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should flash error message if no fileprovided for upload" do
|
test "should flash error message if no fileprovided for upload" do
|
||||||
patch upload_import_url(@empty_import), params: { import: { raw_csv_str: nil } }
|
patch upload_import_url(@empty_import), params: { import: { raw_csv_str: nil } }
|
||||||
assert_response :unprocessable_entity
|
assert_response :unprocessable_entity
|
||||||
assert_equal "Please select a file to upload", flash[:error]
|
assert_equal "Please select a file to upload", flash[:alert]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should get configure" do
|
test "should get configure" do
|
||||||
|
|
|
@ -76,7 +76,7 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
|
||||||
post send_test_email_settings_hosting_path
|
post send_test_email_settings_hosting_path
|
||||||
assert_response :unprocessable_entity
|
assert_response :unprocessable_entity
|
||||||
assert controller.flash[:error].present?
|
assert controller.flash[:alert].present?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
|
||||||
post send_test_email_settings_hosting_path
|
post send_test_email_settings_hosting_path
|
||||||
assert_response :unprocessable_entity
|
assert_response :unprocessable_entity
|
||||||
assert controller.flash[:error].present?
|
assert controller.flash[:alert].present?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
6
test/fixtures/files/help_article.md
vendored
Normal file
6
test/fixtures/files/help_article.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: Placeholder
|
||||||
|
slug: placeholder
|
||||||
|
---
|
||||||
|
|
||||||
|
Test help article
|
|
@ -26,9 +26,15 @@ class Account::SyncTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
@sync.run
|
@sync.run
|
||||||
|
|
||||||
|
streams = capture_turbo_stream_broadcasts [ @account.family, :notifications ]
|
||||||
|
|
||||||
assert_equal "completed", @sync.status
|
assert_equal "completed", @sync.status
|
||||||
assert_equal [ "test balance sync warning", "test holding sync warning" ], @sync.warnings
|
assert_equal [ "test balance sync warning", "test holding sync warning" ], @sync.warnings
|
||||||
assert @sync.last_ran_at
|
assert @sync.last_ran_at
|
||||||
|
|
||||||
|
assert_equal "append", streams.first["action"]
|
||||||
|
assert_equal "remove", streams.second["action"]
|
||||||
|
assert_equal "append", streams.third["action"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handles sync errors" do
|
test "handles sync errors" do
|
||||||
|
|
21
test/models/help/article_test.rb
Normal file
21
test/models/help/article_test.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class Help::ArticleTest < ActiveSupport::TestCase
|
||||||
|
include ActiveJob::TestHelper
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Help::Article.stubs(:root_path).returns(Rails.root.join("test", "fixtures", "files"))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns nil if article not found" do
|
||||||
|
assert_nil Help::Article.find("missing")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "find and renders markdown article" do
|
||||||
|
article = Help::Article.find("placeholder")
|
||||||
|
|
||||||
|
assert_equal "Placeholder", article.title
|
||||||
|
assert_equal "Test help article", article.content
|
||||||
|
assert_equal "<p>Test help article</p>\n", article.html
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue