From 6baffe753953e5d2e6fc2ee305407338613bceb0 Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Thu, 24 Oct 2024 11:02:27 -0400 Subject: [PATCH] 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 --- .env.example | 9 ++++ .env.local.example | 5 +++ .env.test | 8 ++++ .gitignore | 4 +- README.md | 2 +- app/assets/images/maybe-plus-background.png | Bin 0 -> 11889 bytes app/assets/images/maybe-plus-logo.png | Bin 0 -> 9449 bytes app/assets/images/stripe-logo.svg | 6 +++ app/controllers/application_controller.rb | 15 +++++++ app/controllers/categories_controller.rb | 20 ++++++--- app/controllers/merchants_controller.rb | 9 +++- app/controllers/subscriptions_controller.rb | 26 +++++++---- app/controllers/tags_controller.rb | 16 +++++-- app/helpers/settings_helper.rb | 2 +- app/helpers/styled_form_builder.rb | 7 ++- app/models/account_import.rb | 8 ++++ app/models/category.rb | 1 + app/models/demo/generator.rb | 3 +- app/models/family.rb | 2 +- app/models/merchant.rb | 1 + app/views/categories/_category.html.erb | 10 +++-- app/views/categories/_form.html.erb | 4 +- app/views/layouts/with_sidebar.html.erb | 7 ++- app/views/merchants/_form.html.erb | 4 +- app/views/merchants/_merchant.html.erb | 4 +- app/views/onboardings/preferences.html.erb | 4 +- app/views/pages/dashboard.html.erb | 4 +- app/views/settings/billings/show.html.erb | 41 +++++++++++++++--- app/views/shared/_subscribe_modal.html.erb | 25 +++++++++++ app/views/shared/_subscribe_prompt.html.erb | 16 ------- app/views/tags/_form.html.erb | 4 +- app/views/tags/_tag.html.erb | 10 +++-- config/locales/views/categories/en.yml | 3 ++ config/locales/views/merchants/en.yml | 1 + config/locales/views/settings/en.yml | 2 - config/locales/views/shared/en.yml | 6 --- config/locales/views/tags/en.yml | 3 ++ config/routes.rb | 6 ++- ...7_add_subscription_timestamp_to_session.rb | 5 +++ db/schema.rb | 3 +- test/fixtures/families.yml | 2 + test/fixtures/files/imports/accounts.csv | 2 +- test/test_helper.rb | 2 - 43 files changed, 231 insertions(+), 81 deletions(-) create mode 100644 .env.local.example create mode 100644 .env.test create mode 100644 app/assets/images/maybe-plus-background.png create mode 100644 app/assets/images/maybe-plus-logo.png create mode 100644 app/assets/images/stripe-logo.svg create mode 100644 app/views/shared/_subscribe_modal.html.erb delete mode 100644 app/views/shared/_subscribe_prompt.html.erb create mode 100644 db/migrate/20241024142537_add_subscription_timestamp_to_session.rb diff --git a/.env.example b/.env.example index 2622021b..fd3e93c4 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,12 @@ +# ================================ PLEASE READ ========================================== +# This file outlines all the possible environment variables supported by the Maybe app. +# +# This includes several features that are for our "hosted" version of Maybe, which most +# open-source contributors won't need. +# +# If you are developing locally, you should be referencing `.env.local.example` instead. +# ======================================================================================= + # Custom port config # For users who have other applications listening at 3000, this allows them to set a value puma will listen to. PORT=3000 diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 00000000..d393f623 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,5 @@ +# To enable / disable self-hosting features. +SELF_HOSTED=false + +# Enable Synth market data (careful, this will use your API credits) +SYNTH_API_KEY=yourapikeyhere diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..f47801f1 --- /dev/null +++ b/.env.test @@ -0,0 +1,8 @@ +SELF_HOSTED=false +SYNTH_API_KEY=fookey + +# Set to true if you want SimpleCov reports generated +COVERAGE=false + +# Set to true to run test suite serially +DISABLE_PARALLELIZATION=false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3e1cbccd..dc09d53e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,8 @@ # Ignore all environment files (except templates). /.env* !/.env*.erb -!.env.example -!.env.test.example +!.env.test +!.env*.example # Ignore all logfiles and tempfiles. /log/* diff --git a/README.md b/README.md index d6dd1082..0372c4bd 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ After cloning the repo, the basic setup commands are: ```sh cd maybe -cp .env.example .env +cp .env.local.example .env.local bin/setup bin/dev diff --git a/app/assets/images/maybe-plus-background.png b/app/assets/images/maybe-plus-background.png new file mode 100644 index 0000000000000000000000000000000000000000..29d626d1b67fa7d0cff8f2909165f4888982158f GIT binary patch literal 11889 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/maybe-plus-logo.png b/app/assets/images/maybe-plus-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3cede18619c361820aef170f8f6b993f78b6c12d GIT binary patch literal 9449 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/stripe-logo.svg b/app/assets/images/stripe-logo.svg new file mode 100644 index 00000000..d95ded83 --- /dev/null +++ b/app/assets/images/stripe-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8fd5c552..e9788920 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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? diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 443770fe..ddc1ecaa 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -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]) diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb index 33fe156c..252c5de6 100644 --- a/app/controllers/merchants_controller.rb +++ b/app/controllers/merchants_controller.rb @@ -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 diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb index ef634b83..e2c19535 100644 --- a/app/controllers/subscriptions_controller.rb +++ b/app/controllers/subscriptions_controller.rb @@ -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 diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index c2feb537..2b81e7cd 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -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 diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index f127905d..4f4e0272 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -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 } ] diff --git a/app/helpers/styled_form_builder.rb b/app/helpers/styled_form_builder.rb index f9e060af..c7e27b0a 100644 --- a/app/helpers/styled_form_builder.rb +++ b/app/helpers/styled_form_builder.rb @@ -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 diff --git a/app/models/account_import.rb b/app/models/account_import.rb index 3987a1ff..98e7e0d0 100644 --- a/app/models/account_import.rb +++ b/app/models/account_import.rb @@ -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 diff --git a/app/models/category.rb b/app/models/category.rb index 3744295a..4a2d6361 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -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? diff --git a/app/models/demo/generator.rb b/app/models/demo/generator.rb index 20e5c01c..ba210b2b 100644 --- a/app/models/demo/generator.rb +++ b/app/models/demo/generator.rb @@ -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! diff --git a/app/models/family.rb b/app/models/family.rb index c4949a4d..ddfdab3e 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -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 diff --git a/app/models/merchant.rb b/app/models/merchant.rb index 3d1448e2..e363f6aa 100644 --- a/app/models/merchant.rb +++ b/app/models/merchant.rb @@ -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) } diff --git a/app/views/categories/_category.html.erb b/app/views/categories/_category.html.erb index f25e5c25..97f19775 100644 --- a/app/views/categories/_category.html.erb +++ b/app/views/categories/_category.html.erb @@ -9,11 +9,15 @@
<%= 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" %> - <%= t(".delete") %> + <%= lucide_icon "trash-2", class: "shrink-0 w-5 h-5" %> + <%= t(".delete") %> + <% end %> + <% else %> + <%= contextual_menu_destructive_item t(".delete"), category_path(category), turbo_confirm: nil %> <% end %>
<% end %> diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb index 5f36f8d9..313e48ba 100644 --- a/app/views/categories/_form.html.erb +++ b/app/views/categories/_form.html.erb @@ -1,5 +1,5 @@
- <%= 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| %>
<%= render partial: "shared/color_avatar", locals: { name: category.name, color: category.color } %> @@ -13,7 +13,7 @@ <% end %>
- <%= 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" } %>
diff --git a/app/views/layouts/with_sidebar.html.erb b/app/views/layouts/with_sidebar.html.erb index ca824d45..6551526b 100644 --- a/app/views/layouts/with_sidebar.html.erb +++ b/app/views/layouts/with_sidebar.html.erb @@ -7,7 +7,12 @@ <%= render "layouts/sidebar" %> <% end %>
-
+ +
"> + <% if require_upgrade? %> + <%= render "shared/subscribe_modal" %> + <% end %> + <%= yield %>
diff --git a/app/views/merchants/_form.html.erb b/app/views/merchants/_form.html.erb index 4b748f67..93efecb9 100644 --- a/app/views/merchants/_form.html.erb +++ b/app/views/merchants/_form.html.erb @@ -1,5 +1,5 @@
- <%= 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| %>
<%= render partial: "shared/color_avatar", locals: { name: @merchant.name, color: @merchant.color } %> @@ -13,7 +13,7 @@ <% end %>
- <%= 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" } %>
diff --git a/app/views/merchants/_merchant.html.erb b/app/views/merchants/_merchant.html.erb index 33fba7ee..2b964140 100644 --- a/app/views/merchants/_merchant.html.erb +++ b/app/views/merchants/_merchant.html.erb @@ -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 %>
<% end %> diff --git a/app/views/onboardings/preferences.html.erb b/app/views/onboardings/preferences.html.erb index 84712a3e..26b0ac1f 100644 --- a/app/views/onboardings/preferences.html.erb +++ b/app/views/onboardings/preferences.html.erb @@ -8,8 +8,8 @@

