├── .dockerignore ├── .gitattributes ├── .gitignore ├── .ruby-version ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Procfile.dev ├── README.md ├── Rakefile ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── dotsIcon.svg │ │ ├── eye-off.svg │ │ └── eye.svg │ └── stylesheets │ │ ├── application.css │ │ └── application.tailwind.css ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── reasons_controller.rb │ └── storefronts_controller.rb ├── helpers │ ├── application_helper.rb │ └── storefronts_helper.rb ├── javascript │ ├── application.js │ └── controllers │ │ ├── application.js │ │ ├── drag_controller.js │ │ ├── index.js │ │ ├── modal_controller.js │ │ └── nested_form_controller.js ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── reason.rb │ └── storefront.rb └── views │ ├── layouts │ └── application.html.erb │ └── storefronts │ ├── _form.html.erb │ ├── _reason.html.erb │ ├── _storefront.html.erb │ ├── _storefront.json.jbuilder │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ └── show.json.jbuilder ├── bin ├── bundle ├── dev ├── docker-entrypoint ├── importmap ├── rails ├── rake └── setup ├── config.ru ├── config ├── application.rb ├── boot.rb ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── importmap.rb ├── initializers │ ├── assets.rb │ ├── content_security_policy.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── permissions_policy.rb │ ├── simple_form.rb │ └── simple_form_tailwind.rb ├── locales │ ├── en.yml │ └── simple_form.en.yml ├── puma.rb ├── routes.rb └── tailwind.config.js ├── db ├── migrate │ ├── 20231221221744_create_storefronts.rb │ └── 20231221221802_create_reasons.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── storage ├── .keep ├── development.sqlite3 ├── development.sqlite3-shm └── development.sqlite3-wal ├── test ├── application_system_test_case.rb ├── controllers │ ├── .keep │ └── storefronts_controller_test.rb ├── fixtures │ ├── files │ │ └── .keep │ ├── reasons.yml │ └── storefronts.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── models │ ├── .keep │ ├── reason_test.rb │ └── storefront_test.rb ├── system │ ├── .keep │ └── storefronts_test.rb └── test_helper.rb ├── tmp ├── .keep ├── pids │ └── .keep └── storage │ └── .keep └── vendor ├── .keep └── javascript └── .keep /.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | 6 | # Ignore bundler config. 7 | /.bundle 8 | 9 | # Ignore all environment files (except templates). 10 | /.env* 11 | !/.env*.erb 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore pidfiles, but keep the directory. 24 | /tmp/pids/* 25 | !/tmp/pids/.keep 26 | 27 | # Ignore storage (uploaded files in development and any SQLite databases). 28 | /storage/* 29 | !/storage/.keep 30 | /tmp/storage/* 31 | !/tmp/storage/.keep 32 | 33 | # Ignore assets. 34 | /node_modules/ 35 | /app/assets/builds/* 36 | !/app/assets/builds/.keep 37 | /public/assets 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | /.idea/* 7 | 8 | # Ignore bundler config. 9 | /.bundle 10 | 11 | # Ignore all environment files (except templates). 12 | /.env* 13 | !/.env*.erb 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Ignore pidfiles, but keep the directory. 22 | /tmp/pids/* 23 | !/tmp/pids/ 24 | !/tmp/pids/.keep 25 | 26 | # Ignore storage (uploaded files in development and any SQLite databases). 27 | /storage/test.sqlite3 28 | !/storage/.keep 29 | /tmp/storage/* 30 | !/tmp/storage/ 31 | !/tmp/storage/.keep 32 | 33 | /public/assets 34 | 35 | # Ignore master key for decrypting credentials and more. 36 | /config/master.key 37 | 38 | /app/assets/builds/* 39 | !/app/assets/builds/.keep 40 | /node_modules -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1 2 | 3 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile 4 | ARG RUBY_VERSION=3.0.1 5 | FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base 6 | 7 | # Rails app lives here 8 | WORKDIR /rails 9 | 10 | # Set production environment 11 | ENV RAILS_ENV="production" \ 12 | BUNDLE_DEPLOYMENT="1" \ 13 | BUNDLE_PATH="/usr/local/bundle" \ 14 | BUNDLE_WITHOUT="development" 15 | 16 | 17 | # Throw-away build stage to reduce size of final image 18 | FROM base as build 19 | 20 | # Install packages needed to build gems 21 | RUN apt-get update -qq && \ 22 | apt-get install --no-install-recommends -y build-essential git pkg-config 23 | 24 | # Install application gems 25 | COPY Gemfile Gemfile.lock ./ 26 | RUN bundle install && \ 27 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ 28 | bundle exec bootsnap precompile --gemfile 29 | 30 | # Copy application code 31 | COPY . . 32 | 33 | # Precompile bootsnap code for faster boot times 34 | RUN bundle exec bootsnap precompile app/ lib/ 35 | 36 | # Precompiling assets for production without requiring secret RAILS_MASTER_KEY 37 | RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile 38 | 39 | 40 | # Final stage for app image 41 | FROM base 42 | 43 | # Install packages needed for deployment 44 | RUN apt-get update -qq && \ 45 | apt-get install --no-install-recommends -y curl libsqlite3-0 && \ 46 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 47 | 48 | # Copy built artifacts: gems, application 49 | COPY --from=build /usr/local/bundle /usr/local/bundle 50 | COPY --from=build /rails /rails 51 | 52 | # Run and own only the runtime files as a non-root user for security 53 | RUN useradd rails --create-home --shell /bin/bash && \ 54 | chown -R rails:rails db log storage tmp 55 | USER rails:rails 56 | 57 | # Entrypoint prepares the database. 58 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 59 | 60 | # Start the server by default, this can be overwritten at runtime 61 | EXPOSE 3000 62 | CMD ["./bin/rails", "server"] 63 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby "3.2.2" 4 | 5 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" 6 | gem "rails", "~> 7.1.2" 7 | 8 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] 9 | gem "sprockets-rails" 10 | 11 | # Use sqlite3 as the database for Active Record 12 | gem "sqlite3", "~> 1.4" 13 | 14 | # Use the Puma web server [https://github.com/puma/puma] 15 | gem "puma", ">= 5.0" 16 | 17 | # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] 18 | gem "importmap-rails" 19 | 20 | # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] 21 | gem "turbo-rails" 22 | 23 | # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] 24 | gem "stimulus-rails" 25 | 26 | # Use Tailwind CSS [https://github.com/rails/tailwindcss-rails] 27 | gem "tailwindcss-rails" 28 | 29 | # Build JSON APIs with ease [https://github.com/rails/jbuilder] 30 | gem "jbuilder" 31 | 32 | gem 'simple_form' 33 | gem 'acts_as_list' 34 | 35 | # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] 36 | # gem "kredis" 37 | 38 | # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] 39 | # gem "bcrypt", "~> 3.1.7" 40 | 41 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 42 | gem "tzinfo-data", platforms: %i[ mswin mswin64 mingw x64_mingw jruby ] 43 | 44 | # Reduces boot times through caching; required in config/boot.rb 45 | gem "bootsnap", require: false 46 | 47 | group :development, :test do 48 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem 49 | gem "debug", platforms: %i[ mri mswin mswin64 mingw x64_mingw ] 50 | end 51 | 52 | group :development do 53 | # Use console on exceptions pages [https://github.com/rails/web-console] 54 | gem "web-console" 55 | 56 | # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler] 57 | # gem "rack-mini-profiler" 58 | 59 | # Speed up commands on slow machines / big apps [https://github.com/rails/spring] 60 | # gem "spring" 61 | end 62 | 63 | group :test do 64 | # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] 65 | gem "capybara" 66 | gem "selenium-webdriver" 67 | end 68 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (7.1.2) 5 | actionpack (= 7.1.2) 6 | activesupport (= 7.1.2) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | zeitwerk (~> 2.6) 10 | actionmailbox (7.1.2) 11 | actionpack (= 7.1.2) 12 | activejob (= 7.1.2) 13 | activerecord (= 7.1.2) 14 | activestorage (= 7.1.2) 15 | activesupport (= 7.1.2) 16 | mail (>= 2.7.1) 17 | net-imap 18 | net-pop 19 | net-smtp 20 | actionmailer (7.1.2) 21 | actionpack (= 7.1.2) 22 | actionview (= 7.1.2) 23 | activejob (= 7.1.2) 24 | activesupport (= 7.1.2) 25 | mail (~> 2.5, >= 2.5.4) 26 | net-imap 27 | net-pop 28 | net-smtp 29 | rails-dom-testing (~> 2.2) 30 | actionpack (7.1.2) 31 | actionview (= 7.1.2) 32 | activesupport (= 7.1.2) 33 | nokogiri (>= 1.8.5) 34 | racc 35 | rack (>= 2.2.4) 36 | rack-session (>= 1.0.1) 37 | rack-test (>= 0.6.3) 38 | rails-dom-testing (~> 2.2) 39 | rails-html-sanitizer (~> 1.6) 40 | actiontext (7.1.2) 41 | actionpack (= 7.1.2) 42 | activerecord (= 7.1.2) 43 | activestorage (= 7.1.2) 44 | activesupport (= 7.1.2) 45 | globalid (>= 0.6.0) 46 | nokogiri (>= 1.8.5) 47 | actionview (7.1.2) 48 | activesupport (= 7.1.2) 49 | builder (~> 3.1) 50 | erubi (~> 1.11) 51 | rails-dom-testing (~> 2.2) 52 | rails-html-sanitizer (~> 1.6) 53 | activejob (7.1.2) 54 | activesupport (= 7.1.2) 55 | globalid (>= 0.3.6) 56 | activemodel (7.1.2) 57 | activesupport (= 7.1.2) 58 | activerecord (7.1.2) 59 | activemodel (= 7.1.2) 60 | activesupport (= 7.1.2) 61 | timeout (>= 0.4.0) 62 | activestorage (7.1.2) 63 | actionpack (= 7.1.2) 64 | activejob (= 7.1.2) 65 | activerecord (= 7.1.2) 66 | activesupport (= 7.1.2) 67 | marcel (~> 1.0) 68 | activesupport (7.1.2) 69 | base64 70 | bigdecimal 71 | concurrent-ruby (~> 1.0, >= 1.0.2) 72 | connection_pool (>= 2.2.5) 73 | drb 74 | i18n (>= 1.6, < 2) 75 | minitest (>= 5.1) 76 | mutex_m 77 | tzinfo (~> 2.0) 78 | addressable (2.8.6) 79 | public_suffix (>= 2.0.2, < 6.0) 80 | base64 (0.2.0) 81 | bigdecimal (3.1.5) 82 | bindex (0.8.1) 83 | bootsnap (1.17.0) 84 | msgpack (~> 1.2) 85 | builder (3.2.4) 86 | capybara (3.39.2) 87 | addressable 88 | matrix 89 | mini_mime (>= 0.1.3) 90 | nokogiri (~> 1.8) 91 | rack (>= 1.6.0) 92 | rack-test (>= 0.6.3) 93 | regexp_parser (>= 1.5, < 3.0) 94 | xpath (~> 3.2) 95 | concurrent-ruby (1.2.2) 96 | connection_pool (2.4.1) 97 | crass (1.0.6) 98 | date (3.3.4) 99 | debug (1.9.0) 100 | irb (~> 1.10) 101 | reline (>= 0.3.8) 102 | drb (2.2.0) 103 | ruby2_keywords 104 | erubi (1.12.0) 105 | globalid (1.2.1) 106 | activesupport (>= 6.1) 107 | i18n (1.14.1) 108 | concurrent-ruby (~> 1.0) 109 | importmap-rails (1.2.3) 110 | actionpack (>= 6.0.0) 111 | activesupport (>= 6.0.0) 112 | railties (>= 6.0.0) 113 | io-console (0.7.1) 114 | irb (1.11.0) 115 | rdoc 116 | reline (>= 0.3.8) 117 | jbuilder (2.11.5) 118 | actionview (>= 5.0.0) 119 | activesupport (>= 5.0.0) 120 | loofah (2.22.0) 121 | crass (~> 1.0.2) 122 | nokogiri (>= 1.12.0) 123 | mail (2.8.1) 124 | mini_mime (>= 0.1.1) 125 | net-imap 126 | net-pop 127 | net-smtp 128 | marcel (1.0.2) 129 | matrix (0.4.2) 130 | mini_mime (1.1.5) 131 | minitest (5.20.0) 132 | msgpack (1.7.2) 133 | mutex_m (0.2.0) 134 | net-imap (0.4.8) 135 | date 136 | net-protocol 137 | net-pop (0.1.2) 138 | net-protocol 139 | net-protocol (0.2.2) 140 | timeout 141 | net-smtp (0.4.0) 142 | net-protocol 143 | nio4r (2.7.0) 144 | nokogiri (1.15.5-aarch64-linux) 145 | racc (~> 1.4) 146 | nokogiri (1.15.5-arm-linux) 147 | racc (~> 1.4) 148 | nokogiri (1.15.5-arm64-darwin) 149 | racc (~> 1.4) 150 | nokogiri (1.15.5-x86-linux) 151 | racc (~> 1.4) 152 | nokogiri (1.15.5-x86_64-darwin) 153 | racc (~> 1.4) 154 | nokogiri (1.15.5-x86_64-linux) 155 | racc (~> 1.4) 156 | psych (5.1.2) 157 | stringio 158 | public_suffix (5.0.4) 159 | puma (6.4.0) 160 | nio4r (~> 2.0) 161 | racc (1.7.3) 162 | rack (3.0.8) 163 | rack-session (2.0.0) 164 | rack (>= 3.0.0) 165 | rack-test (2.1.0) 166 | rack (>= 1.3) 167 | rackup (2.1.0) 168 | rack (>= 3) 169 | webrick (~> 1.8) 170 | rails (7.1.2) 171 | actioncable (= 7.1.2) 172 | actionmailbox (= 7.1.2) 173 | actionmailer (= 7.1.2) 174 | actionpack (= 7.1.2) 175 | actiontext (= 7.1.2) 176 | actionview (= 7.1.2) 177 | activejob (= 7.1.2) 178 | activemodel (= 7.1.2) 179 | activerecord (= 7.1.2) 180 | activestorage (= 7.1.2) 181 | activesupport (= 7.1.2) 182 | bundler (>= 1.15.0) 183 | railties (= 7.1.2) 184 | rails-dom-testing (2.2.0) 185 | activesupport (>= 5.0.0) 186 | minitest 187 | nokogiri (>= 1.6) 188 | rails-html-sanitizer (1.6.0) 189 | loofah (~> 2.21) 190 | nokogiri (~> 1.14) 191 | railties (7.1.2) 192 | actionpack (= 7.1.2) 193 | activesupport (= 7.1.2) 194 | irb 195 | rackup (>= 1.0.0) 196 | rake (>= 12.2) 197 | thor (~> 1.0, >= 1.2.2) 198 | zeitwerk (~> 2.6) 199 | rake (13.1.0) 200 | rdoc (6.6.2) 201 | psych (>= 4.0.0) 202 | regexp_parser (2.8.3) 203 | reline (0.4.1) 204 | io-console (~> 0.5) 205 | rexml (3.2.6) 206 | ruby2_keywords (0.0.5) 207 | rubyzip (2.3.2) 208 | selenium-webdriver (4.16.0) 209 | rexml (~> 3.2, >= 3.2.5) 210 | rubyzip (>= 1.2.2, < 3.0) 211 | websocket (~> 1.0) 212 | simple_form (5.3.0) 213 | actionpack (>= 5.2) 214 | activemodel (>= 5.2) 215 | sprockets (4.2.1) 216 | concurrent-ruby (~> 1.0) 217 | rack (>= 2.2.4, < 4) 218 | sprockets-rails (3.4.2) 219 | actionpack (>= 5.2) 220 | activesupport (>= 5.2) 221 | sprockets (>= 3.0.0) 222 | sqlite3 (1.6.9-aarch64-linux) 223 | sqlite3 (1.6.9-arm-linux) 224 | sqlite3 (1.6.9-arm64-darwin) 225 | sqlite3 (1.6.9-x86-linux) 226 | sqlite3 (1.6.9-x86_64-darwin) 227 | sqlite3 (1.6.9-x86_64-linux) 228 | stimulus-rails (1.3.0) 229 | railties (>= 6.0.0) 230 | stringio (3.1.0) 231 | tailwindcss-rails (2.1.0) 232 | railties (>= 6.0.0) 233 | tailwindcss-rails (2.1.0-aarch64-linux) 234 | railties (>= 6.0.0) 235 | tailwindcss-rails (2.1.0-arm-linux) 236 | railties (>= 6.0.0) 237 | tailwindcss-rails (2.1.0-arm64-darwin) 238 | railties (>= 6.0.0) 239 | tailwindcss-rails (2.1.0-x86_64-darwin) 240 | railties (>= 6.0.0) 241 | tailwindcss-rails (2.1.0-x86_64-linux) 242 | railties (>= 6.0.0) 243 | thor (1.3.0) 244 | timeout (0.4.1) 245 | turbo-rails (1.5.0) 246 | actionpack (>= 6.0.0) 247 | activejob (>= 6.0.0) 248 | railties (>= 6.0.0) 249 | tzinfo (2.0.6) 250 | concurrent-ruby (~> 1.0) 251 | web-console (4.2.1) 252 | actionview (>= 6.0.0) 253 | activemodel (>= 6.0.0) 254 | bindex (>= 0.4.0) 255 | railties (>= 6.0.0) 256 | webrick (1.8.1) 257 | websocket (1.2.10) 258 | websocket-driver (0.7.6) 259 | websocket-extensions (>= 0.1.0) 260 | websocket-extensions (0.1.5) 261 | xpath (3.2.0) 262 | nokogiri (~> 1.8) 263 | zeitwerk (2.6.12) 264 | 265 | PLATFORMS 266 | aarch64-linux 267 | arm-linux 268 | arm64-darwin 269 | x86-linux 270 | x86_64-darwin 271 | x86_64-linux 272 | 273 | DEPENDENCIES 274 | bootsnap 275 | capybara 276 | debug 277 | importmap-rails 278 | jbuilder 279 | puma (>= 5.0) 280 | rails (~> 7.1.2) 281 | selenium-webdriver 282 | simple_form 283 | sprockets-rails 284 | sqlite3 (~> 1.4) 285 | stimulus-rails 286 | tailwindcss-rails 287 | turbo-rails 288 | tzinfo-data 289 | web-console 290 | 291 | RUBY VERSION 292 | ruby 3.2.2p53 293 | 294 | BUNDLED WITH 295 | 2.5.2 296 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: env RUBY_DEBUG_OPEN=true bin/rails server -p 3000 2 | css: bin/rails tailwindcss:watch 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Invisible Commerce Rails Frontend Test 2 | 3 | Thank you for applying to Invisible Commerce! This is a small test to help us get a sense of your skills and how you approach problems. 4 | 5 | ## The Task 6 | 7 | We have a simple Rails app that allows editing of custom "Return Reasons". We'd like you to take the [Figma design](https://www.figma.com/file/ZrgctOIZqB7CMDZwkKcQrs/Front-Engineer-Exercise?type=design&node-id=1%3A384&mode=dev) and implement it in the app. 8 | 9 | This involves a few things: 10 | 1. Update the application layout to match the design (using Tailwind CSS, already included in the app) 11 | 2. Update the page template to match the design (`app/views/storefronts/edit.html.erb`) 12 | 3. Move the nested form for each Reason into a modal, and update the design to match 13 | 4. Enable drag-and-drop reordering of the Reasons 14 | 15 | ## Getting Started 16 | 17 | We are using Ruby 3.2.2 and Rails 7.1.2 in this project. Make sure your environment is set up to match. 18 | 19 | 1. Fork this repo and clone it locally 20 | 2. Run `bundle install` 21 | 3. Run `./bin/dev` 22 | 4. Visit `http://localhost:3000/` (which will point to `/storefronts/1/edit`) 23 | 24 | The included SQLite database has been pre-populated a single Storefront with 7 Reasons, which matches the Figma design. 25 | You can add more Reasons by clicking the "Add Reason" button and then saving or delete them by clicking the "Remove" button and saving. 26 | 27 | ## Notes 28 | 29 | - For the forms, we are using `simple_form` gem and there is an included `simple_form_tailwind.rb` initializer that you can use to customize the markup 30 | - There is already a `modal` Stimulus controller included in the app, which you can use to implement the modal form 31 | - For CSS portions, you are required to use Tailwind CSS 32 | - For JS portions, you are required to use Stimulus/Hotwire/Turbo 33 | - We will be evaluating your work based on the quality of the code, the accuracy of the implementation, and the attention to detail in the design 34 | - We will also be evaluating the "Railsyness" of your code, so please try to stick to the idioms and conventions of the framework 35 | 36 | ## Submitting Your Work 37 | 38 | When you're done, please send us a link to your fork of this repo. 39 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/app/assets/builds/.keep -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../javascript .js 4 | //= link_tree ../../../vendor/javascript .js 5 | //= link_tree ../builds 6 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/dotsIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/assets/images/eye-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/assets/images/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer utilities { 6 | body { 7 | @apply bg-black; 8 | } 9 | } 10 | 11 | @layer components { 12 | .btn-primary { 13 | @apply py-2 px-4 bg-yellow-400; 14 | } 15 | .btn { 16 | @apply py-2 px-4 bg-gray-200; 17 | } 18 | .btn-danger { 19 | @apply py-2 px-4 bg-red-200; 20 | } 21 | } 22 | 23 | .h1 { 24 | @apply font-semibold text-2xl; 25 | } 26 | 27 | .home { 28 | @apply shadow-md border-2 border-red-500; 29 | } 30 | 31 | .footer { 32 | width: calc(100% - 250px); 33 | @apply fixed bottom-0 right-0 bg-white p-2 md:p-4 flex items-center justify-between shadow; 34 | } 35 | 36 | .footer .p { 37 | @apply text-base font-medium; 38 | } 39 | .footer .btn { 40 | background:rgba(255, 201, 51, 1); 41 | padding:14px, 24px, 14px, 24px; 42 | border-radius:8px; 43 | -webkit-border-radius:8px; 44 | -moz-border-radius:8px; 45 | -ms-border-radius:8px; 46 | -o-border-radius:8px; 47 | } 48 | 49 | .text { 50 | color: var(--Secondary-Main, #3164F6); 51 | font-family: Inter; 52 | font-size: 16px; 53 | font-style: normal; 54 | font-weight: 600; 55 | line-height: 140%; /* 22.4px */ 56 | } 57 | 58 | 59 | .ul{ 60 | background-color: #F8F8FB; 61 | border:1px solid #E3E3E3; 62 | border-radius: 8px; 63 | -webkit-border-radius: 8px; 64 | -moz-border-radius: 8px; 65 | -ms-border-radius: 8px; 66 | -o-border-radius: 8px; 67 | } 68 | 69 | .list { 70 | border-top: 1px solid #E3E3E3; 71 | background-color: #F8F8FB; 72 | border-bottom-left-radius: 8px; 73 | border-bottom-right-radius: 8px; 74 | } 75 | 76 | .modal{ 77 | display: flex; 78 | align-items: center; 79 | justify-content: space-between; 80 | padding: 8px 0px; 81 | border-bottom: 1px solid rgba(227, 230, 232, 1); 82 | } 83 | .modalHeading{ 84 | color: var(--Secondary-Main, black); 85 | font-family: Inter; 86 | font-size: 24px; 87 | font-style: normal; 88 | font-weight: 600; 89 | } 90 | 91 | .hint{ 92 | color: var(--Neutral-Dark-Grey, #292929); 93 | font-family: Inter; 94 | font-size: 14px; 95 | font-style: normal; 96 | font-weight: 400; 97 | line-height: 140%; /* 19.6px */ 98 | letter-spacing: -0.154px; 99 | margin-top: -10px; 100 | margin-bottom: 20px; 101 | max-width: 544px; 102 | } 103 | 104 | .eyeBox{ 105 | display: flex; 106 | align-items: center; 107 | margin-bottom: 10px; 108 | margin-top: 10px; 109 | } 110 | 111 | .eyeText{ 112 | color: var(--Neutral-Dark-Grey, #292929); 113 | /* Body Large/Semi Bold */ 114 | font-family: Inter; 115 | font-size: 16px; 116 | font-style: normal; 117 | font-weight: 600; 118 | line-height: 140%; 119 | } 120 | 121 | .eyeHint{ 122 | margin-left: 30px; 123 | } 124 | 125 | .dashboard-form-fields{ 126 | margin-top: 20px; 127 | } 128 | 129 | 130 | .continue{ 131 | border-radius: 8px; 132 | background: #FFC933; 133 | display: flex; 134 | width: 95px; 135 | padding: 8px 24px; 136 | justify-content: center; 137 | align-items: center; 138 | gap: 4px; 139 | } 140 | 141 | .remove{ 142 | display: flex; 143 | width: 95px; 144 | padding: 8px 24px; 145 | justify-content: center; 146 | align-items: center; 147 | gap: 4px; 148 | border-radius: 8px; 149 | border: 2px solid var(--Additional-Error, #EB4747); 150 | color:#EB4747; 151 | -webkit-border-radius: 8px; 152 | -moz-border-radius: 8px; 153 | -ms-border-radius: 8px; 154 | -o-border-radius: 8px; 155 | } 156 | 157 | .buttonDiv{ 158 | display: flex; 159 | align-items: center; 160 | justify-content: space-between; 161 | padding: 8px 0px; 162 | } 163 | 164 | .mainDiv{ 165 | max-width: 750px; 166 | margin: auto; 167 | } 168 | 169 | .edit{ 170 | color: var(--Secondary-Main, #3164F6); 171 | /* Body Large/Semi Bold */ 172 | font-family: Inter; 173 | font-size: 16px; 174 | font-style: normal; 175 | font-weight: 600; 176 | } 177 | 178 | .abbr:where([title]){ 179 | display: none !important; 180 | } 181 | 182 | 183 | abbr{ 184 | display:none !important; 185 | } -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/reasons_controller.rb: -------------------------------------------------------------------------------- 1 | class ReasonsController < ApplicationController 2 | before_action :set_reason, only: %i[update_order show] 3 | 4 | def update_order 5 | @reason.insert_at(params[:ordering].to_i) if @reason.present? 6 | end 7 | 8 | def show 9 | render json: @reason 10 | rescue ActiveRecord::RecordNotFound 11 | render json: { error: 'Reason not found' }, status: :not_found 12 | end 13 | 14 | private 15 | 16 | def set_reason 17 | @reason = Reason.find(params[:id]) 18 | end 19 | end -------------------------------------------------------------------------------- /app/controllers/storefronts_controller.rb: -------------------------------------------------------------------------------- 1 | class StorefrontsController < ApplicationController 2 | before_action :set_storefront, only: %i[ show edit update destroy ] 3 | 4 | # GET /storefronts or /storefronts.json 5 | def index 6 | @storefronts = Storefront.all 7 | end 8 | 9 | # GET /storefronts/1 or /storefronts/1.json 10 | def show 11 | end 12 | 13 | # GET /storefronts/new 14 | def new 15 | @storefront = Storefront.new 16 | end 17 | 18 | # GET /storefronts/1/edit 19 | def edit 20 | end 21 | 22 | # POST /storefronts or /storefronts.json 23 | def create 24 | @storefront = Storefront.new(storefront_params) 25 | 26 | respond_to do |format| 27 | if @storefront.save 28 | format.html { redirect_to storefront_url(@storefront), notice: "Storefront was successfully created." } 29 | format.json { render :show, status: :created, location: @storefront } 30 | else 31 | format.html { render :new, status: :unprocessable_entity } 32 | format.json { render json: @storefront.errors, status: :unprocessable_entity } 33 | end 34 | end 35 | end 36 | 37 | # PATCH/PUT /storefronts/1 or /storefronts/1.json 38 | def update 39 | respond_to do |format| 40 | if @storefront.update(storefront_params) 41 | format.html { redirect_to [:edit, @storefront], notice: "Storefront was successfully updated." } 42 | format.json { render :edit, status: :ok, location: @storefront } 43 | else 44 | format.html { render :edit, status: :unprocessable_entity } 45 | format.json { render json: @storefront.errors, status: :unprocessable_entity } 46 | end 47 | end 48 | end 49 | 50 | # DELETE /storefronts/1 or /storefronts/1.json 51 | def destroy 52 | @storefront.destroy! 53 | 54 | respond_to do |format| 55 | format.html { redirect_to storefronts_url, notice: "Storefront was successfully destroyed." } 56 | format.json { head :no_content } 57 | end 58 | end 59 | 60 | private 61 | # Use callbacks to share common setup or constraints between actions. 62 | def set_storefront 63 | @storefront = Storefront.find(params[:id]) 64 | @reasons = @storefront.reasons.ordered 65 | end 66 | 67 | # Only allow a list of trusted parameters through. 68 | def storefront_params 69 | params.fetch(:storefront, {}).permit( 70 | :name, 71 | { 72 | reasons_attributes: [ 73 | :id, 74 | :code, 75 | :label, 76 | :ordering, 77 | :active, 78 | :_destroy, 79 | { restricted_resolution_types: [] } 80 | ] 81 | } 82 | ) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/storefronts_helper.rb: -------------------------------------------------------------------------------- 1 | module StorefrontsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | import "@hotwired/turbo-rails" 3 | import "controllers" 4 | -------------------------------------------------------------------------------- /app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.debug = false 7 | window.Stimulus = application 8 | 9 | export { application } 10 | -------------------------------------------------------------------------------- /app/javascript/controllers/drag_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | import $ from "jquery"; 3 | import Sortable from "sortablejs" 4 | 5 | export default class extends Controller { 6 | connect() { 7 | this.sortable = Sortable.create(this.element, { 8 | onEnd: this.end.bind(this) 9 | }) 10 | } 11 | end(event){ 12 | let id = event.item.dataset.id 13 | $.ajax({ 14 | headers: { 15 | "X-CSRF-Token": $('meta[name="csrf-token"]').attr("content"), 16 | }, 17 | url: "/reasons/"+id+"/update_order", 18 | type: 'PATCH', 19 | dataType: "json", 20 | data: { 21 | ordering: event.newIndex + 1 22 | }, 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Import and register all your controllers from the importmap under controllers/* 2 | 3 | import { application } from "controllers/application" 4 | 5 | // Eager load all controllers defined in the import map under controllers/**/*_controller 6 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" 7 | eagerLoadControllersFrom("controllers", application) 8 | 9 | // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) 10 | // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" 11 | // lazyLoadControllersFrom("controllers", application) 12 | -------------------------------------------------------------------------------- /app/javascript/controllers/modal_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | import $ from "jquery"; 3 | 4 | export default class extends Controller { 5 | static targets = [ 6 | 'modal' 7 | ] 8 | 9 | async show(e) { 10 | e.preventDefault() 11 | const modalId = e.target.getAttribute("data-modal-id"); 12 | const modal = document.getElementById(modalId); 13 | const reasonId = e.currentTarget.dataset.id 14 | modal.classList.remove("hidden"); 15 | modal.setAttribute("aria-hidden", "false"); 16 | const reasonDetails = await this.fetchReasonDetails(reasonId); 17 | this.updateModalContent(modalId, reasonDetails); 18 | } 19 | 20 | async fetchReasonDetails(reasonId) { 21 | let res; 22 | try { 23 | await $.ajax({ 24 | headers: { 25 | "X-CSRF-Token": $('meta[name="csrf-token"]').attr("content"), 26 | }, 27 | url: "/reasons/"+reasonId, 28 | type: 'GET', 29 | dataType: "json", 30 | data: { 31 | id: reasonId 32 | }, 33 | success: function(response){ 34 | res = response 35 | }, 36 | }); 37 | } catch (error) { 38 | console.error(error); 39 | window.alert(error); 40 | } 41 | return res; 42 | } 43 | 44 | updateModalContent(modalId, reasonDetails) { 45 | const modal = document.getElementById(modalId); 46 | modal.querySelector('input[name="storefront[reasons_attributes][0][label]"]').value = reasonDetails.label; 47 | modal.querySelector('input[name="storefront[reasons_attributes][0][code]"]').value = reasonDetails.code; 48 | } 49 | 50 | hide(e) { 51 | e.preventDefault() 52 | const modalId = e.target.getAttribute("data-modal-id"); 53 | const modal = document.getElementById(modalId); 54 | modal.classList.add("hidden"); 55 | modal.setAttribute("aria-hidden", "true") 56 | } 57 | } -------------------------------------------------------------------------------- /app/javascript/controllers/nested_form_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | static targets = ["addItem", "template"] 5 | static values = { index: String } 6 | 7 | addAssociation(event) { 8 | console.log('addAssociation') 9 | event.preventDefault() 10 | console.log(this.indexValue); 11 | const child_index_name = this.indexValue 12 | const content = this.templateTarget.innerHTML.replace(new RegExp(child_index_name, "g"), new Date().valueOf()) 13 | this.addItemTarget.insertAdjacentHTML('beforebegin', content) 14 | } 15 | 16 | removeAssociation(event, isRemoveBundle) { 17 | let item = event 18 | if (!isRemoveBundle) { 19 | event.preventDefault() 20 | item = event.target.closest(".nested-form-wrapper") 21 | } 22 | const isBundleItem = item.getAttribute('data-bundle-item') 23 | const orderClass = this.getInputClass(isBundleItem, isRemoveBundle) 24 | this.destroyInput(item, orderClass) 25 | if (!isRemoveBundle && isBundleItem) { 26 | this.checkBundle(event) 27 | } 28 | } 29 | getInputClass(isBundleItem, isRemoveBundle) { 30 | return isBundleItem ? '.bundle-destroy' : isRemoveBundle ? '.order-destroy' : '' 31 | } 32 | destroyInput(item, orderClass) { 33 | const destroyInput = item.querySelector(`input${orderClass}[name*='_destroy']`); 34 | destroyInput.value = 1; 35 | this.clearRequired(item); 36 | item.style.display = 'none'; 37 | } 38 | clearRequired(item) { 39 | item.querySelectorAll("input[required]").forEach((node) => { 40 | node.required = false 41 | }) 42 | item.querySelectorAll("select[required]").forEach((node) => { 43 | node.required = false 44 | }) 45 | } 46 | 47 | checkBundle(event) { 48 | event.preventDefault() 49 | const itemWrap = event.target.closest('.bundle-items') 50 | let hasItem = false 51 | itemWrap.querySelectorAll('.bundle-item').forEach(item => { 52 | if (item.style.display != 'none') 53 | hasItem = true 54 | }) 55 | if (!hasItem) { 56 | const removeItem = itemWrap.closest('.rma-item-wrap').querySelector('.nested-form-wrapper') 57 | this.removeAssociation(removeItem, true) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/reason.rb: -------------------------------------------------------------------------------- 1 | class Reason < ApplicationRecord 2 | acts_as_list column: :ordering 3 | belongs_to :storefront 4 | 5 | validates :code, presence: true, uniqueness: { scope: :storefront_id } 6 | validates :label, presence: true 7 | 8 | scope :ordered, -> { order(ordering: :asc) } 9 | end 10 | -------------------------------------------------------------------------------- /app/models/storefront.rb: -------------------------------------------------------------------------------- 1 | class Storefront < ApplicationRecord 2 | validates :name, presence: true, uniqueness: true 3 | 4 | has_many :reasons, inverse_of: :storefront, dependent: :destroy 5 | 6 | accepts_nested_attributes_for :reasons, reject_if: proc { |attributes| 7 | attributes['code'].blank? || attributes['label'].blank? 8 | }, allow_destroy: true 9 | end 10 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RailsFrontendTest 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | 10 | 11 | 12 | <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> 13 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> 14 | <%= javascript_importmap_tags %> 15 | 16 | 17 | 18 |
19 |
20 |
21 | <%= yield %> 22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/views/storefronts/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= simple_form_for storefront, data: { controller: 'nested-form', nested_form_index_value: 'NEW_RECORD' } do |form| %> 3 | <% if storefront.errors.any? %> 4 |
5 |

