diff --git a/Gemfile b/Gemfile index 7bc3434..e8c0ccb 100644 --- a/Gemfile +++ b/Gemfile @@ -61,3 +61,14 @@ group :test do gem "capybara" gem "selenium-webdriver" end +# admin +gem 'devise' + +gem "sprockets-rails" +gem "sassc-rails" +gem "font-awesome-sass", '~> 5.15.1' + + + + +gem "shop_now", path: "vendor/plugins/shop_now" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index c9bf4d8..8f61946 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,9 @@ +PATH + remote: vendor/plugins/shop_now + specs: + shop_now (0.1.0) + rails (>= 8.0.3) + GEM remote: https://rubygems.org/ specs: @@ -76,6 +82,7 @@ GEM public_suffix (>= 2.0.2, < 7.0) ast (2.4.3) base64 (0.3.0) + bcrypt (3.1.20) bcrypt_pbkdf (1.1.1) benchmark (0.4.1) bigdecimal (3.2.3) @@ -101,6 +108,12 @@ GEM debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) + devise (4.9.4) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) dotenv (3.1.8) drb (2.2.3) ed25519 (1.4.0) @@ -109,6 +122,8 @@ GEM et-orbi (1.4.0) tzinfo ffi (1.17.2-x86_64-linux-gnu) + font-awesome-sass (5.15.1) + sassc (>= 1.11) fugit (1.11.2) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) @@ -178,6 +193,7 @@ GEM nio4r (2.7.4) nokogiri (1.18.10-x86_64-linux-gnu) racc (~> 1.4) + orm_adapter (0.5.0) ostruct (0.6.3) parallel (1.27.0) parser (3.3.9.0) @@ -245,6 +261,9 @@ GEM regexp_parser (2.11.3) reline (0.6.2) io-console (~> 0.5) + responders (3.2.0) + actionpack (>= 7.0) + railties (>= 7.0) rexml (3.4.4) rubocop (1.81.1) json (~> 2.3) @@ -279,6 +298,14 @@ GEM ffi (~> 1.12) logger rubyzip (3.1.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt securerandom (0.4.1) selenium-webdriver (4.35.0) base64 (~> 0.2) @@ -302,6 +329,14 @@ GEM fugit (~> 1.11.0) railties (>= 7.1) thor (>= 1.3.1) + sprockets (4.2.2) + concurrent-ruby (~> 1.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) + sprockets (>= 3.0.0) sqlite3 (2.7.4-x86_64-linux-gnu) sshkit (1.24.0) base64 @@ -315,6 +350,7 @@ GEM stringio (3.1.7) thor (1.4.0) thruster (0.1.15-x86_64-linux) + tilt (2.6.1) timeout (0.4.3) tsort (0.2.0) turbo-rails (2.0.17) @@ -325,8 +361,10 @@ GEM unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.1.0) - uri (1.0.3) + uri (1.1.0) useragent (0.16.11) + warden (1.2.9) + rack (>= 2.0.9) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -349,6 +387,8 @@ DEPENDENCIES brakeman capybara debug + devise + font-awesome-sass (~> 5.15.1) image_processing (~> 1.12) importmap-rails jbuilder @@ -357,10 +397,13 @@ DEPENDENCIES puma (>= 5.0) rails (~> 8.0.3) rubocop-rails-omakase + sassc-rails selenium-webdriver + shop_now! solid_cable solid_cache solid_queue + sprockets-rails sqlite3 (>= 2.1) stimulus-rails thruster diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..5918193 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,2 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000..7b8888f --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,11 @@ +// app/javascript/application.js or app/assets/javascripts/application.js +document.addEventListener("DOMContentLoaded", () => { + const flash = document.getElementById("flash-message"); + if (flash) { + setTimeout(() => { + flash.style.transition = "opacity 0.5s ease"; + flash.style.opacity = "0"; + setTimeout(() => flash.remove(), 500); // remove from DOM after fade + }, 3000); // 3 seconds + } +}); diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css new file mode 100644 index 0000000..9b6bcb0 --- /dev/null +++ b/app/assets/stylesheets/actiontext.css @@ -0,0 +1,440 @@ +/* + * Default Trix editor styles. See Action Text overwrites below. +*/ + +trix-editor { + border: 1px solid #bbb; + border-radius: 3px; + margin: 0; + padding: 0.4em 0.6em; + min-height: 5em; + outline: none; } + +trix-toolbar * { + box-sizing: border-box; } + +trix-toolbar .trix-button-row { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + overflow-x: auto; } + +trix-toolbar .trix-button-group { + display: flex; + margin-bottom: 10px; + border: 1px solid #bbb; + border-top-color: #ccc; + border-bottom-color: #888; + border-radius: 3px; } + trix-toolbar .trix-button-group:not(:first-child) { + margin-left: 1.5vw; } + @media (max-width: 768px) { + trix-toolbar .trix-button-group:not(:first-child) { + margin-left: 0; } } + +trix-toolbar .trix-button-group-spacer { + flex-grow: 1; } + @media (max-width: 768px) { + trix-toolbar .trix-button-group-spacer { + display: none; } } + +trix-toolbar .trix-button { + position: relative; + float: left; + color: rgba(0, 0, 0, 0.6); + font-size: 0.75em; + font-weight: 600; + white-space: nowrap; + padding: 0 0.5em; + margin: 0; + outline: none; + border: none; + border-bottom: 1px solid #ddd; + border-radius: 0; + background: transparent; } + trix-toolbar .trix-button:not(:first-child) { + border-left: 1px solid #ccc; } + trix-toolbar .trix-button.trix-active { + background: #cbeefa; + color: black; } + trix-toolbar .trix-button:not(:disabled) { + cursor: pointer; } + trix-toolbar .trix-button:disabled { + color: rgba(0, 0, 0, 0.125); } + @media (max-width: 768px) { + trix-toolbar .trix-button { + letter-spacing: -0.01em; + padding: 0 0.3em; } } + +trix-toolbar .trix-button--icon { + font-size: inherit; + width: 2.6em; + height: 1.6em; + max-width: calc(0.8em + 4vw); + text-indent: -9999px; } + @media (max-width: 768px) { + trix-toolbar .trix-button--icon { + height: 2em; + max-width: calc(0.8em + 3.5vw); } } + trix-toolbar .trix-button--icon::before { + display: inline-block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.6; + content: ""; + background-position: center; + background-repeat: no-repeat; + background-size: contain; } + @media (max-width: 768px) { + trix-toolbar .trix-button--icon::before { + right: 6%; + left: 6%; } } + trix-toolbar .trix-button--icon.trix-active::before { + opacity: 1; } + trix-toolbar .trix-button--icon:disabled::before { + opacity: 0.125; } + +trix-toolbar .trix-button--icon-attach::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.5%2018V7.5c0-2.25%203-2.25%203%200V18c0%204.125-6%204.125-6%200V7.5c0-6.375%209-6.375%209%200V18%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-miterlimit%3D%2210%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); + top: 8%; + bottom: 4%; } + +trix-toolbar .trix-button--icon-bold::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6.522%2019.242a.5.5%200%200%201-.5-.5V5.35a.5.5%200%200%201%20.5-.5h5.783c1.347%200%202.46.345%203.24.982.783.64%201.216%201.562%201.216%202.683%200%201.13-.587%202.129-1.476%202.71a.35.35%200%200%200%20.049.613c1.259.56%202.101%201.742%202.101%203.22%200%201.282-.483%202.334-1.363%203.063-.876.726-2.132%201.12-3.66%201.12h-5.89ZM9.27%207.347v3.362h1.97c.766%200%201.347-.17%201.733-.464.38-.291.587-.716.587-1.27%200-.53-.183-.928-.513-1.198-.334-.273-.838-.43-1.505-.43H9.27Zm0%205.606v3.791h2.389c.832%200%201.448-.177%201.853-.497.399-.315.614-.786.614-1.423%200-.62-.22-1.077-.63-1.385-.418-.313-1.053-.486-1.905-.486H9.27Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-italic::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M9%205h6.5v2h-2.23l-2.31%2010H13v2H6v-2h2.461l2.306-10H9V5Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-link::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M18.948%205.258a4.337%204.337%200%200%200-6.108%200L11.217%206.87a.993.993%200%200%200%200%201.41c.392.39%201.027.39%201.418%200l1.623-1.613a2.323%202.323%200%200%201%203.271%200%202.29%202.29%200%200%201%200%203.251l-2.393%202.38a3.021%203.021%200%200%201-4.255%200l-.05-.049a1.007%201.007%200%200%200-1.418%200%20.993.993%200%200%200%200%201.41l.05.049a5.036%205.036%200%200%200%207.091%200l2.394-2.38a4.275%204.275%200%200%200%200-6.072Zm-13.683%2013.6a4.337%204.337%200%200%200%206.108%200l1.262-1.255a.993.993%200%200%200%200-1.41%201.007%201.007%200%200%200-1.418%200L9.954%2017.45a2.323%202.323%200%200%201-3.27%200%202.29%202.29%200%200%201%200-3.251l2.344-2.331a2.579%202.579%200%200%201%203.631%200c.392.39%201.027.39%201.419%200a.993.993%200%200%200%200-1.41%204.593%204.593%200%200%200-6.468%200l-2.345%202.33a4.275%204.275%200%200%200%200%206.072Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-strike::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6%2014.986c.088%202.647%202.246%204.258%205.635%204.258%203.496%200%205.713-1.728%205.713-4.463%200-.275-.02-.536-.062-.781h-3.461c.398.293.573.654.573%201.123%200%201.035-1.074%201.787-2.646%201.787-1.563%200-2.773-.762-2.91-1.924H6ZM6.432%2010h3.763c-.632-.314-.914-.715-.914-1.273%200-1.045.977-1.739%202.432-1.739%201.475%200%202.52.723%202.617%201.914h2.764c-.05-2.548-2.11-4.238-5.39-4.238-3.145%200-5.392%201.719-5.392%204.316%200%20.363.04.703.12%201.02ZM4%2011a1%201%200%201%200%200%202h15a1%201%200%201%200%200-2H4Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-quote::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M4.581%208.471c.44-.5%201.056-.834%201.758-.995C8.074%207.17%209.201%207.822%2010%208.752c1.354%201.578%201.33%203.555.394%205.277-.941%201.731-2.788%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.121-.49.16-.764.294-.286.567-.566.791-.835.222-.266.413-.54.524-.815.113-.28.156-.597.026-.908-.128-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.674-2.7c0-.905.283-1.59.72-2.088Zm9.419%200c.44-.5%201.055-.834%201.758-.995%201.734-.306%202.862.346%203.66%201.276%201.355%201.578%201.33%203.555.395%205.277-.941%201.731-2.789%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.122-.49.16-.764.294-.286.567-.566.791-.835.222-.266.412-.54.523-.815.114-.28.157-.597.026-.908-.127-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.672-2.701c0-.905.283-1.59.72-2.088Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-heading-1::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.5%207.5v-3h-12v3H14v13h3v-13h4.5ZM9%2013.5h3.5v-3h-10v3H6v7h3v-7Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-code::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3.293%2011.293a1%201%200%200%200%200%201.414l4%204a1%201%200%201%200%201.414-1.414L5.414%2012l3.293-3.293a1%201%200%200%200-1.414-1.414l-4%204Zm13.414%205.414%204-4a1%201%200%200%200%200-1.414l-4-4a1%201%200%201%200-1.414%201.414L18.586%2012l-3.293%203.293a1%201%200%200%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-bullet-list::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%207.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203ZM8%206a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-2.5-5a1.5%201.5%200%201%201-3%200%201.5%201.5%200%200%201%203%200ZM5%2019.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-number-list::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%204h2v4H4V5H3V4Zm5%202a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-3.5-7H6v1l-1.5%202H6v1H3v-1l1.667-2H3v-1h2.5ZM3%2017v-1h3v4H3v-1h2v-.5H4v-1h1V17H3Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-undo::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%2014a1%201%200%200%200%201%201h6a1%201%200%201%200%200-2H6.257c2.247-2.764%205.151-3.668%207.579-3.264%202.589.432%204.739%202.356%205.174%205.405a1%201%200%200%200%201.98-.283c-.564-3.95-3.415-6.526-6.825-7.095C11.084%207.25%207.63%208.377%205%2011.39V8a1%201%200%200%200-2%200v6Zm2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-redo::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21%2014a1%201%200%200%201-1%201h-6a1%201%200%201%201%200-2h3.743c-2.247-2.764-5.151-3.668-7.579-3.264-2.589.432-4.739%202.356-5.174%205.405a1%201%200%200%201-1.98-.283c.564-3.95%203.415-6.526%206.826-7.095%203.08-.513%206.534.614%209.164%203.626V8a1%201%200%201%201%202%200v6Zm-2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-decrease-nesting-level::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-3.707-5.707a1%201%200%200%200%200%201.414l2%202a1%201%200%201%200%201.414-1.414L4.414%2012l1.293-1.293a1%201%200%200%200-1.414-1.414l-2%202Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-button--icon-increase-nesting-level::before { + background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-2.293-2.293%202-2a1%201%200%200%200%200-1.414l-2-2a1%201%200%201%200-1.414%201.414L3.586%2012l-1.293%201.293a1%201%200%201%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } + +trix-toolbar .trix-dialogs { + position: relative; } + +trix-toolbar .trix-dialog { + position: absolute; + top: 0; + left: 0; + right: 0; + font-size: 0.75em; + padding: 15px 10px; + background: #fff; + box-shadow: 0 0.3em 1em #ccc; + border-top: 2px solid #888; + border-radius: 5px; + z-index: 5; } + +trix-toolbar .trix-input--dialog { + font-size: inherit; + font-weight: normal; + padding: 0.5em 0.8em; + margin: 0 10px 0 0; + border-radius: 3px; + border: 1px solid #bbb; + background-color: #fff; + box-shadow: none; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; } + trix-toolbar .trix-input--dialog.validate:invalid { + box-shadow: #F00 0px 0px 1.5px 1px; } + +trix-toolbar .trix-button--dialog { + font-size: inherit; + padding: 0.5em; + border-bottom: none; } + +trix-toolbar .trix-dialog--link { + max-width: 600px; } + +trix-toolbar .trix-dialog__link-fields { + display: flex; + align-items: baseline; } + trix-toolbar .trix-dialog__link-fields .trix-input { + flex: 1; } + trix-toolbar .trix-dialog__link-fields .trix-button-group { + flex: 0 0 content; + margin: 0; } + +trix-editor [data-trix-mutable]:not(.attachment__caption-editor) { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +trix-editor [data-trix-mutable]::-moz-selection, +trix-editor [data-trix-cursor-target]::-moz-selection, trix-editor [data-trix-mutable] ::-moz-selection { + background: none; } + +trix-editor [data-trix-mutable]::selection, +trix-editor [data-trix-cursor-target]::selection, trix-editor [data-trix-mutable] ::selection { + background: none; } + +trix-editor .attachment__caption-editor:focus[data-trix-mutable]::-moz-selection { + background: highlight; } + +trix-editor .attachment__caption-editor:focus[data-trix-mutable]::selection { + background: highlight; } + +trix-editor [data-trix-mutable].attachment.attachment--file { + box-shadow: 0 0 0 2px highlight; + border-color: transparent; } + +trix-editor [data-trix-mutable].attachment img { + box-shadow: 0 0 0 2px highlight; } + +trix-editor .attachment { + position: relative; } + trix-editor .attachment:hover { + cursor: default; } + +trix-editor .attachment--preview .attachment__caption:hover { + cursor: text; } + +trix-editor .attachment__progress { + position: absolute; + z-index: 1; + height: 20px; + top: calc(50% - 10px); + left: 5%; + width: 90%; + opacity: 0.9; + transition: opacity 200ms ease-in; } + trix-editor .attachment__progress[value="100"] { + opacity: 0; } + +trix-editor .attachment__caption-editor { + display: inline-block; + width: 100%; + margin: 0; + padding: 0; + font-size: inherit; + font-family: inherit; + line-height: inherit; + color: inherit; + text-align: center; + vertical-align: top; + border: none; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; } + +trix-editor .attachment__toolbar { + position: absolute; + z-index: 1; + top: -0.9em; + left: 0; + width: 100%; + text-align: center; } + +trix-editor .trix-button-group { + display: inline-flex; } + +trix-editor .trix-button { + position: relative; + float: left; + color: #666; + white-space: nowrap; + font-size: 80%; + padding: 0 0.8em; + margin: 0; + outline: none; + border: none; + border-radius: 0; + background: transparent; } + trix-editor .trix-button:not(:first-child) { + border-left: 1px solid #ccc; } + trix-editor .trix-button.trix-active { + background: #cbeefa; } + trix-editor .trix-button:not(:disabled) { + cursor: pointer; } + +trix-editor .trix-button--remove { + text-indent: -9999px; + display: inline-block; + padding: 0; + outline: none; + width: 1.8em; + height: 1.8em; + line-height: 1.8em; + border-radius: 50%; + background-color: #fff; + border: 2px solid highlight; + box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25); } + trix-editor .trix-button--remove::before { + display: inline-block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0.7; + content: ""; + background-image: url("data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E"); + background-position: center; + background-repeat: no-repeat; + background-size: 90%; } + trix-editor .trix-button--remove:hover { + border-color: #333; } + trix-editor .trix-button--remove:hover::before { + opacity: 1; } + +trix-editor .attachment__metadata-container { + position: relative; } + +trix-editor .attachment__metadata { + position: absolute; + left: 50%; + top: 2em; + transform: translate(-50%, 0); + max-width: 90%; + padding: 0.1em 0.6em; + font-size: 0.8em; + color: #fff; + background-color: rgba(0, 0, 0, 0.7); + border-radius: 3px; } + trix-editor .attachment__metadata .attachment__name { + display: inline-block; + max-width: 100%; + vertical-align: bottom; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + trix-editor .attachment__metadata .attachment__size { + margin-left: 0.2em; + white-space: nowrap; } + +.trix-content { + line-height: 1.5; + overflow-wrap: break-word; + word-break: break-word; } + .trix-content * { + box-sizing: border-box; + margin: 0; + padding: 0; } + .trix-content h1 { + font-size: 1.2em; + line-height: 1.2; } + .trix-content blockquote { + border: 0 solid #ccc; + border-left-width: 0.3em; + margin-left: 0.3em; + padding-left: 0.6em; } + .trix-content [dir=rtl] blockquote, + .trix-content blockquote[dir=rtl] { + border-width: 0; + border-right-width: 0.3em; + margin-right: 0.3em; + padding-right: 0.6em; } + .trix-content li { + margin-left: 1em; } + .trix-content [dir=rtl] li { + margin-right: 1em; } + .trix-content pre { + display: inline-block; + width: 100%; + vertical-align: top; + font-family: monospace; + font-size: 0.9em; + padding: 0.5em; + white-space: pre; + background-color: #eee; + overflow-x: auto; } + .trix-content img { + max-width: 100%; + height: auto; } + .trix-content .attachment { + display: inline-block; + position: relative; + max-width: 100%; } + .trix-content .attachment a { + color: inherit; + text-decoration: none; } + .trix-content .attachment a:hover, .trix-content .attachment a:visited:hover { + color: inherit; } + .trix-content .attachment__caption { + text-align: center; } + .trix-content .attachment__caption .attachment__name + .attachment__size::before { + content: ' \2022 '; } + .trix-content .attachment--preview { + width: 100%; + text-align: center; } + .trix-content .attachment--preview .attachment__caption { + color: #666; + font-size: 0.9em; + line-height: 1.2; } + .trix-content .attachment--file { + color: #333; + line-height: 1; + margin: 0 2px 2px 2px; + padding: 0.4em 1em; + border: 1px solid #bbb; + border-radius: 5px; } + .trix-content .attachment-gallery { + display: flex; + flex-wrap: wrap; + position: relative; } + .trix-content .attachment-gallery .attachment { + flex: 1 0 33%; + padding: 0 0.5em; + max-width: 33%; } + .trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment { + flex-basis: 50%; + max-width: 50%; } + +/* + * We need to override trix.css’s image gallery styles to accommodate the + * element we wrap around attachments. Otherwise, + * images in galleries will be squished by the max-width: 33%; rule. +*/ +.trix-content .attachment-gallery > action-text-attachment, +.trix-content .attachment-gallery > .attachment { + flex: 1 0 33%; + padding: 0 0.5em; + max-width: 33%; +} + +.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--4 > .attachment { + flex-basis: 50%; + max-width: 50%; +} + +.trix-content action-text-attachment .attachment { + padding: 0 !important; + max-width: 100% !important; +} diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css.scss similarity index 76% rename from app/assets/stylesheets/application.css rename to app/assets/stylesheets/application.css.scss index fe93333..d23eb91 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css.scss @@ -8,3 +8,13 @@ * * Consider organizing styles into separate files for maintainability. */ +@import "font-awesome"; + +#flash-message { + position: fixed; + top: 20px; + right: 20px; + padding: 12px 20px; + border-radius: 8px; + z-index: 1000; +} diff --git a/app/assets/stylesheets/flash.css.scss b/app/assets/stylesheets/flash.css.scss new file mode 100644 index 0000000..bc7dc45 --- /dev/null +++ b/app/assets/stylesheets/flash.css.scss @@ -0,0 +1,25 @@ +.alert { + position: fixed; + top: 20px; + right: 20px; + z-index: 1050; + min-width: 300px; + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: opacity 0.3s ease-in-out; + + &.alert-success { + background: rgba(40, 167, 69, 0.95); + border-color: #28a745; + color: white; + } + + &.alert-danger { + background: rgba(220, 53, 69, 0.95); + border-color: #dc3545; + color: white; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/flash_messages.css b/app/assets/stylesheets/flash_messages.css new file mode 100644 index 0000000..b8f4af6 --- /dev/null +++ b/app/assets/stylesheets/flash_messages.css @@ -0,0 +1,31 @@ +.alert { + position: fixed; + top: 20px; + right: 20px; + z-index: 1050; + min-width: 300px; + padding: 15px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out; + cursor: pointer; + opacity: 1; + transform: translateX(0); +} + +.alert.fade-out { + opacity: 0; + transform: translateX(100%); +} + +.alert-success { + background: rgba(40, 167, 69, 0.95); + border: 1px solid #28a745; + color: white; +} + +.alert-danger { + background: rgba(220, 53, 69, 0.95); + border: 1px solid #dc3545; + color: white; +} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8176851..937f6bd 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,7 @@ class ApplicationController < ActionController::Base # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. + before_action :authenticate_user! + allow_browser versions: :modern before_action :set_assets_url def set_assets_url diff --git a/app/controllers/expenses_controller.rb b/app/controllers/expenses_controller.rb index 3ecbee2..97a9aa6 100644 --- a/app/controllers/expenses_controller.rb +++ b/app/controllers/expenses_controller.rb @@ -16,7 +16,8 @@ class ExpensesController < ApplicationController # GET /expenses/1 or /expenses/1.json def show - @expense = Expense.find(params[:id]) + @expense = Expense.find_by(id: params[:id]) + redirect_to expenses_path, alert: "Expense not found" unless @expense # Totals @total_expenses = Expense.sum(:amount) @@ -97,7 +98,10 @@ class ExpensesController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_expense - @expense = Expense.find(params[:id]) + @expense = Expense.find_by(id: params[:id]) + unless @expense + redirect_to expenses_path, alert: "Expense not found" + end end # Only allow a list of trusted parameters through. diff --git a/app/controllers/institutions_controller.rb b/app/controllers/institutions_controller.rb index 0e0cfbc..97835fe 100644 --- a/app/controllers/institutions_controller.rb +++ b/app/controllers/institutions_controller.rb @@ -34,9 +34,12 @@ class InstitutionsController < ApplicationController @weekly_expense_items = Expense.where(created_at: Time.current.all_week) @monthly_expense_items = Expense.where(created_at: Time.current.all_month) - - # index is a collection action — do not attempt to load a single institution here - @students = Student.all + # Financial Summary + @total_income = Income.sum(:amount) + @total_expense = Expense.sum(:amount) + @profit = Finance.profit + # index is a collection action — do not attempt to load a single institution here + @students = Student.all end @@ -48,6 +51,8 @@ class InstitutionsController < ApplicationController # GET /institutions/1 or /institutions/1.json def show + @institution = Institution.find(params[:id]) + @students = @institution.students end # GET /institutions/contact @@ -78,6 +83,7 @@ class InstitutionsController < ApplicationController end end + # PATCH/PUT /institutions/1 or /institutions/1.json def update respond_to do |format| diff --git a/app/controllers/layouts_controller.rb b/app/controllers/layouts_controller.rb new file mode 100644 index 0000000..01bc1fa --- /dev/null +++ b/app/controllers/layouts_controller.rb @@ -0,0 +1,4 @@ +class LayoutsController < ApplicationController + def index + end +end diff --git a/app/controllers/students_controller.rb b/app/controllers/students_controller.rb index 2929b0f..ac1e47c 100644 --- a/app/controllers/students_controller.rb +++ b/app/controllers/students_controller.rb @@ -3,8 +3,8 @@ class StudentsController < ApplicationController before_action :set_institution # set_student will safely find a student either scoped to @institution or globally before_action :set_student, only: %i[ show edit update destroy ] - # ensure nested behaviour for index/new/create (these expect an institution) - before_action :ensure_institution_for_nested_actions, only: %i[index new create] + # ensure nested behaviour for new/create (these expect an institution). Allow index to show all students. + before_action :ensure_institution_for_nested_actions, only: %i[new create] # GET /students or /students.json def index diff --git a/app/helpers/cart_items_helper.rb b/app/helpers/cart_items_helper.rb new file mode 100644 index 0000000..f30f683 --- /dev/null +++ b/app/helpers/cart_items_helper.rb @@ -0,0 +1,2 @@ +module CartItemsHelper +end diff --git a/app/helpers/carts_helper.rb b/app/helpers/carts_helper.rb new file mode 100644 index 0000000..d99c380 --- /dev/null +++ b/app/helpers/carts_helper.rb @@ -0,0 +1,2 @@ +module CartsHelper +end diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb new file mode 100644 index 0000000..ab5c42b --- /dev/null +++ b/app/helpers/products_helper.rb @@ -0,0 +1,2 @@ +module ProductsHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 0d7b494..b847918 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,8 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" +import * as bootstrap from "bootstrap" import "controllers" +import "flash_messages" + +import "trix" +import "@rails/actiontext" diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js index 1213e85..5048655 100644 --- a/app/javascript/controllers/application.js +++ b/app/javascript/controllers/application.js @@ -1,6 +1,15 @@ import { Application } from "@hotwired/stimulus" +import DropdownController from "./dropdown_controller" +import HelloController from "./hello_controller" +import { definitionsFromContext } from "@hotwired/stimulus-loading" const application = Application.start() +const context = require.context("./", true, /\.js$/) +application.load(definitionsFromContext(context)) + +// Register custom controllers +application.register("dropdown", DropdownController) +application.register("hello", HelloController) // Configure Stimulus development experience application.debug = false diff --git a/app/javascript/controllers/dropdown_controller.js b/app/javascript/controllers/dropdown_controller.js new file mode 100644 index 0000000..abd56da --- /dev/null +++ b/app/javascript/controllers/dropdown_controller.js @@ -0,0 +1,12 @@ +import { Controller } from "@hotwired/stimulus" +import { Dropdown } from "bootstrap" + +export default class extends Controller { + connect() { + // Initialize all dropdowns on the page + const dropdownElementList = [].slice.call(document.querySelectorAll('.dropdown-toggle')) + dropdownElementList.map(function (dropdownToggleEl) { + return new Dropdown(dropdownToggleEl) + }) + } +} \ No newline at end of file diff --git a/app/javascript/controllers/flash_controller.js b/app/javascript/controllers/flash_controller.js new file mode 100644 index 0000000..840589b --- /dev/null +++ b/app/javascript/controllers/flash_controller.js @@ -0,0 +1,14 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + setTimeout(() => { + if (this.element) { + this.element.style.opacity = '0' + setTimeout(() => { + this.element.remove() + }, 300) // Remove after fade animation + } + }, 3000) // Start fading after 3 seconds + } +} \ No newline at end of file diff --git a/app/javascript/dropdown.js b/app/javascript/dropdown.js new file mode 100644 index 0000000..2cde8b8 --- /dev/null +++ b/app/javascript/dropdown.js @@ -0,0 +1,7 @@ +document.addEventListener('turbo:load', function() { + // Initialize all dropdowns + var dropdownElementList = [].slice.call(document.querySelectorAll('.dropdown-toggle')) + var dropdownList = dropdownElementList.map(function (dropdownToggleEl) { + return new bootstrap.Dropdown(dropdownToggleEl) + }) +}) \ No newline at end of file diff --git a/app/javascript/flash_messages.js b/app/javascript/flash_messages.js new file mode 100644 index 0000000..41f6f83 --- /dev/null +++ b/app/javascript/flash_messages.js @@ -0,0 +1,21 @@ +document.addEventListener('turbo:load', () => { + const flashMessages = document.querySelectorAll('.alert'); + flashMessages.forEach(flash => { + // Add fade-out class after 3 seconds + setTimeout(() => { + flash.classList.add('fade-out'); + // Remove element after animation completes + setTimeout(() => { + flash.remove(); + }, 500); // matches the CSS transition duration + }, 3000); + + // Optional: Allow clicking to dismiss + flash.addEventListener('click', () => { + flash.classList.add('fade-out'); + setTimeout(() => { + flash.remove(); + }, 500); + }); + }); +}); \ No newline at end of file diff --git a/app/javascript/rails_admin.js b/app/javascript/rails_admin.js new file mode 100644 index 0000000..d0c3bed --- /dev/null +++ b/app/javascript/rails_admin.js @@ -0,0 +1 @@ +import "rails_admin/src/rails_admin/base"; diff --git a/app/models/admin.rb b/app/models/admin.rb new file mode 100644 index 0000000..7a7be2f --- /dev/null +++ b/app/models/admin.rb @@ -0,0 +1,6 @@ +class Admin < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable +end diff --git a/app/models/admin_user.rb b/app/models/admin_user.rb new file mode 100644 index 0000000..9b798db --- /dev/null +++ b/app/models/admin_user.rb @@ -0,0 +1,18 @@ +class AdminUser < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, + :recoverable, :rememberable, :validatable + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, + :recoverable, :rememberable, :validatable + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, + :recoverable, :rememberable, :validatable + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, + :recoverable, :rememberable, :validatable +end diff --git a/app/models/finance.rb b/app/models/finance.rb new file mode 100644 index 0000000..e3c580d --- /dev/null +++ b/app/models/finance.rb @@ -0,0 +1,6 @@ + +class Finance + def self.profit + Income.sum(:amount) - Expense.sum(:amount) + end +end diff --git a/app/models/student.rb b/app/models/student.rb index dd27047..8e94d68 100644 --- a/app/models/student.rb +++ b/app/models/student.rb @@ -1,4 +1,6 @@ class Student < ApplicationRecord belongs_to :institution has_one_attached :photo + + end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..4756799 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,6 @@ +class User < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable +end diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb new file mode 100644 index 0000000..49ba357 --- /dev/null +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -0,0 +1,14 @@ +
attachment--<%= blob.filename.extension %>"> + <% if blob.representable? %> + <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> + <% end %> + +
+ <% if caption = blob.try(:caption) %> + <%= caption %> + <% else %> + <%= blob.filename %> + <%= number_to_human_size blob.byte_size %> + <% end %> +
+
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb new file mode 100644 index 0000000..b12dd0c --- /dev/null +++ b/app/views/devise/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

