1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-19 13:19:39 +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

View file

@ -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 # Custom port config
# For users who have other applications listening at 3000, this allows them to set a value puma will listen to. # For users who have other applications listening at 3000, this allows them to set a value puma will listen to.
PORT=3000 PORT=3000

5
.env.local.example Normal file
View file

@ -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

8
.env.test Normal file
View file

@ -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

4
.gitignore vendored
View file

@ -10,8 +10,8 @@
# Ignore all environment files (except templates). # Ignore all environment files (except templates).
/.env* /.env*
!/.env*.erb !/.env*.erb
!.env.example !.env.test
!.env.test.example !.env*.example
# Ignore all logfiles and tempfiles. # Ignore all logfiles and tempfiles.
/log/* /log/*

View file

@ -49,7 +49,7 @@ After cloning the repo, the basic setup commands are:
```sh ```sh
cd maybe cd maybe
cp .env.example .env cp .env.local.example .env.local
bin/setup bin/setup
bin/dev bin/dev

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 Onboardable, Localize, AutoSync, Authentication, Invitable, SelfHostable, StoreLocation, Impersonatable
include Pagy::Backend include Pagy::Backend
helper_method :require_upgrade?, :subscription_pending?
private 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 def with_sidebar
return "turbo_rails/frame" if turbo_frame_request? return "turbo_rails/frame" if turbo_frame_request?

View file

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

View file

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

View file

@ -1,16 +1,14 @@
class SubscriptionsController < ApplicationController class SubscriptionsController < ApplicationController
def new def new
client = Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
if Current.family.stripe_customer_id.blank? if Current.family.stripe_customer_id.blank?
customer = client.v1.customers.create( customer = stripe_client.v1.customers.create(
email: Current.family.primary_user.email, email: Current.family.primary_user.email,
metadata: { family_id: Current.family.id } metadata: { family_id: Current.family.id }
) )
Current.family.update(stripe_customer_id: customer.id) Current.family.update(stripe_customer_id: customer.id)
end end
session = client.v1.checkout.sessions.create({ session = stripe_client.v1.checkout.sessions.create({
customer: Current.family.stripe_customer_id, customer: Current.family.stripe_customer_id,
line_items: [ { line_items: [ {
price: ENV["STRIPE_PLAN_ID"], price: ENV["STRIPE_PLAN_ID"],
@ -18,7 +16,7 @@ class SubscriptionsController < ApplicationController
} ], } ],
mode: "subscription", mode: "subscription",
allow_promotion_codes: true, allow_promotion_codes: true,
success_url: settings_billing_url, success_url: success_subscription_url + "?session_id={CHECKOUT_SESSION_ID}",
cancel_url: settings_billing_url cancel_url: settings_billing_url
}) })
@ -26,12 +24,24 @@ class SubscriptionsController < ApplicationController
end end
def show def show
client = Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"]) portal_session = stripe_client.v1.billing_portal.sessions.create(
portal_session = client.v1.billing_portal.sessions.create(
customer: Current.family.stripe_customer_id, customer: Current.family.stripe_customer_id,
return_url: settings_billing_url return_url: settings_billing_url
) )
redirect_to portal_session.url, allow_other_host: true, status: :see_other redirect_to portal_session.url, allow_other_host: true, status: :see_other
end 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 end

View file

@ -1,7 +1,7 @@
class TagsController < ApplicationController class TagsController < ApplicationController
layout :with_sidebar layout :with_sidebar
before_action :set_tag, only: %i[edit update] before_action :set_tag, only: %i[edit update destroy]
def index def index
@tags = Current.family.tags.alphabetically @tags = Current.family.tags.alphabetically
@ -12,8 +12,13 @@ class TagsController < ApplicationController
end end
def create def create
Current.family.tags.create!(tag_params) @tag = Current.family.tags.new(tag_params)
if @tag.save
redirect_to tags_path, notice: t(".created") redirect_to tags_path, notice: t(".created")
else
redirect_to tags_path, alert: t(".error", error: @tag.errors.full_messages.to_sentence)
end
end end
def edit def edit
@ -24,6 +29,11 @@ class TagsController < ApplicationController
redirect_to tags_path, notice: t(".updated") redirect_to tags_path, notice: t(".updated")
end end
def destroy
@tag.destroy!
redirect_to tags_path, notice: t(".deleted")
end
private private
def set_tag 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.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.billing_label"), path: :settings_billing_path },
{ name: I18n.t("settings.nav.accounts_label"), path: :accounts_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.tags_label"), path: :tags_path },
{ name: I18n.t("settings.nav.categories_label"), path: :categories_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.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.whats_new_label"), path: :changelog_path },
{ name: I18n.t("settings.nav.feedback_label"), path: :feedback_path } { name: I18n.t("settings.nav.feedback_label"), path: :feedback_path }
] ]

View file

@ -49,7 +49,12 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
end end
def submit(value = nil, options = {}) 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) value, options = nil, value if value.is_a?(Hash)
super(value, merged_options) super(value, merged_options)
end end

View file

@ -14,6 +14,14 @@ class AccountImport < Import
) )
account.save! 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 end
end end

View file

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

View file

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

View file

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

View file

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

View file

@ -9,12 +9,16 @@
<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"> <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) %> <%= contextual_menu_modal_action_item t(".edit"), edit_category_path(category) %>
<% if category.transactions.any? %>
<%= link_to new_category_deletion_path(category), <%= 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", 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 %> data: { turbo_frame: :modal } do %>
<%= lucide_icon "trash-2", class: "shrink-0 w-5 h-5" %> <%= lucide_icon "trash-2", class: "shrink-0 w-5 h-5" %>
<span class="text-sm"><%= t(".delete") %></span> <span class="text-sm"><%= t(".delete") %></span>
<% end %> <% end %>
<% else %>
<%= contextual_menu_destructive_item t(".delete"), category_path(category), turbo_confirm: nil %>
<% end %>
</div> </div>
<% end %> <% end %>
</div> </div>

View file

@ -1,5 +1,5 @@
<div data-controller="color-avatar"> <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"> <section class="space-y-4">
<div class="w-fit m-auto"> <div class="w-fit m-auto">
<%= render partial: "shared/color_avatar", locals: { name: category.name, color: category.color } %> <%= render partial: "shared/color_avatar", locals: { name: category.name, color: category.color } %>
@ -13,7 +13,7 @@
<% end %> <% end %>
</div> </div>
<div class="relative flex items-center border border-gray-200 rounded-lg"> <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> </div>
</section> </section>

View file

@ -7,7 +7,12 @@
<%= render "layouts/sidebar" %> <%= render "layouts/sidebar" %>
<% end %> <% end %>
</div> </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 %> <%= yield %>
</main> </main>
</div> </div>

View file

@ -1,5 +1,5 @@
<div data-controller="color-avatar"> <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"> <section class="space-y-4">
<div class="w-fit m-auto"> <div class="w-fit m-auto">
<%= render partial: "shared/color_avatar", locals: { name: @merchant.name, color: @merchant.color } %> <%= render partial: "shared/color_avatar", locals: { name: @merchant.name, color: @merchant.color } %>
@ -13,7 +13,7 @@
<% end %> <% end %>
</div> </div>
<div class="relative flex items-center border border-gray-200 rounded-lg"> <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> </div>
</section> </section>

View file

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

View file

@ -8,8 +8,8 @@
<p class="text-gray-500 text-sm"><%= t(".subtitle") %></p> <p class="text-gray-500 text-sm"><%= t(".subtitle") %></p>
</div> </div>
<div class="p-1 mb-2"> <div class="p-1 bg-alpha-black-25 mb-2 rounded-lg">
<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="bg-white p-4 rounded-lg flex gap-8 shadow-xs">
<div class="space-y-2"> <div class="space-y-2">
<%= tag.p t(".example"), class: "text-gray-500 text-sm" %> <%= tag.p t(".example"), class: "text-gray-500 text-sm" %>
<%= tag.p "$2,323.25", class: "text-gray-900 font-medium text-2xl" %> <%= tag.p "$2,323.25", class: "text-gray-900 font-medium text-2xl" %>

View file

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

View file

@ -5,11 +5,42 @@
<div class="space-y-4"> <div class="space-y-4">
<h1 class="text-gray-900 text-xl font-medium mb-4"><%= t(".page_title") %></h1> <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 %> <%= settings_section title: t(".subscription_title"), subtitle: t(".subscription_subtitle") do %>
<% if @user.family.stripe_plan_id.blank? %> <div class="space-y-4">
<%= 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 } %> <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 %> <% 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 } %> <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 %> <% 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 %> <% end %>
<%= settings_nav_footer %> <%= 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"> <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"> <section class="space-y-4">
<div class="w-fit m-auto"> <div class="w-fit m-auto">
<%= render partial: "shared/color_avatar", locals: { name: tag.name, color: tag.color } %> <%= render partial: "shared/color_avatar", locals: { name: tag.name, color: tag.color } %>
@ -13,7 +13,7 @@
<% end %> <% end %>
</div> </div>
<div class="relative flex items-center border border-gray-200 rounded-lg"> <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> </div>
</section> </section>

View file

@ -12,12 +12,16 @@
<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"> <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) %> <%= contextual_menu_modal_action_item t(".edit"), edit_tag_path(tag) %>
<% if tag.transactions.any? %>
<%= link_to new_tag_deletion_path(tag), <%= 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", 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 %> data: { turbo_frame: :modal } do %>
<%= lucide_icon "trash-2", class: "w-5 h-5" %> <%= lucide_icon "trash-2", class: "w-5 h-5" %>
<span><%= t(".delete") %></span> <span><%= t(".delete") %></span>
<% end %> <% end %>
<% else %>
<%= contextual_menu_destructive_item t(".delete"), tag_path(tag), turbo_confirm: nil %>
<% end %>
</div> </div>
<% end %> <% end %>
</div> </div>

View file

@ -5,7 +5,10 @@ en:
delete: Delete category delete: Delete category
edit: Edit category edit: Edit category
create: create:
failure: 'Failed to create category: %{error}'
success: New transaction category created successfully success: New transaction category created successfully
destroy:
success: Category deleted successfully
edit: edit:
edit: Edit category edit: Edit category
form: form:

View file

@ -2,6 +2,7 @@
en: en:
merchants: merchants:
create: create:
error: 'Error creating merchant: %{error}'
success: New merchant created successfully success: New merchant created successfully
destroy: destroy:
success: Merchant deleted successfully success: Merchant deleted successfully

View file

@ -3,9 +3,7 @@ en:
settings: settings:
billings: billings:
show: show:
manage_subscription_button: Manage subscription
page_title: Billing page_title: Billing
subscribe_button: Subscribe
subscription_subtitle: Manage your subscription and billing details subscription_subtitle: Manage your subscription and billing details
subscription_title: Manage subscription subscription_title: Manage subscription
nav: nav:

View file

@ -13,12 +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
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: upgrade_notification:
app_upgraded: The app has been upgraded to %{version}. app_upgraded: The app has been upgraded to %{version}.
dismiss: Dismiss dismiss: Dismiss

View file

@ -3,6 +3,9 @@ en:
tags: tags:
create: create:
created: Tag created created: Tag created
error: 'Error creating tag: %{error}'
destroy:
deleted: Tag deleted
edit: edit:
edit: Edit tag edit: Edit tag
form: form:

View file

@ -26,9 +26,11 @@ Rails.application.routes.draw do
resource :billing, only: :show resource :billing, only: :show
end 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 resources :deletions, only: %i[new create], module: :tag
end end

View file

@ -0,0 +1,5 @@
class AddSubscriptionTimestampToSession < ActiveRecord::Migration[7.2]
def change
add_column :sessions, :subscribed_at, :datetime
end
end

3
db/schema.rb generated
View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto" enable_extension "pgcrypto"
enable_extension "plpgsql" 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 "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.uuid "active_impersonator_session_id" 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 ["active_impersonator_session_id"], name: "index_sessions_on_active_impersonator_session_id"
t.index ["user_id"], name: "index_sessions_on_user_id" t.index ["user_id"], name: "index_sessions_on_user_id"
end end

View file

@ -1,6 +1,8 @@
empty: empty:
name: Family name: Family
stripe_subscription_status: active
dylan_family: dylan_family:
name: The Dylan Family name: The Dylan Family
stripe_subscription_status: active

View file

@ -1,5 +1,5 @@
type,name,amount,currency type,name,amount,currency
Checking,Main Checking Account,5000.00,USD Checking,Main Checking Account,5000.00,USD
Savings,Emergency Fund,10000.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 Investment,Retirement Portfolio,75000.00,USD

1 type name amount currency
2 Checking Main Checking Account 5000.00 USD
3 Savings Emergency Fund 10000.00 USD
4 Credit Card Rewards Credit Card -1500.00 1500.00 USD
5 Investment Retirement Portfolio 75000.00 USD

View file

@ -7,8 +7,6 @@ end
require_relative "../config/environment" require_relative "../config/environment"
ENV["SELF_HOSTED"] = "false"
ENV["UPGRADES_ENABLED"] = "false"
ENV["RAILS_ENV"] ||= "test" ENV["RAILS_ENV"] ||= "test"
# Fixes Segfaults on M1 Macs when running tests in parallel (temporary workaround) # Fixes Segfaults on M1 Macs when running tests in parallel (temporary workaround)