<%= pluralize(storefront.errors.count, "error") %> prohibited this storefront from being saved:

6 | 11 |
12 | <% end %> 13 | 14 |
15 |
16 | 21 | 22 |
23 |
24 |
25 | 28 |
29 |
30 | <% @reasons.each do |item| %> 31 |
32 |
33 |
34 | Image Alt Text 35 | Image Alt Text 36 | <%= item.label %>
37 | 38 |
39 |
40 | <% end %> 41 |
42 |
43 |
44 |
45 |
46 | 47 | 55 | 62 | <% end %> 63 |
64 | -------------------------------------------------------------------------------- /app/views/storefronts/_reason.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | <%= 8 | f.input :label, 9 | label: 'Displayed reason', 10 | required: true, 11 | placeholder: 'Enter Displayed reason...', 12 | input_html: { 13 | style: 'background-color: #f8f8fb; border: 1px solid #E3E3E3; box-shadow:none; border-radius:12px; margin-top:8px;' 14 | } 15 | %> 16 |

Provide a friendly description for the reason that will be displayed to the customer.

17 | <%= 18 | f.input :code, 19 | label: 'Internal return code', 20 | required: true, 21 | placeholder: 'Enter Internal return code...', 22 | input_html: { 23 | style: 'background-color: #f8f8fb; border: 1px solid #E3E3E3; box-shadow:none; border-radius:12px; margin-top: 8px;' # Adjust padding as needed 24 | } 25 | %> 26 | 27 |

For internal use only. Must be unique. To aid in analytics, we do not recommend changing the code once set.

28 |
Image Alt Text

Reason is visible to customers

29 |

When hidden, this return reason is not offered as an option to the customers. Click on the eye icon to make the return reason visible.

30 | 31 |
32 | 33 | 36 | <%= f.hidden_field :_destroy %> 37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /app/views/storefronts/_storefront.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= link_to storefront.name, [:edit, storefront] %> 3 |
4 | -------------------------------------------------------------------------------- /app/views/storefronts/_storefront.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! storefront, :id, :created_at, :updated_at 2 | json.url storefront_url(storefront, format: :json) 3 | -------------------------------------------------------------------------------- /app/views/storefronts/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Returns

4 | 5 |
6 |
7 |
8 |
9 | Return Reasons 10 |

Add or edit return reasons you would like to show the customers during the return request process.

11 |
12 |
13 |
14 | 15 |
16 | <%= render "form", storefront: @storefront %> 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /app/views/storefronts/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if notice.present? %> 3 |

<%= notice %>

4 | <% end %> 5 | 6 |
7 |

Storefronts

8 | <%= link_to "New storefront", new_storefront_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %> 9 |
10 | 11 |
12 | <%= render @storefronts %> 13 |
14 |
15 | -------------------------------------------------------------------------------- /app/views/storefronts/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @storefronts, partial: "storefronts/storefront", as: :storefront 2 | -------------------------------------------------------------------------------- /app/views/storefronts/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

New storefront

3 | 4 | <%= render "form", storefront: @storefront %> 5 | 6 | <%= link_to "Back to storefronts", storefronts_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> 7 |
8 | -------------------------------------------------------------------------------- /app/views/storefronts/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% if notice.present? %> 4 |

<%= notice %>

5 | <% end %> 6 | 7 | <%= render @storefront %> 8 | 9 | <%= link_to "Edit this storefront", edit_storefront_path(@storefront), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> 10 |
11 | <%= button_to "Destroy this storefront", storefront_path(@storefront), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %> 12 |
13 | <%= link_to "Back to storefronts", storefronts_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> 14 |
15 |
16 | -------------------------------------------------------------------------------- /app/views/storefronts/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "storefronts/storefront", storefront: @storefront 2 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || 66 | cli_arg_version || 67 | bundler_requirement_for(lockfile_version) 68 | end 69 | 70 | def bundler_requirement_for(version) 71 | return "#{Gem::Requirement.default}.a" unless version 72 | 73 | bundler_gem_version = Gem::Version.new(version) 74 | 75 | bundler_gem_version.approximate_recommendation 76 | end 77 | 78 | def load_bundler! 79 | ENV["BUNDLE_GEMFILE"] ||= gemfile 80 | 81 | activate_bundler 82 | end 83 | 84 | def activate_bundler 85 | gem_error = activation_error_handling do 86 | gem "bundler", bundler_requirement 87 | end 88 | return if gem_error.nil? 89 | require_error = activation_error_handling do 90 | require "bundler/version" 91 | end 92 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 93 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 94 | exit 42 95 | end 96 | 97 | def activation_error_handling 98 | yield 99 | nil 100 | rescue StandardError, LoadError => e 101 | e 102 | end 103 | end 104 | 105 | m.load_bundler! 106 | 107 | if m.invoked_as_script? 108 | load Gem.bin_path("bundler", "bundle") 109 | end 110 | -------------------------------------------------------------------------------- /bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if ! gem list foreman -i --silent; then 4 | echo "Installing foreman..." 5 | gem install foreman 6 | fi 7 | 8 | exec foreman start -f Procfile.dev "$@" 9 | -------------------------------------------------------------------------------- /bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # If running the rails server then create or migrate existing database 4 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 5 | ./bin/rails db:prepare 6 | fi 7 | 8 | exec "${@}" 9 | -------------------------------------------------------------------------------- /bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | # require "active_job/railtie" 7 | require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | # require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | require "rails/test_unit/railtie" 16 | 17 | # Require the gems listed in Gemfile, including any gems 18 | # you've limited to :test, :development, or :production. 19 | Bundler.require(*Rails.groups) 20 | 21 | module RailsFrontendTest 22 | class Application < Rails::Application 23 | # Initialize configuration defaults for originally generated Rails version. 24 | config.load_defaults 7.1 25 | 26 | # Please, add to the `ignore` list any other `lib` subdirectories that do 27 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 28 | # Common ones are `templates`, `generators`, or `middleware`, for example. 29 | config.autoload_lib(ignore: %w(assets tasks)) 30 | 31 | # Configuration for the application, engines, and railties goes here. 32 | # 33 | # These settings can be overridden in specific environments using the files 34 | # in config/environments, which are processed later. 35 | # 36 | # config.time_zone = "Central Time (US & Canada)" 37 | # config.eager_load_paths << Rails.root.join("extras") 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | K9qPgOPTP7xBEup7BEDgdgymEtU0QhsazhhQ5ufCSP3m6Z5pqa16Gw1PiiGX1k4ccsiY41u4oJ249Y/2JJ9IgjQc5cTpwtC/KSbS1QDmE7W8PVdq7MeOZMRyyXsgJkPTgyfmVbfKU9c3JYk2BphfqtoREx3yeJv0ZPXaKCarMBltvWVfI+ofKCiJthmUyyrVpAuplNMGY27PfHBEYeMhgio5V7gYLRSamMFbC9vXiW72iIHAcC2T5LWfvSyElu15BKFwx9d2LohOxmKfesP+lXqYtVA8iWcJO4CuSIrsLEBcod3gSDwjPeC2r4eLNIW4a4WHzwDdwyRTmX1pkSi9w+FVZq1dN2zO2tWgCbTa4StqY3YcgTqWT8VcVlZtuZ+79z0oLP18TzVcDe3p4eMkU3YJn5HJ--OB6C2I8chSe/arFE--nvX4G9e5dcEGR/uWzwc5MQ== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: storage/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.enable_reloading = true 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join("tmp/caching-dev.txt").exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { 28 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 29 | } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Print deprecation notices to the Rails logger. 37 | config.active_support.deprecation = :log 38 | 39 | # Raise exceptions for disallowed deprecations. 40 | config.active_support.disallowed_deprecation = :raise 41 | 42 | # Tell Active Support which deprecation messages to disallow. 43 | config.active_support.disallowed_deprecation_warnings = [] 44 | 45 | # Raise an error on page load if there are pending migrations. 46 | config.active_record.migration_error = :page_load 47 | 48 | # Highlight code that triggered database queries in logs. 49 | config.active_record.verbose_query_logs = true 50 | 51 | # Suppress logger output for asset requests. 52 | config.assets.quiet = true 53 | 54 | # Raises error for missing translations. 55 | # config.i18n.raise_on_missing_translations = true 56 | 57 | # Annotate rendered view with file names. 58 | # config.action_view.annotate_rendered_view_with_filenames = true 59 | 60 | # Raise error when a before_action's only/except options reference missing actions 61 | config.action_controller.raise_on_missing_callback_actions = true 62 | end 63 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.enable_reloading = false 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment 20 | # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. 24 | # config.public_file_server.enabled = false 25 | 26 | # Compress CSS using a preprocessor. 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 33 | # config.asset_host = "http://assets.example.com" 34 | 35 | # Specifies the header that your server uses for sending files. 36 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 37 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 38 | 39 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 40 | # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. 41 | # config.assume_ssl = true 42 | 43 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 44 | config.force_ssl = true 45 | 46 | # Log to STDOUT by default 47 | config.logger = ActiveSupport::Logger.new(STDOUT) 48 | .tap { |logger| logger.formatter = ::Logger::Formatter.new } 49 | .then { |logger| ActiveSupport::TaggedLogging.new(logger) } 50 | 51 | # Prepend all log lines with the following tags. 52 | config.log_tags = [ :request_id ] 53 | 54 | # Info include generic and useful information about system operation, but avoids logging too much 55 | # information to avoid inadvertent exposure of personally identifiable information (PII). If you 56 | # want to log everything, set the level to "debug". 57 | config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") 58 | 59 | # Use a different cache store in production. 60 | # config.cache_store = :mem_cache_store 61 | 62 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 63 | # the I18n.default_locale when a translation cannot be found). 64 | config.i18n.fallbacks = true 65 | 66 | # Don't log any deprecations. 67 | config.active_support.report_deprecations = false 68 | 69 | # Do not dump schema after migrations. 70 | config.active_record.dump_schema_after_migration = false 71 | 72 | # Enable DNS rebinding protection and other `Host` header attacks. 73 | # config.hosts = [ 74 | # "example.com", # Allow requests from example.com 75 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 76 | # ] 77 | # Skip DNS rebinding protection for the default health check endpoint. 78 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 79 | end 80 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV["CI"].present? 19 | 20 | # Configure public file server for tests with Cache-Control for performance. 21 | config.public_file_server.enabled = true 22 | config.public_file_server.headers = { 23 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 24 | } 25 | 26 | # Show full error reports and disable caching. 27 | config.consider_all_requests_local = true 28 | config.action_controller.perform_caching = false 29 | config.cache_store = :null_store 30 | 31 | # Render exception templates for rescuable exceptions and raise for other exceptions. 32 | config.action_dispatch.show_exceptions = :rescuable 33 | 34 | # Disable request forgery protection in test environment. 35 | config.action_controller.allow_forgery_protection = false 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raise exceptions for disallowed deprecations. 41 | config.active_support.disallowed_deprecation = :raise 42 | 43 | # Tell Active Support which deprecation messages to disallow. 44 | config.active_support.disallowed_deprecation_warnings = [] 45 | 46 | # Raises error for missing translations. 47 | # config.i18n.raise_on_missing_translations = true 48 | 49 | # Annotate rendered view with file names. 50 | # config.action_view.annotate_rendered_view_with_filenames = true 51 | 52 | # Raise error when a before_action's only/except options reference missing actions 53 | config.action_controller.raise_on_missing_callback_actions = true 54 | end 55 | -------------------------------------------------------------------------------- /config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin "application", preload: true 4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true 5 | pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true 6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true 7 | pin_all_from "app/javascript/controllers", under: "controllers" 8 | pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.7.1/dist/jquery.js" 9 | pin "sortablejs", to: "https://ga.jspm.io/npm:sortablejs@1.15.1/modular/sortable.esm.js" 10 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /config/initializers/simple_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # Uncomment this and change the path if necessary to include your own 4 | # components. 5 | # See https://github.com/heartcombo/simple_form#custom-components to know 6 | # more about custom components. 7 | # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } 8 | # 9 | # Use this setup block to configure all options available in SimpleForm. 10 | SimpleForm.setup do |config| 11 | # Wrappers are used by the form builder to generate a 12 | # complete input. You can remove any component from the 13 | # wrapper, change the order or even add your own to the 14 | # stack. The options given below are used to wrap the 15 | # whole input. 16 | config.wrappers :default, class: :input, 17 | hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b| 18 | ## Extensions enabled by default 19 | # Any of these extensions can be disabled for a 20 | # given input by passing: `f.input EXTENSION_NAME => false`. 21 | # You can make any of these extensions optional by 22 | # renaming `b.use` to `b.optional`. 23 | 24 | # Determines whether to use HTML5 (:email, :url, ...) 25 | # and required attributes 26 | b.use :html5 27 | 28 | # Calculates placeholders automatically from I18n 29 | # You can also pass a string as f.input placeholder: "Placeholder" 30 | b.use :placeholder 31 | 32 | ## Optional extensions 33 | # They are disabled unless you pass `f.input EXTENSION_NAME => true` 34 | # to the input. If so, they will retrieve the values from the model 35 | # if any exists. If you want to enable any of those 36 | # extensions by default, you can change `b.optional` to `b.use`. 37 | 38 | # Calculates maxlength from length validations for string inputs 39 | # and/or database column lengths 40 | b.optional :maxlength 41 | 42 | # Calculate minlength from length validations for string inputs 43 | b.optional :minlength 44 | 45 | # Calculates pattern from format validations for string inputs 46 | b.optional :pattern 47 | 48 | # Calculates min and max from length validations for numeric inputs 49 | b.optional :min_max 50 | 51 | # Calculates readonly automatically from readonly attributes 52 | b.optional :readonly 53 | 54 | ## Inputs 55 | # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid' 56 | b.use :label_input 57 | b.use :hint, wrap_with: { tag: :span, class: :hint } 58 | b.use :error, wrap_with: { tag: :span, class: :error } 59 | 60 | ## full_messages_for 61 | # If you want to display the full error message for the attribute, you can 62 | # use the component :full_error, like: 63 | # 64 | # b.use :full_error, wrap_with: { tag: :span, class: :error } 65 | end 66 | 67 | # The default wrapper to be used by the FormBuilder. 68 | config.default_wrapper = :default 69 | 70 | # Define the way to render check boxes / radio buttons with labels. 71 | # Defaults to :nested for bootstrap config. 72 | # inline: input + label 73 | # nested: label > input 74 | config.boolean_style = :nested 75 | 76 | # Default class for buttons 77 | config.button_class = 'btn' 78 | 79 | # Method used to tidy up errors. Specify any Rails Array method. 80 | # :first lists the first message for each field. 81 | # Use :to_sentence to list all errors for each field. 82 | # config.error_method = :first 83 | 84 | # Default tag used for error notification helper. 85 | config.error_notification_tag = :div 86 | 87 | # CSS class to add for error notification helper. 88 | config.error_notification_class = 'error_notification' 89 | 90 | # Series of attempts to detect a default label method for collection. 91 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 92 | 93 | # Series of attempts to detect a default value method for collection. 94 | # config.collection_value_methods = [ :id, :to_s ] 95 | 96 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 97 | # config.collection_wrapper_tag = nil 98 | 99 | # You can define the class to use on all collection wrappers. Defaulting to none. 100 | # config.collection_wrapper_class = nil 101 | 102 | # You can wrap each item in a collection of radio/check boxes with a tag, 103 | # defaulting to :span. 104 | # config.item_wrapper_tag = :span 105 | 106 | # You can define a class to use in all item wrappers. Defaulting to none. 107 | # config.item_wrapper_class = nil 108 | 109 | # How the label text should be generated altogether with the required text. 110 | # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } 111 | 112 | # You can define the class to use on all labels. Default is nil. 113 | # config.label_class = nil 114 | 115 | # You can define the default class to be used on forms. Can be overridden 116 | # with `html: { :class }`. Defaulting to none. 117 | # config.default_form_class = nil 118 | 119 | # You can define which elements should obtain additional classes 120 | # config.generate_additional_classes_for = [:wrapper, :label, :input] 121 | 122 | # Whether attributes are required by default (or not). Default is true. 123 | # config.required_by_default = true 124 | 125 | # Tell browsers whether to use the native HTML5 validations (novalidate form option). 126 | # These validations are enabled in SimpleForm's internal config but disabled by default 127 | # in this configuration, which is recommended due to some quirks from different browsers. 128 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, 129 | # change this configuration to true. 130 | config.browser_validations = false 131 | 132 | # Custom mappings for input types. This should be a hash containing a regexp 133 | # to match as key, and the input type that will be used when the field name 134 | # matches the regexp as value. 135 | # config.input_mappings = { /count/ => :integer } 136 | 137 | # Custom wrappers for input types. This should be a hash containing an input 138 | # type as key and the wrapper that will be used for all inputs with specified type. 139 | # config.wrapper_mappings = { string: :prepend } 140 | 141 | # Namespaces where SimpleForm should look for custom input classes that 142 | # override default inputs. 143 | # config.custom_inputs_namespaces << "CustomInputs" 144 | 145 | # Default priority for time_zone inputs. 146 | # config.time_zone_priority = nil 147 | 148 | # Default priority for country inputs. 149 | # config.country_priority = nil 150 | 151 | # When false, do not use translations for labels. 152 | # config.translate_labels = true 153 | 154 | # Automatically discover new inputs in Rails' autoload path. 155 | # config.inputs_discovery = true 156 | 157 | # Cache SimpleForm inputs discovery 158 | # config.cache_discovery = !Rails.env.development? 159 | 160 | # Default class for inputs 161 | # config.input_class = nil 162 | 163 | # Define the default class of the input wrapper of the boolean input. 164 | config.boolean_label_class = 'checkbox' 165 | 166 | # Defines if the default input wrapper class should be included in radio 167 | # collection wrappers. 168 | # config.include_default_input_wrapper_class = true 169 | 170 | # Defines which i18n scope will be used in Simple Form. 171 | # config.i18n_scope = 'simple_form' 172 | 173 | # Defines validation classes to the input_field. By default it's nil. 174 | # config.input_field_valid_class = 'is-valid' 175 | # config.input_field_error_class = 'is-invalid' 176 | end 177 | -------------------------------------------------------------------------------- /config/initializers/simple_form_tailwind.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Use this setup block to configure all options available in SimpleForm. 4 | SimpleForm.setup do |config| 5 | # Default class for buttons 6 | config.button_class = 'my-2 bg-blue-500 hover:bg-blue-700 text-white font-bold text-sm py-2 px-4 rounded' 7 | 8 | # Define the default class of the input wrapper of the boolean input. 9 | config.boolean_label_class = '' 10 | 11 | # How the label text should be generated altogether with the required text. 12 | config.label_text = lambda { |label, required, explicit_label| "#{label} #{required}" } 13 | 14 | # Define the way to render check boxes / radio buttons with labels. 15 | config.boolean_style = :inline 16 | 17 | # You can wrap each item in a collection of radio/check boxes with a tag 18 | config.item_wrapper_tag = :div 19 | 20 | # Defines if the default input wrapper class should be included in radio 21 | # collection wrappers. 22 | config.include_default_input_wrapper_class = false 23 | 24 | # CSS class to add for error notification helper. 25 | config.error_notification_class = 'text-white px-6 py-4 border-0 rounded relative mb-4 bg-red-400' 26 | 27 | # Method used to tidy up errors. Specify any Rails Array method. 28 | # :first lists the first message for each field. 29 | # :to_sentence to list all errors for each field. 30 | config.error_method = :to_sentence 31 | 32 | # add validation classes to `input_field` 33 | config.input_field_error_class = 'border-red-500' 34 | config.input_field_valid_class = 'border-green-400' 35 | config.label_class = 'text-sm font-medium text-gray-600' 36 | 37 | 38 | # vertical forms 39 | # 40 | # vertical default_wrapper 41 | config.wrappers :vertical_form, tag: 'div', class: 'mb-4' do |b| 42 | b.use :html5 43 | b.use :placeholder 44 | b.optional :maxlength 45 | b.optional :minlength 46 | b.optional :pattern 47 | b.optional :min_max 48 | b.optional :readonly 49 | b.use :label, class: 'block', error_class: 'text-red-500' 50 | b.use :input, class: 'shadow appearance-none border border-gray-300 rounded w-full py-2 px-3 bg-white focus:outline-none focus:ring-0 focus:border-blue-500 text-gray-400 leading-6 transition-colors duration-200 ease-in-out', error_class: 'border-red-500', valid_class: 'border-green-400' 51 | b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } 52 | b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } 53 | end 54 | 55 | # vertical input for boolean (aka checkboxes) 56 | config.wrappers :vertical_boolean, tag: 'div', class: 'mb-4 flex items-start', error_class: '' do |b| 57 | b.use :html5 58 | b.optional :readonly 59 | b.wrapper tag: 'div', class: 'flex items-center h-5' do |ba| 60 | ba.use :input, class: 'focus:ring-2 focus:ring-indigo-500 ring-offset-2 h-4 w-4 text-indigo-600 border-gray-300 rounded' 61 | end 62 | b.wrapper tag: 'div', class: 'ml-3 text-sm' do |bb| 63 | bb.use :label, class: 'block', error_class: 'text-red-500' 64 | bb.use :hint, wrap_with: { tag: 'p', class: 'block text-grey-700 text-xs italic' } 65 | bb.use :full_error, wrap_with: { tag: 'p', class: 'block text-red-500 text-xs italic' } 66 | end 67 | 68 | end 69 | 70 | # vertical input for radio buttons and check boxes 71 | config.wrappers :vertical_collection, item_wrapper_class: 'flex items-center', item_label_class: 'my-1 ml-3 block text-sm font-medium text-gray-400', tag: 'div', class: 'my-4' do |b| 72 | b.use :html5 73 | b.optional :readonly 74 | b.wrapper :legend_tag, tag: 'legend', class: 'text-sm font-medium text-gray-600', error_class: 'text-red-500' do |ba| 75 | ba.use :label_text 76 | end 77 | b.use :input, class: 'focus:ring-2 focus:ring-indigo-500 ring-offset-2 h-4 w-4 text-indigo-600 border-gray-300 rounded', error_class: 'text-red-500', valid_class: 'text-green-400' 78 | b.use :full_error, wrap_with: { tag: 'p', class: 'block mt-2 text-red-500 text-xs italic' } 79 | b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } 80 | end 81 | 82 | # vertical file input 83 | config.wrappers :vertical_file, tag: 'div', class: '' do |b| 84 | b.use :html5 85 | b.use :placeholder 86 | b.optional :maxlength 87 | b.optional :minlength 88 | b.optional :readonly 89 | b.use :label, class: 'text-sm font-medium text-gray-600 block', error_class: 'text-red-500' 90 | b.use :input, class: 'w-full text-gray-500 px-3 py-2 border rounded', error_class: 'text-red-500 border-red-500', valid_class: 'text-green-400' 91 | b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } 92 | b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } 93 | end 94 | 95 | # vertical multi select 96 | config.wrappers :vertical_multi_select, tag: 'div', class: 'my-4', error_class: 'f', valid_class: '' do |b| 97 | b.use :html5 98 | b.optional :readonly 99 | b.wrapper :legend_tag, tag: 'legend', class: 'text-sm font-medium text-gray-600', error_class: 'text-red-500' do |ba| 100 | ba.use :label_text 101 | end 102 | b.wrapper tag: 'div', class: 'inline-flex space-x-1' do |ba| 103 | # ba.use :input, class: 'flex w-auto w-auto text-gray-500 text-sm border-gray-300 rounded p-2', error_class: 'text-red-500', valid_class: 'text-green-400' 104 | ba.use :input, class: 'flex w-auto w-auto shadow appearance-none border border-gray-300 rounded w-full p-2 bg-white focus:outline-none focus:border-blue-500 text-gray-400 leading-4 transition-colors duration-200 ease-in-out' 105 | end 106 | b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } 107 | b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } 108 | end 109 | 110 | # vertical range input 111 | config.wrappers :vertical_range, tag: 'div', class: 'my-4', error_class: 'text-red-500', valid_class: 'text-green-400' do |b| 112 | b.use :html5 113 | b.use :placeholder 114 | b.optional :readonly 115 | b.optional :step 116 | b.use :label, class: 'text-sm font-medium text-gray-600 block', error_class: 'text-red-500' 117 | b.wrapper tag: 'div', class: 'flex items-center h-5' do |ba| 118 | ba.use :input, class: 'rounded-lg overflow-hidden appearance-none bg-gray-400 h-3 w-full text-gray-300', error_class: 'text-red-500', valid_class: 'text-green-400' 119 | end 120 | b.use :full_error, wrap_with: { tag: 'p', class: 'mt-2 text-red-500 text-xs italic' } 121 | b.use :hint, wrap_with: { tag: 'p', class: 'mt-2 text-grey-700 text-xs italic' } 122 | end 123 | 124 | # The default wrapper to be used by the FormBuilder. 125 | config.default_wrapper = :vertical_form 126 | 127 | # Custom wrappers for input types. This should be a hash containing an input 128 | # type as key and the wrapper that will be used for all inputs with specified type. 129 | config.wrapper_mappings = { 130 | boolean: :vertical_boolean, 131 | check_boxes: :vertical_collection, 132 | date: :vertical_multi_select, 133 | datetime: :vertical_multi_select, 134 | file: :vertical_file, 135 | radio_buttons: :vertical_collection, 136 | range: :vertical_range, 137 | time: :vertical_multi_select 138 | } 139 | end 140 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | # include_blanks: 27 | # defaults: 28 | # age: 'Rather not say' 29 | # prompts: 30 | # defaults: 31 | # age: 'Select your age' 32 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # This configuration file will be evaluated by Puma. The top-level methods that 2 | # are invoked here are part of Puma's configuration DSL. For more information 3 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 4 | 5 | # Puma can serve each request in a thread from an internal thread pool. 6 | # The `threads` method setting takes two numbers: a minimum and maximum. 7 | # Any libraries that use thread pools should be configured to match 8 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 9 | # and maximum; this matches the default thread size of Active Record. 10 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 11 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 12 | threads min_threads_count, max_threads_count 13 | 14 | # Specifies that the worker count should equal the number of processors in production. 15 | if ENV["RAILS_ENV"] == "production" 16 | require "concurrent-ruby" 17 | worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count }) 18 | workers worker_count if worker_count > 1 19 | end 20 | 21 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 22 | # terminating a worker in development environments. 23 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 24 | 25 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 26 | port ENV.fetch("PORT") { 3000 } 27 | 28 | # Specifies the `environment` that Puma will run in. 29 | environment ENV.fetch("RAILS_ENV") { "development" } 30 | 31 | # Specifies the `pidfile` that Puma will use. 32 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 33 | 34 | # Allow puma to be restarted by `bin/rails restart` command. 35 | plugin :tmp_restart 36 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :storefronts 3 | resources :reasons do 4 | patch 'update_order', on: :member 5 | end 6 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 7 | 8 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 9 | # Can be used by load balancers and uptime monitors to verify that the app is live. 10 | get "up" => "rails/health#show", as: :rails_health_check 11 | 12 | # Defines the root path route ("/") 13 | root "storefronts#edit", id: 1 14 | end 15 | -------------------------------------------------------------------------------- /config/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | module.exports = { 4 | content: [ 5 | './public/*.html', 6 | './app/helpers/**/*.rb', 7 | './app/javascript/**/*.js', 8 | './app/views/**/*.{erb,haml,html,slim}' 9 | ], 10 | theme: { 11 | extend: { 12 | fontFamily: { 13 | sans: ['Inter var', ...defaultTheme.fontFamily.sans], 14 | }, 15 | }, 16 | }, 17 | plugins: [ 18 | require('@tailwindcss/forms'), 19 | require('@tailwindcss/aspect-ratio'), 20 | require('@tailwindcss/typography'), 21 | require('@tailwindcss/container-queries'), 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /db/migrate/20231221221744_create_storefronts.rb: -------------------------------------------------------------------------------- 1 | class CreateStorefronts < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :storefronts do |t| 4 | t.string :name 5 | t.timestamps 6 | end 7 | 8 | add_index :storefronts, :name, unique: true 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20231221221802_create_reasons.rb: -------------------------------------------------------------------------------- 1 | class CreateReasons < ActiveRecord::Migration[7.1] 2 | def change 3 | create_table :reasons do |t| 4 | t.references :storefront, null: false, foreign_key: true 5 | t.string :code 6 | t.string :label 7 | t.json :restricted_resolution_types 8 | t.boolean :active 9 | t.integer :ordering 10 | 11 | t.timestamps 12 | end 13 | 14 | add_index :reasons, [:storefront_id, :code], unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.1].define(version: 2023_12_21_221802) do 14 | create_table "reasons", force: :cascade do |t| 15 | t.integer "storefront_id", null: false 16 | t.string "code" 17 | t.string "label" 18 | t.json "restricted_resolution_types" 19 | t.boolean "active" 20 | t.integer "ordering" 21 | t.datetime "created_at", null: false 22 | t.datetime "updated_at", null: false 23 | t.index ["storefront_id", "code"], name: "index_reasons_on_storefront_id_and_code", unique: true 24 | t.index ["storefront_id"], name: "index_reasons_on_storefront_id" 25 | end 26 | 27 | create_table "storefronts", force: :cascade do |t| 28 | t.string "name" 29 | t.datetime "created_at", null: false 30 | t.datetime "updated_at", null: false 31 | t.index ["name"], name: "index_storefronts_on_name", unique: true 32 | end 33 | 34 | add_foreign_key "reasons", "storefronts" 35 | end 36 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should ensure the existence of records required to run the application in every environment (production, 2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Example: 6 | # 7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 8 | # MovieGenre.find_or_create_by!(name: genre_name) 9 | # end 10 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "jquery": "^3.7.1", 4 | "sortablejs": "^1.15.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/storage/.keep -------------------------------------------------------------------------------- /storage/development.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/storage/development.sqlite3 -------------------------------------------------------------------------------- /storage/development.sqlite3-shm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/storage/development.sqlite3-shm -------------------------------------------------------------------------------- /storage/development.sqlite3-wal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/storage/development.sqlite3-wal -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/storefronts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class StorefrontsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @storefront = storefronts(:one) 6 | end 7 | 8 | test "should get index" do 9 | get storefronts_url 10 | assert_response :success 11 | end 12 | 13 | test "should get new" do 14 | get new_storefront_url 15 | assert_response :success 16 | end 17 | 18 | test "should create storefront" do 19 | assert_difference("Storefront.count") do 20 | post storefronts_url, params: { storefront: { } } 21 | end 22 | 23 | assert_redirected_to storefront_url(Storefront.last) 24 | end 25 | 26 | test "should show storefront" do 27 | get storefront_url(@storefront) 28 | assert_response :success 29 | end 30 | 31 | test "should get edit" do 32 | get edit_storefront_url(@storefront) 33 | assert_response :success 34 | end 35 | 36 | test "should update storefront" do 37 | patch storefront_url(@storefront), params: { storefront: { } } 38 | assert_redirected_to storefront_url(@storefront) 39 | end 40 | 41 | test "should destroy storefront" do 42 | assert_difference("Storefront.count", -1) do 43 | delete storefront_url(@storefront) 44 | end 45 | 46 | assert_redirected_to storefronts_url 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/reasons.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | storefront: one 5 | code: MyString 6 | label: MyString 7 | restricted_resolution_types: 8 | active: false 9 | ordering: 1 10 | 11 | two: 12 | storefront: two 13 | code: MyString 14 | label: MyString 15 | restricted_resolution_types: 16 | active: false 17 | ordering: 1 18 | -------------------------------------------------------------------------------- /test/fixtures/storefronts.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the "{}" from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/test/integration/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/test/models/.keep -------------------------------------------------------------------------------- /test/models/reason_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ReasonTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/storefront_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class StorefrontTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/test/system/.keep -------------------------------------------------------------------------------- /test/system/storefronts_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class StorefrontsTest < ApplicationSystemTestCase 4 | setup do 5 | @storefront = storefronts(:one) 6 | end 7 | 8 | test "visiting the index" do 9 | visit storefronts_url 10 | assert_selector "h1", text: "Storefronts" 11 | end 12 | 13 | test "should create storefront" do 14 | visit storefronts_url 15 | click_on "New storefront" 16 | 17 | click_on "Create Storefront" 18 | 19 | assert_text "Storefront was successfully created" 20 | click_on "Back" 21 | end 22 | 23 | test "should update Storefront" do 24 | visit storefront_url(@storefront) 25 | click_on "Edit this storefront", match: :first 26 | 27 | click_on "Update Storefront" 28 | 29 | assert_text "Storefront was successfully updated" 30 | click_on "Back" 31 | end 32 | 33 | test "should destroy Storefront" do 34 | visit storefront_url(@storefront) 35 | click_on "Destroy this storefront", match: :first 36 | 37 | assert_text "Storefront was successfully destroyed" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | module ActiveSupport 6 | class TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/tmp/.keep -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/tmp/pids/.keep -------------------------------------------------------------------------------- /tmp/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/tmp/storage/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/vendor/.keep -------------------------------------------------------------------------------- /vendor/javascript/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abbasikov/Rails-frontend-test/0497c2ac2853f300ba20419e89a96a08cc686c9a/vendor/javascript/.keep --------------------------------------------------------------------------------