Resend confirmation instructions

+ +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
+ +
+ <%= f.submit "Resend confirmation instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 0000000..dc55f64 --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 0000000..32f4ba8 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 0000000..b41daf4 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 0000000..f667dc1 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 0000000..41e148b --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 0000000..5fbb9ff --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb new file mode 100644 index 0000000..9b486b8 --- /dev/null +++ b/app/views/devise/passwords/new.html.erb @@ -0,0 +1,16 @@ +

Forgot your password?

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Send me reset password instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb new file mode 100644 index 0000000..b82e336 --- /dev/null +++ b/app/views/devise/registrations/edit.html.erb @@ -0,0 +1,43 @@ +

Edit <%= resource_name.to_s.humanize %>

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + +
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "new-password" %> + <% if @minimum_password_length %> +
+ <%= @minimum_password_length %> characters minimum + <% end %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Update" %> +
+<% end %> + +

Cancel my account

+ +
Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
+ +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb new file mode 100644 index 0000000..d655b66 --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,29 @@ +

Sign up

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Sign up" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 0000000..5ede964 --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,26 @@ +

Log in

+ +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %> +
+ + <% if devise_mapping.rememberable? %> +
+ <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
+ <% end %> + +
+ <%= f.submit "Log in" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb new file mode 100644 index 0000000..cabfe30 --- /dev/null +++ b/app/views/devise/shared/_error_messages.html.erb @@ -0,0 +1,15 @@ +<% if resource.errors.any? %> +
+

