diff --git a/app/assets/images/ai-dark.svg b/app/assets/images/ai-dark.svg
new file mode 100644
index 00000000..bdcbfea6
--- /dev/null
+++ b/app/assets/images/ai-dark.svg
@@ -0,0 +1,71 @@
+
\ No newline at end of file
diff --git a/app/assets/images/ai.svg b/app/assets/images/ai.svg
index ee2c6462..94f17808 100644
--- a/app/assets/images/ai.svg
+++ b/app/assets/images/ai.svg
@@ -1,85 +1,71 @@
-
+
\ No newline at end of file
diff --git a/app/assets/tailwind/maybe-design-system.css b/app/assets/tailwind/maybe-design-system.css
index 4649a6bc..e03ccb1b 100644
--- a/app/assets/tailwind/maybe-design-system.css
+++ b/app/assets/tailwind/maybe-design-system.css
@@ -330,7 +330,7 @@
}
.form-field__input {
- @apply border-none bg-transparent text-sm opacity-100 w-full p-0;
+ @apply text-primary border-none bg-transparent text-sm opacity-100 w-full p-0;
@apply focus:opacity-100 focus:outline-hidden focus:ring-0;
@apply placeholder-shown:opacity-50;
@apply disabled:text-subdued;
diff --git a/app/controllers/concerns/onboardable.rb b/app/controllers/concerns/onboardable.rb
index 66f89ba9..9e7dd144 100644
--- a/app/controllers/concerns/onboardable.rb
+++ b/app/controllers/concerns/onboardable.rb
@@ -18,13 +18,9 @@ module Onboardable
return unless Current.user
return unless redirectable_path?(request.path)
- # Check if trial was started VERY recently (e.g., within the last few seconds)
- # If so, assume onboarding was just completed in the previous request, even if onboarded_at appears blank momentarily.
- trial_just_started = Current.family.trial_started_at.present? && Current.family.trial_started_at > 10.seconds.ago
-
- if Current.user.onboarded_at.blank? && !trial_just_started
+ if !Current.user.onboarded?
redirect_to onboarding_path
- elsif !Current.family.subscribed? && !Current.family.trialing?
+ elsif !Current.family.subscribed? && !Current.family.trialing? && !self_hosted?
redirect_to upgrade_subscription_path
end
end
diff --git a/app/controllers/onboardings_controller.rb b/app/controllers/onboardings_controller.rb
index 9b98be3b..0f616ce4 100644
--- a/app/controllers/onboardings_controller.rb
+++ b/app/controllers/onboardings_controller.rb
@@ -14,7 +14,6 @@ class OnboardingsController < ApplicationController
end
private
-
def set_user
@user = Current.user
end
diff --git a/app/controllers/subscriptions_controller.rb b/app/controllers/subscriptions_controller.rb
index 895c7192..45f4548a 100644
--- a/app/controllers/subscriptions_controller.rb
+++ b/app/controllers/subscriptions_controller.rb
@@ -8,16 +8,14 @@ class SubscriptionsController < ApplicationController
end
def start_trial
- if Current.family.trial_started_at.present?
- redirect_to root_path, alert: "You've already started or completed your trial"
- else
- Family.transaction do
- Current.family.update(trial_started_at: Time.current)
- Current.user.update(onboarded_at: Time.current)
+ unless Current.family.trialing?
+ ActiveRecord::Base.transaction do
+ Current.user.update!(onboarded_at: Time.current)
+ Current.family.update!(trial_started_at: Time.current)
end
-
- redirect_to root_path, notice: "Your trial has started"
end
+
+ redirect_to root_path, notice: "Welcome to Maybe!"
end
def new
diff --git a/app/helpers/imports_helper.rb b/app/helpers/imports_helper.rb
index 4a0dce76..cabc31ac 100644
--- a/app/helpers/imports_helper.rb
+++ b/app/helpers/imports_helper.rb
@@ -46,11 +46,11 @@ module ImportsHelper
end
def cell_class(row, field)
- base = "text-sm focus:ring-gray-900 focus:border-gray-900 w-full max-w-full disabled:text-subdued"
+ base = "bg-container text-sm focus:ring-gray-900 theme-dark:focus:ring-gray-100 focus:border-solid w-full max-w-full disabled:text-subdued"
row.valid? # populate errors
- border = row.errors.key?(field) ? "border-red-500" : "border-transparent"
+ border = row.errors.key?(field) ? "border-destructive" : "border-transparent"
[ base, border ].join(" ")
end
diff --git a/app/models/account.rb b/app/models/account.rb
index f07b442f..85167e22 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -62,8 +62,18 @@ class Account < ApplicationRecord
end
def institution_domain
- return nil unless plaid_account&.plaid_item&.institution_url.present?
- URI.parse(plaid_account.plaid_item.institution_url).host.gsub(/^www\./, "")
+ url_string = plaid_account&.plaid_item&.institution_url
+ return nil unless url_string.present?
+
+ begin
+ uri = URI.parse(url_string)
+ # Use safe navigation on .host before calling gsub
+ uri.host&.gsub(/^www\./, "")
+ rescue URI::InvalidURIError
+ # Log a warning if the URL is invalid and return nil
+ Rails.logger.warn("Invalid institution URL encountered for account #{id}: #{url_string}")
+ nil
+ end
end
def destroy_later
diff --git a/app/models/provider/stripe.rb b/app/models/provider/stripe.rb
index 09c67911..d5f5242c 100644
--- a/app/models/provider/stripe.rb
+++ b/app/models/provider/stripe.rb
@@ -12,9 +12,9 @@ class Provider::Stripe
case event.type
when /^customer\.subscription\./
- SubscriptionEventProcessor.new(client).process(event)
+ SubscriptionEventProcessor.new(event: event, client: client).process
when /^customer\./
- CustomerEventProcessor.new(client).process(event)
+ CustomerEventProcessor.new(event: event, client: client).process
else
Rails.logger.info "Unhandled event type: #{event.type}"
end
@@ -63,6 +63,6 @@ class Provider::Stripe
attr_reader :client, :webhook_secret
def retrieve_event(event_id)
- client.v2.core.events.retrieve(event_id)
+ client.v1.events.retrieve(event_id)
end
end
diff --git a/app/models/rule/action_executor/set_transaction_merchant.rb b/app/models/rule/action_executor/set_transaction_merchant.rb
new file mode 100644
index 00000000..492ece52
--- /dev/null
+++ b/app/models/rule/action_executor/set_transaction_merchant.rb
@@ -0,0 +1,30 @@
+class Rule::ActionExecutor::SetTransactionMerchant < Rule::ActionExecutor
+ def type
+ "select"
+ end
+
+ def options
+ family.merchants.pluck(:name, :id)
+ end
+
+ def execute(transaction_scope, value: nil, ignore_attribute_locks: false)
+ merchant = family.merchants.find_by_id(value)
+ return unless merchant
+
+ scope = transaction_scope
+ unless ignore_attribute_locks
+ scope = scope.enrichable(:merchant_id)
+ end
+
+ scope.each do |txn|
+ Rule.transaction do
+ txn.log_enrichment!(
+ attribute_name: "merchant_id",
+ attribute_value: merchant.id,
+ source: "rule"
+ )
+ txn.update!(merchant: merchant)
+ end
+ end
+ end
+end
diff --git a/app/models/rule/registry/transaction_resource.rb b/app/models/rule/registry/transaction_resource.rb
index 1bcdf8a7..628d8cde 100644
--- a/app/models/rule/registry/transaction_resource.rb
+++ b/app/models/rule/registry/transaction_resource.rb
@@ -14,7 +14,8 @@ class Rule::Registry::TransactionResource < Rule::Registry
def action_executors
enabled_executors = [
Rule::ActionExecutor::SetTransactionCategory.new(rule),
- Rule::ActionExecutor::SetTransactionTags.new(rule)
+ Rule::ActionExecutor::SetTransactionTags.new(rule),
+ Rule::ActionExecutor::SetTransactionMerchant.new(rule)
]
if ai_enabled?
diff --git a/app/models/user.rb b/app/models/user.rb
index 00dfd5af..d211878a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -152,6 +152,10 @@ class User < ApplicationRecord
totp.provisioning_uri(email)
end
+ def onboarded?
+ onboarded_at.present?
+ end
+
private
def ensure_valid_profile_image
return unless profile_image.attached?
diff --git a/app/views/accounts/show/_chart.html.erb b/app/views/accounts/show/_chart.html.erb
index c94adeb3..1661554c 100644
--- a/app/views/accounts/show/_chart.html.erb
+++ b/app/views/accounts/show/_chart.html.erb
@@ -4,14 +4,14 @@
<% default_value_title = account.asset? ? t(".balance") : t(".owed") %>
-
-
+
+
<%= tag.p title || default_value_title, class: "text-sm font-medium text-secondary" %>
<%= tooltip %>
- <%= tag.p format_money(account.balance_money), class: "text-primary text-3xl font-medium" %>
+ <%= tag.p format_money(account.balance_money), class: "text-primary text-3xl font-medium truncate" %>
<%= form_with url: request.path, method: :get, data: { controller: "auto-submit-form" } do |form| %>
diff --git a/app/views/accounts/show/_template.html.erb b/app/views/accounts/show/_template.html.erb
index 20d352b5..cfac9402 100644
--- a/app/views/accounts/show/_template.html.erb
+++ b/app/views/accounts/show/_template.html.erb
@@ -16,7 +16,7 @@
<%= render "accounts/show/chart", account: account %>
<% end %>
-
<% if tabs.present? %>
<%= tabs %>
<% else %>
diff --git a/app/views/assistant_messages/_assistant_message.html.erb b/app/views/assistant_messages/_assistant_message.html.erb
index ced5d254..59356a78 100644
--- a/app/views/assistant_messages/_assistant_message.html.erb
+++ b/app/views/assistant_messages/_assistant_message.html.erb
@@ -15,7 +15,7 @@
<%= render "assistant_messages/tool_calls", message: assistant_message %>
<% end %>
-
+
<%= render "chats/ai_avatar" %>
<%= markdown(assistant_message.content) %>
diff --git a/app/views/chats/_ai_avatar.html.erb b/app/views/chats/_ai_avatar.html.erb
index baa5a6f6..ec5b47dd 100644
--- a/app/views/chats/_ai_avatar.html.erb
+++ b/app/views/chats/_ai_avatar.html.erb
@@ -1,4 +1,7 @@
-
+<%# locals: (theme: "light") %>
+
+
<%# Never use svg as an image tag, it appears blurry in Safari %>
- <%= inline_svg_tag "ai.svg", alt: "AI", class: "w-full h-full" %>
+ <%= inline_svg_tag "ai-dark.svg", alt: "AI", class: "w-full h-full hidden theme-dark:block" %>
+ <%= inline_svg_tag "ai.svg", alt: "AI", class: "w-full h-full theme-dark:hidden" %>
diff --git a/app/views/chats/_ai_consent.html.erb b/app/views/chats/_ai_consent.html.erb
index 960aa6b3..f3a97c3e 100644
--- a/app/views/chats/_ai_consent.html.erb
+++ b/app/views/chats/_ai_consent.html.erb
@@ -1,5 +1,5 @@
-
+
<%= render "chats/ai_avatar" %>
diff --git a/app/views/chats/_ai_greeting.html.erb b/app/views/chats/_ai_greeting.html.erb
index 30243fbf..954a0f72 100644
--- a/app/views/chats/_ai_greeting.html.erb
+++ b/app/views/chats/_ai_greeting.html.erb
@@ -1,4 +1,4 @@
-
+
<%= render "chats/ai_avatar" %>
diff --git a/app/views/chats/_thinking_indicator.html.erb b/app/views/chats/_thinking_indicator.html.erb
index efd779b7..e1ba8921 100644
--- a/app/views/chats/_thinking_indicator.html.erb
+++ b/app/views/chats/_thinking_indicator.html.erb
@@ -1,6 +1,6 @@
<%# locals: (chat:, message: "Thinking ...") -%>
-
+
<%= render "chats/ai_avatar" %>
<%= message %>
diff --git a/app/views/import/cleans/show.html.erb b/app/views/import/cleans/show.html.erb
index 1412cb5e..4a743d01 100644
--- a/app/views/import/cleans/show.html.erb
+++ b/app/views/import/cleans/show.html.erb
@@ -14,7 +14,7 @@
<%= icon "check-circle", size: "sm", color: "success" %>
-
Your data has been cleaned
+
Your data has been cleaned
<%= render LinkComponent.new(
@@ -45,13 +45,13 @@
-
+
<% @import.column_keys.each do |key| %>
<%= import_col_label(key) %>
<% end %>
-
+
<% @rows.each do |row| %>
<%= render "import/rows/form", row: row %>
<% end %>
diff --git a/app/views/import/rows/_form.html.erb b/app/views/import/rows/_form.html.erb
index b5057d63..e7f5a4a9 100644
--- a/app/views/import/rows/_form.html.erb
+++ b/app/views/import/rows/_form.html.erb
@@ -1,6 +1,6 @@
<%# locals: (row:) %>
-
+
<% row.import.column_keys.each_with_index do |key, idx| %>
<%= turbo_frame_tag dom_id(row, key), title: row.valid? ? nil : row.errors.full_messages.join(", ") do %>
<%= form_with(
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index e77f1916..25432ff0 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -79,8 +79,31 @@
<% if content_for?(:sidebar) %>
<%= yield :sidebar %>
<% else %>
-
<%= form_with model: @user do |form| %>
- <%= form.hidden_field :redirect_to, value: "trial" %>
+ <%= form.hidden_field :redirect_to, value: self_hosted? ? "home" : "trial" %>
<%= form.hidden_field :set_onboarding_goals_at, value: Time.current %>
+ <% if self_hosted? %>
+ <%= form.hidden_field :onboarded_at, value: Time.current %>
+ <% end %>
+
<% [
{ icon: "layers", label: "See all my accounts in one piece", value: "unified_accounts" },
diff --git a/app/views/onboardings/show.html.erb b/app/views/onboardings/show.html.erb
index 9a2837e6..f5e5a5f9 100644
--- a/app/views/onboardings/show.html.erb
+++ b/app/views/onboardings/show.html.erb
@@ -1,4 +1,6 @@
-<%= content_for :previous_path, onboarding_path %>
+<%= content_for :prev_nav do %>
+ <%= image_tag "logomark-color.svg", class: "w-10 h-10" %>
+<% end %>
<%= content_for :header_nav do %>
<%= render "onboardings/onboarding_nav", user: @user %>
diff --git a/app/views/onboardings/trial.html.erb b/app/views/onboardings/trial.html.erb
index 4a7e1ab5..19c7310f 100644
--- a/app/views/onboardings/trial.html.erb
+++ b/app/views/onboardings/trial.html.erb
@@ -32,7 +32,8 @@
<%= render ButtonComponent.new(
text: "Try Maybe for 14 days",
href: start_trial_subscription_path,
- full_width: true
+ full_width: true,
+ data: { turbo: false }
) %>
@@ -90,10 +91,8 @@
Comprehensive transaction tracking experience
-
-
- <%= render "chats/ai_avatar" %>
-
+
+ <%= render "chats/ai_avatar" %>
Unlimited access and chats with Maybe AI
diff --git a/app/views/shared/_transaction_type_tabs.html.erb b/app/views/shared/_transaction_type_tabs.html.erb
index c2b53b2f..b2cfe0fd 100644
--- a/app/views/shared/_transaction_type_tabs.html.erb
+++ b/app/views/shared/_transaction_type_tabs.html.erb
@@ -1,23 +1,23 @@
-