<%= t(".subtitle") %>

-
-
+
+
<%= tag.p t(".example"), class: "text-gray-500 text-sm" %> <%= tag.p "$2,323.25", class: "text-gray-900 font-medium text-2xl" %> diff --git a/app/views/pages/dashboard.html.erb b/app/views/pages/dashboard.html.erb index b25e8497..f3846a4a 100644 --- a/app/views/pages/dashboard.html.erb +++ b/app/views/pages/dashboard.html.erb @@ -24,9 +24,7 @@
- <% if !Current.family.subscribed? && !self_hosted? %> - <%= render "shared/subscribe_prompt" %> - <% elsif @accounts.empty? %> + <% if @accounts.empty? %> <%= render "shared/no_account_empty_state" %> <% else %>
diff --git a/app/views/settings/billings/show.html.erb b/app/views/settings/billings/show.html.erb index c3ed66bc..45b9c702 100644 --- a/app/views/settings/billings/show.html.erb +++ b/app/views/settings/billings/show.html.erb @@ -5,11 +5,42 @@

<%= t(".page_title") %>

<%= 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 %> +
+
+
+
+ <%= lucide_icon "gem", class: "w-5 h-5 text-gray-500" %> +
+ +
+ <% if @user.family.subscribed? || subscription_pending? %> +