+ <%= I18n.t("errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase) + %> +

+ +
+<% end %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb new file mode 100644 index 0000000..7a75304 --- /dev/null +++ b/app/views/devise/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
+ <% end %> +<% end %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb new file mode 100644 index 0000000..ffc34de --- /dev/null +++ b/app/views/devise/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

Resend unlock instructions

+ +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Resend unlock instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/exclusive_traditional_records/_exclusive_traditional_record.html.erb b/app/views/exclusive_traditional_records/_exclusive_traditional_record.html.erb index 28bdfbe..d4e4530 100644 --- a/app/views/exclusive_traditional_records/_exclusive_traditional_record.html.erb +++ b/app/views/exclusive_traditional_records/_exclusive_traditional_record.html.erb @@ -21,6 +21,7 @@ <% else %> No PDF file uploaded. <% end %> + diff --git a/app/views/expenses/report.html.erb b/app/views/expenses/report.html.erb index 81714e5..5a555f2 100644 --- a/app/views/expenses/report.html.erb +++ b/app/views/expenses/report.html.erb @@ -156,7 +156,7 @@
-

Today's Expenses

+

Today's Expenses

@@ -180,7 +180,7 @@
-

This Week's Expenses

+

This Week's Expenses

AmountDate
@@ -204,7 +204,7 @@
-

This Month's Expenses

+

This Month's Expenses

AmountDate
diff --git a/app/views/incomes/report.html.erb b/app/views/incomes/report.html.erb index 2f1648d..56acba0 100644 --- a/app/views/incomes/report.html.erb +++ b/app/views/incomes/report.html.erb @@ -154,7 +154,7 @@
-

Today's Income

+

Today's Income

AmountDate
@@ -178,7 +178,7 @@
-

This Week's Income

+

This Week's Income

AmountDate
@@ -202,7 +202,7 @@
-

This Month's Income

+

This Month's Income

AmountDate
diff --git a/app/views/institutions/_form.html.erb b/app/views/institutions/_form.html.erb index 8301f5e..61ab3ba 100644 --- a/app/views/institutions/_form.html.erb +++ b/app/views/institutions/_form.html.erb @@ -17,8 +17,8 @@
- <%= form.label :institution_type, style: "display: block" %> - <%= form.text_field :institution_type %> + <%= form.label :manager_name, style: "display: block" %> + <%= form.text_field :manager_name %>
diff --git a/app/views/institutions/index.html.erb b/app/views/institutions/index.html.erb index ee92300..edbe97e 100644 --- a/app/views/institutions/index.html.erb +++ b/app/views/institutions/index.html.erb @@ -5,8 +5,7 @@ html, body { margin: 0; padding: 0; font-family: "Poppins", sans-serif; - /* Dark gradient background */ - background: linear-gradient(135deg, #0f1724, #1e293b); /* deep navy/charcoal */ + background: linear-gradient(135deg, #0f1724, #1e293b); color: #e6eef6; } @@ -14,7 +13,6 @@ html, body { min-height: 100vh; display: flex; flex-direction: column; - justify-content: flex-start; } .container { @@ -27,18 +25,17 @@ html, body { /* ---------------- Header ---------------- */ .page-header { text-align: center; - margin-bottom: 15px; + margin-bottom: 30px; } .page-header h1 { font-weight: 700; - font-size: 3rem; - background: linear-gradient(90deg, #1e88e5, #43a047, #1e88e5); + font-size: 2.9rem; + background: linear-gradient(90deg, #1e88e5, #43a047, #fdd835, #fb8c00, #e53935); background-size: 200% auto; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: gradient-move 3s linear infinite; - margin-bottom: 5px; } @keyframes gradient-move { @@ -47,156 +44,194 @@ html, body { } .logo-img { - width: 100px; - height: 100px; - object-fit: cover; + width: 120px; + height: 120px; border-radius: 50%; - box-shadow: 0 4px 10px rgba(0,0,0,0.15); - margin-bottom: 10px; + box-shadow: 0 6px 16px rgba(0,0,0,0.3); + margin-bottom: 15px; } .divider { - width: 80px; + width: 100px; height: 4px; background: linear-gradient(to right, #1e88e5, #43a047); margin: 15px auto; border-radius: 3px; } -/* ---------------- Stats Cards ---------------- */ +/* ---------------- Stats Row ---------------- */ .stats-row { display: flex; flex-wrap: wrap; justify-content: center; - gap: 12px; - margin-bottom: 24px; + gap: 15px; + margin-bottom: 35px; } .stat-card { flex: 0 0 auto; - width: 140px; /* compact cards */ - margin: 6px; + width: 140px; padding: 12px 16px; - background: rgba(255,255,255,0.03); - border: 1px solid rgba(255,255,255,0.06); - border-radius: 12px; - box-shadow: 0 6px 18px rgba(2,6,23,0.6); - transition: transform 0.18s ease, box-shadow 0.18s ease; + background: rgba(255,255,255,0.05); + border-radius: 15px; text-align: center; + box-shadow: 0 6px 20px rgba(0,0,0,0.3); + transition: 0.3s ease; } .stat-card:hover { - transform: translateY(-3px); - box-shadow: 0 10px 22px rgba(2,6,23,0.7); + transform: translateY(-4px); + box-shadow: 0 10px 25px rgba(0,0,0,0.5); } .stat-label { font-weight: 600; - color: rgba(230,238,246,0.8); - margin-bottom: 6px; - font-size: 0.8rem; + font-size: 0.85rem; + color: #cfd8dc; } .stat-value { - font-size: 1.4rem; /* smaller count */ + font-size: 1.5rem; font-weight: 700; - color: #7dd3fc; /* soft cyan */ + color: #7dd3fc; } /* ---------------- Glass Cards ---------------- */ .glass-card { - background: rgba(255,255,255,0.9); + background: rgba(255,255,255,0.1); backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); border-radius: 18px; - box-shadow: 0 10px 25px rgba(0,0,0,0.1); padding: 25px; - transition: 0.3s ease; width: 100%; + transition: 0.3s ease; } .glass-card:hover { - transform: translateY(-3px); - box-shadow: 0 12px 30px rgba(0,0,0,0.15); + transform: translateY(-5px); + box-shadow: 0 12px 40px rgba(0,0,0,0.25); } /* ---------------- Section Titles ---------------- */ .section-title { font-weight: 700; - font-size: 1.5rem; + font-size: 1.4rem; margin-bottom: 20px; text-align: center; } /* ---------------- Item Cards ---------------- */ .item-card { - border-radius: 15px; - background: #ffffff; - border: 1px solid #e0e0e0; - padding: 15px; + border-radius: 12px; + background: rgba(255,255,255,0.95); + border: 1px solid rgba(0,0,0,0.1); + padding: 12px 15px; + margin-bottom: 10px; transition: all 0.3s ease; - display: flex; - flex-direction: column; - justify-content: space-between; } .item-card:hover { transform: scale(1.03); - background: #f9f9f9; - box-shadow: 0 8px 20px rgba(0,0,0,0.1); + box-shadow: 0 6px 20px rgba(0,0,0,0.15); } .item-title { - font-size: 1.1rem; + font-size: 1.05rem; font-weight: 600; - color: #0d47a1; + color: #1e88e5; margin-bottom: 5px; } .item-desc { - font-size: 0.95rem; + font-size: 0.9rem; color: #37474f; - margin-bottom: 10px; } .item-footer { text-align: right; + margin-top: 8px; } /* ---------------- Buttons ---------------- */ .btn-custom { - border-radius: 25px; + padding: 10px 18px; + border-radius: 12px; font-weight: 600; - padding: 7px 18px; transition: 0.3s ease; + text-decoration: none; + display: inline-block; } -.btn-primary-custom { background-color: #1e88e5; color: #fff; } -.btn-primary-custom:hover { background-color: #1565c0; } +.btn-primary-custom { background: #1e88e5; color: #fff; } +.btn-primary-custom:hover { background: #1565c0; transform: translateY(-2px); } -.btn-success-custom { background-color: #43a047; color: #fff; } -.btn-success-custom:hover { background-color: #2e7d32; } +.btn-success-custom { background: #43a047; color: #fff; } +.btn-success-custom:hover { background: #2e7d32; transform: translateY(-2px); } -/* ---------------- Ziyara Section ---------------- */ -.ziyara-card { - border-top: 5px solid #fbc02d; - background: rgba(255,255,240,0.95); +/* ---------------- Expense & Income Cards ---------------- */ +.expense-card, .income-card { + background: rgba(255,255,255,0.1); + backdrop-filter: blur(12px); + border-radius: 18px; + padding: 25px; + width: 100%; + text-align: center; + transition: transform 0.3s ease, box-shadow 0.3s ease; } -.ziyara-title { - background: linear-gradient(90deg, #fdd835, #fbc02d, #f57f17); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - animation: gradient-move 4s linear infinite; +.expense-card:hover, .income-card:hover { + transform: translateY(-5px); + box-shadow: 0 12px 40px rgba(0,0,0,0.25); +} + +.expense-card h1, .income-card h1 { + font-size: 1.6rem; + margin-bottom: 15px; + font-weight: 700; + color: #ffffff; +} + +.expense-card .btn, .income-card .btn { + width: 80%; + margin: 0 auto; + padding: 10px 0; } /* ---------------- Responsive ---------------- */ @media (max-width: 992px) { .stats-row { flex-direction: column; align-items: center; } + .row.g-4 { flex-direction: column; } + .col-lg-6, .col-lg-5 { max-width: 95%; margin: 0 auto; } } +.finance-card { + background: rgba(255,255,255,0.08); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + border-radius: 20px; + border: 1px solid rgba(255,255,255,0.2); + box-shadow: 0 10px 30px rgba(0,0,0,0.2); + padding: 30px; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.finance-card:hover { + transform: translateY(-5px); + box-shadow: 0 15px 40px rgba(0,0,0,0.25); +} + +.stat-value { + font-size: 1.6rem; + font-weight: 700; + color: #facc15; /* golden color for finance numbers */ + margin: 10px 0; + text-align: center; +} +
+
AmountDate