From 65e1bc6eddd30018f3f7b5778ce107119e84f236 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Fri, 18 Apr 2025 18:53:10 +0530 Subject: [PATCH] Feature: Implement Mobile Responsiveness (#2092) * WIP * WIP * WIP * WIP * WIP * WIP * WIP * format * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * fix conflict * fix conflict * chore: run rubocop * fix test * update PWA logo * fix tests * chore: lint * fix test * Refactor: Remove duplicate data attribute in activity partial and add chat form rendering in chats index --------- Co-authored-by: Josh Pigford --- Gemfile | 1 + Gemfile.lock | 2 + app/assets/images/icon-assistant.svg | 37 ++ app/assets/images/icon-csv.svg | 5 + app/assets/tailwind/application.css | 12 + app/assets/tailwind/maybe-design-system.css | 8 + app/controllers/import/uploads_controller.rb | 7 + app/controllers/registrations_controller.rb | 26 ++ app/helpers/application_helper.rb | 4 + app/helpers/forms_helper.rb | 8 +- app/helpers/imports_helper.rb | 5 + app/helpers/languages_helper.rb | 388 +++++++++--------- app/helpers/menus_helper.rb | 7 +- app/helpers/settings_helper.rb | 12 +- .../controllers/file_upload_controller.js | 74 ++++ .../mobile_cell_interaction_controller.js | 149 +++++++ .../password_validator_controller.js | 63 +++ .../password_visibility_controller.js | 19 + .../controllers/preserve_scroll_controller.js | 39 ++ .../profile_image_preview_controller.js | 15 +- .../controllers/theme_controller.js | 3 +- app/views/accounts/index.html.erb | 13 +- app/views/accounts/new/_container.html.erb | 9 +- app/views/accounts/show/_activity.html.erb | 6 +- app/views/budgets/_budget_header.html.erb | 2 +- app/views/budgets/show.html.erb | 6 +- app/views/chats/_ai_avatar.html.erb | 3 +- app/views/chats/_ai_greeting.html.erb | 8 +- app/views/chats/index.html.erb | 8 +- app/views/chats/new.html.erb | 2 +- app/views/chats/show.html.erb | 2 +- app/views/import/cleans/show.html.erb | 41 +- app/views/import/confirms/_mappings.html.erb | 83 ++-- app/views/import/rows/_form.html.erb | 43 +- app/views/import/uploads/show.html.erb | 58 +-- app/views/imports/_nav.html.erb | 11 +- app/views/imports/_table.html.erb | 76 ++-- app/views/layouts/application.html.erb | 10 +- app/views/layouts/auth.html.erb | 22 +- app/views/layouts/imports.html.erb | 8 +- app/views/layouts/settings.html.erb | 9 +- .../layouts/shared/_breadcrumbs.html.erb | 2 +- app/views/layouts/shared/_head.html.erb | 9 +- app/views/layouts/shared/_htmldoc.html.erb | 4 +- app/views/layouts/sidebar/_nav_item.html.erb | 19 +- app/views/layouts/wizard.html.erb | 2 +- app/views/messages/_chat_form.html.erb | 27 +- app/views/onboardings/_header.html.erb | 2 +- app/views/onboardings/preferences.html.erb | 2 +- app/views/onboardings/profile.html.erb | 14 +- app/views/pages/changelog.html.erb | 8 +- app/views/pages/dashboard.html.erb | 16 +- .../pages/dashboard/_balance_sheet.html.erb | 39 +- app/views/pages/feedback.html.erb | 18 +- app/views/pwa/manifest.json.erb | 6 +- app/views/registrations/new.html.erb | 59 ++- app/views/sessions/new.html.erb | 2 +- app/views/settings/_settings_nav.html.erb | 77 +++- .../settings/_settings_nav_item.html.erb | 2 +- .../_settings_nav_link_large.html.erb | 29 +- .../settings/_user_avatar_field.html.erb | 22 +- .../hostings/_danger_zone_settings.html.erb | 6 +- app/views/settings/preferences/show.html.erb | 2 +- app/views/settings/profiles/show.html.erb | 16 +- app/views/settings/securities/show.html.erb | 32 +- app/views/shared/_drawer.html.erb | 2 +- app/views/shared/_icon_custom.html.erb | 6 + app/views/shared/_icon_image.html.erb | 6 + app/views/shared/_modal.html.erb | 4 +- app/views/shared/_modal_form.html.erb | 4 +- app/views/shared/_notification.html.erb | 2 +- app/views/transactions/_form.html.erb | 10 +- .../transactions/_selection_bar.html.erb | 2 +- app/views/transactions/_summary.html.erb | 2 +- app/views/transactions/_transaction.html.erb | 14 +- app/views/transactions/index.html.erb | 23 +- .../transactions/searches/_form.html.erb | 4 +- .../transactions/searches/_menu.html.erb | 8 +- app/views/transfers/_form.html.erb | 10 +- app/views/users/_user_menu.html.erb | 2 +- bin/dev | 8 +- config/locales/views/imports/en.yml | 2 + config/locales/views/registrations/en.yml | 1 + config/locales/views/sessions/en.yml | 1 + config/locales/views/settings/en.yml | 4 +- config/routes.rb | 2 + public/logo-pwa.png | Bin 0 -> 225225 bytes public/site.webmanifest | 2 +- .../registrations_controller_test.rb | 12 +- test/system/imports_test.rb | 8 + test/system/transactions_test.rb | 2 +- 91 files changed, 1333 insertions(+), 527 deletions(-) create mode 100644 app/assets/images/icon-assistant.svg create mode 100644 app/assets/images/icon-csv.svg create mode 100644 app/javascript/controllers/file_upload_controller.js create mode 100644 app/javascript/controllers/mobile_cell_interaction_controller.js create mode 100644 app/javascript/controllers/password_validator_controller.js create mode 100644 app/javascript/controllers/password_visibility_controller.js create mode 100644 app/javascript/controllers/preserve_scroll_controller.js create mode 100644 app/views/shared/_icon_custom.html.erb create mode 100644 app/views/shared/_icon_image.html.erb create mode 100644 public/logo-pwa.png diff --git a/Gemfile b/Gemfile index 7d219c27..70927ecd 100644 --- a/Gemfile +++ b/Gemfile @@ -79,6 +79,7 @@ group :development do gem "web-console" gem "faker" gem "benchmark-ips" + gem "foreman" end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index dac7842a..16375f51 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -179,6 +179,7 @@ GEM ffi (1.17.1-x86_64-darwin) ffi (1.17.1-x86_64-linux-gnu) ffi (1.17.1-x86_64-linux-musl) + foreman (0.88.1) globalid (1.2.1) activesupport (>= 6.1) hashdiff (1.1.2) @@ -550,6 +551,7 @@ DEPENDENCIES faraday faraday-multipart faraday-retry + foreman hotwire-livereload hotwire_combobox i18n-tasks diff --git a/app/assets/images/icon-assistant.svg b/app/assets/images/icon-assistant.svg new file mode 100644 index 00000000..7ba1299f --- /dev/null +++ b/app/assets/images/icon-assistant.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/icon-csv.svg b/app/assets/images/icon-csv.svg new file mode 100644 index 00000000..ce835a31 --- /dev/null +++ b/app/assets/images/icon-csv.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/app/assets/tailwind/application.css b/app/assets/tailwind/application.css index 4b78a2db..1a0c15cf 100644 --- a/app/assets/tailwind/application.css +++ b/app/assets/tailwind/application.css @@ -167,3 +167,15 @@ } } /* The following Markdown CSS has been removed as requested */ + +.mt-safe { + margin-top: env(safe-area-inset-top); +} + +.pt-safe { + padding-top: env(safe-area-inset-top); +} + +.pb-safe { + padding-bottom: env(safe-area-inset-bottom); +} \ No newline at end of file diff --git a/app/assets/tailwind/maybe-design-system.css b/app/assets/tailwind/maybe-design-system.css index 61a3de14..d6126796 100644 --- a/app/assets/tailwind/maybe-design-system.css +++ b/app/assets/tailwind/maybe-design-system.css @@ -760,4 +760,12 @@ @variant theme-dark { @apply bg-alpha-black-700; } +} + +@utility bg-nav-indicator { + @apply bg-black; + + @variant theme-dark { + @apply bg-white; + } } \ No newline at end of file diff --git a/app/controllers/import/uploads_controller.rb b/app/controllers/import/uploads_controller.rb index d30e0082..e51b5278 100644 --- a/app/controllers/import/uploads_controller.rb +++ b/app/controllers/import/uploads_controller.rb @@ -6,6 +6,13 @@ class Import::UploadsController < ApplicationController def show end + def sample_csv + send_data @import.csv_template.to_csv, + filename: "#{@import.type.underscore.split('_').first}_sample.csv", + type: "text/csv", + disposition: "attachment" + end + def update if csv_valid?(csv_str) @import.account = Current.family.accounts.find_by(id: params.dig(:import, :account_id)) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 0d8d6e92..b57b508d 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -6,6 +6,7 @@ class RegistrationsController < ApplicationController before_action :set_user, only: :create before_action :set_invitation before_action :claim_invite_code, only: :create, if: :invite_code_required? + before_action :validate_password_requirements, only: :create def new @user = User.new(email: @invitation&.email) @@ -53,4 +54,29 @@ class RegistrationsController < ApplicationController redirect_to new_registration_path, alert: t("registrations.create.invalid_invite_code") end end + + def validate_password_requirements + password = user_params[:password] + return if password.blank? # Let Rails built-in validations handle blank passwords + + if password.length < 8 + @user.errors.add(:password, "must be at least 8 characters") + end + + unless password.match?(/[A-Z]/) && password.match?(/[a-z]/) + @user.errors.add(:password, "must include both uppercase and lowercase letters") + end + + unless password.match?(/\d/) + @user.errors.add(:password, "must include at least one number") + end + + unless password.match?(/[!@#$%^&*(),.?":{}|<>]/) + @user.errors.add(:password, "must include at least one special character") + end + + if @user.errors.present? + render :new, status: :unprocessable_entity + end + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2089e0f7..64321595 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -5,6 +5,10 @@ module ApplicationHelper render partial: "shared/icon", locals: { key:, size:, color: } end + def icon_custom(key, size: "md", color: "current") + render partial: "shared/icon_custom", locals: { key:, size:, color: } + end + # Convert alpha (0-1) to 8-digit hex (00-FF) def hex_with_alpha(hex, alpha) alpha_hex = (alpha * 255).round.to_s(16).rjust(2, "0") diff --git a/app/helpers/forms_helper.rb b/app/helpers/forms_helper.rb index d2f6cb04..5e311810 100644 --- a/app/helpers/forms_helper.rb +++ b/app/helpers/forms_helper.rb @@ -10,9 +10,9 @@ module FormsHelper render partial: "shared/modal_form", locals: { title:, subtitle:, content: } end - def radio_tab_tag(form:, name:, value:, label:, icon:, checked: false, disabled: false) + def radio_tab_tag(form:, name:, value:, label:, icon:, checked: false, disabled: false, class: nil) form.label name, for: form.field_id(name, value), class: "group has-disabled:cursor-not-allowed" do - concat radio_tab_contents(label:, icon:) + concat radio_tab_contents(label:, icon:, class:) concat form.radio_button(name, value, checked:, disabled:, class: "hidden") end end @@ -29,8 +29,8 @@ end 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-container group-has-checked:text-gray-800 group-has-checked:shadow-sm") do + def radio_tab_contents(label:, icon:, class: nil) + tag.div(class: "flex px-4 py-1 rounded-lg items-center space-x-2 justify-center text-sm md:text-normal 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 diff --git a/app/helpers/imports_helper.rb b/app/helpers/imports_helper.rb index 67930cc1..4a0dce76 100644 --- a/app/helpers/imports_helper.rb +++ b/app/helpers/imports_helper.rb @@ -55,6 +55,11 @@ module ImportsHelper [ base, border ].join(" ") end + def cell_is_valid?(row, field) + row.valid? # populate errors + !row.errors.key?(field) + end + private def permitted_import_types %w[transaction_import trade_import account_import mint_import] diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb index 7ebf267e..c26a692e 100644 --- a/app/helpers/languages_helper.rb +++ b/app/helpers/languages_helper.rb @@ -154,200 +154,200 @@ module LanguagesHelper ].freeze COUNTRY_MAPPING = { - AF: "Afghanistan", - AL: "Albania", - DZ: "Algeria", - AD: "Andorra", - AO: "Angola", - AG: "Antigua and Barbuda", - AR: "Argentina", - AM: "Armenia", - AU: "Australia", - AT: "Austria", - AZ: "Azerbaijan", - BS: "Bahamas", - BH: "Bahrain", - BD: "Bangladesh", - BB: "Barbados", - BY: "Belarus", - BE: "Belgium", - BZ: "Belize", - BJ: "Benin", - BT: "Bhutan", - BO: "Bolivia", - BA: "Bosnia and Herzegovina", - BW: "Botswana", - BR: "Brazil", - BN: "Brunei", - BG: "Bulgaria", - BF: "Burkina Faso", - BI: "Burundi", - KH: "Cambodia", - CM: "Cameroon", - CA: "Canada", - CV: "Cape Verde", - CF: "Central African Republic", - TD: "Chad", - CL: "Chile", - CN: "China", - CO: "Colombia", - KM: "Comoros", - CG: "Congo", - CD: "Congo, Democratic Republic of the", - CR: "Costa Rica", - CI: "Cรดte d'Ivoire", - HR: "Croatia", - CU: "Cuba", - CY: "Cyprus", - CZ: "Czech Republic", - DK: "Denmark", - DJ: "Djibouti", - DM: "Dominica", - DO: "Dominican Republic", - EC: "Ecuador", - EG: "Egypt", - SV: "El Salvador", - GQ: "Equatorial Guinea", - ER: "Eritrea", - EE: "Estonia", - ET: "Ethiopia", - FJ: "Fiji", - FI: "Finland", - FR: "France", - GA: "Gabon", - GM: "Gambia", - GE: "Georgia", - DE: "Germany", - GH: "Ghana", - GR: "Greece", - GD: "Grenada", - GT: "Guatemala", - GN: "Guinea", - GW: "Guinea-Bissau", - GY: "Guyana", - HT: "Haiti", - HN: "Honduras", - HU: "Hungary", - IS: "Iceland", - IN: "India", - ID: "Indonesia", - IR: "Iran", - IQ: "Iraq", - IE: "Ireland", - IL: "Israel", - IT: "Italy", - JM: "Jamaica", - JP: "Japan", - JO: "Jordan", - KZ: "Kazakhstan", - KE: "Kenya", - KI: "Kiribati", - KP: "North Korea", - KR: "South Korea", - KW: "Kuwait", - KG: "Kyrgyzstan", - LA: "Laos", - LV: "Latvia", - LB: "Lebanon", - LS: "Lesotho", - LR: "Liberia", - LY: "Libya", - LI: "Liechtenstein", - LT: "Lithuania", - LU: "Luxembourg", - MK: "North Macedonia", - MG: "Madagascar", - MW: "Malawi", - MY: "Malaysia", - MV: "Maldives", - ML: "Mali", - MT: "Malta", - MH: "Marshall Islands", - MR: "Mauritania", - MU: "Mauritius", - MX: "Mexico", - FM: "Micronesia", - MD: "Moldova", - MC: "Monaco", - MN: "Mongolia", - ME: "Montenegro", - MA: "Morocco", - MZ: "Mozambique", - MM: "Myanmar", - NA: "Namibia", - NR: "Nauru", - NP: "Nepal", - NL: "Netherlands", - NZ: "New Zealand", - NI: "Nicaragua", - NE: "Niger", - NG: "Nigeria", - NO: "Norway", - OM: "Oman", - PK: "Pakistan", - PW: "Palau", - PA: "Panama", - PG: "Papua New Guinea", - PY: "Paraguay", - PE: "Peru", - PH: "Philippines", - PL: "Poland", - PT: "Portugal", - QA: "Qatar", - RO: "Romania", - RU: "Russia", - RW: "Rwanda", - KN: "Saint Kitts and Nevis", - LC: "Saint Lucia", - VC: "Saint Vincent and the Grenadines", - WS: "Samoa", - SM: "San Marino", - ST: "Sao Tome and Principe", - SA: "Saudi Arabia", - SN: "Senegal", - RS: "Serbia", - SC: "Seychelles", - SL: "Sierra Leone", - SG: "Singapore", - SK: "Slovakia", - SI: "Slovenia", - SB: "Solomon Islands", - SO: "Somalia", - ZA: "South Africa", - SS: "South Sudan", - ES: "Spain", - LK: "Sri Lanka", - SD: "Sudan", - SR: "Suriname", - SE: "Sweden", - CH: "Switzerland", - SY: "Syria", - TW: "Taiwan", - TJ: "Tajikistan", - TZ: "Tanzania", - TH: "Thailand", - TL: "Timor-Leste", - TG: "Togo", - TO: "Tonga", - TT: "Trinidad and Tobago", - TN: "Tunisia", - TR: "Turkey", - TM: "Turkmenistan", - TV: "Tuvalu", - UG: "Uganda", - UA: "Ukraine", - AE: "United Arab Emirates", - GB: "United Kingdom", - US: "United States", - UY: "Uruguay", - UZ: "Uzbekistan", - VU: "Vanuatu", - VA: "Vatican City", - VE: "Venezuela", - VN: "Vietnam", - YE: "Yemen", - ZM: "Zambia", - ZW: "Zimbabwe" + AF: "๐Ÿ‡ฆ๐Ÿ‡ซ Afghanistan", + AL: "๐Ÿ‡ฆ๐Ÿ‡ฑ Albania", + DZ: "๐Ÿ‡ฉ๐Ÿ‡ฟ Algeria", + AD: "๐Ÿ‡ฆ๐Ÿ‡ฉ Andorra", + AO: "๐Ÿ‡ฆ๐Ÿ‡ด Angola", + AG: "๐Ÿ‡ฆ๐Ÿ‡ฌ Antigua and Barbuda", + AR: "๐Ÿ‡ฆ๐Ÿ‡ท Argentina", + AM: "๐Ÿ‡ฆ๐Ÿ‡ฒ Armenia", + AU: "๐Ÿ‡ฆ๐Ÿ‡บ Australia", + AT: "๐Ÿ‡ฆ๐Ÿ‡น Austria", + AZ: "๐Ÿ‡ฆ๐Ÿ‡ฟ Azerbaijan", + BS: "๐Ÿ‡ง๐Ÿ‡ธ Bahamas", + BH: "๐Ÿ‡ง๐Ÿ‡ญ Bahrain", + BD: "๐Ÿ‡ง๐Ÿ‡ฉ Bangladesh", + BB: "๐Ÿ‡ง๐Ÿ‡ง Barbados", + BY: "๐Ÿ‡ง๐Ÿ‡พ Belarus", + BE: "๐Ÿ‡ง๐Ÿ‡ช Belgium", + BZ: "๐Ÿ‡ง๐Ÿ‡ฟ Belize", + BJ: "๐Ÿ‡ง๐Ÿ‡ฏ Benin", + BT: "๐Ÿ‡ง๐Ÿ‡น Bhutan", + BO: "๐Ÿ‡ง๐Ÿ‡ด Bolivia", + BA: "๐Ÿ‡ง๐Ÿ‡ฆ Bosnia and Herzegovina", + BW: "๐Ÿ‡ง๐Ÿ‡ผ Botswana", + BR: "๐Ÿ‡ง๐Ÿ‡ท Brazil", + BN: "๐Ÿ‡ง๐Ÿ‡ณ Brunei", + BG: "๐Ÿ‡ง๐Ÿ‡ฌ Bulgaria", + BF: "๐Ÿ‡ง๐Ÿ‡ซ Burkina Faso", + BI: "๐Ÿ‡ง๐Ÿ‡ฎ Burundi", + KH: "๐Ÿ‡ฐ๐Ÿ‡ญ Cambodia", + CM: "๐Ÿ‡จ๐Ÿ‡ฒ Cameroon", + CA: "๐Ÿ‡จ๐Ÿ‡ฆ Canada", + CV: "๐Ÿ‡จ๐Ÿ‡ป Cape Verde", + CF: "๐Ÿ‡จ๐Ÿ‡ซ Central African Republic", + TD: "๐Ÿ‡น๐Ÿ‡ฉ Chad", + CL: "๐Ÿ‡จ๐Ÿ‡ฑ Chile", + CN: "๐Ÿ‡จ๐Ÿ‡ณ China", + CO: "๐Ÿ‡จ๐Ÿ‡ด Colombia", + KM: "๐Ÿ‡ฐ๐Ÿ‡ฒ Comoros", + CG: "๐Ÿ‡จ๐Ÿ‡ฌ Congo", + CD: "๐Ÿ‡จ๐Ÿ‡ฉ Congo, Democratic Republic of the", + CR: "๐Ÿ‡จ๐Ÿ‡ท Costa Rica", + CI: "๐Ÿ‡จ๐Ÿ‡ฎ Cรดte d'Ivoire", + HR: "๐Ÿ‡ญ๐Ÿ‡ท Croatia", + CU: "๐Ÿ‡จ๐Ÿ‡บ Cuba", + CY: "๐Ÿ‡จ๐Ÿ‡พ Cyprus", + CZ: "๐Ÿ‡จ๐Ÿ‡ฟ Czech Republic", + DK: "๐Ÿ‡ฉ๐Ÿ‡ฐ Denmark", + DJ: "๐Ÿ‡ฉ๐Ÿ‡ฏ Djibouti", + DM: "๐Ÿ‡ฉ๐Ÿ‡ฒ Dominica", + DO: "๐Ÿ‡ฉ๐Ÿ‡ด Dominican Republic", + EC: "๐Ÿ‡ช๐Ÿ‡จ Ecuador", + EG: "๐Ÿ‡ช๐Ÿ‡ฌ Egypt", + SV: "๐Ÿ‡ธ๐Ÿ‡ป El Salvador", + GQ: "๐Ÿ‡ฌ๐Ÿ‡ถ Equatorial Guinea", + ER: "๐Ÿ‡ช๐Ÿ‡ท Eritrea", + EE: "๐Ÿ‡ช๐Ÿ‡ช Estonia", + ET: "๐Ÿ‡ช๐Ÿ‡น Ethiopia", + FJ: "๐Ÿ‡ซ๐Ÿ‡ฏ Fiji", + FI: "๐Ÿ‡ซ๐Ÿ‡ฎ Finland", + FR: "๐Ÿ‡ซ๐Ÿ‡ท France", + GA: "๐Ÿ‡ฌ๐Ÿ‡ฆ Gabon", + GM: "๐Ÿ‡ฌ๐Ÿ‡ฒ Gambia", + GE: "๐Ÿ‡ฌ๐Ÿ‡ช Georgia", + DE: "๐Ÿ‡ฉ๐Ÿ‡ช Germany", + GH: "๐Ÿ‡ฌ๐Ÿ‡ญ Ghana", + GR: "๐Ÿ‡ฌ๐Ÿ‡ท Greece", + GD: "๐Ÿ‡ฌ๐Ÿ‡ฉ Grenada", + GT: "๐Ÿ‡ฌ๐Ÿ‡น Guatemala", + GN: "๐Ÿ‡ฌ๐Ÿ‡ณ Guinea", + GW: "๐Ÿ‡ฌ๐Ÿ‡ผ Guinea-Bissau", + GY: "๐Ÿ‡ฌ๐Ÿ‡พ Guyana", + HT: "๐Ÿ‡ญ๐Ÿ‡น Haiti", + HN: "๐Ÿ‡ญ๐Ÿ‡ณ Honduras", + HU: "๐Ÿ‡ญ๐Ÿ‡บ Hungary", + IS: "๐Ÿ‡ฎ๐Ÿ‡ธ Iceland", + IN: "๐Ÿ‡ฎ๐Ÿ‡ณ India", + ID: "๐Ÿ‡ฎ๐Ÿ‡ฉ Indonesia", + IR: "๐Ÿ‡ฎ๐Ÿ‡ท Iran", + IQ: "๐Ÿ‡ฎ๐Ÿ‡ถ Iraq", + IE: "๐Ÿ‡ฎ๐Ÿ‡ช Ireland", + IL: "๐Ÿ‡ฎ๐Ÿ‡ฑ Israel", + IT: "๐Ÿ‡ฎ๐Ÿ‡น Italy", + JM: "๐Ÿ‡ฏ๐Ÿ‡ฒ Jamaica", + JP: "๐Ÿ‡ฏ๐Ÿ‡ต Japan", + JO: "๐Ÿ‡ฏ๐Ÿ‡ด Jordan", + KZ: "๐Ÿ‡ฐ๐Ÿ‡ฟ Kazakhstan", + KE: "๐Ÿ‡ฐ๐Ÿ‡ช Kenya", + KI: "๐Ÿ‡ฐ๐Ÿ‡ฎ Kiribati", + KP: "๐Ÿ‡ฐ๐Ÿ‡ต North Korea", + KR: "๐Ÿ‡ฐ๐Ÿ‡ท South Korea", + KW: "๐Ÿ‡ฐ๐Ÿ‡ผ Kuwait", + KG: "๐Ÿ‡ฐ๐Ÿ‡ฌ Kyrgyzstan", + LA: "๐Ÿ‡ฑ๐Ÿ‡ฆ Laos", + LV: "๐Ÿ‡ฑ๐Ÿ‡ป Latvia", + LB: "๐Ÿ‡ฑ๐Ÿ‡ง Lebanon", + LS: "๐Ÿ‡ฑ๐Ÿ‡ธ Lesotho", + LR: "๐Ÿ‡ฑ๐Ÿ‡ท Liberia", + LY: "๐Ÿ‡ฑ๐Ÿ‡พ Libya", + LI: "๐Ÿ‡ฑ๐Ÿ‡ฎ Liechtenstein", + LT: "๐Ÿ‡ฑ๐Ÿ‡น Lithuania", + LU: "๐Ÿ‡ฑ๐Ÿ‡บ Luxembourg", + MK: "๐Ÿ‡ฒ๐Ÿ‡ฐ North Macedonia", + MG: "๐Ÿ‡ฒ๐Ÿ‡ฌ Madagascar", + MW: "๐Ÿ‡ฒ๐Ÿ‡ผ Malawi", + MY: "๐Ÿ‡ฒ๐Ÿ‡พ Malaysia", + MV: "๐Ÿ‡ฒ๐Ÿ‡ป Maldives", + ML: "๐Ÿ‡ฒ๐Ÿ‡ฑ Mali", + MT: "๐Ÿ‡ฒ๐Ÿ‡น Malta", + MH: "๐Ÿ‡ฒ๐Ÿ‡ญ Marshall Islands", + MR: "๐Ÿ‡ฒ๐Ÿ‡ท Mauritania", + MU: "๐Ÿ‡ฒ๐Ÿ‡บ Mauritius", + MX: "๐Ÿ‡ฒ๐Ÿ‡ฝ Mexico", + FM: "๐Ÿ‡ซ๐Ÿ‡ฒ Micronesia", + MD: "๐Ÿ‡ฒ๐Ÿ‡ฉ Moldova", + MC: "๐Ÿ‡ฒ๐Ÿ‡จ Monaco", + MN: "๐Ÿ‡ฒ๐Ÿ‡ณ Mongolia", + ME: "๐Ÿ‡ฒ๐Ÿ‡ช Montenegro", + MA: "๐Ÿ‡ฒ๐Ÿ‡ฆ Morocco", + MZ: "๐Ÿ‡ฒ๐Ÿ‡ฟ Mozambique", + MM: "๐Ÿ‡ฒ๐Ÿ‡ฒ Myanmar", + NA: "๐Ÿ‡ณ๐Ÿ‡ฆ Namibia", + NR: "๐Ÿ‡ณ๐Ÿ‡ท Nauru", + NP: "๐Ÿ‡ณ๐Ÿ‡ต Nepal", + NL: "๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands", + NZ: "๐Ÿ‡ณ๐Ÿ‡ฟ New Zealand", + NI: "๐Ÿ‡ณ๐Ÿ‡ฎ Nicaragua", + NE: "๐Ÿ‡ณ๐Ÿ‡ช Niger", + NG: "๐Ÿ‡ณ๐Ÿ‡ฌ Nigeria", + NO: "๐Ÿ‡ณ๐Ÿ‡ด Norway", + OM: "๐Ÿ‡ด๐Ÿ‡ฒ Oman", + PK: "๐Ÿ‡ต๐Ÿ‡ฐ Pakistan", + PW: "๐Ÿ‡ต๐Ÿ‡ผ Palau", + PA: "๐Ÿ‡ต๐Ÿ‡ฆ Panama", + PG: "๐Ÿ‡ต๐Ÿ‡ฌ Papua New Guinea", + PY: "๐Ÿ‡ต๐Ÿ‡พ Paraguay", + PE: "๐Ÿ‡ต๐Ÿ‡ช Peru", + PH: "๐Ÿ‡ต๐Ÿ‡ญ Philippines", + PL: "๐Ÿ‡ต๐Ÿ‡ฑ Poland", + PT: "๐Ÿ‡ต๐Ÿ‡น Portugal", + QA: "๐Ÿ‡ถ๐Ÿ‡ฆ Qatar", + RO: "๐Ÿ‡ท๐Ÿ‡ด Romania", + RU: "๐Ÿ‡ท๐Ÿ‡บ Russia", + RW: "๐Ÿ‡ท๐Ÿ‡ผ Rwanda", + KN: "๐Ÿ‡ฐ๐Ÿ‡ณ Saint Kitts and Nevis", + LC: "๐Ÿ‡ฑ๐Ÿ‡จ Saint Lucia", + VC: "๐Ÿ‡ป๐Ÿ‡จ Saint Vincent and the Grenadines", + WS: "๐Ÿ‡ผ๐Ÿ‡ธ Samoa", + SM: "๐Ÿ‡ธ๐Ÿ‡ฒ San Marino", + ST: "๐Ÿ‡ธ๐Ÿ‡น Sao Tome and Principe", + SA: "๐Ÿ‡ธ๐Ÿ‡ฆ Saudi Arabia", + SN: "๐Ÿ‡ธ๐Ÿ‡ณ Senegal", + RS: "๐Ÿ‡ท๐Ÿ‡ธ Serbia", + SC: "๐Ÿ‡ธ๐Ÿ‡จ Seychelles", + SL: "๐Ÿ‡ธ๐Ÿ‡ฑ Sierra Leone", + SG: "๐Ÿ‡ธ๐Ÿ‡ฌ Singapore", + SK: "๐Ÿ‡ธ๐Ÿ‡ฐ Slovakia", + SI: "๐Ÿ‡ธ๐Ÿ‡ฎ Slovenia", + SB: "๐Ÿ‡ธ๐Ÿ‡ง Solomon Islands", + SO: "๐Ÿ‡ธ๐Ÿ‡ด Somalia", + ZA: "๐Ÿ‡ฟ๐Ÿ‡ฆ South Africa", + SS: "๐Ÿ‡ธ๐Ÿ‡ธ South Sudan", + ES: "๐Ÿ‡ช๐Ÿ‡ธ Spain", + LK: "๐Ÿ‡ฑ๐Ÿ‡ฐ Sri Lanka", + SD: "๐Ÿ‡ธ๐Ÿ‡ฉ Sudan", + SR: "๐Ÿ‡ธ๐Ÿ‡ท Suriname", + SE: "๐Ÿ‡ธ๐Ÿ‡ช Sweden", + CH: "๐Ÿ‡จ๐Ÿ‡ญ Switzerland", + SY: "๐Ÿ‡ธ๐Ÿ‡พ Syria", + TW: "๐Ÿ‡น๐Ÿ‡ผ Taiwan", + TJ: "๐Ÿ‡น๐Ÿ‡ฏ Tajikistan", + TZ: "๐Ÿ‡น๐Ÿ‡ฟ Tanzania", + TH: "๐Ÿ‡น๐Ÿ‡ญ Thailand", + TL: "๐Ÿ‡น๐Ÿ‡ฑ Timor-Leste", + TG: "๐Ÿ‡น๐Ÿ‡ฌ Togo", + TO: "๐Ÿ‡น๐Ÿ‡ด Tonga", + TT: "๐Ÿ‡น๐Ÿ‡น Trinidad and Tobago", + TN: "๐Ÿ‡น๐Ÿ‡ณ Tunisia", + TR: "๐Ÿ‡น๐Ÿ‡ท Turkey", + TM: "๐Ÿ‡น๐Ÿ‡ฒ Turkmenistan", + TV: "๐Ÿ‡น๐Ÿ‡ป Tuvalu", + UG: "๐Ÿ‡บ๐Ÿ‡ฌ Uganda", + UA: "๐Ÿ‡บ๐Ÿ‡ฆ Ukraine", + AE: "๐Ÿ‡ฆ๐Ÿ‡ช United Arab Emirates", + GB: "๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom", + US: "๐Ÿ‡บ๐Ÿ‡ธ United States", + UY: "๐Ÿ‡บ๐Ÿ‡พ Uruguay", + UZ: "๐Ÿ‡บ๐Ÿ‡ฟ Uzbekistan", + VU: "๐Ÿ‡ป๐Ÿ‡บ Vanuatu", + VA: "๐Ÿ‡ป๐Ÿ‡ฆ Vatican City", + VE: "๐Ÿ‡ป๐Ÿ‡ช Venezuela", + VN: "๐Ÿ‡ป๐Ÿ‡ณ Vietnam", + YE: "๐Ÿ‡พ๐Ÿ‡ช Yemen", + ZM: "๐Ÿ‡ฟ๐Ÿ‡ฒ Zambia", + ZW: "๐Ÿ‡ฟ๐Ÿ‡ผ Zimbabwe" }.freeze def country_options diff --git a/app/helpers/menus_helper.rb b/app/helpers/menus_helper.rb index 34134888..0ddc9e5f 100644 --- a/app/helpers/menus_helper.rb +++ b/app/helpers/menus_helper.rb @@ -6,8 +6,8 @@ module MenusHelper end end - def contextual_menu_modal_action_item(label, url, icon: "pencil-line", turbo_frame: :modal) - link_to url, class: "flex items-center rounded-md text-primary hover:bg-container-hover p-2 gap-2", data: { action: "click->menu#close", turbo_frame: turbo_frame } do + def contextual_menu_modal_action_item(label, url, icon: "pencil-line", turbo_frame: :modal, class_name: nil) + link_to url, class: "flex items-center rounded-md text-primary hover:bg-container-hover p-2 gap-2 #{class_name}", data: { action: "click->menu#close", turbo_frame: turbo_frame } do concat(lucide_icon(icon, class: "shrink-0 w-5 h-5 text-secondary")) concat(tag.span(label, class: "text-sm")) end @@ -33,7 +33,8 @@ module MenusHelper private def contextual_menu_icon(icon) tag.button class: "w-9 h-9 flex justify-center items-center hover:bg-surface-hover rounded-lg cursor-pointer focus:outline-none focus-visible:outline-none", data: { menu_target: "button" } do - lucide_icon icon, class: "w-5 h-5 text-secondary" + concat lucide_icon("more-vertical", class: "w-5 h-5 text-secondary md:hidden") + concat lucide_icon(icon, class: "w-5 h-5 text-secondary hidden md:block") end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index e15414a5..674bea8b 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -40,7 +40,17 @@ module SettingsHelper previous_setting = adjacent_setting(request.path, -1) next_setting = adjacent_setting(request.path, 1) - content_tag :div, class: "flex justify-between gap-4" do + content_tag :div, class: "hidden md:flex flex-row justify-between gap-4" do + concat(previous_setting) + concat(next_setting) + end + end + + def settings_nav_footer_mobile + previous_setting = adjacent_setting(request.path, -1) + next_setting = adjacent_setting(request.path, 1) + + content_tag :div, class: "md:hidden flex flex-col gap-4" do concat(previous_setting) concat(next_setting) end diff --git a/app/javascript/controllers/file_upload_controller.js b/app/javascript/controllers/file_upload_controller.js new file mode 100644 index 00000000..6ad3e5d9 --- /dev/null +++ b/app/javascript/controllers/file_upload_controller.js @@ -0,0 +1,74 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["input", "fileName", "uploadArea", "uploadText"] + + connect() { + if (this.hasInputTarget) { + this.inputTarget.addEventListener("change", this.fileSelected.bind(this)) + } + + // Find the form element + this.form = this.element.closest("form") + if (this.form) { + this.form.addEventListener("turbo:submit-start", this.formSubmitting.bind(this)) + } + } + + disconnect() { + if (this.hasInputTarget) { + this.inputTarget.removeEventListener("change", this.fileSelected.bind(this)) + } + + if (this.form) { + this.form.removeEventListener("turbo:submit-start", this.formSubmitting.bind(this)) + } + } + + triggerFileInput() { + if (this.hasInputTarget) { + this.inputTarget.click() + } + } + + fileSelected() { + if (this.hasInputTarget && this.inputTarget.files.length > 0) { + const fileName = this.inputTarget.files[0].name + + if (this.hasFileNameTarget) { + // Find the paragraph element inside the fileName target + const fileNameText = this.fileNameTarget.querySelector('p') + if (fileNameText) { + fileNameText.textContent = fileName + } + + this.fileNameTarget.classList.remove("hidden") + } + + if (this.hasUploadTextTarget) { + this.uploadTextTarget.classList.add("hidden") + } + + + } + } + + formSubmitting() { + if (this.hasFileNameTarget && this.hasInputTarget && this.inputTarget.files.length > 0) { + const fileNameText = this.fileNameTarget.querySelector('p') + if (fileNameText) { + fileNameText.textContent = `Uploading ${this.inputTarget.files[0].name}...` + } + + // Change the icon to a loader + const iconContainer = this.fileNameTarget.querySelector('.lucide-file-text') + if (iconContainer) { + iconContainer.classList.add('animate-pulse') + } + } + + if (this.hasUploadAreaTarget) { + this.uploadAreaTarget.classList.add("opacity-70") + } + } +} \ No newline at end of file diff --git a/app/javascript/controllers/mobile_cell_interaction_controller.js b/app/javascript/controllers/mobile_cell_interaction_controller.js new file mode 100644 index 00000000..1eb0d82b --- /dev/null +++ b/app/javascript/controllers/mobile_cell_interaction_controller.js @@ -0,0 +1,149 @@ +import { Controller } from "@hotwired/stimulus"; + +// Connects to data-controller="mobile-cell-interaction" +export default class extends Controller { + static targets = ["field", "highlight", "errorTooltip", "errorIcon"]; + static values = { error: String }; + + touchTimeout = null; + activeTooltip = null; + documentClickHandler = null; + + connect() { + this.documentClickHandler = this.handleDocumentClick.bind(this); + document.addEventListener('click', this.documentClickHandler); + } + + disconnect() { + if (this.documentClickHandler) { + document.removeEventListener('click', this.documentClickHandler); + } + } + + handleDocumentClick(event) { + if (event.target.closest('[data-mobile-cell-interaction-target="errorTooltip"]') || + event.target.closest('[data-mobile-cell-interaction-target="errorIcon"]')) { + return; + } + + this.hideAllErrorTooltips(); + } + + highlightCell(event) { + const field = event.target; + const highlight = this.findHighlightForField(field); + if (highlight) { + highlight.style.opacity = '1'; + } + } + + unhighlightCell(event) { + const field = event.target; + const highlight = this.findHighlightForField(field); + if (highlight) { + highlight.style.opacity = '0'; + } + + this.hideAllErrorTooltips(); + } + + handleCellTouch(event) { + if (this.touchTimeout) { + clearTimeout(this.touchTimeout); + } + + const field = event.target; + + const highlight = this.findHighlightForField(field); + if (highlight) { + highlight.style.opacity = '1'; + + this.touchTimeout = window.setTimeout(() => { + if (document.activeElement !== field) { + highlight.style.opacity = '0'; + } + }, 1000); + } + + if (this.hasErrorValue && this.errorValue) { + this.showErrorTooltip(); + } + } + + toggleErrorMessage(event) { + const errorIcon = event.currentTarget; + const cellContainer = errorIcon.closest('div'); + const field = cellContainer.querySelector('input'); + + if (field) { + field.focus(); + } + + const tooltip = this.errorTooltipTarget; + + this.hideAllTooltipsExcept(tooltip); + + if (tooltip.classList.contains('hidden')) { + tooltip.classList.remove('hidden'); + this.activeTooltip = tooltip; + + setTimeout(() => { + if (tooltip === this.activeTooltip) { + tooltip.classList.add('hidden'); + this.activeTooltip = null; + } + }, 3000); + } else { + tooltip.classList.add('hidden'); + this.activeTooltip = null; + } + + event.stopPropagation(); + } + + showErrorTooltip() { + if (this.hasErrorTooltipTarget) { + const tooltip = this.errorTooltipTarget; + tooltip.classList.remove('hidden'); + this.activeTooltip = tooltip; + + setTimeout(() => { + if (tooltip === this.activeTooltip) { + tooltip.classList.add('hidden'); + this.activeTooltip = null; + } + }, 3000); + } + } + + hideAllErrorTooltips() { + document.querySelectorAll('[data-mobile-cell-interaction-target="errorTooltip"]').forEach(tooltip => { + tooltip.classList.add('hidden'); + }); + this.activeTooltip = null; + } + + hideAllTooltipsExcept(tooltipToKeep) { + document.querySelectorAll('[data-mobile-cell-interaction-target="errorTooltip"]').forEach(tooltip => { + if (tooltip !== tooltipToKeep) { + tooltip.classList.add('hidden'); + } + }); + } + + selectCell(event) { + const errorIcon = event.currentTarget; + const cellContainer = errorIcon.closest('div'); + const field = cellContainer.querySelector('input'); + + if (field) { + field.focus(); + event.stopPropagation(); + } + } + + findHighlightForField(field) { + const container = field.closest('div'); + return container ? container.querySelector('[data-mobile-cell-interaction-target="highlight"]') : null; + } +} \ No newline at end of file diff --git a/app/javascript/controllers/password_validator_controller.js b/app/javascript/controllers/password_validator_controller.js new file mode 100644 index 00000000..4de9c6fd --- /dev/null +++ b/app/javascript/controllers/password_validator_controller.js @@ -0,0 +1,63 @@ +import { Controller } from "@hotwired/stimulus"; + +// Connects to data-controller="password-validator" +export default class extends Controller { + static targets = ["input", "requirementType", "blockLine"]; + + connect() { + this.validate(); + } + + validate() { + const password = this.inputTarget.value; + let requirementsMet = 0; + + // Check each requirement and count how many are met + const lengthValid = password.length >= 8; + const caseValid = /[A-Z]/.test(password) && /[a-z]/.test(password); + const numberValid = /\d/.test(password); + const specialValid = /[!@#$%^&*(),.?":{}|<>]/.test(password); + + // Update individual requirement text + this.validateRequirementText("length", lengthValid); + this.validateRequirementText("case", caseValid); + this.validateRequirementText("number", numberValid); + this.validateRequirementText("special", specialValid); + + // Count total requirements met + if (lengthValid) requirementsMet++; + if (caseValid) requirementsMet++; + if (numberValid) requirementsMet++; + if (specialValid) requirementsMet++; + + // Update block lines sequentially + this.updateBlockLines(requirementsMet); + } + + validateRequirementText(type, isValid) { + this.requirementTypeTargets.forEach((target) => { + if (target.dataset.requirementType === type) { + if (isValid) { + target.classList.remove("text-secondary"); + target.classList.add("text-green-600"); + } else { + target.classList.remove("text-green-600"); + target.classList.add("text-secondary"); + } + } + }); + } + + updateBlockLines(requirementsMet) { + // Update block lines sequentially based on total requirements met + this.blockLineTargets.forEach((line, index) => { + if (index < requirementsMet) { + line.classList.remove("bg-gray-200"); + line.classList.add("bg-green-600"); + } else { + line.classList.remove("bg-green-600"); + line.classList.add("bg-gray-200"); + } + }); + } +} diff --git a/app/javascript/controllers/password_visibility_controller.js b/app/javascript/controllers/password_visibility_controller.js new file mode 100644 index 00000000..ebe68eae --- /dev/null +++ b/app/javascript/controllers/password_visibility_controller.js @@ -0,0 +1,19 @@ +import { Controller } from "@hotwired/stimulus"; + +// Connects to data-controller="password-visibility" +export default class extends Controller { + static targets = ["input", "showIcon", "hideIcon"]; + + connect() { + this.hideIconTarget.classList.add("hidden"); + } + + toggle() { + const input = this.inputTarget; + const type = input.type === "password" ? "text" : "password"; + input.type = type; + + this.showIconTarget.classList.toggle("hidden"); + this.hideIconTarget.classList.toggle("hidden"); + } +} diff --git a/app/javascript/controllers/preserve_scroll_controller.js b/app/javascript/controllers/preserve_scroll_controller.js new file mode 100644 index 00000000..c2110fd1 --- /dev/null +++ b/app/javascript/controllers/preserve_scroll_controller.js @@ -0,0 +1,39 @@ +/* + https://dev.to/konnorrogers/maintain-scroll-position-in-turbo-without-data-turbo-permanent-2b1i + modified to add support for horizontal scrolling + */ +if (!window.scrollPositions) { + window.scrollPositions = {}; +} + +function preserveScroll() { + document.querySelectorAll("[data-preserve-scroll]").forEach((element) => { + scrollPositions[element.id] = { + top: element.scrollTop, + left: element.scrollLeft + }; + }); +} + +function restoreScroll(event) { + document.querySelectorAll("[data-preserve-scroll]").forEach((element) => { + if (scrollPositions[element.id]) { + element.scrollTop = scrollPositions[element.id].top; + element.scrollLeft = scrollPositions[element.id].left; + } + }); + + if (!event.detail.newBody) return; + // event.detail.newBody is the body element to be swapped in. + // https://turbo.hotwired.dev/reference/events + event.detail.newBody.querySelectorAll("[data-preserve-scroll]").forEach((element) => { + if (scrollPositions[element.id]) { + element.scrollTop = scrollPositions[element.id].top; + element.scrollLeft = scrollPositions[element.id].left; + } + }); +} + +window.addEventListener("turbo:before-cache", preserveScroll); +window.addEventListener("turbo:before-render", restoreScroll); +window.addEventListener("turbo:render", restoreScroll); diff --git a/app/javascript/controllers/profile_image_preview_controller.js b/app/javascript/controllers/profile_image_preview_controller.js index 7e568bee..83134e38 100644 --- a/app/javascript/controllers/profile_image_preview_controller.js +++ b/app/javascript/controllers/profile_image_preview_controller.js @@ -8,6 +8,9 @@ export default class extends Controller { "deleteProfileImage", "input", "clearBtn", + "uploadText", + "changeText", + "cameraIcon" ]; clearFileInput() { @@ -17,6 +20,12 @@ export default class extends Controller { this.attachedImageTarget.classList.add("hidden"); this.previewImageTarget.classList.add("hidden"); this.deleteProfileImageTarget.value = "1"; + this.uploadTextTarget.classList.remove("hidden"); + this.changeTextTarget.classList.add("hidden"); + this.changeTextTarget.setAttribute("aria-hidden", "true"); + this.uploadTextTarget.setAttribute("aria-hidden", "false"); + this.cameraIconTarget.classList.remove("!hidden"); + } showFileInputPreview(event) { @@ -28,7 +37,11 @@ export default class extends Controller { this.previewImageTarget.classList.remove("hidden"); this.clearBtnTarget.classList.remove("hidden"); this.deleteProfileImageTarget.value = "0"; - + this.uploadTextTarget.classList.add("hidden"); + this.changeTextTarget.classList.remove("hidden"); + this.changeTextTarget.setAttribute("aria-hidden", "false"); + this.uploadTextTarget.setAttribute("aria-hidden", "true"); + this.cameraIconTarget.classList.add("!hidden"); this.previewImageTarget.querySelector("img").src = URL.createObjectURL(file); } diff --git a/app/javascript/controllers/theme_controller.js b/app/javascript/controllers/theme_controller.js index d01edf7f..47815efc 100644 --- a/app/javascript/controllers/theme_controller.js +++ b/app/javascript/controllers/theme_controller.js @@ -50,7 +50,8 @@ export default class extends Controller { } systemPrefersDark() { - return window.matchMedia("(prefers-color-scheme: dark)").matches + return false + // return window.matchMedia("(prefers-color-scheme: dark)").matches } handleSystemThemeChange = (event) => { diff --git a/app/views/accounts/index.html.erb b/app/views/accounts/index.html.erb index 7d2eb4f7..d8c9b821 100644 --- a/app/views/accounts/index.html.erb +++ b/app/views/accounts/index.html.erb @@ -4,17 +4,20 @@
<%= button_to sync_all_accounts_path, disabled: Current.family.syncing?, - class: "btn btn--outline flex items-center gap-2", + class: "md:btn md:btn--outline flex items-center justify-center gap-2 w-9 h-9 md:w-auto md:h-auto rounded-full md:rounded-lg", title: t(".sync") do %> <%= lucide_icon "refresh-cw", class: "w-5 h-5" %> - <%= t(".sync") %> + <% end %> <%= link_to new_account_path(return_to: accounts_path), data: { turbo_frame: "modal" }, - class: "btn btn--primary flex items-center gap-1" do %> - <%= lucide_icon("plus", class: "w-5 h-5") %> -

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

+ class: "btn btn--primary flex items-center justify-center gap-1 w-9 h-9 md:w-auto md:h-auto rounded-full md:rounded-lg" do %> +
+ + <%= lucide_icon("plus")%> +
+ <% end %>
diff --git a/app/views/accounts/new/_container.html.erb b/app/views/accounts/new/_container.html.erb index ca4ce59d..72a6e639 100644 --- a/app/views/accounts/new/_container.html.erb +++ b/app/views/accounts/new/_container.html.erb @@ -1,8 +1,8 @@ <%# locals: (title:, back_path: nil) %> <%= modal do %> -
-
+
+
<% if back_path %> <%= link_to back_path, class: "flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 focus:outline-gray-300 focus:outline" do %> <%= lucide_icon("arrow-left", class: "text-secondary w-5 h-5") %> @@ -10,6 +10,9 @@ <% end %> <%= title %> +
@@ -19,7 +22,7 @@ <%= yield %>
-
+