You are currently subscribed to Maybe+

+

Manage your billing settings here.

+ <% else %> +

You are currently not subscribed

+

Once you subscribe to Maybe+, you’ll see your billing settings here.

+ <% end %> +
+
+ + <% if @user.family.subscribed? || subscription_pending? %> + <%= link_to subscription_path, class: "btn btn--secondary flex items-center gap-1" do %> + Manage + <%= 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 %> + Subscribe + <%= lucide_icon "external-link", class: "w-5 h-5 shrink-0 text-gray-500" %> + <% end %> + <% end %> +
+ +
+ <%= image_tag "stripe-logo.svg", class: "w-5 h-5 shrink-0" %> +

Managed via Stripe

+
+
<% end %> <%= settings_nav_footer %> diff --git a/app/views/shared/_subscribe_modal.html.erb b/app/views/shared/_subscribe_modal.html.erb new file mode 100644 index 00000000..463c195a --- /dev/null +++ b/app/views/shared/_subscribe_modal.html.erb @@ -0,0 +1,25 @@ + diff --git a/app/views/shared/_subscribe_prompt.html.erb b/app/views/shared/_subscribe_prompt.html.erb deleted file mode 100644 index 35315af4..00000000 --- a/app/views/shared/_subscribe_prompt.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -
-
- <%= lucide_icon "circle-fading-arrow-up", class: "w-8 h-8 text-green-500" %> - -
-

<%= t(".title") %>

-

<%= t(".subtitle") %>

-

<%= t(".guarantee") %>

-
- - <%= link_to new_subscription_path, class: "btn btn--primary flex items-center gap-1" do %> - <%= lucide_icon("credit-card", class: "w-5 h-5") %> - <%= t(".subscribe") %> - <% end %> -
-
diff --git a/app/views/tags/_form.html.erb b/app/views/tags/_form.html.erb index 99b1c706..395a1f03 100644 --- a/app/views/tags/_form.html.erb +++ b/app/views/tags/_form.html.erb @@ -1,5 +1,5 @@
- <%= 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| %>
<%= render partial: "shared/color_avatar", locals: { name: tag.name, color: tag.color } %> @@ -13,7 +13,7 @@ <% end %>
- <%= 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" } %>
diff --git a/app/views/tags/_tag.html.erb b/app/views/tags/_tag.html.erb index 5a65a341..ed325d74 100644 --- a/app/views/tags/_tag.html.erb +++ b/app/views/tags/_tag.html.erb @@ -12,11 +12,15 @@
<%= 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" %> - <%= t(".delete") %> + <%= lucide_icon "trash-2", class: "w-5 h-5" %> + <%= t(".delete") %> + <% end %> + <% else %> + <%= contextual_menu_destructive_item t(".delete"), tag_path(tag), turbo_confirm: nil %> <% end %>
<% end %> diff --git a/config/locales/views/categories/en.yml b/config/locales/views/categories/en.yml index 5ef738c0..6dd57a4c 100644 --- a/config/locales/views/categories/en.yml +++ b/config/locales/views/categories/en.yml @@ -5,7 +5,10 @@ en: delete: Delete category edit: Edit category create: + failure: 'Failed to create category: %{error}' success: New transaction category created successfully + destroy: + success: Category deleted successfully edit: edit: Edit category form: diff --git a/config/locales/views/merchants/en.yml b/config/locales/views/merchants/en.yml index b95aa729..3f31dd59 100644 --- a/config/locales/views/merchants/en.yml +++ b/config/locales/views/merchants/en.yml @@ -2,6 +2,7 @@ en: merchants: create: + error: 'Error creating merchant: %{error}' success: New merchant created successfully destroy: success: Merchant deleted successfully diff --git a/config/locales/views/settings/en.yml b/config/locales/views/settings/en.yml index 205be0f8..0e2fe635 100644 --- a/config/locales/views/settings/en.yml +++ b/config/locales/views/settings/en.yml @@ -3,9 +3,7 @@ en: settings: billings: show: - manage_subscription_button: Manage subscription page_title: Billing - subscribe_button: Subscribe subscription_subtitle: Manage your subscription and billing details subscription_title: Manage subscription nav: diff --git a/config/locales/views/shared/en.yml b/config/locales/views/shared/en.yml index 89ad83bc..80f4fc3d 100644 --- a/config/locales/views/shared/en.yml +++ b/config/locales/views/shared/en.yml @@ -13,12 +13,6 @@ en: no_account_subtitle: Since no accounts have been added, there's no data to display. Add your first accounts to start viewing dashboard data. no_account_title: No accounts yet - subscribe_prompt: - guarantee: We're reasonable people here. If you're not happy or something doesn't - work, we'll gladly refund you. - subscribe: Upgrade your account - subtitle: To continue using Maybe, please subscribe! - title: Upgrade upgrade_notification: app_upgraded: The app has been upgraded to %{version}. dismiss: Dismiss diff --git a/config/locales/views/tags/en.yml b/config/locales/views/tags/en.yml index 4a78e350..a1f2ba32 100644 --- a/config/locales/views/tags/en.yml +++ b/config/locales/views/tags/en.yml @@ -3,6 +3,9 @@ en: tags: create: created: Tag created + error: 'Error creating tag: %{error}' + destroy: + deleted: Tag deleted edit: edit: Edit tag form: diff --git a/config/routes.rb b/config/routes.rb index 94fcd02c..2d6fe452 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,9 +26,11 @@ Rails.application.routes.draw do resource :billing, only: :show end - resource :subscription, only: %i[new show] + resource :subscription, only: %i[new show] do + get :success, on: :collection + end - resources :tags, except: %i[show destroy] do + resources :tags, except: :show do resources :deletions, only: %i[new create], module: :tag end diff --git a/db/migrate/20241024142537_add_subscription_timestamp_to_session.rb b/db/migrate/20241024142537_add_subscription_timestamp_to_session.rb new file mode 100644 index 00000000..0f9677a2 --- /dev/null +++ b/db/migrate/20241024142537_add_subscription_timestamp_to_session.rb @@ -0,0 +1,5 @@ +class AddSubscriptionTimestampToSession < ActiveRecord::Migration[7.2] + def change + add_column :sessions, :subscribed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index d9fb2e48..7fe73452 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_10_22_221544) do +ActiveRecord::Schema[7.2].define(version: 2024_10_24_142537) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -496,6 +496,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_22_221544) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.uuid "active_impersonator_session_id" + t.datetime "subscribed_at" t.index ["active_impersonator_session_id"], name: "index_sessions_on_active_impersonator_session_id" t.index ["user_id"], name: "index_sessions_on_user_id" end diff --git a/test/fixtures/families.yml b/test/fixtures/families.yml index c1b0831a..57697046 100644 --- a/test/fixtures/families.yml +++ b/test/fixtures/families.yml @@ -1,6 +1,8 @@ empty: name: Family + stripe_subscription_status: active dylan_family: name: The Dylan Family + stripe_subscription_status: active diff --git a/test/fixtures/files/imports/accounts.csv b/test/fixtures/files/imports/accounts.csv index fb3974f8..a24b1f81 100644 --- a/test/fixtures/files/imports/accounts.csv +++ b/test/fixtures/files/imports/accounts.csv @@ -1,5 +1,5 @@ type,name,amount,currency Checking,Main Checking Account,5000.00,USD Savings,Emergency Fund,10000.00,USD -Credit Card,Rewards Credit Card,-1500.00,USD +Credit Card,Rewards Credit Card,1500.00,USD Investment,Retirement Portfolio,75000.00,USD diff --git a/test/test_helper.rb b/test/test_helper.rb index b3bacfa8..6b386535 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,8 +7,6 @@ end require_relative "../config/environment" -ENV["SELF_HOSTED"] = "false" -ENV["UPGRADES_ENABLED"] = "false" ENV["RAILS_ENV"] ||= "test" # Fixes Segfaults on M1 Macs when running tests in parallel (temporary workaround)