1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-10 07:55:21 +02:00

Pre-launch design sync with Figma spec (#2154)

* Add lookbook + viewcomponent, organize design system file

* Build menu component

* Button updates

* More button fixes

* Replace all menus with new ViewComponent

* Checkpoint: fix tests, all buttons and menus converted

* Split into Link and Button components for clarity

* Button cleanup

* Simplify custom confirmation configuration in views

* Finalize button, link component API

* Add toggle field to custom form builder + Component

* Basic tabs component

* Custom tabs, convert all menu / tab instances in app

* Gem updates

* Centralized icon helper

* Update all icon usage to central helper

* Lint fixes

* Centralize all disclosure instances

* Dialog replacements

* Consolidation of all dialog styles

* Test fixes

* Fix app layout issues, move to component with slots

* Layout simplification

* Flakey test fix

* Fix dashboard mobile issues

* Finalize homepage

* Lint fixes

* Fix shadows and borders in dark mode

* Fix tests

* Remove stale class

* Fix filled icon logic

* Move transparent? to public interface
This commit is contained in:
Zach Gollwitzer 2025-04-30 18:14:22 -04:00 committed by GitHub
parent 1aafed5f8b
commit 90a9546f32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
291 changed files with 4143 additions and 3104 deletions

View file

@ -1,33 +1,26 @@
<div class="flex flex-col items-center justify-start h-full p-6 text-center">
<div class="border border-secondary rounded-lg p-4 bg-container">
<div class="w-16 h-16 bg-surface-inset rounded-full flex items-center justify-center mx-auto mb-4">
<%= icon("sparkles") %>
</div>
<h3 class="text-lg font-medium text-primary mb-2">Enable Personal Finance AI</h3>
<p class="text-gray-600 mb-6 text-sm">
<% if Current.user.ai_available? %>
Our personal finance AI can help answer questions about your finances and provide insights based on your data.
To use this feature, you'll need to explicitly enable it.
<% else %>
To use the AI assistant, you need to set the <code class="bg-surface-inset px-1 py-0.5 rounded font-mono text-xs">OPENAI_ACCESS_TOKEN</code>
environment variable in your self-hosted instance.
<% end %>
</p>
<% unless self_hosted? %>
<p class="text-gray-600 mb-6 text-sm">
NOTE: During beta testing, we'll be monitoring usage and AI conversations to improve the assistant.
</p>
<% end %>
<% if Current.user.ai_available? %>
<%= form_with url: user_path(Current.user), method: :patch, class: "w-full", data: { turbo: false } do |form| %>
<%= form.hidden_field "user[ai_enabled]", value: true %>
<%= form.hidden_field "user[redirect_to]", value: "home" %>
<%= form.submit "Enable AI Assistant", class: "cursor-pointer hover:bg-black w-full py-2 px-4 bg-gray-800 text-white rounded-lg text-sm font-medium" %>
<% end %>
<% end %>
<div class="rounded-lg p-4 bg-container shadow-border-xs">
<div class="flex justify-center">
<%= render "chats/ai_avatar" %>
</div>
<h3 class="text-sm font-medium text-primary mb-1 -mt-2 text-center">Enable Maybe AI</h3>
<p class="text-gray-600 mb-4 text-sm text-center">
<% if Current.user.ai_available? %>
Maybe AI can answer financial questions and provide insights based on your data. To use this feature you'll need to explicitly enable it.
<% else %>
To use the AI assistant, you need to set the <code class="bg-surface-inset px-1 py-0.5 rounded font-mono text-xs">OPENAI_ACCESS_TOKEN</code>
environment variable in your self-hosted instance.
<% end %>
</p>
<% if Current.user.ai_available? %>
<%= form_with url: user_path(Current.user), method: :patch, class: "w-full", data: { turbo: false } do |form| %>
<%= form.hidden_field "user[ai_enabled]", value: true %>
<%= form.hidden_field "user[redirect_to]", value: "home" %>
<%= form.submit "Enable Maybe AI", class: "cursor-pointer hover:bg-black w-full py-2 px-4 bg-gray-800 text-white rounded-lg text-sm font-medium" %>
<% end %>
<% end %>
<p class="text-xs text-secondary text-center mt-2">Disable anytime. All data sent to our LLM providers is anonymized.</p>
</div>

View file

@ -9,8 +9,14 @@
</p>
</div>
<%= contextual_menu icon: "more-vertical" do %>
<%= contextual_menu_item("Edit chat", url: edit_chat_path(chat), icon: "pencil", turbo_frame: dom_id(chat, :title)) %>
<%= contextual_menu_destructive_item("Delete chat", chat_path(chat)) %>
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
<% menu.with_item(variant: "link", text: "Edit chat", href: edit_chat_path(chat), icon: "pencil", frame: dom_id(chat, "title")) %>
<% menu.with_item(
variant: "button",
text: "Delete chat",
href: chat_path(chat),
icon: "trash-2",
method: :delete,
confirm: CustomConfirm.for_resource_deletion("Chat")) %>
<% end %>
<% end %>

View file

@ -13,12 +13,23 @@
</div>
</div>
<%= contextual_menu icon: "more-vertical", id: "chat-menu" do %>
<%= contextual_menu_item "Start new chat", url: new_chat_path, icon: "plus" %>
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
<% menu.with_item(variant: "link", text: "Start new chat", href: new_chat_path, icon: "plus") %>
<% unless chat.new_record? %>
<%= contextual_menu_item "Edit chat title", url: edit_chat_path(chat, ctx: "chat"), icon: "pencil", turbo_frame: dom_id(chat, "title") %>
<%= contextual_menu_destructive_item "Delete chat", chat_path(chat), turbo_confirm: "Are you sure you want to delete this chat?" %>
<% menu.with_item(
variant: "link",
text: "Edit chat title",
href: edit_chat_path(chat, ctx: "chat"),
icon: "pencil", data: { turbo_frame: dom_id(chat, "title") }) %>
<% menu.with_item(
variant: "button",
text: "Delete chat",
href: chat_path(chat),
icon: "trash-2",
method: :delete,
confirm: CustomConfirm.for_resource_deletion("Chat")) %>
<% end %>
<% end %>
</nav>

View file

@ -10,8 +10,9 @@
<div class="flex items-center justify-between gap-2">
<p class="text-xs text-red-500">Failed to generate response. Please try again.</p>
<%= button_to retry_chat_path(chat), method: :post, class: "btn btn--primary" do %>
<span>Retry</span>
<% end %>
<%= render ButtonComponent.new(
text: "Retry",
href: retry_chat_path(chat),
) %>
</div>
</div>

View file

@ -1,31 +1,30 @@
<%= turbo_frame_tag chat_frame do %>
<div class="flex flex-col h-full md:p-4">
<nav class="mb-6">
<% back_path = @last_viewed_chat ? chat_path(@last_viewed_chat) : new_chat_path %>
<%= link_to back_path, class: "w-9 h-9 flex items-center justify-center rounded-lg hover:bg-surface-hover" do %>
<%= icon("arrow-left", color: "gray" ) %>
<% end %>
</nav>
<div class="grow flex flex-col">
<h1 class="text-xl font-medium mb-6">Chats</h1>
<div data-controller="chat hotkey">
<%= turbo_frame_tag chat_frame do %>
<div class="flex flex-col h-full md:p-4">
<% if @chats.any? %>
<div class="space-y-2 px-0.5">
<%= render @chats %>
</div>
<% else %>
<div class="text-center py-12 bg-container rounded-lg border border-primary">
<div class="w-16 h-16 bg-surface-inset rounded-full flex items-center justify-center mx-auto mb-4">
<%= icon("message-square", size: "lg") %>
</div>
<h3 class="text-lg font-medium text-primary mb-1">No chats yet</h3>
<p class="text-gray-500 mb-4">Start a new conversation with the AI assistant</p>
</div>
<div class="mt-auto p-4 lg:mt-auto">
<%= render "messages/chat_form", chat: nil %>
</div>
<nav class="mb-6">
<% back_path = @last_viewed_chat ? chat_path(@last_viewed_chat) : new_chat_path %>
<%= link_to back_path, class: "w-9 h-9 flex items-center justify-center rounded-lg hover:bg-surface-hover" do %>
<%= icon("arrow-left", color: "gray" ) %>
<% end %>
</nav>
<% end %>
<div class="grow flex flex-col">
<% if @chats.any? %>
<h1 class="text-xl font-medium mb-6">Chats</h1>
<div class="space-y-2 px-0.5">
<%= render @chats %>
</div>
<% else %>
<h1 class="sr-only">Chats</h1>
<div class="mt-auto py-8">
<%= render "chats/ai_greeting" %>
</div>
<%= render "messages/chat_form" %>
<% end %>
</div>
</div>
</div>
<% end %>
<% end %>
</div>

View file

@ -1,35 +1,38 @@
<%= turbo_frame_tag chat_frame do %>
<%= turbo_stream_from @chat %>
<div data-controller="chat hotkey">
<%= turbo_frame_tag chat_frame do %>
<%= turbo_stream_from @chat %>
<h1 class="sr-only"><%= @chat.title %></h1>
<h1 class="sr-only"><%= @chat.title %></h1>
<div class="flex flex-col h-full">
<div class="md:p-4">
<%= render "chats/chat_nav", chat: @chat %>
</div>
<div class="flex flex-col h-full">
<div class="md:p-4">
<%= render "chats/chat_nav", chat: @chat %>
</div>
<div id="messages" class="grow overflow-y-auto p-4 space-y-6" data-chat-target="messages">
<% if @chat.conversation_messages.any? %>
<% @chat.conversation_messages.ordered.each do |message| %>
<%= render message %>
<div id="messages" class="grow overflow-y-auto p-4 space-y-6 pb-24 lg:pb-4" data-chat-target="messages">
<% if @chat.conversation_messages.any? %>
<% @chat.conversation_messages.ordered.each do |message| %>
<%= render message %>
<% end %>
<% else %>
<div class="mt-auto">
<%= render "chats/ai_greeting", context: "chat" %>
</div>
<% end %>
<% else %>
<div class="mt-auto">
<%= render "chats/ai_greeting", context: "chat" %>
</div>
<% end %>
<% if params[:thinking].present? %>
<%= render "chats/thinking_indicator", chat: @chat %>
<% end %>
<% if params[:thinking].present? %>
<%= render "chats/thinking_indicator", chat: @chat %>
<% end %>
<% if @chat.error.present? && @chat.needs_assistant_response? %>
<%= render "chats/error", chat: @chat %>
<% end %>
<% if @chat.error.present? && @chat.needs_assistant_response? %>
<%= render "chats/error", chat: @chat %>
<% end %>
</div>
<%# DESKTOP - Chat form %>
<div class="p-4 lg:mt-auto fixed lg:static left-0 bottom-16 w-full bg-surface">
<%= render "messages/chat_form", chat: @chat %>
</div>
</div>
<div class="p-4 lg:mt-auto">
<%= render "messages/chat_form", chat: @chat %>
</div>
</div>
<% end %>
<% end %>
</div>