mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 05:09:38 +02:00
Implement dark mode (#2078)
* User theme settings * Initial rough pass on colors * More progress on dark mode
This commit is contained in:
parent
52d170e36c
commit
88a6373e84
98 changed files with 580 additions and 196 deletions
|
@ -15,7 +15,7 @@ The codebase uses TailwindCSS v4.x (the newest version) with a custom design sys
|
|||
|
||||
- Always start by referencing [maybe-design-system.css](mdc:app/assets/tailwind/maybe-design-system.css) to see the base primitives, functional tokens, and component tokens we use in the codebase
|
||||
- Always prefer using the functional "tokens" defined in @maybe-design-system.css when possible.
|
||||
- Example 1: use `text-primary` rather than `text-gray-900`
|
||||
- Example 1: use `text-primary` rather than `text-primary`
|
||||
- Example 2: use `bg-container` rather than `bg-white`
|
||||
- Example 3: use `border border-primary` rather than `border border-gray-200`
|
||||
- Never create new styles in [maybe-design-system.css](mdc:app/assets/tailwind/maybe-design-system.css) or [application.css](mdc:app/assets/tailwind/application.css) without explicitly receiving permission to do so
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
One-off styling (3rd party overrides, etc.) should be done in the application.css file.
|
||||
*/
|
||||
|
||||
@custom-variant theme-dark (&:where([data-theme=dark], [data-theme=dark] *));
|
||||
|
||||
@theme {
|
||||
/* Font families */
|
||||
--font-sans: 'Geist', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
|
@ -241,78 +243,235 @@
|
|||
/* Design system color utilities */
|
||||
@utility text-primary {
|
||||
@apply text-gray-900;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-white;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-secondary {
|
||||
@apply text-gray-500;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-subdued {
|
||||
@apply text-gray-400;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-600;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-link {
|
||||
@apply text-blue-600;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-surface {
|
||||
@apply bg-gray-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-black;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-surface-hover {
|
||||
@apply bg-gray-100;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-surface-inset {
|
||||
@apply bg-gray-100;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-surface-inset-hover {
|
||||
@apply bg-gray-200;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-container {
|
||||
@apply bg-white;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-container-hover {
|
||||
@apply bg-gray-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-container-inset {
|
||||
@apply bg-gray-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-container-inset-hover {
|
||||
@apply bg-gray-100;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-inverse {
|
||||
@apply bg-gray-800;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-inverse-hover {
|
||||
@apply bg-gray-700;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
}
|
||||
|
||||
@utility bg-overlay {
|
||||
@apply bg-alpha-black-200;
|
||||
background-color: rgba(var(--color-gray-100), 0.5);
|
||||
|
||||
@variant theme-dark {
|
||||
background-color: var(--color-alpha-black-900);
|
||||
}
|
||||
}
|
||||
|
||||
@utility border-primary {
|
||||
@apply border-alpha-black-300;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-400;
|
||||
}
|
||||
}
|
||||
|
||||
@utility border-secondary {
|
||||
@apply border-alpha-black-200;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-300;
|
||||
}
|
||||
}
|
||||
|
||||
@utility border-tertiary {
|
||||
@apply border-alpha-black-100;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-200;
|
||||
}
|
||||
}
|
||||
|
||||
@utility border-subdued {
|
||||
@apply border-alpha-black-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-100;
|
||||
}
|
||||
}
|
||||
|
||||
@utility border-solid {
|
||||
@apply border-black;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-white;
|
||||
}
|
||||
}
|
||||
|
||||
@utility border-destructive {
|
||||
@apply border-red-500;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-red-400;
|
||||
}
|
||||
}
|
||||
|
||||
/* Foreground Colors */
|
||||
@utility fg-gray {
|
||||
@apply text-gray-500;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
@utility fg-contrast {
|
||||
@apply text-gray-400;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-500;
|
||||
}
|
||||
}
|
||||
|
||||
@utility fg-inverse {
|
||||
@apply text-white;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
@utility fg-primary {
|
||||
@apply text-gray-900;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-white;
|
||||
}
|
||||
}
|
||||
|
||||
@utility fg-primary-variant {
|
||||
@apply text-gray-800;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
@utility fg-secondary {
|
||||
@apply text-gray-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
@utility fg-secondary-variant {
|
||||
@apply text-gray-100;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-600;
|
||||
}
|
||||
}
|
||||
|
||||
@utility fg-subdued {
|
||||
@apply text-gray-400;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-500;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
@ -331,6 +490,15 @@
|
|||
details>summary {
|
||||
@apply list-none;
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
@apply border-gray-300 text-indigo-600 focus:ring-indigo-600; /* Default light mode */
|
||||
|
||||
@variant theme-dark {
|
||||
/* Dark mode radio button base and checked styles */
|
||||
@apply border-gray-600 bg-gray-700 checked:bg-blue-500 focus:ring-blue-500 focus:ring-offset-gray-800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
|
@ -341,31 +509,63 @@
|
|||
}
|
||||
|
||||
.btn--primary {
|
||||
@apply bg-gray-900 text-white hover:bg-gray-700 disabled:bg-gray-50 disabled:hover:bg-gray-50 disabled:text-gray-400;
|
||||
@apply button-bg-primary text-white disabled:text-gray-400;
|
||||
@apply hover:button-bg-primary-hover;
|
||||
@apply disabled:button-bg-disabled disabled:hover:button-bg-disabled;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply button-bg-primary fg-primary;
|
||||
@apply hover:button-bg-primary-hover;
|
||||
@apply disabled:button-bg-disabled disabled:hover:button-bg-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.btn--secondary {
|
||||
@apply bg-gray-50 hover:bg-gray-100 text-gray-900;
|
||||
@apply button-bg-secondary text-primary;
|
||||
@apply hover:button-bg-secondary-hover;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply button-bg-secondary text-white;
|
||||
@apply hover:button-bg-secondary-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.btn--outline {
|
||||
@apply border border-alpha-black-200 text-gray-900 hover:bg-gray-50 disabled:bg-gray-50 disabled:hover:bg-gray-50 disabled:text-gray-400;
|
||||
@apply border border-alpha-black-200 text-primary disabled:button-bg-disabled disabled:hover:button-bg-disabled disabled:text-gray-400;
|
||||
@apply hover:button-bg-outline-hover;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply border-alpha-white-300 text-white disabled:button-bg-disabled disabled:hover:button-bg-disabled disabled:text-gray-600;
|
||||
@apply hover:button-bg-outline-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
@apply border border-transparent text-gray-900 hover:bg-gray-100;
|
||||
@apply border border-transparent text-primary hover:button-bg-ghost-hover;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply fg-primary hover:button-bg-ghost-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.btn--destructive {
|
||||
@apply bg-red-500 text-white hover:bg-red-600 disabled:bg-red-50 disabled:hover:bg-red-50 disabled:text-red-400;
|
||||
@apply button-bg-destructive text-white hover:button-bg-destructive-hover disabled:button-bg-disabled disabled:hover:button-bg-disabled disabled:text-red-400;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply button-bg-destructive text-white hover:button-bg-destructive-hover disabled:button-bg-disabled disabled:hover:button-bg-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-field {
|
||||
@apply flex flex-col gap-1 relative px-3 py-2 rounded-md border bg-white border-alpha-black-100 shadow-xs w-full;
|
||||
@apply focus-within:border-gray-900 focus-within:shadow-none focus-within:ring-4 focus-within:ring-gray-100;
|
||||
@apply flex flex-col gap-1 relative px-3 py-2 rounded-md border bg-container border-secondary shadow-xs w-full;
|
||||
@apply focus-within:border-secondary focus-within:shadow-none focus-within:ring-4 focus-within:ring-alpha-black-200;
|
||||
@apply transition-all duration-300;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply focus-within:ring-alpha-white-300;
|
||||
}
|
||||
|
||||
/* Add styles for multiple select within form fields */
|
||||
select[multiple] {
|
||||
@apply py-2 pr-2 space-y-0.5 overflow-y-auto;
|
||||
|
@ -375,25 +575,25 @@
|
|||
}
|
||||
|
||||
option:checked {
|
||||
@apply after:content-['\2713'] bg-white after:text-gray-500 after:ml-2;
|
||||
@apply after:content-['\2713'] bg-container-inset after:text-gray-500 after:ml-2;
|
||||
}
|
||||
|
||||
option:active,
|
||||
option:focus {
|
||||
@apply bg-white;
|
||||
@apply bg-container-inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-field__label {
|
||||
@apply block text-xs text-gray-500 peer-disabled:text-gray-400;
|
||||
@apply block text-xs text-secondary peer-disabled:text-subdued;
|
||||
}
|
||||
|
||||
.form-field__input {
|
||||
@apply 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-gray-400;
|
||||
@apply disabled:text-subdued;
|
||||
@apply text-ellipsis overflow-hidden whitespace-nowrap;
|
||||
@apply transition-opacity duration-300;
|
||||
|
||||
|
@ -403,11 +603,11 @@
|
|||
}
|
||||
|
||||
.form-field__radio {
|
||||
@apply text-gray-900;
|
||||
@apply text-primary;
|
||||
}
|
||||
|
||||
.form-field__submit {
|
||||
@apply cursor-pointer rounded-lg bg-black p-3 text-center text-white hover:bg-gray-700;
|
||||
@apply cursor-pointer rounded-lg bg-surface p-3 text-center text-white hover:bg-surface-hover;
|
||||
}
|
||||
|
||||
/* Checkboxes */
|
||||
|
@ -455,4 +655,109 @@
|
|||
.tooltip {
|
||||
@apply hidden absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Specific override for strong tags in prose under dark mode */
|
||||
.prose:where([data-theme=dark], [data-theme=dark] *) strong {
|
||||
color: theme(colors.white) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Button Backgrounds */
|
||||
@utility button-bg-primary {
|
||||
@apply bg-gray-900; /* Maps to fg-primary light */
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-white; /* Maps to fg-primary dark */
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-primary-hover {
|
||||
@apply bg-gray-800; /* Maps to fg-primary-variant light */
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-50; /* Maps to fg-primary-variant dark */
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-secondary {
|
||||
@apply bg-gray-50; /* Maps to fg-secondary light */
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-700; /* Maps to fg-secondary dark */
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-secondary-hover {
|
||||
@apply bg-gray-100; /* Maps to fg-secondary-variant light */
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-600; /* Maps to fg-secondary-variant dark */
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-disabled {
|
||||
@apply bg-gray-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-destructive {
|
||||
@apply bg-red-500;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-red-400;
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-destructive-hover {
|
||||
@apply bg-red-600;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-ghost-hover {
|
||||
@apply bg-gray-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
@utility button-bg-outline-hover {
|
||||
@apply bg-gray-100;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tab Styles */
|
||||
@utility tab-item-active {
|
||||
@apply bg-white;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
@utility tab-item-hover {
|
||||
@apply bg-gray-200;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
}
|
||||
|
||||
@utility tab-bg-group {
|
||||
@apply bg-gray-50;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply bg-alpha-black-700;
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ class UsersController < ApplicationController
|
|||
|
||||
def user_params
|
||||
params.require(:user).permit(
|
||||
:first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at, :show_sidebar, :default_period, :show_ai_sidebar, :ai_enabled,
|
||||
:first_name, :last_name, :email, :profile_image, :redirect_to, :delete_profile_image, :onboarded_at, :show_sidebar, :default_period, :show_ai_sidebar, :ai_enabled, :theme,
|
||||
family_attributes: [ :name, :currency, :country, :locale, :date_format, :timezone, :id, :data_enrichment_enabled ]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ module FormsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def period_select(form:, selected:, classes: "border border-secondary rounded-lg text-sm pr-7 cursor-pointer text-primary focus:outline-hidden focus:ring-0")
|
||||
def period_select(form:, selected:, classes: "border border-secondary bg-container-inset rounded-lg text-sm pr-7 cursor-pointer text-primary focus:outline-hidden focus:ring-0")
|
||||
periods_for_select = Period.all.map { |period| [ period.label_short, period.key ] }
|
||||
|
||||
form.select(:period, periods_for_select, { selected: selected.key }, class: classes, data: { "auto-submit-form-target": "auto" })
|
||||
|
@ -30,7 +30,7 @@ end
|
|||
|
||||
private
|
||||
def radio_tab_contents(label:, icon:)
|
||||
tag.div(class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-subdued group-has-checked:bg-white group-has-checked:text-gray-800 group-has-checked:shadow-sm") do
|
||||
tag.div(class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-subdued group-has-checked:bg-container group-has-checked:text-gray-800 group-has-checked:shadow-sm") do
|
||||
concat lucide_icon(icon, class: "w-5 h-5")
|
||||
concat tag.span(label, class: "group-has-checked:font-semibold")
|
||||
end
|
||||
|
|
|
@ -38,7 +38,7 @@ module MenusHelper
|
|||
end
|
||||
|
||||
def contextual_menu_content(&block)
|
||||
tag.div class: "min-w-[200px] p-1 z-50 shadow-border-xs bg-white rounded-lg hidden",
|
||||
tag.div class: "min-w-[200px] p-1 z-50 shadow-border-xs bg-container rounded-lg hidden",
|
||||
data: { menu_target: "content" } do
|
||||
capture(&block)
|
||||
end
|
||||
|
|
73
app/javascript/controllers/theme_controller.js
Normal file
73
app/javascript/controllers/theme_controller.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { userPreference: String }
|
||||
|
||||
connect() {
|
||||
this.applyTheme()
|
||||
this.startSystemThemeListener()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.stopSystemThemeListener()
|
||||
}
|
||||
|
||||
// Called automatically by Stimulus when the userPreferenceValue changes (e.g., after form submit/page reload)
|
||||
userPreferenceValueChanged() {
|
||||
this.applyTheme()
|
||||
}
|
||||
|
||||
// Called when a theme radio button is clicked
|
||||
updateTheme(event) {
|
||||
const selectedTheme = event.currentTarget.value
|
||||
if (selectedTheme === "system") {
|
||||
this.setTheme(this.systemPrefersDark())
|
||||
} else if (selectedTheme === "dark") {
|
||||
this.setTheme(true)
|
||||
} else {
|
||||
this.setTheme(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Applies theme based on the userPreferenceValue (from server)
|
||||
applyTheme() {
|
||||
if (this.userPreferenceValue === "system") {
|
||||
this.setTheme(this.systemPrefersDark())
|
||||
} else if (this.userPreferenceValue === "dark") {
|
||||
this.setTheme(true)
|
||||
} else {
|
||||
this.setTheme(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Sets or removes the data-theme attribute
|
||||
setTheme(isDark) {
|
||||
if (isDark) {
|
||||
document.documentElement.setAttribute("data-theme", "dark")
|
||||
} else {
|
||||
document.documentElement.removeAttribute("data-theme")
|
||||
}
|
||||
}
|
||||
|
||||
systemPrefersDark() {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
}
|
||||
|
||||
handleSystemThemeChange = (event) => {
|
||||
// Only apply system theme changes if the user preference is currently 'system'
|
||||
if (this.userPreferenceValue === "system") {
|
||||
this.setTheme(event.matches)
|
||||
}
|
||||
}
|
||||
|
||||
startSystemThemeListener() {
|
||||
this.darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
this.darkMediaQuery.addEventListener("change", this.handleSystemThemeChange)
|
||||
}
|
||||
|
||||
stopSystemThemeListener() {
|
||||
if (this.darkMediaQuery) {
|
||||
this.darkMediaQuery.removeEventListener("change", this.handleSystemThemeChange)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (date:, entries:, content:, totals: false) %>
|
||||
|
||||
<div id="entry-group-<%= date %>" class="bg-gray-25 rounded-xl p-1 w-full" data-bulk-select-target="group">
|
||||
<div id="entry-group-<%= date %>" class="bg-container-inset rounded-xl p-1 w-full" data-bulk-select-target="group">
|
||||
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-secondary">
|
||||
<div class="flex pl-0.5 items-center gap-4">
|
||||
<%= check_box_tag "#{date}_entries_selection",
|
||||
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="bg-white shadow-border-xs rounded-md divide-y divide-alpha-black-50">
|
||||
<div class="bg-container shadow-border-xs rounded-md divide-y divide-alpha-black-50">
|
||||
<%= content %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="bg-white space-y-4 p-5 shadow-border-xs rounded-xl">
|
||||
<div class="bg-container space-y-4 p-5 shadow-border-xs rounded-xl">
|
||||
<div class="p-5 flex justify-center items-center">
|
||||
<%= tag.p t(".loading"), class: "text-secondary animate-pulse text-sm" %>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%= turbo_frame_tag dom_id(@account, "holdings") do %>
|
||||
<div class="bg-white space-y-4 p-5 rounded-xl shadow-border-xs">
|
||||
<div class="bg-container space-y-4 p-5 rounded-xl shadow-border-xs">
|
||||
<div class="flex items-center justify-between">
|
||||
<%= tag.h2 t(".holdings"), class: "font-medium text-lg" %>
|
||||
<%= link_to new_account_trade_path(account_id: @account.id),
|
||||
|
@ -11,7 +11,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-25 p-1">
|
||||
<div class="rounded-xl bg-container-inset p-1">
|
||||
<div class="grid grid-cols-12 items-center uppercase text-xs font-medium text-secondary px-4 py-2">
|
||||
<%= tag.p t(".name"), class: "col-span-4" %>
|
||||
<%= tag.p t(".weight"), class: "col-span-2 justify-self-end" %>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<%= tag.p t(".return"), class: "col-span-2 justify-self-end" %>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg bg-white shadow-border-xs">
|
||||
<div class="rounded-lg bg-container shadow-border-xs">
|
||||
<%= render "account/holdings/cash", account: @account %>
|
||||
|
||||
<%= render "account/holdings/ruler" %>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<fieldset class="bg-gray-50 rounded-lg p-1 grid grid-flow-col justify-stretch gap-x-2">
|
||||
<%= radio_tab_tag form: f, name: :nature, value: :outflow, label: t(".expense"), icon: "minus-circle", checked: params[:nature] == "outflow" || params[:nature].nil? %>
|
||||
<%= radio_tab_tag form: f, name: :nature, value: :inflow, label: t(".income"), icon: "plus-circle", checked: params[:nature] == "inflow" %>
|
||||
<%= link_to new_transfer_path, data: { turbo_frame: :modal }, class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-subdued group-has-checked:bg-white group-has-checked:text-gray-800 group-has-checked:shadow-sm" do %>
|
||||
<%= link_to new_transfer_path, data: { turbo_frame: :modal }, class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-subdued group-has-checked:bg-container group-has-checked:text-gray-800 group-has-checked:shadow-sm" do %>
|
||||
<%= lucide_icon "arrow-right-left", class: "w-5 h-5" %>
|
||||
<%= tag.span t(".transfer") %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%= turbo_frame_tag "bulk_transaction_edit_drawer" do %>
|
||||
<dialog data-controller="modal"
|
||||
data-action="mousedown->modal#clickOutside"
|
||||
class="bg-white shadow-border-xs rounded-2xl max-h-[calc(100vh-32px)] h-full max-w-[480px] w-full mt-4 mr-4 ml-auto">
|
||||
class="bg-container shadow-border-xs rounded-2xl max-h-[calc(100vh-32px)] h-full max-w-[480px] w-full mt-4 mr-4 ml-auto">
|
||||
<%= styled_form_with url: bulk_update_account_transactions_path, scope: "bulk_update", class: "h-full", data: { turbo_frame: "_top" } do |form| %>
|
||||
<div class="flex h-full flex-col justify-between p-4 gap-4">
|
||||
<div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%= turbo_frame_tag dom_id(@account, "valuations") do %>
|
||||
<div class="bg-white space-y-4 p-5 shadow-border-xs rounded-xl">
|
||||
<div class="bg-container space-y-4 p-5 shadow-border-xs rounded-xl">
|
||||
<div class="flex items-center justify-between">
|
||||
<%= tag.h2 t(".valuations"), class: "font-medium text-lg" %>
|
||||
<%= link_to new_account_valuation_path(@account),
|
||||
|
@ -10,7 +10,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-25 p-1">
|
||||
<div class="rounded-xl bg-container-inset p-1">
|
||||
<div class="grid grid-cols-10 items-center uppercase text-xs font-medium text-secondary px-4 py-2">
|
||||
<%= tag.p t(".date"), class: "col-span-5" %>
|
||||
<%= tag.p t(".value"), class: "col-span-2 justify-self-end" %>
|
||||
|
@ -18,7 +18,7 @@
|
|||
<%= tag.div class: "col-span-1" %>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg bg-white shadow-border-xs">
|
||||
<div class="rounded-lg bg-container shadow-border-xs">
|
||||
<%= turbo_frame_tag dom_id(@account.entries.account_valuations.new) %>
|
||||
|
||||
<% if @entries.any? %>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
class="space-y-3"
|
||||
data-controller="tabs"
|
||||
data-tabs-local-storage-key-value="account-sidebar-tabs"
|
||||
data-tabs-active-class="bg-white shadow-sm text-primary"
|
||||
data-tabs-active-class="bg-surface shadow-sm text-primary"
|
||||
data-tabs-inactive-class="text-secondary"
|
||||
data-tabs-default-tab-value="assets-tab">
|
||||
<div class="bg-surface-inset rounded-lg p-1 flex">
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<%= turbo_frame_tag "#{account_group.key}_sparkline", src: accountable_sparkline_path(account_group.key), loading: "lazy" do %>
|
||||
<div class="flex items-center w-8 h-4 ml-auto">
|
||||
<div class="w-6 h-px bg-gray-200"></div>
|
||||
<div class="w-6 h-px bg-surface-inset"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<%= render "accounts/logo", account: account, size: "sm", color: account_group.color %>
|
||||
|
||||
<div class="min-w-0 grow">
|
||||
<%= tag.p account.name, class: "text-sm font-medium mb-0.5 truncate" %>
|
||||
<%= tag.p account.name, class: "text-sm text-primary font-medium mb-0.5 truncate" %>
|
||||
<%= tag.p account.subtype&.humanize.presence || account_group.name, class: "text-sm text-secondary truncate" %>
|
||||
</div>
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
|||
|
||||
<%= turbo_frame_tag dom_id(account, :sparkline), src: sparkline_account_path(account), loading: "lazy" do %>
|
||||
<div class="flex items-center w-8 h-5 ml-auto">
|
||||
<div class="w-6 h-px bg-gray-200"></div>
|
||||
<div class="w-6 h-px bg-surface-inset"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (title:, content:) %>
|
||||
|
||||
<div class="rounded-xl bg-white shadow-xs border border-alpha-black-25 p-4">
|
||||
<div class="rounded-xl bg-container shadow-xs border border-alpha-black-25 p-4">
|
||||
<h4 class="text-secondary text-sm"><%= title %></h4>
|
||||
<p class="text-xl font-medium text-primary">
|
||||
<%= content %>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<%# locals: (accounts:) %>
|
||||
|
||||
<% accounts.group_by(&:accountable_type).sort_by { |group, _| group }.each do |group, accounts| %>
|
||||
<div class="bg-gray-25 p-1 rounded-xl">
|
||||
<div class="bg-container-inset p-1 rounded-xl">
|
||||
<div class="flex items-center px-4 py-2 text-xs font-medium text-secondary">
|
||||
<p><%= Accountable.from_type(group).display_name %></p>
|
||||
<span class="text-subdued mx-2">·</span>
|
||||
<p><%= accounts.count %></p>
|
||||
<p class="ml-auto"><%= totals_by_currency(collection: accounts, money_method: :balance_money) %></p>
|
||||
</div>
|
||||
<div class="bg-white">
|
||||
<div class="bg-container">
|
||||
<% accounts.each do |account| %>
|
||||
<%= render account %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (accounts:) %>
|
||||
|
||||
<details open class="group bg-white p-4 shadow-border-xs rounded-xl">
|
||||
<details open class="group bg-container p-4 shadow-border-xs rounded-xl">
|
||||
<summary class="flex items-center gap-2 focus-visible:outline-hidden">
|
||||
<%= lucide_icon "chevron-right", class: "group-open:transform group-open:rotate-90 text-secondary w-5" %>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (account:) %>
|
||||
|
||||
<%= turbo_frame_tag dom_id(account, "entries") do %>
|
||||
<div class="bg-white p-5 shadow-border-xs rounded-xl" data-controller="focus-record" data-focus-record-id-value="<%= @focused_record ? dom_id(@focused_record) : nil %>">
|
||||
<div class="bg-container p-5 shadow-border-xs rounded-xl" data-controller="focus-record" data-focus-record-id-value="<%= @focused_record ? dom_id(@focused_record) : nil %>">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<%= tag.h2 t(".title"), class: "font-medium text-lg" %>
|
||||
<% unless @account.plaid_account_id.present? %>
|
||||
|
@ -10,7 +10,7 @@
|
|||
<%= lucide_icon("plus", class: "w-4 h-4") %>
|
||||
<%= tag.span t(".new") %>
|
||||
</button>
|
||||
<div data-menu-target="content" class="z-10 hidden bg-white rounded-lg border border-alpha-black-25 shadow-xs p-1">
|
||||
<div data-menu-target="content" class="z-10 hidden bg-container rounded-lg border border-alpha-black-25 shadow-xs p-1">
|
||||
<%= link_to new_account_valuation_path(account_id: @account.id), data: { turbo_frame: :modal }, class: "block p-2 rounded-lg hover:bg-gray-50 flex items-center gap-2" do %>
|
||||
<%= lucide_icon("circle-dollar-sign", class: "text-secondary w-5 h-5") %>
|
||||
<%= tag.span t(".new_balance"), class: "text-sm" %>
|
||||
|
@ -62,7 +62,7 @@
|
|||
<%= render "account/entries/selection_bar" %>
|
||||
</div>
|
||||
|
||||
<div class="grid bg-gray-25 rounded-xl grid-cols-12 items-center uppercase text-xs font-medium text-secondary px-5 py-3 mb-4">
|
||||
<div class="grid bg-container-inset rounded-xl grid-cols-12 items-center uppercase text-xs font-medium text-secondary px-5 py-3 mb-4">
|
||||
<div class="pl-0.5 col-span-8 flex items-center gap-4">
|
||||
<%= check_box_tag "selection_entry",
|
||||
class: "checkbox checkbox--light",
|
||||
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<div class="rounded-tl-lg rounded-tr-lg bg-white border-alpha-black-25 shadow-xs">
|
||||
<div class="rounded-tl-lg rounded-tr-lg bg-container border-alpha-black-25 shadow-xs">
|
||||
<div class="space-y-4">
|
||||
<% calculator = Account::BalanceTrendCalculator.for(@entries) %>
|
||||
<%= entries_by_date(@entries) do |entries| %>
|
||||
|
@ -85,7 +85,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-white rounded-bl-lg rounded-br-lg">
|
||||
<div class="p-4 bg-container rounded-bl-lg rounded-br-lg">
|
||||
<%= render "shared/pagination", pagy: @pagy %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<% period = @period || Period.last_30_days %>
|
||||
<% default_value_title = account.asset? ? t(".balance") : t(".owed") %>
|
||||
|
||||
<div id="<%= dom_id(account, :chart) %>" class="bg-white shadow-xs rounded-xl border border-alpha-black-25 rounded-lg space-y-2">
|
||||
<div id="<%= dom_id(account, :chart) %>" class="bg-container shadow-xs rounded-xl border border-alpha-black-25 rounded-lg space-y-2">
|
||||
<div class="flex justify-between px-4 pt-4 mb-2">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-1">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (account:) %>
|
||||
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<% if account.plaid_account_id.present? %>
|
||||
<%= link_to accounts_path,
|
||||
data: { turbo_frame: :_top },
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
data: { turbo: false },
|
||||
class: [
|
||||
"px-2 py-1.5 rounded-md border border-transparent",
|
||||
"bg-white shadow-xs border-alpha-black-50": is_selected
|
||||
"bg-container shadow-xs border-alpha-black-50": is_selected
|
||||
] %>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (budget_category:) %>
|
||||
|
||||
<%= turbo_frame_tag dom_id(budget_category), class: "w-full" do %>
|
||||
<%= link_to budget_budget_category_path(budget_category.budget, budget_category), class: "group w-full p-4 flex items-center gap-3 bg-white", data: { turbo_frame: "drawer" } do %>
|
||||
<%= link_to budget_budget_category_path(budget_category.budget, budget_category), class: "group w-full p-4 flex items-center gap-3 bg-container", data: { turbo_frame: "drawer" } do %>
|
||||
|
||||
<% if budget_category.initialized? %>
|
||||
<div class="w-10 h-10 group-hover:scale-105 transition-all duration-300">
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<div class="mx-auto max-w-lg">
|
||||
<% if @budget.family.categories.empty? %>
|
||||
<div class="bg-white shadow-border-xs rounded-lg p-4">
|
||||
<div class="bg-container shadow-border-xs rounded-lg p-4">
|
||||
<%= render "budget_categories/no_categories" %>
|
||||
</div>
|
||||
<% else %>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<p class="ml-auto">Amount</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white py-1 shadow-border-xs rounded-md">
|
||||
<div class="bg-container py-1 shadow-border-xs rounded-md">
|
||||
<% if budget.family.categories.expenses.empty? %>
|
||||
<div class="py-8">
|
||||
<%= render "budget_categories/no_categories" %>
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
step[:is_complete] ? "text-green-600" : "text-secondary"
|
||||
end %>
|
||||
<% step_class = if is_current
|
||||
"bg-gray-900 text-white"
|
||||
"bg-primary text-white"
|
||||
else
|
||||
step[:is_complete] ? "bg-green-600/10 border-alpha-black-25" : "bg-gray-50"
|
||||
step[:is_complete] ? "bg-green-600/10 border-alpha-black-25" : "bg-container-inset"
|
||||
end %>
|
||||
|
||||
<%= link_to step[:path], class: "flex items-center gap-3" do %>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (family:, year:) %>
|
||||
|
||||
<%= turbo_frame_tag "budget_picker" do %>
|
||||
<div class="bg-white shadow-border-xs p-3 rounded-xl space-y-4">
|
||||
<div class="bg-container shadow-border-xs p-3 rounded-xl space-y-4">
|
||||
<div class="flex items-center gap-2 justify-between">
|
||||
<% last_month_of_previous_year = Date.new(year - 1, 12, 1) %>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-[300px] space-y-4">
|
||||
<div class="h-[300px] bg-white rounded-xl shadow-border-xs p-8">
|
||||
<div class="h-[300px] bg-container rounded-xl shadow-border-xs p-8">
|
||||
<% if @budget.available_to_allocate.negative? %>
|
||||
<%= render "budgets/over_allocation_warning", budget: @budget %>
|
||||
<% else %>
|
||||
|
@ -25,7 +25,7 @@
|
|||
budget_path(@budget, tab: "budgeted"),
|
||||
class: class_names(
|
||||
base_classes,
|
||||
"bg-white shadow-xs text-primary": selected_tab == "budgeted",
|
||||
"bg-container shadow-xs text-primary": selected_tab == "budgeted",
|
||||
"text-secondary": selected_tab != "budgeted"
|
||||
) %>
|
||||
|
||||
|
@ -33,23 +33,23 @@
|
|||
budget_path(@budget, tab: "actuals"),
|
||||
class: class_names(
|
||||
base_classes,
|
||||
"bg-white shadow-xs text-primary": selected_tab == "actuals",
|
||||
"bg-container shadow-xs text-primary": selected_tab == "actuals",
|
||||
"text-secondary": selected_tab != "actuals"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-border-xs">
|
||||
<div class="bg-container rounded-xl shadow-border-xs">
|
||||
<%= render selected_tab == "budgeted" ? "budgets/budgeted_summary" : "budgets/actuals_summary", budget: @budget %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="bg-white rounded-xl shadow-border-xs">
|
||||
<div class="bg-container rounded-xl shadow-border-xs">
|
||||
<%= render "budgets/actuals_summary", budget: @budget %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grow bg-white rounded-xl shadow-border-xs p-4">
|
||||
<div class="grow bg-container rounded-xl shadow-border-xs p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-medium">Categories</h2>
|
||||
|
||||
|
@ -61,7 +61,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-25 rounded-xl p-1">
|
||||
<div class="bg-container-inset rounded-xl p-1">
|
||||
<%= render "budgets/budget_categories", budget: @budget %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (category:) %>
|
||||
|
||||
<div id="<%= dom_id(category) %>" class="flex justify-between items-center px-4 pb-4 <%= "pt-4" unless category.subcategory? %> <%= "pb-4" unless category.subcategories.any? %> bg-white">
|
||||
<div id="<%= dom_id(category) %>" class="flex justify-between items-center px-4 pb-4 <%= "pt-4" unless category.subcategory? %> <%= "pb-4" unless category.subcategories.any? %> bg-container">
|
||||
<div class="flex w-full items-center gap-2.5">
|
||||
<% if category.subcategory? %>
|
||||
<%= lucide_icon "corner-down-right", class: "shrink-0 w-5 h-5 text-subdued ml-2" %>
|
||||
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= contextual_menu_modal_action_item t(".edit"), edit_category_path(category) %>
|
||||
|
||||
<% if category.transactions.any? %>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<%# locals: (title:, categories:) %>
|
||||
|
||||
<div class="rounded-xl bg-gray-25 space-y-1 p-1">
|
||||
<div class="rounded-xl bg-container-inset space-y-1 p-1">
|
||||
<div class="flex items-center gap-1.5 px-4 py-2 text-xs font-medium text-secondary uppercase">
|
||||
<p><%= title %></p>
|
||||
<span class="text-subdued">·</span>
|
||||
<p><%= categories.count %></p>
|
||||
</div>
|
||||
|
||||
<div class="shadow-border-xs rounded-md bg-white">
|
||||
<div class="shadow-border-xs rounded-md bg-container">
|
||||
<div class="overflow-hidden rounded-md">
|
||||
<% Category::Group.for(categories).each_with_index do |group, idx| %>
|
||||
<%= render group.category %>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<%= icon("pen", size: "sm") %>
|
||||
</summary>
|
||||
|
||||
<div class=" absolute z-50 bg-white p-4 border border-alpha-black-25 rounded-2xl shadow-xs h-fit left-66 top-24">
|
||||
<div class=" absolute z-50 bg-container p-4 border border-alpha-black-25 rounded-2xl shadow-xs h-fit left-66 top-24">
|
||||
<div class="flex gap-2 flex-col mb-4" data-category-target="selection" style="<%= "display:none;" if @category.subcategory? %>">
|
||||
<div data-category-target="pickerSection"></div>
|
||||
<h4 class="text-gray-500 text-sm">Color</h4>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<%= render partial: "categories/badge", locals: { category: transaction.category } %>
|
||||
</button>
|
||||
<div data-menu-target="content" class="absolute z-10 hidden w-screen mt-2 max-w-min cursor-default">
|
||||
<div class="w-80 text-sm font-semibold leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-80 text-sm font-semibold leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= turbo_frame_tag "category_dropdown", src: category_dropdown_path(category_id: transaction.category_id, transaction_id: transaction.id), loading: :lazy do %>
|
||||
<div class="p-6 flex items-center justify-center">
|
||||
<p class="text-sm text-secondary animate-pulse"><%= t(".loading") %></p>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<div class="bg-white">
|
||||
<div class="bg-container">
|
||||
<div class="h-px bg-alpha-black-50 ml-4 mr-6"></div>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<% end %>
|
||||
</header>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4">
|
||||
<div class="bg-container shadow-border-xs rounded-xl p-4">
|
||||
<% if @categories.any? %>
|
||||
<div class="space-y-4">
|
||||
<% if @categories.incomes.any? %>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
data: {
|
||||
turbo: false,
|
||||
controller: "deletion",
|
||||
deletion_dangerous_action_class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
|
||||
deletion_dangerous_action_class: "form-field__submit bg-container text-red-600 border hover:bg-red-50",
|
||||
deletion_safe_action_class: "form-field__submit border border-transparent",
|
||||
deletion_submit_text_when_not_replacing_value: t(".delete_and_leave_uncategorized", category_name: @category.name),
|
||||
deletion_submit_text_when_replacing_value: t(".delete_and_recategorize", category_name: @category.name) } do |f| %>
|
||||
|
@ -15,7 +15,7 @@
|
|||
{ data: { deletion_target: "replacementField", action: "deletion#updateSubmitButton" } } %>
|
||||
|
||||
<%= f.submit t(".delete_and_leave_uncategorized", category_name: @category.name),
|
||||
class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
|
||||
class: "form-field__submit bg-container text-red-600 border hover:bg-red-50",
|
||||
data: { deletion_target: "submitButton" } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<% end %>
|
||||
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= link_to edit_category_path(category),
|
||||
class: "block w-full py-2 px-3 space-x-2 text-primary hover:bg-gray-50 flex items-center rounded-lg",
|
||||
data: { turbo_frame: :modal } do %>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%= turbo_frame_tag "category_dropdown" do %>
|
||||
<div class="flex flex-col relative" data-controller="list-filter">
|
||||
<div class="grow p-1.5">
|
||||
<div class="relative flex items-center bg-white border border-gray-200 rounded-lg">
|
||||
<div class="relative flex items-center bg-container border border-gray-200 rounded-lg">
|
||||
<input placeholder="<%= t(".search_placeholder") %>" autocomplete="nope" type="search" class="placeholder:text-sm placeholder:text-secondary font-normal h-10 relative pl-10 w-full border-none rounded-lg" data-list-filter-target="input" data-action="list-filter#filter">
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-secondary ml-2 absolute inset-0 transform top-1/2 -translate-y-1/2") %>
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<div class="flex flex-col items-center justify-start h-full p-6 text-center">
|
||||
<div class="border border-gray-200 rounded-lg p-4 bg-white">
|
||||
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<div class="border border-gray-200 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-gray-900 mb-2">Enable Personal Finance AI</h3>
|
||||
<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-gray-100 px-1 py-0.5 rounded font-mono text-xs">OPENAI_ACCESS_TOKEN</code>
|
||||
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>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<p>Hey <%= Current.user&.first_name || "there" %>! I'm an AI built by Maybe to help with your finances. I have access to the web and your account data.</p>
|
||||
|
||||
<p>
|
||||
You can use <span class="bg-white border border-gray-200 px-1.5 py-0.5 rounded font-mono text-xs">/</span> to access commands
|
||||
You can use <span class="bg-container border border-gray-200 px-1.5 py-0.5 rounded font-mono text-xs">/</span> to access commands
|
||||
</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%= turbo_frame_tag dom_id(@chat, :title), class: "block" do %>
|
||||
<% bg_class = params[:ctx] == "chat" ? "bg-white" : "bg-container-inset" %>
|
||||
<% bg_class = params[:ctx] == "chat" ? "bg-container" : "bg-container-inset" %>
|
||||
<%= styled_form_with model: @chat,
|
||||
class: class_names("p-1 rounded-md font-medium text-primary w-full", bg_class),
|
||||
data: { controller: "auto-submit-form", auto_submit_form_trigger_event_value: "blur" } do |f| %>
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
<%= render @chats %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-center py-12 bg-white rounded-lg border border-gray-200">
|
||||
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<div class="text-center py-12 bg-container rounded-lg border border-gray-200">
|
||||
<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-gray-900 mb-1">No chats yet</h3>
|
||||
<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>
|
||||
<%= link_to "Start a chat", new_chat_path, class: "inline-flex items-center gap-2 py-2 px-4 bg-gray-800 text-white rounded-lg text-sm font-medium" %>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
|
||||
<% if @import.cleaned? %>
|
||||
<div class="bg-white border border-tertiary rounded-lg p-3 flex items-center justify-between">
|
||||
<div class="bg-container border border-tertiary rounded-lg p-3 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "check-circle", class: "w-4 h-4 text-green-500" %>
|
||||
<p class="text-green-500">Your data has been cleaned</p>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<%= link_to "Next step", import_confirm_path(@import), class: "btn btn--primary" %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="bg-white border border-tertiary rounded-lg p-3 flex items-center justify-between">
|
||||
<div class="bg-container border border-tertiary rounded-lg p-3 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "alert-triangle", class: "w-4 h-4 text-red-500" %>
|
||||
<p class="text-red-500 text-sm"><%= t(".errors_notice") %></p>
|
||||
|
@ -28,22 +28,22 @@
|
|||
|
||||
<div class="flex justify-center">
|
||||
<div class="bg-gray-50 rounded-lg inline-flex p-1 space-x-2 text-sm text-primary font-medium">
|
||||
<%= link_to "All rows", import_clean_path(@import, per_page: params[:per_page], view: "all"), class: "p-2 rounded-lg #{params[:view] != 'errors' ? 'bg-white' : ''}" %>
|
||||
<%= link_to "Error rows", import_clean_path(@import, per_page: params[:per_page], view: "errors"), class: "p-2 rounded-lg #{params[:view] == 'errors' ? 'bg-white' : ''}" %>
|
||||
<%= link_to "All rows", import_clean_path(@import, per_page: params[:per_page], view: "all"), class: "p-2 rounded-lg #{params[:view] != 'errors' ? 'bg-container' : ''}" %>
|
||||
<%= link_to "Error rows", import_clean_path(@import, per_page: params[:per_page], view: "errors"), class: "p-2 rounded-lg #{params[:view] == 'errors' ? 'bg-container' : ''}" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pb-12">
|
||||
<div class="bg-gray-25 rounded-xl p-1 mb-6">
|
||||
<div class="bg-container-inset rounded-xl p-1 mb-6">
|
||||
<div style="grid-template-columns: repeat(<%= @import.column_keys.count %>, 1fr)" class="grid items-center uppercase text-xs font-medium text-secondary py-3">
|
||||
<% @import.column_keys.each do |key| %>
|
||||
<div class="px-5"><%= import_col_label(key) %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl divide-y divide-alpha-black-200">
|
||||
<div class="bg-container shadow-border-xs rounded-xl divide-y divide-alpha-black-200">
|
||||
<% @rows.each do |row| %>
|
||||
<%= render "import/rows/form", row: row %>
|
||||
<% end %>
|
||||
|
@ -52,7 +52,7 @@
|
|||
</div>
|
||||
|
||||
<div class="fixed bottom-0 left-1/2 -translate-x-1/2 w-full p-12">
|
||||
<div class="shadow-border-xs rounded-lg p-3 max-w-2xl mx-auto bg-white">
|
||||
<div class="shadow-border-xs rounded-lg p-3 max-w-2xl mx-auto bg-container">
|
||||
<%= render "shared/pagination", pagy: @pagy %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<% end %>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="bg-gray-25 rounded-xl p-1 space-y-1 w-[650px]">
|
||||
<div class="bg-container-inset rounded-xl p-1 space-y-1 w-[650px]">
|
||||
<div class="grid grid-cols-3 gap-2 text-xs font-medium text-secondary uppercase px-5 py-3">
|
||||
<p><%= t(".csv_mapping_label", mapping: mapping_label(mapping_class)) %></p>
|
||||
<p><%= t(".maybe_mapping_label", mapping: mapping_label(mapping_class)) %></p>
|
||||
|
@ -29,7 +29,7 @@
|
|||
|
||||
<div class="shadow-border-xs rounded-md divide-y divide-alpha-black-100 text-sm">
|
||||
<% mappings.sort_by(&:key).each do |mapping| %>
|
||||
<div class="px-5 py-3 bg-white first:rounded-tl-xl first:rounded-tr-xl last:rounded-bl-xl last:rounded-br-xl">
|
||||
<div class="px-5 py-3 bg-container first:rounded-tl-xl first:rounded-tr-xl last:rounded-bl-xl last:rounded-br-xl">
|
||||
<%= render partial: "import/mappings/form", locals: { mapping: mapping } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<p class="text-secondary text-sm"><%= t(".description") %></p>
|
||||
</div>
|
||||
|
||||
<div data-controller="tabs" data-tabs-active-class="bg-white" data-tabs-default-tab-value="csv-paste-tab">
|
||||
<div data-controller="tabs" data-tabs-active-class="bg-container" data-tabs-default-tab-value="csv-paste-tab">
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="bg-gray-50 rounded-lg inline-flex p-1 space-x-2 text-sm text-primary font-medium">
|
||||
<div class="tab-item-active rounded-lg inline-flex p-1 space-x-2 text-sm text-primary font-medium">
|
||||
<button type="button" data-id="csv-paste-tab" class="p-2 rounded-lg" data-tabs-target="btn" data-action="click->tabs#select">Copy & Paste</button>
|
||||
<button type="button" data-id="csv-upload-tab" class="p-2 rounded-lg" data-tabs-target="btn" data-action="click->tabs#select">Upload CSV</button>
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@
|
|||
placeholder: "Paste your CSV file contents here",
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
<% else %>
|
||||
<label for="import_csv_file" class="flex flex-col items-center justify-center w-full h-56 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50">
|
||||
<label for="import_csv_file" class="flex flex-col items-center justify-center w-full h-56 border-2 border-secondary border-dashed rounded-lg cursor-pointer bg-container-inset">
|
||||
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
||||
<%= form.file_field :csv_file, class: "ml-32", "data-auto-submit-form-target": "auto" %>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</div>
|
||||
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= link_to import_path(import),
|
||||
class: "block w-full py-2 px-3 space-x-2 text-primary hover:bg-gray-50 flex items-center rounded-lg" do %>
|
||||
<%= lucide_icon "eye", class: "w-5 h-5 text-secondary" %>
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
step[:is_complete] ? "text-green-600" : "text-secondary"
|
||||
end %>
|
||||
<% step_class = if is_current
|
||||
"bg-gray-900 text-white"
|
||||
"bg-primary text-white"
|
||||
else
|
||||
step[:is_complete] ? "bg-green-600/10 border-alpha-black-25" : "bg-gray-50"
|
||||
step[:is_complete] ? "bg-green-600/10 border-alpha-black-25" : "bg-container-inset"
|
||||
end %>
|
||||
|
||||
<%= link_to step[:path], class: "flex items-center gap-3" do %>
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
</div>
|
||||
|
||||
<div class="mx-auto max-w-2xl space-y-4">
|
||||
<div class="bg-gray-25 rounded-xl p-1 space-y-1">
|
||||
<div class="bg-container-inset rounded-xl p-1 space-y-1">
|
||||
<div class="flex justify-between items-center text-xs font-medium text-secondary uppercase px-5 py-3">
|
||||
<p>item</p>
|
||||
<p class="justify-self-end">count</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-lg text-sm">
|
||||
<div class="bg-container shadow-border-xs rounded-lg text-sm">
|
||||
<% import.dry_run.each do |key, count| %>
|
||||
<% resource = dry_run_resource(key) %>
|
||||
|
||||
<div class="flex items-center justify-between gap-2 bg-white px-5 py-3 rounded-tl-lg rounded-tr-lg">
|
||||
<div class="flex items-center justify-between gap-2 bg-container px-5 py-3 rounded-tl-lg rounded-tr-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="<%= resource.bg_class %> w-8 h-8 rounded-full flex justify-center items-center">
|
||||
<%= lucide_icon resource.icon, class: "#{resource.text_class} w-5 h-5 shrink-0" %>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<%# locals: (headers: [], rows: [], caption: nil) %>
|
||||
<div class="overflow-x-auto">
|
||||
<div class="border border-secondary rounded-md shadow-border-xs text-sm bg-white w-full">
|
||||
<div class="border border-secondary rounded-md shadow-border-xs text-sm bg-container w-full">
|
||||
<div class="grid border-b border-b-alpha-black-200" style="grid-template-columns: repeat(<%= headers.length %>, minmax(0, 1fr))">
|
||||
<% headers.each_with_index do |header, index| %>
|
||||
<div class="
|
||||
bg-gray-25 px-3 py-2.5 font-medium whitespace-nowrap overflow-x-auto
|
||||
bg-container-inset px-3 py-2.5 font-medium whitespace-nowrap overflow-x-auto
|
||||
first:rounded-tl-md last:rounded-tr-md
|
||||
<%= "border-r border-r-alpha-black-200" unless index == headers.length - 1 %>
|
||||
">
|
||||
|
@ -29,7 +29,7 @@
|
|||
<% end %>
|
||||
|
||||
<% if caption %>
|
||||
<div class="px-3 py-2.5 text-center text-xs text-primary rounded-b-md italic bg-gray-25 overflow-x-auto">
|
||||
<div class="px-3 py-2.5 text-center text-xs text-primary rounded-b-md italic bg-container-inset overflow-x-auto">
|
||||
<%= caption %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4">
|
||||
<div class="bg-container shadow-border-xs rounded-xl p-4">
|
||||
<% if @imports.empty? %>
|
||||
<%= render partial: "imports/empty" %>
|
||||
<% else %>
|
||||
<div class="rounded-xl bg-gray-25 p-1">
|
||||
<div class="rounded-xl bg-container-inset p-1">
|
||||
<h2 class="uppercase px-4 py-2 text-secondary text-xs"><%= t(".imports") %> · <%= @imports.size %></h2>
|
||||
|
||||
<div class="border border-alpha-black-100 rounded-lg bg-white shadow-xs">
|
||||
<div class="border border-alpha-black-100 rounded-lg bg-container shadow-xs">
|
||||
<%= render partial: "imports/import", collection: @imports.ordered %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<p class="text-secondary text-sm"><%= t(".description") %></p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-25 p-1">
|
||||
<div class="rounded-xl bg-container-inset p-1">
|
||||
<h3 class="uppercase text-secondary text-xs font-medium px-3 py-1.5"><%= t(".sources") %></h3>
|
||||
<ul class="bg-white shadow-border-xs rounded-lg">
|
||||
<ul class="bg-container shadow-border-xs rounded-lg">
|
||||
<li>
|
||||
<% if @pending_import.present? && (params[:type].nil? || params[:type] == @pending_import.type) %>
|
||||
<%= link_to import_path(@pending_import), class: "flex items-center justify-between p-4 group cursor-pointer", data: { turbo: false } do %>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<% if @invite_codes.present? %>
|
||||
<%= render @invite_codes %>
|
||||
<% else %>
|
||||
<div class="flex flex-col items-center w-full h-64 bg-white text-center justify-center">
|
||||
<div class="flex flex-col items-center w-full h-64 bg-container text-center justify-center">
|
||||
<%= lucide_icon "binary", class: "w-6 h-6 text-sm text-secondary" %>
|
||||
<p class="text-base pt-4"><%= t(".no_invite_codes") %></p>
|
||||
<p class="text-sm text-secondary pt-2 w-2/3"><%= t(".invite_code_description") %></p>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%= render "layouts/shared/htmldoc" do %>
|
||||
<% sidebar_config = app_sidebar_config(Current.user) %>
|
||||
|
||||
<div class="flex flex-col lg:flex-row h-dvh lg:h-full bg-gray-50"
|
||||
<div class="flex flex-col lg:flex-row h-dvh lg:h-full bg-surface"
|
||||
data-controller="sidebar"
|
||||
data-sidebar-user-id-value="<%= Current.user.id %>"
|
||||
data-sidebar-config-value="<%= sidebar_config.to_json %>">
|
||||
|
@ -12,7 +12,7 @@
|
|||
</button>
|
||||
|
||||
<%# Mobile only account sidebar groups %>
|
||||
<%= tag.div class: class_names("hidden bg-gray-50 z-20 absolute inset-0 h-dvh w-full p-4 overflow-y-auto transition-all duration-300"),
|
||||
<%= tag.div class: class_names("hidden bg-surface z-20 absolute inset-0 h-dvh w-full p-4 overflow-y-auto transition-all duration-300"),
|
||||
data: { sidebar_target: "leftPanelMobile" } do %>
|
||||
<div id="account-sidebar-tabs">
|
||||
<div class="mb-4">
|
||||
|
@ -108,7 +108,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<nav class="lg:hidden bg-white shrink-0 z-10 pb-2">
|
||||
<nav class="lg:hidden bg-container shrink-0 z-10 pb-2">
|
||||
<ul class="flex items-center justify-around gap-1">
|
||||
<li>
|
||||
<%= render "layouts/sidebar/nav_item", name: "Home", path: root_path, icon_key: "pie-chart" %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%= render "layouts/shared/htmldoc" do %>
|
||||
<div class="flex flex-col h-dvh">
|
||||
<div class="flex flex-col h-screen px-6 py-12 bg-gray-25">
|
||||
<div class="flex flex-col h-screen px-6 py-12 bg-surface">
|
||||
<div class="grow flex flex-col justify-center">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div class="flex justify-center mb-6">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%= render "layouts/shared/htmldoc" do %>
|
||||
<div class="flex flex-col h-dvh">
|
||||
<div class="flex flex-col h-dvh bg-surface">
|
||||
<header class="flex items-center justify-between p-8">
|
||||
<%= link_to content_for(:previous_path) || imports_path do %>
|
||||
<%= lucide_icon "arrow-left", class: "w-5 h-5 text-secondary" %>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%= render "layouts/shared/htmldoc" do %>
|
||||
<div class="flex h-full bg-gray-25">
|
||||
<div class="flex h-full bg-surface">
|
||||
<div class="p-4 w-96 shrink-0 h-full overflow-y-auto">
|
||||
<%= render "settings/settings_nav" %>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<nav class="flex items-center gap-2 mb-6">
|
||||
<% if sidebar_toggle_enabled %>
|
||||
<button data-action="sidebar#toggleLeftPanel" class="hidden p-2 lg:inline-flex rounded-lg items-center justify-center hover:bg-gray-100 cursor-pointer">
|
||||
<button data-action="sidebar#toggleLeftPanel" class="hidden p-2 lg:inline-flex rounded-lg items-center justify-center hover:bg-container-inset cursor-pointer">
|
||||
<%= icon("panel-left", color: "gray") %>
|
||||
</button>
|
||||
<% end %>
|
||||
|
@ -16,7 +16,7 @@
|
|||
<% if path.present? && index < breadcrumbs.size - 1 %>
|
||||
<%= link_to name, path, class: "text-sm text-gray-500 font-medium" %>
|
||||
<% elsif index == breadcrumbs.size - 1 %>
|
||||
<span class="text-gray-900 font-medium text-sm"><%= name %></span>
|
||||
<span class="text-primary font-medium text-sm"><%= name %></span>
|
||||
<% else %>
|
||||
<span class="text-sm text-gray-500 font-medium"><%= name %></span>
|
||||
<% end %>
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
<% if sidebar_toggle_enabled %>
|
||||
<div class="ml-auto">
|
||||
<button data-action="sidebar#toggleRightPanel" class="p-2 hidden lg:inline-flex rounded-lg items-center justify-center hover:bg-gray-100 cursor-pointer" title="Toggle AI Assistant">
|
||||
<button data-action="sidebar#toggleRightPanel" class="p-2 hidden lg:inline-flex rounded-lg items-center justify-center hover:bg-container-inset cursor-pointer" title="Toggle AI Assistant">
|
||||
<%= icon("panel-right", color: "gray") %>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="h-full text-primary overflow-hidden lg:overflow-auto font-sans <%= @os %>" lang="en">
|
||||
<html class="h-full text-primary overflow-hidden lg:overflow-auto font-sans <%= @os %>" lang="en" data-controller="theme" data-theme-user-preference-value="<%= Current.user&.theme || 'system' %>">
|
||||
<head>
|
||||
<%= render "layouts/shared/head" %>
|
||||
<%= yield :head %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<header class="space-y-6">
|
||||
<% if local_assigns[:title].present? %>
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-3xl font-medium text-gray-900"><%= title %></h1>
|
||||
<h1 class="text-3xl font-medium text-primary"><%= title %></h1>
|
||||
<% if local_assigns[:subtitle].present? %>
|
||||
<p class="text-gray-500"><%= subtitle %></p>
|
||||
<% end %>
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
<%= link_to path, class: "space-y-1 lg:py-1 group block" do %>
|
||||
<div class="grow flex flex-col lg:flex-row gap-1 items-center">
|
||||
<%= tag.div class: class_names("w-4 h-1 lg:w-1 lg:h-4 rounded-bl-sm rounded-br-sm lg:rounded-tr-sm lg:rounded-br-sm lg:rounded-bl-none", "bg-gray-900" => page_active?(path)) %>
|
||||
<%= tag.div class: class_names("w-4 h-1 lg:w-1 lg:h-4 rounded-bl-sm rounded-br-sm lg:rounded-tr-sm lg:rounded-br-sm lg:rounded-bl-none", "bg-container-inset" => page_active?(path)) %>
|
||||
|
||||
<%= tag.div class: class_names("w-8 h-8 flex items-center justify-center mx-auto rounded-lg", page_active?(path) ? "bg-white shadow-xs text-primary" : "group-hover:bg-gray-100 text-secondary") do %>
|
||||
<%= tag.div class: class_names("w-8 h-8 flex items-center justify-center mx-auto rounded-lg", page_active?(path) ? "bg-container-inset shadow-xs text-primary" : "group-hover:bg-container-inset-hover text-secondary") do %>
|
||||
<%= icon(icon_key) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (merchant:) %>
|
||||
|
||||
<div class="flex justify-between items-center p-4 bg-white">
|
||||
<div class="flex justify-between items-center p-4 bg-container">
|
||||
<div class="flex w-full items-center gap-2.5">
|
||||
<% if merchant.icon_url %>
|
||||
<div class="w-8 h-8 rounded-full flex justify-center items-center">
|
||||
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= contextual_menu_modal_action_item t(".edit"), edit_merchant_path(merchant) %>
|
||||
|
||||
<%= contextual_menu_destructive_item t(".delete"),
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<div class="bg-white">
|
||||
<div class="bg-container">
|
||||
<div class="h-px bg-alpha-black-50 ml-14 mr-6"></div>
|
||||
</div>
|
||||
|
|
|
@ -7,16 +7,16 @@
|
|||
<% end %>
|
||||
</header>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4">
|
||||
<div class="bg-container shadow-border-xs rounded-xl p-4">
|
||||
<% if @merchants.any? %>
|
||||
<div class="rounded-xl bg-gray-25 space-y-1 p-1">
|
||||
<div class="rounded-xl bg-container-inset space-y-1 p-1">
|
||||
<div class="flex items-center gap-1.5 px-4 py-2 text-xs font-medium text-secondary uppercase">
|
||||
<p><%= t(".title") %></p>
|
||||
<span class="text-subdued">·</span>
|
||||
<p><%= @merchants.count %></p>
|
||||
</div>
|
||||
|
||||
<div class="border border-alpha-black-25 rounded-md bg-white shadow-border-xs">
|
||||
<div class="border border-alpha-black-25 rounded-md bg-container shadow-border-xs">
|
||||
<div class="overflow-hidden rounded-md">
|
||||
<%= render partial: @merchants, spacer_template: "merchants/ruler" %>
|
||||
</div>
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
<% model = chat && chat.persisted? ? [chat, Message.new] : Chat.new %>
|
||||
|
||||
<%= form_with model: model,
|
||||
class: "flex flex-col gap-2 bg-white px-2 py-1.5 rounded-lg shadow-border-xs",
|
||||
class: "flex flex-col gap-2 bg-container px-2 py-1.5 rounded-lg shadow-border-xs",
|
||||
data: { chat_target: "form" } do |f| %>
|
||||
|
||||
<%# In the future, this will be a dropdown with different AI models %>
|
||||
<%= f.hidden_field :ai_model, value: "gpt-4o" %>
|
||||
|
||||
<%= f.text_area :content, placeholder: "Ask anything ...", value: message_hint,
|
||||
class: "w-full border-0 focus:ring-0 text-sm resize-none px-1",
|
||||
class: "w-full border-0 focus:ring-0 text-sm resize-none px-1 bg-transparent",
|
||||
data: { chat_target: "input", action: "input->chat#autoResize keydown->chat#handleInputKeyDown" },
|
||||
rows: 1 %>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
|
||||
<div class="p-1 bg-alpha-black-25 mb-2 rounded-lg">
|
||||
<div class="bg-white p-4 rounded-lg flex gap-8 shadow-border-xs">
|
||||
<div class="bg-container p-4 rounded-lg flex gap-8 shadow-border-xs">
|
||||
<div class="space-y-2">
|
||||
<%= tag.p t(".example"), class: "text-secondary text-sm" %>
|
||||
<%= tag.p format_money(Money.new(2325.25, params[:currency] || @user.family.currency)), class: "text-primary font-medium text-2xl" %>
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-between items-center gap-4 mb-4">
|
||||
<%= form.text_field :first_name, placeholder: t(".first_name"), label: t(".first_name"), container_class: "bg-white w-1/2", required: true %>
|
||||
<%= form.text_field :last_name, placeholder: t(".last_name"), label: t(".last_name"), container_class: "bg-white w-1/2", required: true %>
|
||||
<%= form.text_field :first_name, placeholder: t(".first_name"), label: t(".first_name"), container_class: "bg-container w-1/2", required: true %>
|
||||
<%= form.text_field :last_name, placeholder: t(".last_name"), label: t(".last_name"), container_class: "bg-container w-1/2", required: true %>
|
||||
</div>
|
||||
<% unless @invitation %>
|
||||
<div class="space-y-4 mb-4">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%= content_for :page_title, t(".title") %>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4 grow overflow-y-auto">
|
||||
<div class="bg-container shadow-border-xs rounded-xl p-4 grow overflow-y-auto">
|
||||
<div class="flex justify-between gap-4 mb-12 last:mb-0">
|
||||
<div class="w-1/3">
|
||||
<div class="px-3 flex items-center gap-3">
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<% content_for :page_header do %>
|
||||
<div class="space-y-1 mb-6">
|
||||
<h1 class="text-3xl font-medium text-gray-900">Welcome back, <%= Current.user.first_name %></h1>
|
||||
<h1 class="text-3xl font-medium text-primary">Welcome back, <%= Current.user.first_name %></h1>
|
||||
<p class="text-gray-500">Here's what's happening with your finances</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="w-full space-y-6 pb-24">
|
||||
<section class="bg-white py-4 rounded-xl shadow-border-xs">
|
||||
<section class="bg-container py-4 rounded-xl shadow-border-xs">
|
||||
<%= render partial: "pages/dashboard/net_worth_chart", locals: { series: @balance_sheet.net_worth_series(period: @period), period: @period } %>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<div class="space-y-4">
|
||||
<% balance_sheet.classification_groups.each do |classification_group| %>
|
||||
<div class="bg-white shadow-border-xs rounded-xl space-y-4 p-4">
|
||||
<div class="bg-container shadow-border-xs rounded-xl space-y-4 p-4">
|
||||
<h2 class="text-lg font-medium"><%= classification_group.display_name %></h2>
|
||||
|
||||
<% if classification_group.account_groups.any? %>
|
||||
|
@ -36,10 +36,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shadow-border-xs rounded-lg bg-white">
|
||||
<div class="shadow-border-xs rounded-lg bg-container">
|
||||
<% classification_group.account_groups.each do |account_group| %>
|
||||
<details class="group rounded-lg open:bg-surface font-medium text-sm">
|
||||
<summary class="cursor-pointer p-4 group-open:bg-surface bg-white rounded-lg flex items-center justify-between">
|
||||
<summary class="cursor-pointer p-4 group-open:bg-surface bg-container rounded-lg flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<%= lucide_icon("chevron-right", class: "group-open:rotate-90 text-secondary w-5 h-5") %>
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<p class="text-base text-subdued">There <%= @invite_codes_count == 1 ? "is" : "are" %> <span class="text-white"><%= @invite_codes_count %> invite <%= "code".pluralize(@invite_codes_count) %></span> remaining.</p>
|
||||
<div class="bg-gray-900 border border-gray-800 p-2 rounded-xl my-4">
|
||||
<p class="text-sm text-subdued mt-1 mb-3 sm:mb-4">Your invite code is <span class="font-mono text-white"><%= @invite_code.token %></span></p>
|
||||
<p><%= link_to "Sign up with this code", new_registration_path(invite: @invite_code.token), class: "block w-full bg-white text-black py-2 px-3 rounded-lg no-underline text-sm sm:text-base hover:bg-gray-200 transition duration-150" %></p>
|
||||
<p><%= link_to "Sign up with this code", new_registration_path(invite: @invite_code.token), class: "block w-full bg-container text-black py-2 px-3 rounded-lg no-underline text-sm sm:text-base hover:bg-gray-200 transition duration-150" %></p>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-subdued">You may need to refresh the page to get a new invite code if someone else claimed it before you.</p>
|
||||
|
@ -43,7 +43,7 @@
|
|||
</p>
|
||||
<% else %>
|
||||
<p class="text-base text-subdued mb-6 sm:mb-8">Sorry, there are <span class="text-white">no invite codes</span> remaining. Join our <%= link_to "Discord server", "https://link.maybe.co/discord", target: "_blank", class: "text-white hover:text-gray-300" %> to get notified when new invite codes are available.</p>
|
||||
<p><%= link_to "Join Discord server", "https://link.maybe.co/discord", target: "_blank", class: "bg-white text-black px-3 py-2 rounded-md no-underline text-base hover:bg-gray-200 transition duration-150" %></p>
|
||||
<p><%= link_to "Join Discord server", "https://link.maybe.co/discord", target: "_blank", class: "bg-container text-black px-3 py-2 rounded-md no-underline text-base hover:bg-gray-200 transition duration-150" %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%= content_for :page_title, "Feedback" %>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4">
|
||||
<div class="bg-container shadow-border-xs rounded-xl p-4">
|
||||
<h2 class="text-lg font-medium text-primary mb-1">Leave feedback</h2>
|
||||
<p class="text-sm text-secondary mb-4">Let us know if you have any specific feedback. Feel free to include links to videos or screenshots.</p>
|
||||
<div class="flex gap-2">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (plaid_item:) %>
|
||||
|
||||
<%= tag.div id: dom_id(plaid_item) do %>
|
||||
<details open class="group bg-white p-4 shadow-border-xs rounded-xl">
|
||||
<details open class="group bg-container p-4 shadow-border-xs rounded-xl">
|
||||
<summary class="flex items-center justify-between gap-2 focus-visible:outline-hidden">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= lucide_icon "chevron-right", class: "group-open:transform group-open:rotate-90 text-secondary w-5" %>
|
||||
|
@ -96,7 +96,7 @@
|
|||
<% end %>
|
||||
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= button_to plaid_item_path(plaid_item),
|
||||
method: :delete,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%# locals: (title:, subtitle: nil, content:) %>
|
||||
<section class="bg-white shadow-border-xs rounded-xl p-4 space-y-4">
|
||||
<section class="bg-container shadow-border-xs rounded-xl p-4 space-y-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-medium text-primary"><%= title %></h2>
|
||||
<% if subtitle.present? %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<%= link_to path, class: class_names(
|
||||
"flex items-center gap-2 btn btn--ghost",
|
||||
page_active?(path) ? "text-primary bg-white shadow-border-xs" : "text-secondary hover:bg-gray-100 border-transparent"
|
||||
page_active?(path) ? "text-primary bg-container shadow-border-xs" : "text-secondary hover:bg-gray-100 border-transparent"
|
||||
), aria: { current: ("page" if page_active?(path)) } do %>
|
||||
<%= lucide_icon(icon, class: "w-5 h-5") if icon %>
|
||||
<%= name %>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%# locals: path, direction, title %>
|
||||
<%= link_to path, class: "w-full bg-white hover:bg-gray-50 rounded-xl border border-alpha-black-25 shadow-xs p-4 flex items-center justify-between" do %>
|
||||
<%= link_to path, class: "w-full bg-container hover:bg-container-inset rounded-xl border border-alpha-black-25 shadow-xs p-4 flex items-center justify-between" do %>
|
||||
<% if direction == 'previous' %>
|
||||
<div class="w-5 h-5 text-secondary">
|
||||
<%= lucide_icon("arrow-left") %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<%= settings_section title: t(".subscription_title"), subtitle: t(".subscription_subtitle") do %>
|
||||
<div class="space-y-4">
|
||||
<div class="p-3 shadow-border-xs bg-white rounded-lg flex justify-between items-center">
|
||||
<div class="p-3 shadow-border-xs bg-container 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-secondary" %>
|
||||
|
|
|
@ -47,26 +47,26 @@
|
|||
|
||||
<%= settings_section title: t(".theme_title"), subtitle: t(".theme_subtitle") do %>
|
||||
<div>
|
||||
<%= styled_form_with model: @user, class: "flex justify-between items-center" do |form| %>
|
||||
<%= styled_form_with model: @user, class: "flex justify-between items-center", data: { controller: "auto-submit-form" } do |form| %>
|
||||
<%= form.hidden_field :redirect_to, value: "preferences" %>
|
||||
<div class="text-center">
|
||||
<%= image_tag("light-mode-preview.png", alt: "Light Theme Preview", class: "h-44 mb-4") %>
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<%= form.radio_button :theme, t(".theme_light"), checked: true %>
|
||||
<%= form.radio_button :theme, "light", checked: @user.theme == "light", data: { auto_submit_form_target: "auto", action: "theme#updateTheme" } %>
|
||||
<%= form.label :theme_light, t(".theme_light"), value: "light" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<%= image_tag("dark-mode-preview.png", alt: "Dark Theme Preview", class: "h-44 mb-4") %>
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<%= form.radio_button :theme, t(".theme_dark"), disabled: true, class: "cursor-not-allowed" %>
|
||||
<%= form.radio_button :theme, "dark", checked: @user.theme == "dark", data: { auto_submit_form_target: "auto", action: "theme#updateTheme" } %>
|
||||
<%= form.label :theme_dark, t(".theme_dark"), value: "dark" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<%= image_tag("system-mode-preview.png", alt: "System Theme Preview", class: "h-44 mb-4") %>
|
||||
<div class="flex items-center gap-2 justify-center">
|
||||
<%= form.radio_button :theme, t(".theme_system"), disabled: true, class: "cursor-not-allowed" %>
|
||||
<%= form.radio_button :theme, "system", checked: @user.theme == "system", data: { auto_submit_form_target: "auto", action: "theme#updateTheme" } %>
|
||||
<%= form.label :theme_system, t(".theme_system"), value: "system" %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -33,12 +33,12 @@
|
|||
"data-auto-submit-form-target": "auto" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="bg-gray-25 rounded-xl p-1">
|
||||
<div class="bg-container-inset rounded-xl p-1">
|
||||
<div class="px-4 py-2">
|
||||
<p class="uppercase text-xs text-secondary font-medium"><%= Current.family.name %> · <%= Current.family.users.size %></p>
|
||||
</div>
|
||||
<% @users.each do |user| %>
|
||||
<div class="flex gap-2 items-center bg-white p-4 border border-alpha-black-25 rounded-lg">
|
||||
<div class="flex gap-2 items-center bg-container p-4 border border-alpha-black-25 rounded-lg">
|
||||
<div class="w-9 h-9 shrink-0">
|
||||
<%= render "settings/user_avatar", user: user %>
|
||||
</div>
|
||||
|
@ -65,7 +65,7 @@
|
|||
<% end %>
|
||||
<% if @pending_invitations.any? %>
|
||||
<% @pending_invitations.each do |invitation| %>
|
||||
<div class="flex gap-2 items-center justify-between bg-white p-4 border border-alpha-black-25 rounded-lg">
|
||||
<div class="flex gap-2 items-center justify-between bg-container p-4 border border-alpha-black-25 rounded-lg">
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="w-9 h-9 shrink-0">
|
||||
<div class="text-white w-full h-full bg-gray-400 rounded-full flex items-center justify-center text-lg uppercase"><%= invitation.email[0] %></div>
|
||||
|
@ -116,7 +116,7 @@
|
|||
<% end %>
|
||||
<% if Current.user.admin? %>
|
||||
<%= link_to new_invitation_path,
|
||||
class: "bg-gray-100 flex items-center justify-center gap-2 text-secondary mt-1 hover:bg-gray-200 rounded-lg px-4 py-2 w-full text-center",
|
||||
class: "bg-container-inset flex items-center justify-center gap-2 text-secondary mt-1 hover:bg-container-inset-hover rounded-lg px-4 py-2 w-full text-center",
|
||||
data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5 text-secondary") %>
|
||||
<%= t(".invite_member") %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<%= settings_section title: t(".mfa_title"), subtitle: t(".mfa_description") do %>
|
||||
<div class="space-y-4">
|
||||
<div class="p-3 shadow-border-xs bg-white rounded-lg flex justify-between items-center">
|
||||
<div class="p-3 shadow-border-xs bg-container 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 "shield-check", class: "w-5 h-5 text-secondary" %>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<div class="flex items-center py-0.5 px-0.5 gap-1 fixed right-1 top-1 shadow-xs border border-alpha-black-50 rounded-md bg-white">
|
||||
<div class="flex items-center py-0.5 px-0.5 gap-1 fixed right-1 top-1 shadow-xs border border-alpha-black-50 rounded-md bg-container">
|
||||
<p class="text-xs text-secondary pl-2">Version: <%= Maybe.version.to_release_tag %></p>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (content:, reload_on_close: false) %>
|
||||
|
||||
<%= turbo_frame_tag "drawer" do %>
|
||||
<dialog class="ml-auto bg-white shadow-border-xs rounded-2xl max-w-[480px] h-full w-full mt-4 mr-4 focus-visible:outline-hidden"
|
||||
<dialog class="ml-auto bg-container shadow-border-xs rounded-2xl max-w-[480px] h-full w-full mt-4 mr-4 focus-visible:outline-hidden"
|
||||
data-controller="modal"
|
||||
data-action="mousedown->modal#clickOutside"
|
||||
data-modal-reload-on-close-value="<%= reload_on_close %>">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (content:, classes:) -%>
|
||||
<%= turbo_frame_tag "modal" do %>
|
||||
<dialog class="m-auto bg-white shadow-border-xs rounded-2xl max-w-[580px] w-min-content h-fit overflow-visible <%= classes %>" data-controller="modal" data-action="mousedown->modal#clickOutside">
|
||||
<dialog class="m-auto bg-container shadow-border-xs rounded-2xl max-w-[580px] w-min-content h-fit overflow-visible <%= classes %>" data-controller="modal" data-action="mousedown->modal#clickOutside">
|
||||
<div class="flex flex-col">
|
||||
<%= content %>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<% type = type.to_sym %>
|
||||
<% action = "animationend->element-removal#remove" if type == :notice %>
|
||||
|
||||
<%= tag.div class: "flex gap-3 rounded-lg border bg-white p-4 group max-w-80 shadow-xs border-alpha-black-25",
|
||||
<%= tag.div class: "flex gap-3 rounded-lg border bg-container p-4 group max-w-80 shadow-xs border-alpha-black-25",
|
||||
data: {
|
||||
controller: "element-removal",
|
||||
action: action
|
||||
|
@ -32,7 +32,7 @@
|
|||
<circle class="origin-center -rotate-90 animate-[stroke-fill_2.2s_300ms_forwards]" stroke="#141414" stroke-opacity="0.4" r="7.2" cx="10" cy="10" stroke-dasharray="43.9822971503" stroke-dashoffset="43.9822971503" />
|
||||
</svg>
|
||||
<div class="absolute -top-2 -right-2">
|
||||
<%= lucide_icon "x", class: "w-5 h-5 p-0.5 hidden group-hover:inline-block border border-alpha-black-50 border-solid rounded-lg bg-white text-subdued cursor-pointer", data: { action: "click->element-removal#remove" } %>
|
||||
<%= lucide_icon "x", class: "w-5 h-5 p-0.5 hidden group-hover:inline-block border border-alpha-black-50 border-solid rounded-lg bg-container text-subdued cursor-pointer", data: { action: "click->element-removal#remove" } %>
|
||||
</div>
|
||||
</div>
|
||||
<% elsif type.to_sym == :alert %>
|
||||
|
|
|
@ -6,27 +6,27 @@
|
|||
<% if pagy.prev %>
|
||||
<%= link_to pagy_url_for(pagy, pagy.prev),
|
||||
data: { turbo_frame: :_top },
|
||||
class: "inline-flex items-center p-2 text-sm font-medium text-secondary hover:border-gray-300 hover:text-gray-700" do %>
|
||||
class: "inline-flex items-center p-2 text-sm font-medium text-secondary bg-container-inset hover:border-secondary hover:text-secondary" do %>
|
||||
<%= lucide_icon("chevron-left", class: "w-5 h-5 text-secondary") %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="inline-flex items-center p-2 text-sm font-medium hover:border-gray-300">
|
||||
<%= lucide_icon("chevron-left", class: "w-5 h-5 text-gray-200") %>
|
||||
<div class="inline-flex items-center p-2 text-sm font-medium hover:border-secondary">
|
||||
<%= lucide_icon("chevron-left", class: "w-5 h-5 text-secondary") %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="rounded-xl p-1 bg-gray-25">
|
||||
<div class="rounded-xl p-1 bg-container-inset">
|
||||
<% pagy.series.each do |series_item| %>
|
||||
<% if series_item.is_a?(Integer) %>
|
||||
<%= link_to pagy_url_for(pagy, series_item),
|
||||
data: { turbo_frame: :_top },
|
||||
class: "rounded-md px-2 py-1 inline-flex items-center text-sm font-medium text-secondary hover:border-gray-300 hover:text-gray-700" do %>
|
||||
class: "rounded-md px-2 py-1 inline-flex items-center text-sm font-medium text-secondary hover:border-secondary hover:text-secondary" do %>
|
||||
<%= series_item %>
|
||||
<% end %>
|
||||
<% elsif series_item.is_a?(String) %>
|
||||
<%= link_to pagy_url_for(pagy, series_item),
|
||||
data: { turbo_frame: :_top },
|
||||
class: "rounded-md px-2 py-1 bg-white border border-alpha-black-25 shadow-xs inline-flex items-center text-sm font-medium text-primary" do %>
|
||||
class: "rounded-md px-2 py-1 bg-container border border-secondary shadow-xs inline-flex items-center text-sm font-medium text-primary" do %>
|
||||
<%= series_item %>
|
||||
<% end %>
|
||||
<% elsif series_item == :gap %>
|
||||
|
@ -38,12 +38,12 @@
|
|||
<% if pagy.next %>
|
||||
<%= link_to pagy_url_for(pagy, pagy.next),
|
||||
data: { turbo_frame: :_top },
|
||||
class: "inline-flex items-center p-2 text-sm font-medium text-secondary hover:border-gray-300 hover:text-gray-700" do %>
|
||||
class: "inline-flex items-center p-2 text-sm font-medium text-secondary hover:border-secondary hover:text-secondary" do %>
|
||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-secondary") %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="inline-flex items-center p-2 text-sm font-medium hover:border-gray-300">
|
||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-gray-200") %>
|
||||
<div class="inline-flex items-center p-2 text-sm font-medium hover:border-secondary">
|
||||
<%= lucide_icon("chevron-right", class: "w-5 h-5 text-secondary") %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -52,6 +52,6 @@
|
|||
<%= select_tag :per_page,
|
||||
options_for_select(["10", "20", "30", "50"], pagy.limit),
|
||||
data: { controller: "selectable-link" },
|
||||
class: "py-1.5 pr-8 text-sm text-primary font-medium border border-gray-200 rounded-lg focus:border-gray-900 focus:ring-gray-900 focus-visible:ring-gray-900" %>
|
||||
class: "py-1.5 pr-8 text-sm text-primary font-medium bg-container-inset border border-secondary rounded-lg focus:border-secondary focus:ring-secondary focus-visible:ring-secondary" %>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div data-controller="modal" data-modal-open-value="true" class="h-full flex items-center justify-center bg-white/90" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<div data-controller="modal" data-modal-open-value="true" class="h-full flex items-center justify-center bg-container/90" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<div class="w-[400px] rounded-xl relative overflow-hidden">
|
||||
<div class="bg-white shadow-border-xs rounded-xl relative z-10">
|
||||
<div class="bg-container shadow-border-xs rounded-xl relative z-10">
|
||||
<div class="rounded-xl" style="background-image: url('<%= asset_path("maybe-plus-background.svg") %>'); background-size: cover; background-position: center top;">
|
||||
<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 pt-2 rounded-xl">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<%= tag.div id: "syncing-notice", class: "flex gap-3 rounded-lg border bg-white p-4 group w-full shadow-xs border-alpha-black-25" do %>
|
||||
<%= tag.div id: "syncing-notice", class: "flex gap-3 rounded-lg border bg-container p-4 group w-full shadow-xs border-alpha-black-25" do %>
|
||||
<div class="h-5 w-5 shrink-0 p-px text-white">
|
||||
<%= lucide_icon "loader", class: "w-5 h-5 text-secondary animate-pulse" %>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
data: {
|
||||
turbo: false,
|
||||
controller: "deletion",
|
||||
deletion_dangerous_action_class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
|
||||
deletion_dangerous_action_class: "form-field__submit bg-container text-red-600 border hover:bg-red-50",
|
||||
deletion_safe_action_class: "form-field__submit border border-transparent",
|
||||
deletion_submit_text_when_not_replacing_value: t(".delete_and_leave_uncategorized", tag_name: @tag.name),
|
||||
deletion_submit_text_when_replacing_value: t(".delete_and_recategorize", tag_name: @tag.name) } do |f| %>
|
||||
|
@ -15,7 +15,7 @@
|
|||
{ data: { deletion_target: "replacementField", action: "deletion#updateSubmitButton" } } %>
|
||||
|
||||
<%= f.submit t(".delete_and_leave_uncategorized", tag_name: @tag.name),
|
||||
class: "form-field__submit bg-white text-red-600 border hover:bg-red-50",
|
||||
class: "form-field__submit bg-container text-red-600 border hover:bg-red-50",
|
||||
data: { deletion_target: "submitButton" } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<div class="bg-white">
|
||||
<div class="bg-container">
|
||||
<div class="h-px bg-alpha-black-50 ml-4 mr-6"></div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%# locals: (tag:) %>
|
||||
|
||||
<div id="<%= dom_id(tag) %>" class="flex justify-between items-center p-4 bg-white">
|
||||
<div id="<%= dom_id(tag) %>" class="flex justify-between items-center p-4 bg-container">
|
||||
<div class="flex w-full items-center gap-2.5">
|
||||
<%= render partial: "shared/color_avatar", locals: { name: tag.name, color: tag.color } %>
|
||||
<p class="text-primary text-sm truncate">
|
||||
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= contextual_menu_modal_action_item t(".edit"), edit_tag_path(tag) %>
|
||||
|
||||
<% if tag.transactions.any? %>
|
||||
|
|
|
@ -7,16 +7,16 @@
|
|||
<% end %>
|
||||
</header>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4">
|
||||
<div class="bg-container shadow-border-xs rounded-xl p-4">
|
||||
<% if @tags.any? %>
|
||||
<div class="rounded-xl bg-gray-25 space-y-1 p-1">
|
||||
<div class="rounded-xl bg-container-inset space-y-1 p-1">
|
||||
<div class="flex items-center gap-1.5 px-4 py-2 text-xs font-medium text-secondary uppercase">
|
||||
<p><%= t(".tags") %></p>
|
||||
<span class="text-subdued">·</span>
|
||||
<p><%= @tags.count %></p>
|
||||
</div>
|
||||
|
||||
<div class="border border-alpha-black-25 rounded-md bg-white shadow-border-xs">
|
||||
<div class="border border-alpha-black-25 rounded-md bg-container shadow-border-xs">
|
||||
<div class="overflow-hidden rounded-md">
|
||||
<%= render partial: @tags, spacer_template: "tags/ruler" %>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%# locals: (totals:) %>
|
||||
<div class="grid grid-cols-3 bg-white rounded-xl shadow-border-xs divide-x divide-alpha-black-100">
|
||||
<div class="grid grid-cols-3 bg-container rounded-xl shadow-border-xs divide-x divide-alpha-black-100">
|
||||
<div class="p-4 space-y-2">
|
||||
<p class="text-sm text-secondary">Total transactions</p>
|
||||
<p class="text-primary font-medium text-xl" id="total-transactions"><%= totals.transactions_count.round(0) %></p>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
data-controller="bulk-select"
|
||||
data-bulk-select-singular-label-value="<%= t(".transaction") %>"
|
||||
data-bulk-select-plural-label-value="<%= t(".transactions") %>"
|
||||
class="flex flex-col bg-white rounded-xl shadow-border-xs p-4">
|
||||
class="flex flex-col bg-container rounded-xl shadow-border-xs p-4">
|
||||
<%= render "transactions/searches/search" %>
|
||||
|
||||
<div id="entry-selection-bar" data-bulk-select-target="selectionBar" class="flex justify-center hidden">
|
||||
|
@ -16,7 +16,7 @@
|
|||
|
||||
<% if @pagy.count > 0 %>
|
||||
<div class="grow overflow-y-auto">
|
||||
<div class="grid grid-cols-12 bg-gray-25 rounded-xl px-5 py-3 text-xs uppercase font-medium text-secondary items-center mb-4">
|
||||
<div class="grid grid-cols-12 bg-container-inset rounded-xl px-5 py-3 text-xs uppercase font-medium text-secondary items-center mb-4">
|
||||
<div class="pl-0.5 col-span-8 flex items-center gap-4">
|
||||
<%= check_box_tag "selection_entry",
|
||||
class: "checkbox checkbox--light",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<div class="flex gap-2 mb-4">
|
||||
<div class="grow">
|
||||
<div class="flex items-center px-3 py-2 gap-2 border border-gray-200 rounded-lg focus-within:ring-gray-100 focus-within:border-gray-900">
|
||||
<div class="flex items-center px-3 py-2 gap-2 border border-secondary rounded-lg focus-within:ring-secondary focus-within:border-secondary">
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-secondary") %>
|
||||
<%= form.text_field :search,
|
||||
placeholder: "Search transactions by name",
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
data-controller="tabs"
|
||||
data-tabs-active-class="bg-gray-25 text-primary"
|
||||
data-tabs-default-tab-value="<%= get_default_transaction_search_filter[:key] %>"
|
||||
class="hidden absolute flex z-10 h-80 w-[540px] top-12 right-0 shadow-border-xs bg-white rounded-lg">
|
||||
class="hidden absolute flex z-10 h-80 w-[540px] top-12 right-0 shadow-border-xs bg-container rounded-lg">
|
||||
<div class="flex w-44 flex-col items-start p-3 text-sm font-medium text-secondary border-r border-r-alpha-black-100">
|
||||
<% transaction_search_filters.each do |filter| %>
|
||||
<button
|
||||
class="flex text-secondary hover:bg-gray-25 items-center gap-2 px-3 rounded-md py-2 w-full"
|
||||
class="flex text-secondary hover:bg-container-inset items-center gap-2 px-3 rounded-md py-2 w-full"
|
||||
type="button"
|
||||
data-id="<%= filter[:key] %>"
|
||||
data-tabs-target="btn"
|
||||
|
@ -30,7 +30,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center gap-2 bg-white p-3">
|
||||
<div class="flex justify-between items-center gap-2 bg-container p-3">
|
||||
<div>
|
||||
<% if @q.present? %>
|
||||
<%= link_to t(".clear_filters"), transactions_path(clear_filters: true), class: "btn btn--ghost" %>
|
||||
|
@ -38,10 +38,10 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<%= button_tag type: "reset", data: { action: "menu#close" }, class: "py-2 px-3 bg-gray-50 rounded-lg text-sm text-primary font-medium" do %>
|
||||
<%= button_tag type: "reset", data: { action: "menu#close" }, class: "py-2 px-3 bg-container-inset rounded-lg text-sm text-primary font-medium" do %>
|
||||
<%= t(".cancel") %>
|
||||
<% end %>
|
||||
<%= form.submit t(".apply"), name: nil, class: "py-2 px-3 bg-gray-900 hover:bg-gray-700 rounded-lg text-sm text-white font-medium cursor-pointer" %>
|
||||
<%= form.submit t(".apply"), name: nil, class: "py-2 px-3 bg-primary hover:bg-primary-dark rounded-lg text-sm text-white font-medium cursor-pointer" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<%= tag.span t(".income") %>
|
||||
<% end %>
|
||||
|
||||
<%= tag.div class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-subdued bg-white text-gray-800 shadow-sm" do %>
|
||||
<%= tag.div class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-subdued bg-container text-gray-800 shadow-sm" do %>
|
||||
<%= lucide_icon "arrow-right-left", class: "w-5 h-5" %>
|
||||
<%= tag.span t(".transfer") %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%# locals: (user_message:) %>
|
||||
|
||||
<div id="<%= dom_id(user_message) %>" class="bg-gray-100 px-3 py-2 rounded-lg max-w-[85%] w-fit ml-auto mb-6">
|
||||
<div id="<%= dom_id(user_message) %>" class="bg-surface-inset px-3 py-2 rounded-lg max-w-[85%] w-fit ml-auto mb-6">
|
||||
<div class="prose prose--ai-chat"><%= markdown(user_message.content) %></div>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</div>
|
||||
</button>
|
||||
|
||||
<div data-menu-target="content" class="hidden absolute w-[276px] z-10 divide-y divide-alpha-black-100 bg-white rounded-xl shadow-border-sm">
|
||||
<div data-menu-target="content" class="hidden absolute w-[276px] z-10 divide-y divide-alpha-black-100 bg-container rounded-xl shadow-border-sm">
|
||||
<div class="px-4 py-3 flex items-center gap-3">
|
||||
<div class="w-9 h-9 shrink-0">
|
||||
<%= render "settings/user_avatar", user: user, variant: :small, lazy: true %>
|
||||
|
|
|
@ -25,7 +25,7 @@ en:
|
|||
page_title: Preferences
|
||||
theme_dark: Dark
|
||||
theme_light: Light
|
||||
theme_subtitle: Choose a preferred theme for the app (coming soon...)
|
||||
theme_subtitle: Choose a preferred theme for the app
|
||||
theme_system: System
|
||||
theme_title: Theme
|
||||
timezone: Timezone
|
||||
|
|
5
db/migrate/20250410144939_add_theme_to_users.rb
Normal file
5
db/migrate/20250410144939_add_theme_to_users.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddThemeToUsers < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :users, :theme, :string, default: "system"
|
||||
end
|
||||
end
|
5
db/schema.rb
generated
5
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_03_19_212839) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_04_10_144939) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
@ -101,7 +101,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_19_212839) do
|
|||
t.decimal "balance", precision: 19, scale: 4
|
||||
t.string "currency"
|
||||
t.boolean "is_active", default: true, null: false
|
||||
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY ((ARRAY['Loan'::character varying, 'CreditCard'::character varying, 'OtherLiability'::character varying])::text[])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
|
||||
t.virtual "classification", type: :string, as: "\nCASE\n WHEN ((accountable_type)::text = ANY (ARRAY[('Loan'::character varying)::text, ('CreditCard'::character varying)::text, ('OtherLiability'::character varying)::text])) THEN 'liability'::text\n ELSE 'asset'::text\nEND", stored: true
|
||||
t.uuid "import_id"
|
||||
t.uuid "plaid_account_id"
|
||||
t.boolean "scheduled_for_deletion", default: false
|
||||
|
@ -614,6 +614,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_19_212839) do
|
|||
t.uuid "last_viewed_chat_id"
|
||||
t.boolean "show_ai_sidebar", default: true
|
||||
t.boolean "ai_enabled", default: false, null: false
|
||||
t.string "theme", default: "system"
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["family_id"], name: "index_users_on_family_id"
|
||||
t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_id"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue