1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-05 05:25:24 +02:00

Beta Testing Round 3 Bug Fixes (#1357)

* Clean up env example files

* Fix duplicate category creations

* Fix duplicate tag and merchant creation

* Add initial valuation to imported accounts

* Add upgrade modal prompt

* Don't hide content on billing page

* Add temporary session for new customers

* Lint fixes

* Fix unused translations

* Fix system tests
This commit is contained in:
Zach Gollwitzer 2024-10-24 11:02:27 -04:00 committed by GitHub
parent 1d20de770f
commit 6baffe7539
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 231 additions and 81 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame 1321315963">
<rect width="20" height="20" rx="10" fill="#635BFF"/>
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M9.35663 7.69056C9.35663 7.20077 9.75747 7.01238 10.4214 7.01238C11.3734 7.01238 12.5759 7.30124 13.5279 7.81615V4.86482C12.4882 4.45037 11.461 4.28711 10.4214 4.28711C7.87854 4.28711 6.1875 5.61835 6.1875 7.84127C6.1875 11.3075 10.9475 10.7549 10.9475 12.2494C10.9475 12.8271 10.4464 13.0155 9.74495 13.0155C8.70527 13.0155 7.37749 12.5885 6.32529 12.0108V14.9998C7.49023 15.5022 8.66769 15.7157 9.74495 15.7157C12.3504 15.7157 14.1416 14.4221 14.1416 12.1741C14.1291 8.43154 9.35663 9.09716 9.35663 7.69056Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 775 B

View file

@ -2,7 +2,22 @@ class ApplicationController < ActionController::Base
include Onboardable, Localize, AutoSync, Authentication, Invitable, SelfHostable, StoreLocation, Impersonatable
include Pagy::Backend
helper_method :require_upgrade?, :subscription_pending?
private
def require_upgrade?
return false if self_hosted?
return false unless Current.session
return false if Current.family.subscribed?
return false if subscription_pending? || request.path == settings_billing_path
true
end
def subscription_pending?
subscribed_at = Current.session.subscribed_at
subscribed_at.present? && subscribed_at <= Time.current && subscribed_at > 1.hour.ago
end
def with_sidebar
return "turbo_rails/frame" if turbo_frame_request?

View file

@ -1,7 +1,7 @@
class CategoriesController < ApplicationController
layout :with_sidebar
before_action :set_category, only: %i[edit update]
before_action :set_category, only: %i[edit update destroy]
before_action :set_transaction, only: :create
def index
@ -13,12 +13,14 @@ class CategoriesController < ApplicationController
end
def create
Category.transaction do
category = Current.family.categories.create!(category_params)
@transaction.update!(category_id: category.id) if @transaction
end
@category = Current.family.categories.new(category_params)
redirect_back_or_to transactions_path, notice: t(".success")
if @category.save
@transaction.update(category_id: @category.id) if @transaction
redirect_back_or_to transactions_path, notice: t(".success")
else
redirect_back_or_to transactions_path, alert: t(".failure", error: @category.errors.full_messages.to_sentence)
end
end
def edit
@ -30,6 +32,12 @@ class CategoriesController < ApplicationController
redirect_back_or_to transactions_path, notice: t(".success")
end
def destroy
@category.destroy
redirect_back_or_to categories_path, notice: t(".success")
end
private
def set_category
@category = Current.family.categories.find(params[:id])

View file

@ -12,8 +12,13 @@ class MerchantsController < ApplicationController
end
def create
Current.family.merchants.create!(merchant_params)
redirect_to merchants_path, notice: t(".success")
@merchant = Current.family.merchants.new(merchant_params)
if @merchant.save
redirect_to merchants_path, notice: t(".success")
else
redirect_to merchants_path, alert: t(".error", error: @merchant.errors.full_messages.to_sentence)
end
end
def edit

View file

@ -1,16 +1,14 @@
class SubscriptionsController < ApplicationController
def new
client = Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
if Current.family.stripe_customer_id.blank?
customer = client.v1.customers.create(
customer = stripe_client.v1.customers.create(
email: Current.family.primary_user.email,
metadata: { family_id: Current.family.id }
)
Current.family.update(stripe_customer_id: customer.id)
end
session = client.v1.checkout.sessions.create({
session = stripe_client.v1.checkout.sessions.create({
customer: Current.family.stripe_customer_id,
line_items: [ {
price: ENV["STRIPE_PLAN_ID"],
@ -18,7 +16,7 @@ class SubscriptionsController < ApplicationController
} ],
mode: "subscription",
allow_promotion_codes: true,
success_url: settings_billing_url,
success_url: success_subscription_url + "?session_id={CHECKOUT_SESSION_ID}",
cancel_url: settings_billing_url
})
@ -26,12 +24,24 @@ class SubscriptionsController < ApplicationController
end
def show
client = Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
portal_session = client.v1.billing_portal.sessions.create(
portal_session = stripe_client.v1.billing_portal.sessions.create(
customer: Current.family.stripe_customer_id,
return_url: settings_billing_url
)
redirect_to portal_session.url, allow_other_host: true, status: :see_other
end
def success
checkout_session = stripe_client.v1.checkout.sessions.retrieve(params[:session_id])
Current.session.update(subscribed_at: Time.at(checkout_session.created))
redirect_to root_path, notice: "You have successfully subscribed to Maybe+."
rescue Stripe::InvalidRequestError
redirect_to settings_billing_path, alert: "Something went wrong processing your subscription. Please contact us to get this fixed."
end
private
def stripe_client
@stripe_client ||= Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
end
end

View file

@ -1,7 +1,7 @@
class TagsController < ApplicationController
layout :with_sidebar
before_action :set_tag, only: %i[edit update]
before_action :set_tag, only: %i[edit update destroy]
def index
@tags = Current.family.tags.alphabetically
@ -12,8 +12,13 @@ class TagsController < ApplicationController
end
def create
Current.family.tags.create!(tag_params)
redirect_to tags_path, notice: t(".created")
@tag = Current.family.tags.new(tag_params)
if @tag.save
redirect_to tags_path, notice: t(".created")
else
redirect_to tags_path, alert: t(".error", error: @tag.errors.full_messages.to_sentence)
end
end
def edit
@ -24,6 +29,11 @@ class TagsController < ApplicationController
redirect_to tags_path, notice: t(".updated")
end
def destroy
@tag.destroy!
redirect_to tags_path, notice: t(".deleted")
end
private
def set_tag

View file

@ -5,10 +5,10 @@ module SettingsHelper
{ name: I18n.t("settings.nav.self_hosting_label"), path: :settings_hosting_path, condition: :self_hosted? },
{ name: I18n.t("settings.nav.billing_label"), path: :settings_billing_path },
{ name: I18n.t("settings.nav.accounts_label"), path: :accounts_path },
{ name: I18n.t("settings.nav.imports_label"), path: :imports_path },
{ name: I18n.t("settings.nav.tags_label"), path: :tags_path },
{ name: I18n.t("settings.nav.categories_label"), path: :categories_path },
{ name: I18n.t("settings.nav.merchants_label"), path: :merchants_path },
{ name: I18n.t("settings.nav.imports_label"), path: :imports_path },
{ name: I18n.t("settings.nav.whats_new_label"), path: :changelog_path },
{ name: I18n.t("settings.nav.feedback_label"), path: :feedback_path }
]

View file

@ -49,7 +49,12 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
end
def submit(value = nil, options = {})
merged_options = { class: "btn btn--primary w-full" }.merge(options)
default_options = {
data: { turbo_submits_with: "Submitting..." },
class: "btn btn--primary w-full"
}
merged_options = default_options.merge(options)
value, options = nil, value if value.is_a?(Hash)
super(value, merged_options)
end

View file

@ -14,6 +14,14 @@ class AccountImport < Import
)
account.save!
account.entries.create!(
amount: row.amount,
currency: row.currency,
date: Date.current,
name: "Imported account value",
entryable: Account::Valuation.new
)
end
end
end

View file

@ -4,6 +4,7 @@ class Category < ApplicationRecord
belongs_to :family
validates :name, :color, :family, presence: true
validates :name, uniqueness: { scope: :family_id }
before_update :clear_internal_category, if: :name_changed?

View file

@ -71,7 +71,8 @@ class Demo::Generator
first_name: "Demo",
last_name: "User",
role: "admin",
password: "password"
password: "password",
onboarded_at: Time.current
end
def create_tags!

View file

@ -132,7 +132,7 @@ class Family < ApplicationRecord
end
def subscribed?
stripe_subscription_status.present? && stripe_subscription_status == "active"
stripe_subscription_status == "active"
end
def primary_user

View file

@ -3,6 +3,7 @@ class Merchant < ApplicationRecord
belongs_to :family
validates :name, :color, :family, presence: true
validates :name, uniqueness: { scope: :family }
scope :alphabetically, -> { order(:name) }

View file

@ -9,11 +9,15 @@
<div class="w-48 p-1 text-sm leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
<%= contextual_menu_modal_action_item t(".edit"), edit_category_path(category) %>
<%= link_to new_category_deletion_path(category),
<% if category.transactions.any? %>
<%= link_to new_category_deletion_path(category),
class: "flex items-center w-full rounded-lg text-red-600 hover:bg-red-50 py-2 px-3 gap-2",
data: { turbo_frame: :modal } do %>
<%= lucide_icon "trash-2", class: "shrink-0 w-5 h-5" %>
<span class="text-sm"><%= t(".delete") %></span>
<%= lucide_icon "trash-2", class: "shrink-0 w-5 h-5" %>
<span class="text-sm"><%= t(".delete") %></span>
<% end %>
<% else %>
<%= contextual_menu_destructive_item t(".delete"), category_path(category), turbo_confirm: nil %>
<% end %>
</div>
<% end %>

View file

@ -1,5 +1,5 @@
<div data-controller="color-avatar">
<%= styled_form_with model: category, class: "space-y-4", data: { turbo: false } do |f| %>
<%= styled_form_with model: category, class: "space-y-4", data: { turbo_frame: :_top } do |f| %>
<section class="space-y-4">
<div class="w-fit m-auto">
<%= render partial: "shared/color_avatar", locals: { name: category.name, color: category.color } %>
@ -13,7 +13,7 @@
<% end %>
</div>
<div class="relative flex items-center border border-gray-200 rounded-lg">
<%= f.text_field :name, placeholder: t(".placeholder"), class: "text-sm font-normal placeholder:text-gray-500 h-10 relative pl-3 w-full border-none rounded-lg", required: true, data: { color_avatar_target: "name" } %>
<%= f.text_field :name, placeholder: t(".placeholder"), required: true, autofocus: true, data: { color_avatar_target: "name" } %>
</div>
</section>

View file

@ -7,7 +7,12 @@
<%= render "layouts/sidebar" %>
<% end %>
</div>
<main class="grow px-20 py-6 h-full overflow-y-auto">
<main class="grow px-20 py-6 h-full relative <%= require_upgrade? ? "overflow-hidden" : "overflow-y-auto" %>">
<% if require_upgrade? %>
<%= render "shared/subscribe_modal" %>
<% end %>
<%= yield %>
</main>
</div>

View file

@ -1,5 +1,5 @@
<div data-controller="color-avatar">
<%= styled_form_with model: @merchant, class: "space-y-4", data: { turbo: false } do |f| %>
<%= styled_form_with model: @merchant, class: "space-y-4", data: { turbo_frame: :_top } do |f| %>
<section class="space-y-4">
<div class="w-fit m-auto">
<%= render partial: "shared/color_avatar", locals: { name: @merchant.name, color: @merchant.color } %>
@ -13,7 +13,7 @@
<% end %>
</div>
<div class="relative flex items-center border border-gray-200 rounded-lg">
<%= f.text_field :name, placeholder: t(".name_placeholder"), class: "text-sm font-normal placeholder:text-gray-500 h-10 relative pl-3 w-full border-none rounded-lg", required: true, data: { color_avatar_target: "name" } %>
<%= f.text_field :name, placeholder: t(".name_placeholder"), autofocus: true, required: true, data: { color_avatar_target: "name" } %>
</div>
</section>

View file

@ -15,11 +15,11 @@
<%= contextual_menu_destructive_item t(".delete"),
merchant_path(merchant),
turbo_frame: "_top",
turbo_confirm: {
turbo_confirm: merchant.transactions.any? ? {
title: t(".confirm_title"),
body: t(".confirm_body"),
accept: t(".confirm_accept")
} %>
} : nil %>
</div>
<% end %>
</div>

View file

@ -8,8 +8,8 @@
<p class="text-gray-500 text-sm"><%= t(".subtitle") %></p>
</div>
<div class="p-1 mb-2">
<div class="bg-white p-4 rounded-lg flex gap-8" style="box-shadow: 0px 0px 0px 1px rgba(11, 11, 11, 0.05), 0px 1px 2px 0px rgba(11, 11, 11, 0.05);">
<div class="p-1 bg-alpha-black-25 mb-2 rounded-lg">
<div class="bg-white p-4 rounded-lg flex gap-8 shadow-xs">
<div class="space-y-2">
<%= tag.p t(".example"), class: "text-gray-500 text-sm" %>
<%= tag.p "$2,323.25", class: "text-gray-900 font-medium text-2xl" %>

View file

@ -24,9 +24,7 @@
</div>
</header>
<% if !Current.family.subscribed? && !self_hosted? %>
<%= render "shared/subscribe_prompt" %>
<% elsif @accounts.empty? %>
<% if @accounts.empty? %>
<%= render "shared/no_account_empty_state" %>
<% else %>
<section class="flex gap-4">

View file

@ -5,11 +5,42 @@
<div class="space-y-4">
<h1 class="text-gray-900 text-xl font-medium mb-4"><%= t(".page_title") %></h1>
<%= settings_section title: t(".subscription_title"), subtitle: t(".subscription_subtitle") do %>
<% if @user.family.stripe_plan_id.blank? %>
<%= link_to t(".subscribe_button"), new_subscription_path, class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2", data: { turbo: false } %>
<% else %>
<%= link_to t(".manage_subscription_button"), subscription_path, class: "w-fit flex text-white text-sm font-medium items-center gap-1 bg-gray-900 rounded-lg p-2", data: { turbo: false } %>
<% end %>
<div class="space-y-4">
<div class="p-3 shadow-xs bg-white border border-alpha-black-200 rounded-lg flex justify-between items-center">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-full bg-gray-25 flex justify-center items-center">
<%= lucide_icon "gem", class: "w-5 h-5 text-gray-500" %>
</div>
<div class="text-sm space-y-1">
<% if @user.family.subscribed? || subscription_pending? %>
<p class="text-gray-900">You are currently subscribed to <span class="font-medium">Maybe+</span></p>
<p class="text-gray-500">Manage your billing settings here.</p>
<% else %>
<p class="text-gray-900">You are currently <span class="font-medium">not subscribed</span></p>
<p class="text-gray-500">Once you subscribe to Maybe+, youll see your billing settings here.</p>
<% end %>
</div>
</div>
<% if @user.family.subscribed? || subscription_pending? %>
<%= link_to subscription_path, class: "btn btn--secondary flex items-center gap-1" do %>
<span>Manage</span>
<%= lucide_icon "external-link", class: "w-5 h-5 shrink-0 text-gray-500" %>
<% end %>
<% else %>
<%= link_to new_subscription_path, class: "btn btn--secondary flex items-center gap-1" do %>
<span>Subscribe</span>
<%= lucide_icon "external-link", class: "w-5 h-5 shrink-0 text-gray-500" %>
<% end %>
<% end %>
</div>
<div class="flex items-center gap-2">
<%= image_tag "stripe-logo.svg", class: "w-5 h-5 shrink-0" %>
<p class="text-gray-500 text-sm">Managed via Stripe</p>
</div>
</div>
<% end %>
<%= settings_nav_footer %>

View file

@ -0,0 +1,25 @@
<div data-controller="modal" data-modal-open-value="true" class="absolute inset-0 z-50 flex items-center justify-center bg-white/90" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="w-[400px] rounded-xl relative overflow-hidden">
<div class="bg-white shadow-xl border border-gray-200 rounded-xl relative z-10">
<div class="rounded-xl" style="background-image: url('<%= asset_path("maybe-plus-background.png") %>'); background-size: cover; background-position: center;">
<div class="text-center rounded-xl" style="background-image: linear-gradient(to bottom, rgba(197,161,119,0.15) 0%, rgba(255,255,255,0.8) 30%, white 40%);">
<div class="p-4 rounded-xl">
<div class="flex justify-center">
<%= image_tag "maybe-plus-logo.png", class: "w-16" %>
</div>
<h2 class="font-medium text-gray-900 mb-2">Join Maybe+</h2>
<div class="text-gray-500 text-sm space-y-4 mb-5">
<p>Nobody likes paywalls, but we need feedback from users willing to pay for Maybe. </p>
<p>To continue using the app, please subscribe. In this early beta testing phase, we require that you upgrade within 1 hour to claim your spot.</p>
</div>
<%= link_to "Upgrade to Maybe+", new_subscription_path, class: "btn btn--primary text-center w-full block" %>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,16 +0,0 @@
<div class="flex justify-center items-center h-[800px]">
<div class="text-center flex flex-col gap-4 items-center max-w-[300px]">
<%= lucide_icon "circle-fading-arrow-up", class: "w-8 h-8 text-green-500" %>
<div class="space-y-1 text-sm">
<p class="text-gray-900 font-medium"><%= t(".title") %></p>
<p class="text-gray-500"><%= t(".subtitle") %></p>
<p class="text-gray-400 text-xs"><%= t(".guarantee") %></p>
</div>
<%= link_to new_subscription_path, class: "btn btn--primary flex items-center gap-1" do %>
<%= lucide_icon("credit-card", class: "w-5 h-5") %>
<span><%= t(".subscribe") %></span>
<% end %>
</div>
</div>

View file

@ -1,5 +1,5 @@
<div data-controller="color-avatar">
<%= styled_form_with model: tag, class: "space-y-4", data: { turbo: false } do |f| %>
<%= styled_form_with model: tag, class: "space-y-4", data: { turbo_frame: :_top } do |f| %>
<section class="space-y-4">
<div class="w-fit m-auto">
<%= render partial: "shared/color_avatar", locals: { name: tag.name, color: tag.color } %>
@ -13,7 +13,7 @@
<% end %>
</div>
<div class="relative flex items-center border border-gray-200 rounded-lg">
<%= f.text_field :name, placeholder: t(".placeholder"), class: "text-sm font-normal placeholder:text-gray-500 h-10 relative pl-3 w-full border-none rounded-lg", required: true, data: { color_avatar_target: "name" } %>
<%= f.text_field :name, placeholder: t(".placeholder"), autofocus: true, required: true, data: { color_avatar_target: "name" } %>
</div>
</section>

View file

@ -12,11 +12,15 @@
<div class="w-48 p-1 text-sm leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
<%= contextual_menu_modal_action_item t(".edit"), edit_tag_path(tag) %>
<%= link_to new_tag_deletion_path(tag),
<% if tag.transactions.any? %>
<%= link_to new_tag_deletion_path(tag),
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
data: { turbo_frame: :modal } do %>
<%= lucide_icon "trash-2", class: "w-5 h-5" %>
<span><%= t(".delete") %></span>
<%= lucide_icon "trash-2", class: "w-5 h-5" %>
<span><%= t(".delete") %></span>
<% end %>
<% else %>
<%= contextual_menu_destructive_item t(".delete"), tag_path(tag), turbo_confirm: nil %>
<% end %>
</div>
<% end %>