Compare commits
23 commits
main
...
gitea-acti
Author | SHA1 | Date | |
---|---|---|---|
482ab7c877 | |||
eeeaf4b238 | |||
fcbbc0d0e8 | |||
2edcc17a7c | |||
2dbeb6c45c | |||
c9819bfa93 | |||
fa3db2e586 | |||
3ba6711cfe | |||
804d074c0b | |||
e19b71652d | |||
8bc962a6fe | |||
fc68fb41da | |||
80906614d5 | |||
c69301eb6c | |||
039583ac5c | |||
5054a28ed8 | |||
d3c6fac69b | |||
b0ec03f90d | |||
ad222c229e | |||
ce376c3a50 | |||
97728e4cff | |||
0aa6f8dc3e | |||
87b5350f1f |
125 changed files with 981 additions and 4451 deletions
|
@ -1,30 +0,0 @@
|
||||||
name: Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "**"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: cth-ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Read .ruby-version file
|
|
||||||
id: ruby
|
|
||||||
run: echo "version=$(cat .ruby-version)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Push image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
RUBY_VERSION=${{ steps.ruby.outputs.version }}
|
|
||||||
tags: |
|
|
||||||
docker.atomaka.com/budget:latest
|
|
||||||
docker.atomaka.com/budget:${{gitea.sha}}
|
|
||||||
- name: Deploy to server
|
|
||||||
run: |
|
|
||||||
curl --oauth2-bearer ${{ secrets.WATCHTOWER_TOKEN }} \
|
|
||||||
${{ secrets.WATCHTOWER_HOST }}/v1/update?images=docker.atomaka.com/budget
|
|
|
@ -5,11 +5,19 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: cth-ubuntu-latest
|
runs-on: ruby-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install Node
|
||||||
|
run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install git ca-certificates curl gnupg --yes
|
||||||
|
mkdir -p /etc/apt/keyrings
|
||||||
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y nodejs
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: https://github.com/ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651
|
- uses: actions/cache@v2
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
with:
|
||||||
path: vendor/bundle
|
path: vendor/bundle
|
||||||
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
||||||
|
@ -22,9 +30,7 @@ jobs:
|
||||||
- name: autoload
|
- name: autoload
|
||||||
run: bin/rails zeitwerk:check
|
run: bin/rails zeitwerk:check
|
||||||
- name: lint
|
- name: lint
|
||||||
run: bin/rubocop
|
run: bin/bundle exec standardrb
|
||||||
- name: security
|
|
||||||
run: bin/brakeman
|
|
||||||
- name: test
|
- name: test
|
||||||
run: |
|
run: |
|
||||||
bin/rails assets:precompile
|
bin/rails assets:precompile
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
# Omakase Ruby styling for Rails
|
|
||||||
inherit_gem:
|
|
||||||
rubocop-rails-omakase: rubocop.yml
|
|
||||||
|
|
||||||
# Your own specialized rules go here
|
|
|
@ -1 +1 @@
|
||||||
3.4.4
|
3.3.1
|
||||||
|
|
42
Dockerfile
42
Dockerfile
|
@ -1,43 +1,33 @@
|
||||||
# syntax = docker/dockerfile:1
|
# syntax = docker/dockerfile:1
|
||||||
|
|
||||||
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
|
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
|
||||||
# docker build -t my-app .
|
ARG RUBY_VERSION=3.2.2
|
||||||
# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app
|
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
|
||||||
|
|
||||||
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
|
|
||||||
|
|
||||||
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
|
|
||||||
ARG RUBY_VERSION
|
|
||||||
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
|
|
||||||
|
|
||||||
# Rails app lives here
|
# Rails app lives here
|
||||||
WORKDIR /rails
|
WORKDIR /rails
|
||||||
|
|
||||||
# Install base packages
|
|
||||||
RUN apt-get update -qq && \
|
|
||||||
apt-get install --no-install-recommends -y curl libjemalloc2 libsqlite3-0 libvips && \
|
|
||||||
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
|
||||||
|
|
||||||
# Set production environment
|
# Set production environment
|
||||||
ENV RAILS_ENV="production" \
|
ENV RAILS_ENV="production" \
|
||||||
BUNDLE_DEPLOYMENT="1" \
|
BUNDLE_DEPLOYMENT="1" \
|
||||||
BUNDLE_PATH="/usr/local/bundle" \
|
BUNDLE_PATH="/usr/local/bundle" \
|
||||||
BUNDLE_WITHOUT="development"
|
BUNDLE_WITHOUT="development"
|
||||||
|
|
||||||
|
|
||||||
# Throw-away build stage to reduce size of final image
|
# Throw-away build stage to reduce size of final image
|
||||||
FROM base AS build
|
FROM base as build
|
||||||
|
|
||||||
# Install packages needed to build gems
|
# Install packages needed to build gems
|
||||||
RUN apt-get update -qq && \
|
RUN apt-get update -qq && \
|
||||||
apt-get install --no-install-recommends -y build-essential git pkg-config libyaml-dev && \
|
apt-get install --no-install-recommends -y build-essential git libvips pkg-config
|
||||||
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
|
||||||
|
|
||||||
# Install application gems
|
# Install application gems
|
||||||
COPY Gemfile Gemfile.lock .ruby-version ./
|
COPY Gemfile Gemfile.lock ./
|
||||||
RUN bundle install && \
|
RUN bundle install && \
|
||||||
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
|
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
|
||||||
bundle exec bootsnap precompile --gemfile
|
bundle exec bootsnap precompile --gemfile
|
||||||
|
|
||||||
|
|
||||||
# Copy application code
|
# Copy application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
@ -48,20 +38,24 @@ RUN bundle exec bootsnap precompile app/ lib/
|
||||||
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
|
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Final stage for app image
|
# Final stage for app image
|
||||||
FROM base
|
FROM base
|
||||||
|
|
||||||
|
# Install packages needed for deployment
|
||||||
|
RUN apt-get update -qq && \
|
||||||
|
apt-get install --no-install-recommends -y curl libsqlite3-0 libvips libjemalloc2 && \
|
||||||
|
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
||||||
|
|
||||||
# Copy built artifacts: gems, application
|
# Copy built artifacts: gems, application
|
||||||
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
|
COPY --from=build /usr/local/bundle /usr/local/bundle
|
||||||
COPY --from=build /rails /rails
|
COPY --from=build /rails /rails
|
||||||
|
|
||||||
# Run and own only the runtime files as a non-root user for security
|
# Run and own only the runtime files as a non-root user for security
|
||||||
RUN groupadd --system --gid 1000 rails && \
|
RUN useradd rails --create-home --shell /bin/bash && \
|
||||||
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
|
|
||||||
chown -R rails:rails db log storage tmp
|
chown -R rails:rails db log storage tmp
|
||||||
USER 1000:1000
|
USER rails:rails
|
||||||
|
|
||||||
|
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
|
||||||
|
|
||||||
# Entrypoint prepares the database.
|
# Entrypoint prepares the database.
|
||||||
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
|
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
|
||||||
|
|
58
Gemfile
58
Gemfile
|
@ -1,70 +1,32 @@
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
ruby file: ".ruby-version"
|
gem "rails", "7.1.3.2"
|
||||||
|
gem "sprockets-rails"
|
||||||
gem "rails", github: "rails/rails", branch: "main"
|
gem "sqlite3", "< 2"
|
||||||
# The modern asset pipeline for Rails [https://github.com/rails/propshaft]
|
gem "puma"
|
||||||
gem "propshaft"
|
|
||||||
# Use sqlite3 as the database for Active Record
|
|
||||||
gem "sqlite3", ">= 1.4"
|
|
||||||
# Use the Puma web server [https://github.com/puma/puma]
|
|
||||||
gem "puma", ">= 5.0"
|
|
||||||
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
|
|
||||||
gem "importmap-rails"
|
gem "importmap-rails"
|
||||||
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
|
|
||||||
gem "turbo-rails"
|
gem "turbo-rails"
|
||||||
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
|
|
||||||
gem "stimulus-rails"
|
gem "stimulus-rails"
|
||||||
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
|
|
||||||
gem "jbuilder"
|
gem "jbuilder"
|
||||||
# Use Redis adapter to run Action Cable in production
|
|
||||||
# gem "redis", ">= 4.0.1"
|
|
||||||
|
|
||||||
gem "solid_cable"
|
|
||||||
gem "solid_cache"
|
|
||||||
gem "solid_queue"
|
|
||||||
|
|
||||||
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
|
|
||||||
# gem "kredis"
|
|
||||||
|
|
||||||
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
|
|
||||||
gem "bcrypt"
|
|
||||||
|
|
||||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
|
||||||
gem "tzinfo-data", platforms: %i[ windows jruby ]
|
|
||||||
|
|
||||||
# Reduces boot times through caching; required in config/boot.rb
|
|
||||||
gem "bootsnap", require: false
|
gem "bootsnap", require: false
|
||||||
|
|
||||||
# Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
|
|
||||||
# gem "kamal", require: false
|
|
||||||
|
|
||||||
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
|
|
||||||
# gem "image_processing", "~> 1.2"
|
# gem "image_processing", "~> 1.2"
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
gem "debug", platforms: %i[mri mingw x64_mingw]
|
||||||
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
|
|
||||||
|
|
||||||
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
|
|
||||||
gem "brakeman", require: false
|
|
||||||
|
|
||||||
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
|
|
||||||
gem "rubocop-rails-omakase", require: false
|
|
||||||
|
|
||||||
# Audit bundle for known vulnerabilities
|
|
||||||
gem "bundler-audit", require: false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
# Use console on exceptions pages [https://github.com/rails/web-console]
|
gem "standard"
|
||||||
gem "web-console"
|
gem "web-console"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
|
|
||||||
gem "capybara"
|
gem "capybara"
|
||||||
gem "cuprite"
|
gem "selenium-webdriver"
|
||||||
|
gem "webdrivers"
|
||||||
end
|
end
|
||||||
|
|
||||||
gem "tailwindcss-rails", "~> 3.3.1"
|
gem "tailwindcss-rails", "~> 2.0"
|
||||||
|
|
438
Gemfile.lock
438
Gemfile.lock
|
@ -1,124 +1,89 @@
|
||||||
GIT
|
GEM
|
||||||
remote: https://github.com/rails/rails.git
|
remote: https://rubygems.org/
|
||||||
revision: 82e9029bbf63a33b69f007927979c5564a6afe9e
|
|
||||||
branch: main
|
|
||||||
specs:
|
specs:
|
||||||
actioncable (8.1.0.alpha)
|
actioncable (7.1.3.2)
|
||||||
actionpack (= 8.1.0.alpha)
|
actionpack (= 7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (8.1.0.alpha)
|
actionmailbox (7.1.3.2)
|
||||||
actionpack (= 8.1.0.alpha)
|
actionpack (= 7.1.3.2)
|
||||||
activejob (= 8.1.0.alpha)
|
activejob (= 7.1.3.2)
|
||||||
activerecord (= 8.1.0.alpha)
|
activerecord (= 7.1.3.2)
|
||||||
activestorage (= 8.1.0.alpha)
|
activestorage (= 7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.7.1)
|
||||||
actionmailer (8.1.0.alpha)
|
net-imap
|
||||||
actionpack (= 8.1.0.alpha)
|
net-pop
|
||||||
actionview (= 8.1.0.alpha)
|
net-smtp
|
||||||
activejob (= 8.1.0.alpha)
|
actionmailer (7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
actionpack (= 7.1.3.2)
|
||||||
mail (>= 2.8.0)
|
actionview (= 7.1.3.2)
|
||||||
|
activejob (= 7.1.3.2)
|
||||||
|
activesupport (= 7.1.3.2)
|
||||||
|
mail (~> 2.5, >= 2.5.4)
|
||||||
|
net-imap
|
||||||
|
net-pop
|
||||||
|
net-smtp
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (8.1.0.alpha)
|
actionpack (7.1.3.2)
|
||||||
actionview (= 8.1.0.alpha)
|
actionview (= 7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
|
racc
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
actiontext (7.1.3.2)
|
||||||
actiontext (8.1.0.alpha)
|
actionpack (= 7.1.3.2)
|
||||||
action_text-trix (~> 2.1.15)
|
activerecord (= 7.1.3.2)
|
||||||
actionpack (= 8.1.0.alpha)
|
activestorage (= 7.1.3.2)
|
||||||
activerecord (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
activestorage (= 8.1.0.alpha)
|
|
||||||
activesupport (= 8.1.0.alpha)
|
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (8.1.0.alpha)
|
actionview (7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (8.1.0.alpha)
|
activejob (7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (8.1.0.alpha)
|
activemodel (7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
activerecord (8.1.0.alpha)
|
activerecord (7.1.3.2)
|
||||||
activemodel (= 8.1.0.alpha)
|
activemodel (= 7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (8.1.0.alpha)
|
activestorage (7.1.3.2)
|
||||||
actionpack (= 8.1.0.alpha)
|
actionpack (= 7.1.3.2)
|
||||||
activejob (= 8.1.0.alpha)
|
activejob (= 7.1.3.2)
|
||||||
activerecord (= 8.1.0.alpha)
|
activerecord (= 7.1.3.2)
|
||||||
activesupport (= 8.1.0.alpha)
|
activesupport (= 7.1.3.2)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (8.1.0.alpha)
|
activesupport (7.1.3.2)
|
||||||
base64
|
base64
|
||||||
benchmark (>= 0.3)
|
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5)
|
||||||
drb
|
drb
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
logger (>= 1.4.2)
|
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
securerandom (>= 0.3)
|
mutex_m
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
tzinfo (~> 2.0)
|
||||||
uri (>= 0.13.1)
|
addressable (2.8.6)
|
||||||
rails (8.1.0.alpha)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
actioncable (= 8.1.0.alpha)
|
ast (2.4.2)
|
||||||
actionmailbox (= 8.1.0.alpha)
|
base64 (0.2.0)
|
||||||
actionmailer (= 8.1.0.alpha)
|
bigdecimal (3.1.7)
|
||||||
actionpack (= 8.1.0.alpha)
|
|
||||||
actiontext (= 8.1.0.alpha)
|
|
||||||
actionview (= 8.1.0.alpha)
|
|
||||||
activejob (= 8.1.0.alpha)
|
|
||||||
activemodel (= 8.1.0.alpha)
|
|
||||||
activerecord (= 8.1.0.alpha)
|
|
||||||
activestorage (= 8.1.0.alpha)
|
|
||||||
activesupport (= 8.1.0.alpha)
|
|
||||||
bundler (>= 1.15.0)
|
|
||||||
railties (= 8.1.0.alpha)
|
|
||||||
railties (8.1.0.alpha)
|
|
||||||
actionpack (= 8.1.0.alpha)
|
|
||||||
activesupport (= 8.1.0.alpha)
|
|
||||||
irb (~> 1.13)
|
|
||||||
rackup (>= 1.0.0)
|
|
||||||
rake (>= 12.2)
|
|
||||||
thor (~> 1.0, >= 1.2.2)
|
|
||||||
zeitwerk (~> 2.6)
|
|
||||||
|
|
||||||
GEM
|
|
||||||
remote: https://rubygems.org/
|
|
||||||
specs:
|
|
||||||
action_text-trix (2.1.15)
|
|
||||||
railties
|
|
||||||
addressable (2.8.7)
|
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
|
||||||
ast (2.4.3)
|
|
||||||
base64 (0.3.0)
|
|
||||||
bcrypt (3.1.20)
|
|
||||||
benchmark (0.4.1)
|
|
||||||
bigdecimal (3.2.2)
|
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.18.6)
|
bootsnap (1.18.3)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (7.0.2)
|
builder (3.2.4)
|
||||||
racc
|
|
||||||
builder (3.3.0)
|
|
||||||
bundler-audit (0.9.2)
|
|
||||||
bundler (>= 1.2.0, < 3)
|
|
||||||
thor (~> 1.0)
|
|
||||||
capybara (3.40.0)
|
capybara (3.40.0)
|
||||||
addressable
|
addressable
|
||||||
matrix
|
matrix
|
||||||
|
@ -128,51 +93,34 @@ GEM
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
concurrent-ruby (1.3.5)
|
concurrent-ruby (1.2.3)
|
||||||
connection_pool (2.5.3)
|
connection_pool (2.4.1)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
cuprite (0.17)
|
date (3.3.4)
|
||||||
capybara (~> 3.0)
|
debug (1.9.2)
|
||||||
ferrum (~> 0.17.0)
|
|
||||||
date (3.4.1)
|
|
||||||
debug (1.10.0)
|
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
drb (2.2.3)
|
drb (2.2.1)
|
||||||
erb (5.0.1)
|
erubi (1.12.0)
|
||||||
erubi (1.13.1)
|
|
||||||
et-orbi (1.2.11)
|
|
||||||
tzinfo
|
|
||||||
ferrum (0.17.1)
|
|
||||||
addressable (~> 2.5)
|
|
||||||
base64 (~> 0.2)
|
|
||||||
concurrent-ruby (~> 1.1)
|
|
||||||
webrick (~> 1.7)
|
|
||||||
websocket-driver (~> 0.7)
|
|
||||||
fugit (1.11.1)
|
|
||||||
et-orbi (~> 1, >= 1.2.11)
|
|
||||||
raabro (~> 1.4)
|
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
i18n (1.14.7)
|
i18n (1.14.4)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
importmap-rails (2.1.0)
|
importmap-rails (2.0.1)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activesupport (>= 6.0.0)
|
activesupport (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
io-console (0.8.0)
|
io-console (0.7.2)
|
||||||
irb (1.15.2)
|
irb (1.12.0)
|
||||||
pp (>= 0.6.0)
|
rdoc
|
||||||
rdoc (>= 4.0.0)
|
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jbuilder (2.13.0)
|
jbuilder (2.11.5)
|
||||||
actionview (>= 5.0.0)
|
actionview (>= 5.0.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
json (2.12.2)
|
json (2.7.2)
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.3)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
logger (1.7.0)
|
loofah (2.22.0)
|
||||||
loofah (2.24.1)
|
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
|
@ -183,184 +131,176 @@ GEM
|
||||||
marcel (1.0.4)
|
marcel (1.0.4)
|
||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
minitest (5.25.5)
|
minitest (5.22.3)
|
||||||
msgpack (1.8.0)
|
msgpack (1.7.2)
|
||||||
net-imap (0.5.8)
|
mutex_m (0.2.0)
|
||||||
|
net-imap (0.4.10)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.2)
|
net-protocol (0.2.2)
|
||||||
timeout
|
timeout
|
||||||
net-smtp (0.5.1)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.4)
|
nio4r (2.7.1)
|
||||||
nokogiri (1.18.8-aarch64-linux-gnu)
|
nokogiri (1.16.4-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.8-arm64-darwin)
|
nokogiri (1.16.4-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
parallel (1.24.0)
|
||||||
racc (~> 1.4)
|
parser (3.3.0.5)
|
||||||
parallel (1.27.0)
|
|
||||||
parser (3.3.8.0)
|
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pp (0.6.2)
|
psych (5.1.2)
|
||||||
prettyprint
|
|
||||||
prettyprint (0.2.0)
|
|
||||||
prism (1.4.0)
|
|
||||||
propshaft (1.1.0)
|
|
||||||
actionpack (>= 7.0.0)
|
|
||||||
activesupport (>= 7.0.0)
|
|
||||||
rack
|
|
||||||
railties (>= 7.0.0)
|
|
||||||
psych (5.2.6)
|
|
||||||
date
|
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.2)
|
public_suffix (5.0.5)
|
||||||
puma (6.6.0)
|
puma (6.4.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
raabro (1.4.0)
|
racc (1.7.3)
|
||||||
racc (1.8.1)
|
rack (3.0.10)
|
||||||
rack (3.1.16)
|
rack-session (2.0.0)
|
||||||
rack-session (2.1.1)
|
|
||||||
base64 (>= 0.1.0)
|
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.2.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.2.1)
|
rackup (2.1.0)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails-dom-testing (2.3.0)
|
webrick (~> 1.8)
|
||||||
|
rails (7.1.3.2)
|
||||||
|
actioncable (= 7.1.3.2)
|
||||||
|
actionmailbox (= 7.1.3.2)
|
||||||
|
actionmailer (= 7.1.3.2)
|
||||||
|
actionpack (= 7.1.3.2)
|
||||||
|
actiontext (= 7.1.3.2)
|
||||||
|
actionview (= 7.1.3.2)
|
||||||
|
activejob (= 7.1.3.2)
|
||||||
|
activemodel (= 7.1.3.2)
|
||||||
|
activerecord (= 7.1.3.2)
|
||||||
|
activestorage (= 7.1.3.2)
|
||||||
|
activesupport (= 7.1.3.2)
|
||||||
|
bundler (>= 1.15.0)
|
||||||
|
railties (= 7.1.3.2)
|
||||||
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.2)
|
rails-html-sanitizer (1.6.0)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
nokogiri (~> 1.14)
|
||||||
|
railties (7.1.3.2)
|
||||||
|
actionpack (= 7.1.3.2)
|
||||||
|
activesupport (= 7.1.3.2)
|
||||||
|
irb
|
||||||
|
rackup (>= 1.0.0)
|
||||||
|
rake (>= 12.2)
|
||||||
|
thor (~> 1.0, >= 1.2.2)
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.3.0)
|
rake (13.2.1)
|
||||||
rdoc (6.14.0)
|
rdoc (6.6.3.1)
|
||||||
erb
|
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
regexp_parser (2.10.0)
|
regexp_parser (2.9.0)
|
||||||
reline (0.6.1)
|
reline (0.5.2)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
rubocop (1.76.1)
|
rexml (3.2.6)
|
||||||
|
rubocop (1.62.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (>= 3.17.0)
|
||||||
lint_roller (~> 1.1.0)
|
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.9.3, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rubocop-ast (>= 1.45.0, < 2.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.45.1)
|
rubocop-ast (1.31.2)
|
||||||
parser (>= 3.3.7.2)
|
parser (>= 3.3.0.4)
|
||||||
prism (~> 1.4)
|
rubocop-performance (1.20.2)
|
||||||
rubocop-performance (1.25.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
lint_roller (~> 1.1)
|
rubocop-ast (>= 1.30.0, < 2.0)
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
|
||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
|
||||||
rubocop-rails (2.32.0)
|
|
||||||
activesupport (>= 4.2.0)
|
|
||||||
lint_roller (~> 1.1)
|
|
||||||
rack (>= 1.1)
|
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
|
||||||
rubocop-ast (>= 1.44.0, < 2.0)
|
|
||||||
rubocop-rails-omakase (1.1.0)
|
|
||||||
rubocop (>= 1.72)
|
|
||||||
rubocop-performance (>= 1.24)
|
|
||||||
rubocop-rails (>= 2.30)
|
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
securerandom (0.4.1)
|
rubyzip (2.3.2)
|
||||||
solid_cable (3.0.8)
|
selenium-webdriver (4.10.0)
|
||||||
actioncable (>= 7.2)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
activejob (>= 7.2)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
activerecord (>= 7.2)
|
websocket (~> 1.0)
|
||||||
railties (>= 7.2)
|
sprockets (4.2.1)
|
||||||
solid_cache (1.0.7)
|
concurrent-ruby (~> 1.0)
|
||||||
activejob (>= 7.2)
|
rack (>= 2.2.4, < 4)
|
||||||
activerecord (>= 7.2)
|
sprockets-rails (3.4.2)
|
||||||
railties (>= 7.2)
|
actionpack (>= 5.2)
|
||||||
solid_queue (1.1.5)
|
activesupport (>= 5.2)
|
||||||
activejob (>= 7.1)
|
sprockets (>= 3.0.0)
|
||||||
activerecord (>= 7.1)
|
sqlite3 (1.7.3-arm64-darwin)
|
||||||
concurrent-ruby (>= 1.3.1)
|
sqlite3 (1.7.3-x86_64-linux)
|
||||||
fugit (~> 1.11.0)
|
standard (1.35.1)
|
||||||
railties (>= 7.1)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
thor (~> 1.3.1)
|
lint_roller (~> 1.0)
|
||||||
sqlite3 (2.7.0-aarch64-linux-gnu)
|
rubocop (~> 1.62.0)
|
||||||
sqlite3 (2.7.0-arm64-darwin)
|
standard-custom (~> 1.0.0)
|
||||||
sqlite3 (2.7.0-x86_64-linux-gnu)
|
standard-performance (~> 1.3)
|
||||||
stimulus-rails (1.3.4)
|
standard-custom (1.0.2)
|
||||||
|
lint_roller (~> 1.0)
|
||||||
|
rubocop (~> 1.50)
|
||||||
|
standard-performance (1.3.1)
|
||||||
|
lint_roller (~> 1.1)
|
||||||
|
rubocop-performance (~> 1.20.2)
|
||||||
|
stimulus-rails (1.3.3)
|
||||||
|
railties (>= 6.0.0)
|
||||||
|
stringio (3.1.0)
|
||||||
|
tailwindcss-rails (2.4.0-arm64-darwin)
|
||||||
|
railties (>= 6.0.0)
|
||||||
|
tailwindcss-rails (2.4.0-x86_64-linux)
|
||||||
|
railties (>= 6.0.0)
|
||||||
|
thor (1.3.1)
|
||||||
|
timeout (0.4.1)
|
||||||
|
turbo-rails (2.0.5)
|
||||||
|
actionpack (>= 6.0.0)
|
||||||
|
activejob (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
stringio (3.1.7)
|
|
||||||
tailwindcss-rails (3.3.2)
|
|
||||||
railties (>= 7.0.0)
|
|
||||||
tailwindcss-ruby (~> 3.0)
|
|
||||||
tailwindcss-ruby (3.4.17-aarch64-linux)
|
|
||||||
tailwindcss-ruby (3.4.17-arm64-darwin)
|
|
||||||
tailwindcss-ruby (3.4.17-x86_64-linux)
|
|
||||||
thor (1.3.2)
|
|
||||||
timeout (0.4.3)
|
|
||||||
turbo-rails (2.0.16)
|
|
||||||
actionpack (>= 7.1.0)
|
|
||||||
railties (>= 7.1.0)
|
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (3.1.4)
|
unicode-display_width (2.5.0)
|
||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
|
||||||
unicode-emoji (4.0.4)
|
|
||||||
uri (1.0.3)
|
|
||||||
useragent (0.16.11)
|
|
||||||
web-console (4.2.1)
|
web-console (4.2.1)
|
||||||
actionview (>= 6.0.0)
|
actionview (>= 6.0.0)
|
||||||
activemodel (>= 6.0.0)
|
activemodel (>= 6.0.0)
|
||||||
bindex (>= 0.4.0)
|
bindex (>= 0.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
webrick (1.9.1)
|
webdrivers (5.3.1)
|
||||||
websocket-driver (0.8.0)
|
nokogiri (~> 1.6)
|
||||||
base64
|
rubyzip (>= 1.3.0)
|
||||||
|
selenium-webdriver (~> 4.0, < 4.11)
|
||||||
|
webrick (1.8.1)
|
||||||
|
websocket (1.2.10)
|
||||||
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.7.3)
|
zeitwerk (2.6.13)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
|
||||||
arm64-darwin-23
|
arm64-darwin-23
|
||||||
arm64-darwin-24
|
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
bcrypt
|
|
||||||
bootsnap
|
bootsnap
|
||||||
brakeman
|
|
||||||
bundler-audit
|
|
||||||
capybara
|
capybara
|
||||||
cuprite
|
|
||||||
debug
|
debug
|
||||||
importmap-rails
|
importmap-rails
|
||||||
jbuilder
|
jbuilder
|
||||||
propshaft
|
puma
|
||||||
puma (>= 5.0)
|
rails (= 7.1.3.2)
|
||||||
rails!
|
selenium-webdriver
|
||||||
rubocop-rails-omakase
|
sprockets-rails
|
||||||
solid_cable
|
sqlite3 (< 2)
|
||||||
solid_cache
|
standard
|
||||||
solid_queue
|
|
||||||
sqlite3 (>= 1.4)
|
|
||||||
stimulus-rails
|
stimulus-rails
|
||||||
tailwindcss-rails (~> 3.3.1)
|
tailwindcss-rails (~> 2.0)
|
||||||
turbo-rails
|
turbo-rails
|
||||||
tzinfo-data
|
|
||||||
web-console
|
web-console
|
||||||
|
webdrivers
|
||||||
RUBY VERSION
|
|
||||||
ruby 3.4.4p34
|
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.6.2
|
2.5.9
|
||||||
|
|
5
app/assets/config/manifest.js
Normal file
5
app/assets/config/manifest.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//= link_tree ../images
|
||||||
|
//= link_directory ../stylesheets .css
|
||||||
|
//= link_tree ../../javascript .js
|
||||||
|
//= link_tree ../../../vendor/javascript .js
|
||||||
|
//= link_tree ../builds
|
|
@ -1,30 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="200" height="48" viewBox="0 0 200 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<!-- Piggy bank icon -->
|
|
||||||
<g>
|
|
||||||
<!-- Piggy body -->
|
|
||||||
<ellipse cx="24" cy="26" rx="14" ry="10" fill="#F472B6" stroke="#DB2777" stroke-width="1.5"/>
|
|
||||||
<!-- Piggy snout -->
|
|
||||||
<ellipse cx="34" cy="26" rx="4" ry="3" fill="#F9A8D4" stroke="#DB2777" stroke-width="1"/>
|
|
||||||
<!-- Nostrils -->
|
|
||||||
<circle cx="33" cy="26" r="0.5" fill="#DB2777"/>
|
|
||||||
<circle cx="35" cy="26" r="0.5" fill="#DB2777"/>
|
|
||||||
<!-- Eye -->
|
|
||||||
<circle cx="28" cy="22" r="1.5" fill="#DB2777"/>
|
|
||||||
<!-- Ear -->
|
|
||||||
<path d="M20 18 L18 16 L20 20" fill="#F472B6" stroke="#DB2777" stroke-width="1" stroke-linejoin="round"/>
|
|
||||||
<!-- Legs -->
|
|
||||||
<rect x="16" y="32" width="3" height="4" rx="1" fill="#F472B6" stroke="#DB2777" stroke-width="1"/>
|
|
||||||
<rect x="29" y="32" width="3" height="4" rx="1" fill="#F472B6" stroke="#DB2777" stroke-width="1"/>
|
|
||||||
<!-- Coin slot -->
|
|
||||||
<rect x="22" y="16" width="8" height="1.5" rx="0.5" fill="#DB2777"/>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- Text -->
|
|
||||||
<text x="48" y="28" font-family="Arial, Helvetica, sans-serif" font-size="22" fill="#ffffff" font-weight="600" dominant-baseline="middle">
|
|
||||||
Family
|
|
||||||
</text>
|
|
||||||
<text x="118" y="28" font-family="Arial, Helvetica, sans-serif" font-size="22" fill="#F472B6" font-weight="600" dominant-baseline="middle">
|
|
||||||
Funds
|
|
||||||
</text>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -2,12 +2,6 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
.msg-notice {
|
|
||||||
@apply bg-green-300 text-green-900;
|
|
||||||
}
|
|
||||||
.msg-alert {
|
|
||||||
@apply bg-red-300 text-red-900;
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
include Authenticatable
|
|
||||||
include Authorizable
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
module Authenticatable
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
before_action :authenticate_user
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def authenticate_user
|
|
||||||
Current.user = User.find_by(id: session[:current_user_id]) || GuestUser.new
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,37 +0,0 @@
|
||||||
module Authorizable
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
before_action :require_registered_user
|
|
||||||
end
|
|
||||||
|
|
||||||
class_methods do
|
|
||||||
def allow_unregistered_user(**args)
|
|
||||||
skip_before_action :require_registered_user, **args
|
|
||||||
end
|
|
||||||
|
|
||||||
def require_unregistered_user(**args)
|
|
||||||
skip_before_action :require_registered_user, **args
|
|
||||||
before_action :require_unregistered_user, **args
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def require_registered_user
|
|
||||||
Current.user.registered? || redirect_to_sign_in
|
|
||||||
end
|
|
||||||
|
|
||||||
def require_unregistered_user
|
|
||||||
Current.user.unregistered? || redirect_to_dashboard
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect_to_sign_in
|
|
||||||
session[:return_url] = request.url
|
|
||||||
redirect_to new_session_url, alert: "You must be logged in to continue."
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect_to_dashboard
|
|
||||||
redirect_to root_url, alert: "You are already logged in."
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -3,7 +3,7 @@ class CreditCardBillsController < ApplicationController
|
||||||
|
|
||||||
# GET /credit_card_bills or /credit_card_bills.json
|
# GET /credit_card_bills or /credit_card_bills.json
|
||||||
def index
|
def index
|
||||||
@credit_card_bills = CreditCardBill.all.order(created_at: :desc)
|
@credit_card_bills = CreditCardBill.all
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /credit_card_bills/1 or /credit_card_bills/1.json
|
# GET /credit_card_bills/1 or /credit_card_bills/1.json
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
class SessionsController < ApplicationController
|
|
||||||
require_unregistered_user only: %i[new create]
|
|
||||||
rate_limit to: 5, within: 1.minute, only: :create, with: -> { redirect_to new_session_url, alert: "Cannot currently login" }
|
|
||||||
# GET /sessions/new
|
|
||||||
def new
|
|
||||||
@session = Session.new
|
|
||||||
end
|
|
||||||
|
|
||||||
# POST /sessions or /sessions.json
|
|
||||||
def create
|
|
||||||
@session = Session.new(session_params)
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
if @session.save
|
|
||||||
session[:current_user_id] = @session.user_id
|
|
||||||
|
|
||||||
format.html { redirect_to redirect_url, notice: "Session was successfully created." }
|
|
||||||
format.json { render :show, status: :created, location: @session }
|
|
||||||
else
|
|
||||||
format.html { render :new, status: :unprocessable_entity, alert: @session.errors }
|
|
||||||
format.json { render json: @session.errors, status: :unprocessable_entity }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# DELETE /sessions/1 or /sessions/1.json
|
|
||||||
def destroy
|
|
||||||
session[:current_user_id] = nil
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { redirect_to new_session_url, notice: "Session was successfully destroyed." }
|
|
||||||
format.json { head :no_content }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def redirect_url
|
|
||||||
session.delete(:return_url) || root_url
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only allow a list of trusted parameters through.
|
|
||||||
def session_params
|
|
||||||
params.require(:session).permit(:email, :password)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,82 +0,0 @@
|
||||||
class UsersController < ApplicationController
|
|
||||||
require_unregistered_user only: %i[new create]
|
|
||||||
|
|
||||||
before_action :set_user, only: %i[ show edit update destroy ]
|
|
||||||
|
|
||||||
# GET /users or /users.json
|
|
||||||
def index
|
|
||||||
@users = User.all
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /users/1 or /users/1.json
|
|
||||||
def show
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /users/new
|
|
||||||
def new
|
|
||||||
@user = User.new
|
|
||||||
end
|
|
||||||
|
|
||||||
# GET /users/1/edit
|
|
||||||
def edit
|
|
||||||
end
|
|
||||||
|
|
||||||
# POST /users or /users.json
|
|
||||||
def create
|
|
||||||
@user = User.new(user_params)
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
if ENV["REGISTRATION_ALLOWED"].blank?
|
|
||||||
format.html { redirect_to new_session_url, alert: "Registration disabled." }
|
|
||||||
format.json { render json: @user.errors, status: :unprocessable_entity }
|
|
||||||
elsif @user.save
|
|
||||||
@session = Session.new(session_params).save
|
|
||||||
session[:current_user_id] = @session.user_id
|
|
||||||
|
|
||||||
format.html { redirect_to user_url(@user), notice: "User was successfully created." }
|
|
||||||
format.json { render :show, status: :created, location: @user }
|
|
||||||
else
|
|
||||||
format.html { render :new, status: :unprocessable_entity }
|
|
||||||
format.json { render json: @user.errors, status: :unprocessable_entity }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# PATCH/PUT /users/1 or /users/1.json
|
|
||||||
def update
|
|
||||||
respond_to do |format|
|
|
||||||
if @user.update(user_params)
|
|
||||||
format.html { redirect_to user_url(@user), notice: "User was successfully updated." }
|
|
||||||
format.json { render :show, status: :ok, location: @user }
|
|
||||||
else
|
|
||||||
format.html { render :edit, status: :unprocessable_entity }
|
|
||||||
format.json { render json: @user.errors, status: :unprocessable_entity }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# DELETE /users/1 or /users/1.json
|
|
||||||
def destroy
|
|
||||||
@user.destroy!
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { redirect_to users_url, notice: "User was successfully destroyed." }
|
|
||||||
format.json { head :no_content }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# Use callbacks to share common setup or constraints between actions.
|
|
||||||
def set_user
|
|
||||||
@user = User.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only allow a list of trusted parameters through.
|
|
||||||
def user_params
|
|
||||||
params.require(:user).permit(:email, :password, :password_confirmation)
|
|
||||||
end
|
|
||||||
|
|
||||||
def session_params
|
|
||||||
user_params.slice(:email, :password)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -2,6 +2,6 @@ module ExpensesHelper
|
||||||
def expense_periods
|
def expense_periods
|
||||||
Expense
|
Expense
|
||||||
.periods
|
.periods
|
||||||
.map { |key, value| [ key.titleize, Expense.periods.key(value) ] }
|
.map { |key, value| [key.titleize, Expense.periods.key(value)] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,6 @@ module IncomesHelper
|
||||||
def members
|
def members
|
||||||
Member
|
Member
|
||||||
.all
|
.all
|
||||||
.map { |member, value| [ member.name, member.id ] }
|
.map { |member, value| [member.name, member.id] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
module SessionsHelper
|
|
||||||
end
|
|
|
@ -1,2 +0,0 @@
|
||||||
module UsersHelper
|
|
||||||
end
|
|
|
@ -1,3 +0,0 @@
|
||||||
class Current < ActiveSupport::CurrentAttributes
|
|
||||||
attribute :user
|
|
||||||
end
|
|
|
@ -1,5 +0,0 @@
|
||||||
class GuestUser
|
|
||||||
def registered? = false
|
|
||||||
|
|
||||||
def unregistered? = true
|
|
||||||
end
|
|
|
@ -1,22 +0,0 @@
|
||||||
class Session
|
|
||||||
include ActiveModel::Model
|
|
||||||
|
|
||||||
include ActiveModel::Attributes
|
|
||||||
include ActiveModel::Validations
|
|
||||||
|
|
||||||
attr_accessor :user_id
|
|
||||||
|
|
||||||
attribute :email, :string
|
|
||||||
attribute :password, :string
|
|
||||||
|
|
||||||
validates :email, presence: true
|
|
||||||
validates :password, presence: true
|
|
||||||
|
|
||||||
def save
|
|
||||||
user = User.authenticate_by(email: email, password: password)
|
|
||||||
|
|
||||||
@user_id = user && user.id
|
|
||||||
|
|
||||||
user.present? && self || nil
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
class User < ApplicationRecord
|
|
||||||
has_secure_password
|
|
||||||
|
|
||||||
def registered? = true
|
|
||||||
|
|
||||||
def unregistered? = false
|
|
||||||
end
|
|
|
@ -1,149 +1,48 @@
|
||||||
<div id="<%= dom_id credit_card_bill %>" class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 space-y-6">
|
<div id="<%= dom_id credit_card_bill %>">
|
||||||
<!-- Bill Header -->
|
<p class="my-5">
|
||||||
<div class="border-b border-gray-200 pb-6">
|
<strong class="block font-medium mb-1">Description:</strong>
|
||||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4"><%= credit_card_bill.description %></h2>
|
<%= credit_card_bill.description %>
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Financial Summary Cards -->
|
<p class="my-5">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<strong class="block font-medium mb-1">Amount:</strong>
|
||||||
<div class="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-4">
|
<%= credit_card_bill.amount %>
|
||||||
<div class="flex items-center">
|
</p>
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<p class="text-sm font-medium text-blue-700">Total Bill</p>
|
|
||||||
<p class="text-xl font-bold text-blue-900"><%= number_to_currency(credit_card_bill.amount) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-4">
|
<p class="my-5">
|
||||||
<div class="flex items-center">
|
<strong class="block font-medium mb-1">Accounted For:</strong>
|
||||||
<div class="flex-shrink-0">
|
<%= Expense.credit_card_monthly_total %>
|
||||||
<svg class="h-8 w-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
</p>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<p class="text-sm font-medium text-green-700">Accounted For</p>
|
|
||||||
<p class="text-xl font-bold text-green-900"><%= number_to_currency(Expense.credit_card_monthly_total) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gradient-to-br from-<%= credit_card_bill.unpaid > 0 ? 'red' : 'gray' %>-50 to-<%= credit_card_bill.unpaid > 0 ? 'red' : 'gray' %>-100 rounded-lg p-4">
|
<p class="my-5">
|
||||||
<div class="flex items-center">
|
<strong class="block font-medium mb-1">Unpaid:</strong>
|
||||||
<div class="flex-shrink-0">
|
<%= credit_card_bill.unpaid %>
|
||||||
<% if credit_card_bill.unpaid > 0 %>
|
</p>
|
||||||
<svg class="h-8 w-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
<% else %>
|
|
||||||
<svg class="h-8 w-8 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<p class="text-sm font-medium text-<%= credit_card_bill.unpaid > 0 ? 'red' : 'gray' %>-700">Unpaid Balance</p>
|
|
||||||
<p class="text-xl font-bold text-<%= credit_card_bill.unpaid > 0 ? 'red' : 'gray' %>-900"><%= number_to_currency(credit_card_bill.unpaid) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Member Burden Breakdown -->
|
<p class="my-5">
|
||||||
<div>
|
<table class="min-w-full mb-8">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<thead class="border-b">
|
||||||
<h3 class="text-lg font-medium text-gray-900">Member Burden Breakdown</h3>
|
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
|
||||||
Based on unpaid amount: <%= number_to_currency(credit_card_bill.unpaid) %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Desktop Table -->
|
|
||||||
<div class="hidden sm:block overflow-x-auto">
|
|
||||||
<table class="min-w-full bg-gray-50 rounded-lg overflow-hidden">
|
|
||||||
<thead class="bg-gray-100">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Member</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Member</th>
|
||||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Burden %</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Burden Percent</th>
|
||||||
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount Owed</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Burden Amount</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200">
|
<tbody>
|
||||||
<% Member.all.each do |member| %>
|
<% Member.all.each do |member| %>
|
||||||
<tr class="hover:bg-gray-100 transition-colors">
|
<tr class="even:bg-gray-50 border-b">
|
||||||
<td class="px-4 py-3">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= member.name %></td>
|
||||||
<div class="flex items-center">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= member.burden_percent * 100 %>%</td>
|
||||||
<div class="h-8 w-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= number_to_currency(member.burden_amount(total_amount: credit_card_bill.unpaid)) %></td>
|
||||||
<span class="text-white text-sm font-medium"><%= member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<p class="text-sm font-medium text-gray-900"><%= member.name %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
|
||||||
<%= (member.burden_percent * 100).round(1) %>%
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
<span class="text-sm font-mono font-medium text-gray-900">
|
|
||||||
<%= number_to_currency(member.burden_amount(total_amount: credit_card_bill.unpaid)) %>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile Cards -->
|
|
||||||
<div class="sm:hidden space-y-3">
|
|
||||||
<% Member.all.each do |member| %>
|
|
||||||
<div class="bg-gray-50 rounded-lg p-4">
|
|
||||||
<div class="flex items-center justify-between mb-2">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="h-8 w-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
|
||||||
<span class="text-white text-sm font-medium"><%= member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<p class="ml-3 text-sm font-medium text-gray-900"><%= member.name %></p>
|
|
||||||
</div>
|
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
|
||||||
<%= (member.burden_percent * 100).round(1) %>%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<p class="text-lg font-mono font-semibold text-gray-900">
|
|
||||||
<%= number_to_currency(member.burden_amount(total_amount: credit_card_bill.unpaid)) %>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<% if action_name != "show" %>
|
<% if action_name != "show" %>
|
||||||
<div class="border-t border-gray-200 pt-6 flex flex-wrap gap-3">
|
<%= link_to "Show this credit card bill", credit_card_bill, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<%= link_to credit_card_bill, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
<%= link_to 'Edit this credit card bill', edit_credit_card_bill_path(credit_card_bill), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<hr class="mt-6">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
View details
|
|
||||||
<% end %>
|
|
||||||
<%= link_to edit_credit_card_bill_path(credit_card_bill), class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
Edit bill
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,75 +1,26 @@
|
||||||
<%= form_with(model: credit_card_bill, class: "space-y-6") do |form| %>
|
<%= form_with(model: credit_card_bill, class: "contents") do |form| %>
|
||||||
<% if credit_card_bill.errors.any? %>
|
<% if credit_card_bill.errors.any? %> <div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||||
<div id="error_explanation" class="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg">
|
<h2><%= pluralize(credit_card_bill.errors.count, "error") %> prohibited this credit_card_bill from being saved:</h2>
|
||||||
<div class="flex">
|
|
||||||
<div class="flex-shrink-0">
|
<ul>
|
||||||
<svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-medium"><%= pluralize(credit_card_bill.errors.count, "error") %> prohibited this credit card bill from being saved:</h3>
|
|
||||||
<div class="mt-2 text-sm">
|
|
||||||
<ul class="list-disc list-inside space-y-1">
|
|
||||||
<% credit_card_bill.errors.each do |error| %>
|
<% credit_card_bill.errors.each do |error| %>
|
||||||
<li><%= error.full_message %></li>
|
<li><%= error.full_message %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 sm:p-6 lg:p-8">
|
<div class="my-5">
|
||||||
<div class="flex items-center mb-6">
|
<%= form.label :description %>
|
||||||
<div class="flex-shrink-0">
|
<%= form.text_field :description, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
|
||||||
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900">Credit Card Bill Information</h3>
|
|
||||||
<p class="text-sm text-gray-500">Enter the details for this credit card statement</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
<div class="my-5">
|
||||||
<div class="lg:col-span-2">
|
<%= form.label :amount %>
|
||||||
<%= form.label :description, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.text_field :amount, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
|
||||||
<%= form.text_field :description, class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm", placeholder: "e.g., Chase Sapphire - January 2024" %>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">A description to identify this credit card bill</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="inline">
|
||||||
<%= form.label :amount, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
||||||
<div class="mt-1 relative rounded-md shadow-sm">
|
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
||||||
<span class="text-gray-500 sm:text-sm">$</span>
|
|
||||||
</div>
|
|
||||||
<%= form.text_field :amount, class: "pl-7 pr-3 py-2 block w-full border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm", placeholder: "0.00" %>
|
|
||||||
</div>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">Total amount on the credit card statement</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="bg-gray-50 rounded-lg p-4 w-full">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="h-6 w-6 text-green-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
|
||||||
</svg>
|
|
||||||
<div>
|
|
||||||
<p class="text-sm font-medium text-gray-700">Auto-calculated unpaid amount</p>
|
|
||||||
<p class="text-xs text-gray-500">Based on total minus accounted expenses</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-3">
|
|
||||||
<%= link_to "Cancel", credit_card_bills_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" %>
|
|
||||||
<%= form.submit class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer" %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,39 +1,8 @@
|
||||||
<div class="mx-auto max-w-4xl px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<div class="mb-8">
|
<h1 class="font-bold text-4xl">Editing credit card bill</h1>
|
||||||
<nav class="flex" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to credit_card_bills_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
|
||||||
</svg>
|
|
||||||
Credit Card Bills
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= link_to @credit_card_bill.description, @credit_card_bill, class: "ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2" %>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">Edit</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Editing credit card bill</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Update the details for this credit card bill.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", credit_card_bill: @credit_card_bill %>
|
<%= render "form", credit_card_bill: @credit_card_bill %>
|
||||||
|
|
||||||
|
<%= link_to "Show this credit card bill", @credit_card_bill, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
<%= link_to "Back to credit card bills", credit_card_bills_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,155 +1,14 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="w-full">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-8 gap-4">
|
|
||||||
<div>
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">Credit Card Bills</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Track and manage your credit card expenses and member burdens</p>
|
|
||||||
</div>
|
|
||||||
<%= link_to new_credit_card_bill_path, class: "rounded-lg py-2.5 px-4 sm:py-3 sm:px-5 bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors inline-flex items-center justify-center sm:justify-start" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New credit card bill
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if @credit_card_bills.any? %>
|
|
||||||
<!-- Summary Cards -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Total Bills</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= @credit_card_bills.count %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Total Amount</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= number_to_currency(@credit_card_bills.sum(:amount)) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Unbudgeted</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= number_to_currency(@credit_card_bills.sum(&:unpaid)) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Desktop Table View -->
|
|
||||||
<div class="hidden lg:block overflow-x-auto bg-white rounded-lg shadow">
|
|
||||||
<table class="min-w-full">
|
|
||||||
<thead class="bg-gray-50 border-b border-gray-200">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Amount</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Unbudgeted</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
|
||||||
<% @credit_card_bills.each do |bill| %>
|
|
||||||
<tr class="hover:bg-gray-50 transition-colors">
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<div class="text-sm font-medium text-gray-900"><%= bill.description %></div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span class="text-sm text-gray-900 font-mono"><%= number_to_currency(bill.amount) %></span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span class="text-sm text-gray-900 font-mono"><%= number_to_currency(bill.unpaid) %></span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
||||||
<%= link_to "View", bill, class: "text-blue-600 hover:text-blue-900 mr-3" %>
|
|
||||||
<%= link_to "Edit", edit_credit_card_bill_path(bill), class: "text-indigo-600 hover:text-indigo-900" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile Card View -->
|
|
||||||
<div class="lg:hidden space-y-4">
|
|
||||||
<% @credit_card_bills.each do |bill| %>
|
|
||||||
<div class="bg-white rounded-lg shadow p-4 hover:shadow-md transition-shadow">
|
|
||||||
<div class="flex justify-between items-start mb-3">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900"><%= bill.description %></h3>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<%= link_to bill, class: "text-blue-600 hover:text-blue-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
<%= link_to edit_credit_card_bill_path(bill), class: "text-indigo-600 hover:text-indigo-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-3">
|
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-sm text-gray-500">Total Amount</span>
|
<h1 class="font-bold text-4xl">Credit card bills</h1>
|
||||||
<span class="text-sm font-mono font-medium"><%= number_to_currency(bill.amount) %></span>
|
<%= link_to 'New credit card bill', new_credit_card_bill_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-sm text-gray-500">Unbudgeted</span>
|
<div id="credit_card_bills" class="min-w-full">
|
||||||
<span class="text-sm font-mono font-medium text-gray-900"><%= number_to_currency(bill.unpaid) %></span>
|
<%= render @credit_card_bills %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="text-center py-12 bg-white rounded-lg shadow">
|
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
|
||||||
</svg>
|
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900">No credit card bills</h3>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">Get started by creating a new credit card bill.</p>
|
|
||||||
<div class="mt-6">
|
|
||||||
<%= link_to new_credit_card_bill_path, class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New credit card bill
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,31 +1,7 @@
|
||||||
<div class="mx-auto max-w-4xl px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<div class="mb-8">
|
<h1 class="font-bold text-4xl">New credit card bill</h1>
|
||||||
<nav class="flex" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to credit_card_bills_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
|
||||||
</svg>
|
|
||||||
Credit Card Bills
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">New credit card bill</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="text-3xl font-bold text-gray-900">New credit card bill</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Add a new credit card bill to track member expenses and burden distribution.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", credit_card_bill: @credit_card_bill %>
|
<%= render "form", credit_card_bill: @credit_card_bill %>
|
||||||
|
|
||||||
|
<%= link_to 'Back to credit card bills', credit_card_bills_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,60 +1,15 @@
|
||||||
<div class="mx-auto max-w-6xl px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full flex">
|
||||||
|
<div class="mx-auto">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="mb-6">
|
|
||||||
<nav class="flex" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to credit_card_bills_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
|
||||||
</svg>
|
|
||||||
Credit Card Bills
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2"><%= @credit_card_bill.description %></span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render @credit_card_bill %>
|
<%= render @credit_card_bill %>
|
||||||
|
|
||||||
<div class="mt-8 flex flex-wrap gap-3">
|
<%= link_to 'Edit this credit card bill', edit_credit_card_bill_path(@credit_card_bill), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<%= link_to edit_credit_card_bill_path(@credit_card_bill), class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
<div class="inline-block ml-2">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<%= button_to 'Destroy this credit card bill', credit_card_bill_path(@credit_card_bill), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
</div>
|
||||||
</svg>
|
<%= link_to 'Back to credit card bills', credit_card_bills_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
Edit credit card bill
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= button_to credit_card_bill_path(@credit_card_bill), method: :delete, form: { data: { turbo_confirm: "Are you sure you want to delete this credit card bill?" } }, class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
||||||
</svg>
|
|
||||||
Delete credit card bill
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= link_to credit_card_bills_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
||||||
</svg>
|
|
||||||
Back to credit card bills
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,110 +1,70 @@
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="m-4 lg:m-8">
|
<div class="m-8">
|
||||||
<div class="overflow-x-auto mb-8">
|
<table class="min-w-full mb-8">
|
||||||
<table class="min-w-full">
|
<thead class="border-b">
|
||||||
<thead class="border-b bg-gray-50">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Member</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Member</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Burden Percent</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Burden Percent</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Burden Amount</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Burden Amount</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @members.each do |member| %>
|
<% @members.each do |member| %>
|
||||||
<tr class="even:bg-gray-50 border-b hover:bg-gray-100">
|
<tr class="even:bg-gray-50 border-b">
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= member.name %></td>
|
||||||
<div class="font-medium"><%= member.name %></div>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= member.burden_percent * 100 %>%</td>
|
||||||
</td>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= number_to_currency(member.burden_amount) %></td>
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
|
|
||||||
<%= member.burden_percent * 100 %>%
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
|
||||||
<span class="font-mono"><%= number_to_currency(member.burden_amount) %></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="min-w-full">
|
<table class="min-w-full">
|
||||||
<thead class="border-b bg-gray-50">
|
<thead class="border-b">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Member</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Member</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">For</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">For</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Amount</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Amount</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Included</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Included</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @incomes.each do |income| %>
|
<% @incomes.each do |income| %>
|
||||||
<tr class="even:bg-gray-50 border-b hover:bg-gray-100">
|
<tr class="even:bg-gray-50 border-b">
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= income.member.name %></td>
|
||||||
<div class="font-medium"><%= income.member.name %></div>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= income.description %></td>
|
||||||
</td>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= number_to_currency(income.amount) %></td>
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= income.included %></td>
|
||||||
<%= income.description %>
|
|
||||||
</td>
|
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
|
||||||
<span class="font-mono"><%= number_to_currency(income.amount) %></span>
|
|
||||||
</td>
|
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
|
||||||
<% if income.included %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">yes</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800">no</span>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="m-4 lg:m-8">
|
<div class="m-8">
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="min-w-full">
|
<table class="min-w-full">
|
||||||
<thead class="border-b bg-gray-50">
|
<thead class="border-b">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Bill</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Bill</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Payment</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Payment</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left">Monthly</th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Period</th>
|
||||||
<th scope="col" class="text-xs sm:text-sm font-medium text-gray-900 px-3 sm:px-6 py-2 sm:py-4 text-left"></th>
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Montly</th>
|
||||||
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">credit_card</th>
|
||||||
|
<th scope="col" class="text-sm font-medium text-gray-900 px-6 py-4 text-left">Estimated</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @expenses.each do |expense| %>
|
<% @expenses.each do |expense| %>
|
||||||
<tr class="even:bg-gray-50 border-b hover:bg-gray-100">
|
<tr class="even:bg-gray-50 border-b">
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= expense.description %></td>
|
||||||
<div class="font-medium"><%= expense.description %></div>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= number_to_currency(expense.payment) %></td>
|
||||||
</td>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= expense.period %></td>
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= number_to_currency(expense.monthly) %></td>
|
||||||
<span class="font-mono"><%= number_to_currency(expense.payment) %></span>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= expense.credit_card %></td>
|
||||||
</td>
|
<td class="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap"><%= expense.estimated %></td>
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
|
||||||
<span class="font-mono"><%= number_to_currency(expense.monthly) %></span>
|
|
||||||
</td>
|
|
||||||
<td class="text-xs sm:text-sm text-gray-900 font-light px-3 sm:px-6 py-2 sm:py-4">
|
|
||||||
<div class="flex flex-wrap gap-1">
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800">
|
|
||||||
<%= expense.period.downcase %>
|
|
||||||
</span>
|
|
||||||
<% if expense.credit_card %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">credit card</span>
|
|
||||||
<% end %>
|
|
||||||
<% if expense.estimated %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">estimated</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,80 +1,32 @@
|
||||||
<div id="<%= dom_id expense %>" class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
<div id="<%= dom_id expense %>">
|
||||||
<div class="mb-6">
|
<p class="my-5">
|
||||||
<h2 class="text-2xl font-semibold text-gray-900 mb-4"><%= expense.description %></h2>
|
<strong class="block font-medium mb-1">Description:</strong>
|
||||||
|
<%= expense.description %>
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<p class="my-5">
|
||||||
<div class="bg-gray-50 rounded-lg p-4">
|
<strong class="block font-medium mb-1">Payment:</strong>
|
||||||
<p class="text-sm text-gray-600 mb-1">Payment Amount</p>
|
<%= expense.payment %>
|
||||||
<p class="text-xl font-mono font-semibold text-gray-900"><%= number_to_currency(expense.payment) %></p>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-gray-50 rounded-lg p-4">
|
<p class="my-5">
|
||||||
<p class="text-sm text-gray-600 mb-1">Monthly Amount</p>
|
<strong class="block font-medium mb-1">Period:</strong>
|
||||||
<p class="text-xl font-mono font-semibold text-gray-900"><%= number_to_currency(expense.monthly) %></p>
|
<%= expense.period %>
|
||||||
</div>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="border-t border-gray-200 pt-4">
|
<p class="my-5">
|
||||||
<p class="text-sm text-gray-600 mb-3">Details</p>
|
<strong class="block font-medium mb-1">Credit card:</strong>
|
||||||
<div class="flex flex-wrap gap-2">
|
<%= expense.credit_card %>
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-purple-100 text-purple-800">
|
</p>
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= expense.period.downcase %>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<% if expense.credit_card %>
|
<p class="my-5">
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-blue-100 text-blue-800">
|
<strong class="block font-medium mb-1">Estimated:</strong>
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
<%= expense.estimated %>
|
||||||
<path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z"/>
|
</p>
|
||||||
<path fill-rule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
credit card
|
|
||||||
</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-gray-100 text-gray-700">
|
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4zm2 6a2 2 0 012-2h8a2 2 0 012 2v4a2 2 0 01-2 2H8a2 2 0 01-2-2v-4zm6 4a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
bank account
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if expense.estimated %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-yellow-100 text-yellow-800">
|
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
estimated
|
|
||||||
</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-md text-sm font-medium bg-green-100 text-green-800">
|
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
actual
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if action_name != "show" %>
|
<% if action_name != "show" %>
|
||||||
<div class="mt-6 pt-6 border-t border-gray-200 flex gap-3">
|
<%= link_to "Show this expense", expense, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<%= link_to expense, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
<%= link_to 'Edit this expense', edit_expense_path(expense), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<hr class="mt-6">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
View details
|
|
||||||
<% end %>
|
|
||||||
<%= link_to edit_expense_path(expense), class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
Edit expense
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,103 +1,47 @@
|
||||||
<%= form_with(model: expense, class: "space-y-6") do |form| %>
|
<%= form_with(model: expense, class: "contents") do |form| %>
|
||||||
<% if expense.errors.any? %>
|
<% if expense.errors.any? %>
|
||||||
<div id="error_explanation" class="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg">
|
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||||
<div class="flex">
|
<h2><%= pluralize(expense.errors.count, "error") %> prohibited this expense from being saved:</h2>
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
<ul>
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-medium"><%= pluralize(expense.errors.count, "error") %> prohibited this expense from being saved:</h3>
|
|
||||||
<div class="mt-2 text-sm">
|
|
||||||
<ul class="list-disc list-inside space-y-1">
|
|
||||||
<% expense.errors.each do |error| %>
|
<% expense.errors.each do |error| %>
|
||||||
<li><%= error.full_message %></li>
|
<li><%= error.full_message %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 sm:p-6 lg:p-8">
|
<div class="my-5">
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Expense Information</h3>
|
<%= form.label :description %>
|
||||||
|
<%= form.text_field :description, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
|
||||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
||||||
<div class="lg:col-span-2">
|
|
||||||
<%= form.label :description, class: "block text-sm font-medium text-gray-700" %>
|
|
||||||
<%= form.text_field :description, class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm", placeholder: "e.g., Netflix subscription" %>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">A brief description of the expense</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="my-5">
|
||||||
<%= form.label :payment, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :payment %>
|
||||||
<div class="mt-1 relative rounded-md shadow-sm">
|
<%= form.text_field :payment, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
||||||
<span class="text-gray-500 sm:text-sm">$</span>
|
|
||||||
</div>
|
|
||||||
<%= form.text_field :payment, class: "pl-7 pr-3 py-2 block w-full border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm", placeholder: "0.00" %>
|
|
||||||
</div>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">The payment amount for this billing period</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="my-5">
|
||||||
<%= form.label :period, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :period %>
|
||||||
<%=
|
<%=
|
||||||
form.select :period,
|
form.select :period,
|
||||||
options_for_select(expense_periods, expense.period),
|
options_for_select(expense_periods, expense.period),
|
||||||
{},
|
{},
|
||||||
class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full"
|
||||||
%>
|
%>
|
||||||
<p class="mt-1 text-sm text-gray-500">How often this expense is billed</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 sm:p-6 lg:p-8">
|
<div class="my-5">
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Payment Details</h3>
|
<%= form.label :credit_card %>
|
||||||
|
<%= form.check_box :credit_card, class: "block mt-2 h-5 w-5" %>
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex-grow">
|
|
||||||
<label for="expense_credit_card" class="text-sm font-medium text-gray-700">Credit Card Payment</label>
|
|
||||||
<p class="text-sm text-gray-500">This expense is paid by credit card</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0 ml-4">
|
|
||||||
<%= form.check_box :credit_card, class: "toggle-checkbox sr-only" %>
|
|
||||||
<label for="expense_credit_card" class="toggle-label relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 bg-gray-200">
|
|
||||||
<span class="toggle-switch pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out translate-x-0"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="my-5">
|
||||||
<div class="flex-grow">
|
<%= form.label :estimated %>
|
||||||
<label for="expense_estimated" class="text-sm font-medium text-gray-700">Estimated Amount</label>
|
<%= form.check_box :estimated, class: "block mt-2 h-5 w-5" %>
|
||||||
<p class="text-sm text-gray-500">This amount is an estimate</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-shrink-0 ml-4">
|
|
||||||
<%= form.check_box :estimated, class: "toggle-checkbox sr-only" %>
|
|
||||||
<label for="expense_estimated" class="toggle-label relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 bg-gray-200">
|
|
||||||
<span class="toggle-switch pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out translate-x-0"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<div class="inline">
|
||||||
.toggle-checkbox:checked + .toggle-label {
|
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
||||||
background-color: rgb(59, 130, 246);
|
|
||||||
}
|
|
||||||
.toggle-checkbox:checked + .toggle-label .toggle-switch {
|
|
||||||
transform: translateX(1.25rem);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-3">
|
|
||||||
<%= link_to "Cancel", expenses_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" %>
|
|
||||||
<%= form.submit class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer" %>
|
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,39 +1,8 @@
|
||||||
<div class="mx-auto max-w-4xl px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<div class="mb-8">
|
<h1 class="font-bold text-4xl">Editing expense</h1>
|
||||||
<nav class="flex" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to expenses_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Expenses
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= link_to @expense.description, @expense, class: "ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2" %>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">Edit</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Editing expense</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Update the details for this expense.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", expense: @expense %>
|
<%= render "form", expense: @expense %>
|
||||||
|
|
||||||
|
<%= link_to "Show this expense", @expense, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
<%= link_to "Back to expenses", expenses_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,130 +1,14 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="w-full">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex justify-between items-center mb-8">
|
<div class="flex justify-between items-center">
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">Expenses</h1>
|
<h1 class="font-bold text-4xl">Expenses</h1>
|
||||||
<%= link_to new_expense_path, class: "rounded-lg py-2.5 px-4 sm:py-3 sm:px-5 bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors flex items-center" do %>
|
<%= link_to 'New expense', new_expense_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New expense
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @expenses.any? %>
|
<div id="expenses" class="min-w-full">
|
||||||
<div class="hidden lg:block overflow-x-auto bg-white rounded-lg shadow">
|
<%= render @expenses %>
|
||||||
<table class="min-w-full">
|
|
||||||
<thead class="bg-gray-50 border-b border-gray-200">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payment</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Monthly</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Details</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
|
||||||
<% @expenses.each do |expense| %>
|
|
||||||
<tr class="hover:bg-gray-50 transition-colors">
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<div class="text-sm font-medium text-gray-900"><%= expense.description %></div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span class="text-sm text-gray-900 font-mono"><%= number_to_currency(expense.payment) %></span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span class="text-sm text-gray-900 font-mono"><%= number_to_currency(expense.monthly) %></span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<div class="flex gap-1">
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800">
|
|
||||||
<%= expense.period.downcase %>
|
|
||||||
</span>
|
|
||||||
<% if expense.credit_card %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">credit card</span>
|
|
||||||
<% end %>
|
|
||||||
<% if expense.estimated %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">estimated</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
||||||
<%= link_to "View", expense, class: "text-blue-600 hover:text-blue-900 mr-3" %>
|
|
||||||
<%= link_to "Edit", edit_expense_path(expense), class: "text-indigo-600 hover:text-indigo-900" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="lg:hidden space-y-4">
|
|
||||||
<% @expenses.each do |expense| %>
|
|
||||||
<div class="bg-white rounded-lg shadow p-4 hover:shadow-md transition-shadow">
|
|
||||||
<div class="flex justify-between items-start mb-3">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900"><%= expense.description %></h3>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<%= link_to expense, class: "text-blue-600 hover:text-blue-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
<%= link_to edit_expense_path(expense), class: "text-indigo-600 hover:text-indigo-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2 mb-3">
|
|
||||||
<div>
|
|
||||||
<p class="text-xs text-gray-500">Payment</p>
|
|
||||||
<p class="text-sm font-mono font-medium"><%= number_to_currency(expense.payment) %></p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-xs text-gray-500">Monthly</p>
|
|
||||||
<p class="text-sm font-mono font-medium"><%= number_to_currency(expense.monthly) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-1">
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800">
|
|
||||||
<%= expense.period.downcase %>
|
|
||||||
</span>
|
|
||||||
<% if expense.credit_card %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">credit card</span>
|
|
||||||
<% end %>
|
|
||||||
<% if expense.estimated %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">estimated</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="text-center py-12 bg-white rounded-lg shadow">
|
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
|
||||||
</svg>
|
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900">No expenses</h3>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">Get started by creating a new expense.</p>
|
|
||||||
<div class="mt-6">
|
|
||||||
<%= link_to new_expense_path, class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New expense
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,31 +1,7 @@
|
||||||
<div class="mx-auto max-w-4xl px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<div class="mb-8">
|
<h1 class="font-bold text-4xl">New expense</h1>
|
||||||
<nav class="flex" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to expenses_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Expenses
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">New expense</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="text-3xl font-bold text-gray-900">New expense</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Add a new recurring expense to track your spending.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", expense: @expense %>
|
<%= render "form", expense: @expense %>
|
||||||
|
|
||||||
|
<%= link_to 'Back to expenses', expenses_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,60 +1,15 @@
|
||||||
<div class="mx-auto max-w-4xl px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full flex">
|
||||||
|
<div class="mx-auto">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="mb-6">
|
|
||||||
<nav class="flex" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to expenses_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Expenses
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2"><%= @expense.description %></span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render @expense %>
|
<%= render @expense %>
|
||||||
|
|
||||||
<div class="mt-8 flex flex-wrap gap-3">
|
<%= link_to 'Edit this expense', edit_expense_path(@expense), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<%= link_to edit_expense_path(@expense), class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
<div class="inline-block ml-2">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<%= button_to 'Destroy this expense', expense_path(@expense), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
</div>
|
||||||
</svg>
|
<%= link_to 'Back to expenses', expenses_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
Edit expense
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= button_to expense_path(@expense), method: :delete, form: { data: { turbo_confirm: "Are you sure you want to delete this expense?" } }, class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
||||||
</svg>
|
|
||||||
Delete expense
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= link_to expenses_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
||||||
</svg>
|
|
||||||
Back to expenses
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
<%= form_with(model: income, class: "space-y-8") do |form| %>
|
<%= form_with(model: income, class: "contents") do |form| %>
|
||||||
<% if income.errors.any? %>
|
<% if income.errors.any? %>
|
||||||
<div id="error_explanation" class="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg">
|
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||||
<div class="flex items-center mb-2">
|
<h2><%= pluralize(income.errors.count, "error") %> prohibited this income from being saved:</h2>
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
<ul>
|
||||||
</svg>
|
|
||||||
<h3 class="text-sm font-medium"><%= pluralize(income.errors.count, "error") %> prohibited this income from being saved:</h3>
|
|
||||||
</div>
|
|
||||||
<ul class="list-disc list-inside text-sm space-y-1">
|
|
||||||
<% income.errors.each do |error| %>
|
<% income.errors.each do |error| %>
|
||||||
<li><%= error.full_message %></li>
|
<li><%= error.full_message %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -15,74 +11,32 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Basic Information -->
|
<div class="my-5">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
<%= form.label :description %>
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Basic Information</h3>
|
<%= form.text_field :description, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div class="md:col-span-2">
|
|
||||||
<%= form.label :description, class: "block text-sm font-medium text-gray-700 mb-2" %>
|
|
||||||
<%= form.text_field :description,
|
|
||||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
|
|
||||||
placeholder: "Enter income description..." %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="my-5">
|
||||||
<%= form.label :amount, class: "block text-sm font-medium text-gray-700 mb-2" %>
|
<%= form.label :included %>
|
||||||
<div class="relative">
|
<%= form.check_box :included, class: "block mt-2 h-5 w-5" %>
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
||||||
<span class="text-gray-500 sm:text-sm">$</span>
|
|
||||||
</div>
|
|
||||||
<%= form.number_field :amount,
|
|
||||||
class: "block w-full pl-7 pr-3 py-2 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
|
|
||||||
placeholder: "0.00",
|
|
||||||
step: "0.01",
|
|
||||||
min: "0" %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="my-5">
|
||||||
<%= form.label :member_id, "Member", class: "block text-sm font-medium text-gray-700 mb-2" %>
|
<%= form.label :amount %>
|
||||||
<%= form.select :member_id,
|
<%= form.text_field :amount, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-5">
|
||||||
|
<%= form.label :member_id %>
|
||||||
|
<%=
|
||||||
|
form.select :member_id,
|
||||||
options_for_select(members, income.member_id),
|
options_for_select(members, income.member_id),
|
||||||
{ prompt: "Select a member..." },
|
{},
|
||||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full"
|
||||||
</div>
|
%>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Budget Settings -->
|
<div class="inline">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Budget Settings</h3>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex-1">
|
|
||||||
<%= form.label :included, "Include in Budget", class: "block text-sm font-medium text-gray-700" %>
|
|
||||||
<p class="text-sm text-gray-500 mt-1">Whether this income should be included in budget calculations</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6">
|
|
||||||
<%= form.check_box :included, class: "toggle-checkbox sr-only" %>
|
|
||||||
<label for="income_included" class="toggle-label relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus-within:outline-none focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 bg-gray-200">
|
|
||||||
<span class="toggle-switch pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out translate-x-0"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Form Actions -->
|
|
||||||
<div class="flex justify-end space-x-3 pt-6 border-t border-gray-200">
|
|
||||||
<%= link_to incomes_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
|
||||||
Cancel
|
|
||||||
<% end %>
|
|
||||||
<%= form.submit class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.toggle-checkbox:checked + .toggle-label {
|
|
||||||
background-color: #3B82F6;
|
|
||||||
}
|
|
||||||
.toggle-checkbox:checked + .toggle-label .toggle-switch {
|
|
||||||
transform: translateX(1.25rem);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,49 +1,27 @@
|
||||||
<div id="<%= dom_id income %>" class="bg-white rounded-lg shadow hover:shadow-md transition-shadow">
|
<div id="<%= dom_id income %>">
|
||||||
<div class="p-6">
|
<p class="my-5">
|
||||||
<div class="flex justify-between items-start mb-4">
|
<strong class="block font-medium mb-1">Description:</strong>
|
||||||
<h3 class="text-lg font-semibold text-gray-900"><%= income.description %></h3>
|
<%= income.description %>
|
||||||
<% if income.included %>
|
</p>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">included</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">excluded</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
|
<p class="my-5">
|
||||||
<div class="flex items-center">
|
<strong class="block font-medium mb-1">Included:</strong>
|
||||||
<div class="h-10 w-10 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
<%= income.included %>
|
||||||
<span class="text-white text-sm font-medium"><%= income.member.name.first.upcase %></span>
|
</p>
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<p class="text-sm font-medium text-gray-900"><%= income.member.name %></p>
|
|
||||||
<p class="text-xs text-gray-500">Member</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-end sm:justify-start">
|
<p class="my-5">
|
||||||
<div class="text-right sm:text-left">
|
<strong class="block font-medium mb-1">Amount:</strong>
|
||||||
<p class="text-2xl font-bold text-gray-900 font-mono"><%= number_to_currency(income.amount) %></p>
|
<%= income.amount %>
|
||||||
<p class="text-xs text-gray-500">Amount</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
<p class="my-5">
|
||||||
</div>
|
<strong class="block font-medium mb-1">Member:</strong>
|
||||||
|
<%= income.member_id %>
|
||||||
|
</p>
|
||||||
|
|
||||||
<% if action_name != "show" %>
|
<% if action_name != "show" %>
|
||||||
<div class="flex gap-2 pt-4 border-t border-gray-200">
|
<%= link_to "Show this income", income, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<%= link_to income, class: "flex-1 inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-blue-600 bg-blue-50 hover:bg-blue-100 rounded-md transition-colors" do %>
|
<%= link_to 'Edit this income', edit_income_path(income), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<hr class="mt-6">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
View
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to edit_income_path(income), class: "flex-1 inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-indigo-600 bg-indigo-50 hover:bg-indigo-100 rounded-md transition-colors" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,42 +1,8 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<!-- Breadcrumb -->
|
<h1 class="font-bold text-4xl">Editing income</h1>
|
||||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to incomes_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Incomes
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= link_to @income, class: "ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2" do %>
|
|
||||||
<%= @income.description %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">Edit</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">Edit Income</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Update the details for "<%= @income.description %>"</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", income: @income %>
|
<%= render "form", income: @income %>
|
||||||
</div>
|
|
||||||
|
<%= link_to "Show this income", @income, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
<%= link_to "Back to incomes", incomes_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,193 +1,14 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="w-full">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-8 gap-4">
|
|
||||||
<div>
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">Incomes</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Track member income sources and budget allocations</p>
|
|
||||||
</div>
|
|
||||||
<%= link_to new_income_path, class: "rounded-lg py-2.5 px-4 sm:py-3 sm:px-5 bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors inline-flex items-center justify-center sm:justify-start" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New income
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if @incomes.any? %>
|
|
||||||
<!-- Summary Cards -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Total Sources</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= @incomes.count %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Total Income</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= number_to_currency(@incomes.sum(:amount)) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Included</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= number_to_currency(@incomes.where(included: true).sum(:amount)) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Excluded</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= number_to_currency(@incomes.where(included: false).sum(:amount)) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Desktop Table View -->
|
|
||||||
<div class="hidden lg:block overflow-x-auto bg-white rounded-lg shadow">
|
|
||||||
<table class="min-w-full">
|
|
||||||
<thead class="bg-gray-50 border-b border-gray-200">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Member</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
|
||||||
<% @incomes.each do |income| %>
|
|
||||||
<tr class="hover:bg-gray-50 transition-colors">
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<div class="text-sm font-medium text-gray-900"><%= income.description %></div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="h-8 w-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
|
||||||
<span class="text-white text-sm font-medium"><%= income.member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<p class="text-sm font-medium text-gray-900"><%= income.member.name %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<span class="text-sm text-gray-900 font-mono"><%= number_to_currency(income.amount) %></span>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<% if income.included %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">included</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">excluded</span>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
||||||
<%= link_to "View", income, class: "text-blue-600 hover:text-blue-900 mr-3" %>
|
|
||||||
<%= link_to "Edit", edit_income_path(income), class: "text-indigo-600 hover:text-indigo-900" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile Card View -->
|
|
||||||
<div class="lg:hidden space-y-4">
|
|
||||||
<% @incomes.each do |income| %>
|
|
||||||
<div class="bg-white rounded-lg shadow p-4 hover:shadow-md transition-shadow">
|
|
||||||
<div class="flex justify-between items-start mb-3">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900"><%= income.description %></h3>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<%= link_to income, class: "text-blue-600 hover:text-blue-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
<%= link_to edit_income_path(income), class: "text-indigo-600 hover:text-indigo-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-3">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="h-8 w-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
|
||||||
<span class="text-white text-sm font-medium"><%= income.member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<span class="ml-3 text-sm font-medium text-gray-900"><%= income.member.name %></span>
|
|
||||||
</div>
|
|
||||||
<span class="text-lg font-mono font-semibold text-gray-900"><%= number_to_currency(income.amount) %></span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-sm text-gray-500">Budget Status</span>
|
<h1 class="font-bold text-4xl">Incomes</h1>
|
||||||
<% if income.included %>
|
<%= link_to 'New income', new_income_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">included</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">excluded</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="incomes" class="min-w-full">
|
||||||
|
<%= render @incomes %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="text-center py-12 bg-white rounded-lg shadow">
|
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
||||||
</svg>
|
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900">No income sources</h3>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">Get started by adding your first income source.</p>
|
|
||||||
<div class="mt-6">
|
|
||||||
<%= link_to new_income_path, class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New income
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,32 +1,7 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<!-- Breadcrumb -->
|
<h1 class="font-bold text-4xl">New income</h1>
|
||||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to incomes_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Incomes
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">New Income</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">New Income</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Add a new income source to your budget</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", income: @income %>
|
<%= render "form", income: @income %>
|
||||||
</div>
|
|
||||||
|
<%= link_to 'Back to incomes', incomes_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,110 +1,15 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full flex">
|
||||||
|
<div class="mx-auto">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Breadcrumb -->
|
<%= render @income %>
|
||||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to incomes_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Incomes
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2"><%= @income.description %></span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
<%= link_to 'Edit this income', edit_income_path(@income), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<div class="mb-8">
|
<div class="inline-block ml-2">
|
||||||
<div class="flex justify-between items-start mb-4">
|
<%= button_to 'Destroy this income', income_path(@income), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
|
||||||
<div>
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900"><%= @income.description %></h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Income source details and budget allocation</p>
|
|
||||||
</div>
|
|
||||||
<% if @income.included %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
|
|
||||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
included
|
|
||||||
</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800">
|
|
||||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
excluded
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Income Details Card -->
|
|
||||||
<div class="bg-white rounded-lg shadow-lg overflow-hidden mb-8">
|
|
||||||
<div class="px-6 py-8">
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
||||||
<!-- Member Info -->
|
|
||||||
<div class="flex items-center space-x-4">
|
|
||||||
<div class="h-16 w-16 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
|
||||||
<span class="text-white text-xl font-medium"><%= @income.member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900">Member</h3>
|
|
||||||
<p class="text-gray-600"><%= @income.member.name %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Amount -->
|
|
||||||
<div class="text-center md:text-right">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Amount</h3>
|
|
||||||
<p class="text-4xl font-bold text-gray-900 font-mono"><%= number_to_currency(@income.amount) %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Actions -->
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<%= link_to edit_income_path(@income), class: "inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
Edit Income
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= button_to income_path(@income), method: :delete,
|
|
||||||
class: "inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors",
|
|
||||||
confirm: "Are you sure you want to delete this income source?" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
||||||
</svg>
|
|
||||||
Delete
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= link_to incomes_path, class: "inline-flex items-center px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium rounded-lg transition-colors" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
||||||
</svg>
|
|
||||||
Back to Incomes
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
<%= link_to 'Back to incomes', incomes_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,74 +12,16 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<nav class="w-full py-4 px-4 md:px-6 bg-gray-900">
|
<nav class="w-100 p-6 bg-gray-900 flex items-center justify-between flex-wrap">
|
||||||
<input type="checkbox" id="mobile-menu" class="peer hidden" />
|
<ul class="flex">
|
||||||
<div class="flex items-center justify-between">
|
<li class="mr-6"><%= link_to "Dashboard", root_path, class: "text-white" %></li>
|
||||||
<div class="flex items-center flex-shrink-0">
|
<li class="mr-6"><%= link_to "Expenses", expenses_path, class: "text-white" %></li>
|
||||||
<%= link_to root_path, class: "mr-4 md:mr-8" do %>
|
<li class="mr-6"><%= link_to "Credit Card Bills", credit_card_bills_path, class: "text-white" %></li>
|
||||||
<%= image_tag "logo.svg", alt: "Family Funds Logo", class: "h-8" %>
|
<li class="mr-6"><%= link_to "Incomes", incomes_path, class: "text-white" %></li>
|
||||||
<% end %>
|
<li class="mr-6"><%= link_to "Members", members_path, class: "text-white" %></li>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Desktop menu -->
|
|
||||||
<div class="hidden lg:flex items-center flex-grow justify-center">
|
|
||||||
<ul class="flex space-x-6">
|
|
||||||
<% if Current.user.registered? %>
|
|
||||||
<li><%= link_to "Dashboard", root_path, class: "text-white hover:text-gray-300" %></li>
|
|
||||||
<li><%= link_to "Expenses", expenses_path, class: "text-white hover:text-gray-300" %></li>
|
|
||||||
<li><%= link_to "Credit Card Bills", credit_card_bills_path, class: "text-white hover:text-gray-300" %></li>
|
|
||||||
<li><%= link_to "Incomes", incomes_path, class: "text-white hover:text-gray-300" %></li>
|
|
||||||
<li><%= link_to "Members", members_path, class: "text-white hover:text-gray-300" %></li>
|
|
||||||
<% else %>
|
|
||||||
<li><%= link_to "Sign up", new_user_path, class: "text-white hover:text-gray-300" %></li>
|
|
||||||
<li><%= link_to "Log in", new_session_path, class: "text-white hover:text-gray-300" %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center space-x-4 flex-shrink-0">
|
|
||||||
<!-- Hamburger menu button -->
|
|
||||||
<label for="mobile-menu"
|
|
||||||
class="lg:hidden text-white hover:text-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 rounded-lg p-2 cursor-pointer"
|
|
||||||
>
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<% if Current.user.registered? %>
|
|
||||||
<%= render 'shared/user_dropdown' %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile menu -->
|
|
||||||
<div class="lg:hidden mt-4 hidden peer-checked:block">
|
|
||||||
<div class="border-t border-gray-700 pt-4">
|
|
||||||
<ul class="space-y-2">
|
|
||||||
<% if Current.user.registered? %>
|
|
||||||
<li><%= link_to "Dashboard", root_path, class: "block text-white hover:text-gray-300 hover:bg-gray-800 px-3 py-2 rounded-md text-base font-medium" %></li>
|
|
||||||
<li><%= link_to "Expenses", expenses_path, class: "block text-white hover:text-gray-300 hover:bg-gray-800 px-3 py-2 rounded-md text-base font-medium" %></li>
|
|
||||||
<li><%= link_to "Credit Card Bills", credit_card_bills_path, class: "block text-white hover:text-gray-300 hover:bg-gray-800 px-3 py-2 rounded-md text-base font-medium" %></li>
|
|
||||||
<li><%= link_to "Incomes", incomes_path, class: "block text-white hover:text-gray-300 hover:bg-gray-800 px-3 py-2 rounded-md text-base font-medium" %></li>
|
|
||||||
<li><%= link_to "Members", members_path, class: "block text-white hover:text-gray-300 hover:bg-gray-800 px-3 py-2 rounded-md text-base font-medium" %></li>
|
|
||||||
<% else %>
|
|
||||||
<li><%= link_to "Sign up", new_user_path, class: "block text-white hover:text-gray-300 hover:bg-gray-800 px-3 py-2 rounded-md text-base font-medium" %></li>
|
|
||||||
<li><%= link_to "Log in", new_session_path, class: "block text-white hover:text-gray-300 hover:bg-gray-800 px-3 py-2 rounded-md text-base font-medium" %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<% if flash.any? %>
|
|
||||||
<% flash.each do |type, msg| %>
|
|
||||||
<div class="py-2 px-4 font-bold msg-<%= type %>">
|
|
||||||
<%= msg %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<main class="container mx-auto mt-28 px-5 flex">
|
<main class="container mx-auto mt-28 px-5 flex">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
<%= form_with(model: member, class: "space-y-8") do |form| %>
|
<%= form_with(model: member, class: "contents") do |form| %>
|
||||||
<% if member.errors.any? %>
|
<% if member.errors.any? %>
|
||||||
<div id="error_explanation" class="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg">
|
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||||
<div class="flex items-center mb-2">
|
<h2><%= pluralize(member.errors.count, "error") %> prohibited this member from being saved:</h2>
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
<ul>
|
||||||
</svg>
|
|
||||||
<h3 class="text-sm font-medium"><%= pluralize(member.errors.count, "error") %> prohibited this member from being saved:</h3>
|
|
||||||
</div>
|
|
||||||
<ul class="list-disc list-inside text-sm space-y-1">
|
|
||||||
<% member.errors.each do |error| %>
|
<% member.errors.each do |error| %>
|
||||||
<li><%= error.full_message %></li>
|
<li><%= error.full_message %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -15,52 +11,17 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Basic Information -->
|
<div class="my-5">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
<%= form.label :name %>
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Basic Information</h3>
|
<%= form.text_field :name, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6">
|
|
||||||
<div>
|
|
||||||
<%= form.label :name, class: "block text-sm font-medium text-gray-700 mb-2" %>
|
|
||||||
<%= form.text_field :name,
|
|
||||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
|
|
||||||
placeholder: "Enter member name..." %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Budget Settings -->
|
<div class="my-5">
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
<%= form.label :pays %>
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Budget Role</h3>
|
<%= form.check_box :pays, class: "block mt-2 h-5 w-5" %>
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex-1">
|
|
||||||
<%= form.label :pays, "Contributing Member", class: "block text-sm font-medium text-gray-700" %>
|
|
||||||
<p class="text-sm text-gray-500 mt-1">Whether this member contributes financially to the household budget</p>
|
|
||||||
</div>
|
|
||||||
<div class="ml-6">
|
|
||||||
<%= form.check_box :pays, class: "toggle-checkbox sr-only" %>
|
|
||||||
<label for="member_pays" class="toggle-label relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus-within:outline-none focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2 bg-gray-200">
|
|
||||||
<span class="toggle-switch pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out translate-x-0"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form Actions -->
|
<div class="inline">
|
||||||
<div class="flex justify-end space-x-3 pt-6 border-t border-gray-200">
|
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
||||||
<%= link_to members_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
|
||||||
Cancel
|
|
||||||
<% end %>
|
|
||||||
<%= form.submit class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer" %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.toggle-checkbox:checked + .toggle-label {
|
|
||||||
background-color: #3B82F6;
|
|
||||||
}
|
|
||||||
.toggle-checkbox:checked + .toggle-label .toggle-switch {
|
|
||||||
transform: translateX(1.25rem);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,48 +1,17 @@
|
||||||
<div id="<%= dom_id member %>" class="bg-white rounded-lg shadow hover:shadow-md transition-shadow">
|
<div id="<%= dom_id member %>">
|
||||||
<div class="p-6">
|
<p class="my-5">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<strong class="block font-medium mb-1">Name:</strong>
|
||||||
<div class="flex items-center">
|
<%= member.name %>
|
||||||
<div class="h-12 w-12 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
</p>
|
||||||
<span class="text-white text-lg font-medium"><%= member.name.first.upcase %></span>
|
|
||||||
</div>
|
<p class="my-5">
|
||||||
<div class="ml-4">
|
<strong class="block font-medium mb-1">Pays:</strong>
|
||||||
<h3 class="text-lg font-semibold text-gray-900"><%= member.name %></h3>
|
<%= member.pays %>
|
||||||
<p class="text-sm text-gray-500">Household Member</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% if member.pays %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
|
|
||||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
contributing
|
|
||||||
</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800">
|
|
||||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
non-contributing
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if action_name != "show" %>
|
<% if action_name != "show" %>
|
||||||
<div class="flex gap-2 pt-4 border-t border-gray-200">
|
<%= link_to "Show this member", member, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<%= link_to member, class: "flex-1 inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-blue-600 bg-blue-50 hover:bg-blue-100 rounded-md transition-colors" do %>
|
<%= link_to 'Edit this member', edit_member_path(member), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<hr class="mt-6">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
View
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to edit_member_path(member), class: "flex-1 inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-indigo-600 bg-indigo-50 hover:bg-indigo-100 rounded-md transition-colors" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,42 +1,8 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<!-- Breadcrumb -->
|
<h1 class="font-bold text-4xl">Editing member</h1>
|
||||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to members_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Members
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= link_to @member, class: "ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2" do %>
|
|
||||||
<%= @member.name %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">Edit</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">Edit Member</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Update the details for "<%= @member.name %>"</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", member: @member %>
|
<%= render "form", member: @member %>
|
||||||
</div>
|
|
||||||
|
<%= link_to "Show this member", @member, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
<%= link_to "Back to members", members_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,164 +1,14 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="w-full">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-8 gap-4">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<h1 class="font-bold text-4xl">Members</h1>
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">Members</h1>
|
<%= link_to 'New member', new_member_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
|
||||||
<p class="mt-2 text-sm text-gray-600">Manage household members and their budget roles</p>
|
|
||||||
</div>
|
|
||||||
<%= link_to new_member_path, class: "rounded-lg py-2.5 px-4 sm:py-3 sm:px-5 bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors inline-flex items-center justify-center sm:justify-start" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New member
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @members.any? %>
|
<div id="members" class="min-w-full">
|
||||||
<!-- Summary Cards -->
|
<%= render @members %>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Total Members</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= @members.count %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Contributing</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= @members.where(pays: true).count %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<svg class="h-8 w-8 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium text-gray-500">Non-Contributing</p>
|
|
||||||
<p class="text-2xl font-semibold text-gray-900"><%= @members.where(pays: false).count %></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Desktop Table View -->
|
|
||||||
<div class="hidden lg:block overflow-x-auto bg-white rounded-lg shadow">
|
|
||||||
<table class="min-w-full">
|
|
||||||
<thead class="bg-gray-50 border-b border-gray-200">
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Member</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
|
|
||||||
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
|
||||||
<% @members.each do |member| %>
|
|
||||||
<tr class="hover:bg-gray-50 transition-colors">
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="h-10 w-10 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
|
||||||
<span class="text-white text-sm font-medium"><%= member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<div class="text-sm font-medium text-gray-900"><%= member.name %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
|
||||||
<% if member.pays %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">contributing</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">non-contributing</span>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
||||||
<%= link_to "View", member, class: "text-blue-600 hover:text-blue-900 mr-3" %>
|
|
||||||
<%= link_to "Edit", edit_member_path(member), class: "text-indigo-600 hover:text-indigo-900" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile Card View -->
|
|
||||||
<div class="lg:hidden space-y-4">
|
|
||||||
<% @members.each do |member| %>
|
|
||||||
<div class="bg-white rounded-lg shadow p-4 hover:shadow-md transition-shadow">
|
|
||||||
<div class="flex justify-between items-start mb-3">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="h-12 w-12 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
|
|
||||||
<span class="text-white text-lg font-medium"><%= member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-lg font-medium text-gray-900"><%= member.name %></h3>
|
|
||||||
<% if member.pays %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">contributing</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">non-contributing</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<%= link_to member, class: "text-blue-600 hover:text-blue-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
<%= link_to edit_member_path(member), class: "text-indigo-600 hover:text-indigo-800" do %>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="text-center py-12 bg-white rounded-lg shadow">
|
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
|
|
||||||
</svg>
|
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900">No members</h3>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">Get started by adding your first household member.</p>
|
|
||||||
<div class="mt-6">
|
|
||||||
<%= link_to new_member_path, class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
||||||
</svg>
|
|
||||||
New member
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,32 +1,7 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<!-- Breadcrumb -->
|
<h1 class="font-bold text-4xl">New member</h1>
|
||||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to members_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Members
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">New Member</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<div class="mb-8">
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900">New Member</h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Add a new household member and set their budget role</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= render "form", member: @member %>
|
<%= render "form", member: @member %>
|
||||||
</div>
|
|
||||||
|
<%= link_to 'Back to members', members_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,118 +1,15 @@
|
||||||
<div class="w-full px-4 py-8">
|
<div class="mx-auto md:w-2/3 w-full flex">
|
||||||
|
<div class="mx-auto">
|
||||||
<% if notice.present? %>
|
<% if notice.present? %>
|
||||||
<div class="mb-6">
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
<p class="py-3 px-4 bg-green-50 text-green-800 font-medium rounded-lg inline-flex items-center" id="notice">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<%= notice %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Breadcrumb -->
|
<%= render @member %>
|
||||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
||||||
<li class="inline-flex items-center">
|
|
||||||
<%= link_to members_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
|
|
||||||
</svg>
|
|
||||||
Members
|
|
||||||
<% end %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2"><%= @member.name %></span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
<%= link_to 'Edit this member', edit_member_path(@member), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
<div class="mb-8">
|
<div class="inline-block ml-2">
|
||||||
<div class="flex justify-between items-start mb-4">
|
<%= button_to 'Destroy this member', member_path(@member), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
|
||||||
<div>
|
|
||||||
<h1 class="font-bold text-3xl sm:text-4xl text-gray-900"><%= @member.name %></h1>
|
|
||||||
<p class="mt-2 text-sm text-gray-600">Household member details and budget role</p>
|
|
||||||
</div>
|
|
||||||
<% if @member.pays %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
|
|
||||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
contributing
|
|
||||||
</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800">
|
|
||||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
non-contributing
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Member Details Card -->
|
|
||||||
<div class="bg-white rounded-lg shadow-lg overflow-hidden mb-8">
|
|
||||||
<div class="px-6 py-8">
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="h-24 w-24 mx-auto rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center mb-4">
|
|
||||||
<span class="text-white text-3xl font-medium"><%= @member.name.first.upcase %></span>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-2xl font-semibold text-gray-900 mb-2"><%= @member.name %></h3>
|
|
||||||
<p class="text-gray-600 mb-4">Household Member</p>
|
|
||||||
<div class="inline-flex items-center">
|
|
||||||
<% if @member.pays %>
|
|
||||||
<div class="flex items-center text-green-600">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="font-medium">Contributing to budget</span>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="flex items-center text-gray-600">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
<span class="font-medium">Not contributing to budget</span>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Actions -->
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<%= link_to edit_member_path(@member), class: "inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
||||||
</svg>
|
|
||||||
Edit Member
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= button_to member_path(@member), method: :delete,
|
|
||||||
class: "inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors",
|
|
||||||
confirm: "Are you sure you want to delete this member?" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
||||||
</svg>
|
|
||||||
Delete
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= link_to members_path, class: "inline-flex items-center px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium rounded-lg transition-colors" do %>
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
||||||
</svg>
|
|
||||||
Back to Members
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
<%= link_to 'Back to members', members_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<%= form_with(model: session, class: "contents") do |form| %>
|
|
||||||
<% if session.errors.any? %>
|
|
||||||
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
|
||||||
<h2><%= pluralize(session.errors.count, "error") %> prohibited this session from being saved:</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<% session.errors.each do |error| %>
|
|
||||||
<li><%= error.full_message %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="my-5">
|
|
||||||
<%= form.label :email %>
|
|
||||||
<%= form.text_field :email, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-5">
|
|
||||||
<%= form.label :password %>
|
|
||||||
<%= form.password_field :password, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inline">
|
|
||||||
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
|
@ -1,2 +0,0 @@
|
||||||
<div id="<%= dom_id session %>">
|
|
||||||
</div>
|
|
|
@ -1,2 +0,0 @@
|
||||||
json.extract! session, :id, :created_at, :updated_at
|
|
||||||
json.url session_url(session, format: :json)
|
|
|
@ -1,7 +0,0 @@
|
||||||
<div class="mx-auto md:w-2/3 w-full">
|
|
||||||
<h1 class="font-bold text-4xl">New session</h1>
|
|
||||||
|
|
||||||
<%= render "form", session: @session %>
|
|
||||||
|
|
||||||
<%= link_to "Back to sessions", sessions_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
|
||||||
</div>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<div class="relative group">
|
|
||||||
<input type="checkbox" id="user-dropdown" class="peer hidden" />
|
|
||||||
<label for="user-dropdown"
|
|
||||||
class="flex items-center space-x-2 text-white hover:text-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 rounded-lg p-2 cursor-pointer"
|
|
||||||
>
|
|
||||||
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white font-semibold text-sm flex-shrink-0">
|
|
||||||
<%= Current.user.email.first.upcase %>
|
|
||||||
</div>
|
|
||||||
<span class="hidden xl:block font-medium truncate max-w-48"><%= Current.user.email %></span>
|
|
||||||
<svg class="w-4 h-4 transform transition-transform duration-200 flex-shrink-0 peer-checked:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- Invisible overlay to close dropdown when clicking outside -->
|
|
||||||
<label for="user-dropdown" class="fixed inset-0 z-40 hidden peer-checked:block cursor-default"></label>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 z-50 hidden peer-checked:block"
|
|
||||||
>
|
|
||||||
<div class="py-1">
|
|
||||||
<%= link_to user_path(Current.user), class: "flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" do %>
|
|
||||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
|
||||||
</svg>
|
|
||||||
Profile
|
|
||||||
<% end %>
|
|
||||||
<hr class="my-1 border-gray-200">
|
|
||||||
<%= link_to session_path,
|
|
||||||
data: {turbo_method: :delete},
|
|
||||||
class: "flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" do %>
|
|
||||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
|
|
||||||
</svg>
|
|
||||||
Log out
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,32 +0,0 @@
|
||||||
<%= form_with(model: user, class: "contents") do |form| %>
|
|
||||||
<% if user.errors.any? %>
|
|
||||||
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
|
||||||
<h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<% user.errors.each do |error| %>
|
|
||||||
<li><%= error.full_message %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="my-5">
|
|
||||||
<%= form.label :email %>
|
|
||||||
<%= form.text_field :email, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-5">
|
|
||||||
<%= form.label :password %>
|
|
||||||
<%= form.password_field :password, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-5">
|
|
||||||
<%= form.label :password_confirmation %>
|
|
||||||
<%= form.password_field :password_confirmation, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inline">
|
|
||||||
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<div id="<%= dom_id user %>">
|
|
||||||
<p class="my-5">
|
|
||||||
<strong class="block font-medium mb-1">Email:</strong>
|
|
||||||
<%= user.email %>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,2 +0,0 @@
|
||||||
json.extract! user, :id, :email, :created_at, :updated_at
|
|
||||||
json.url user_url(user, format: :json)
|
|
|
@ -1,8 +0,0 @@
|
||||||
<div class="mx-auto md:w-2/3 w-full">
|
|
||||||
<h1 class="font-bold text-4xl">Editing user</h1>
|
|
||||||
|
|
||||||
<%= render "form", user: @user %>
|
|
||||||
|
|
||||||
<%= link_to "Show this user", @user, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
|
||||||
<%= link_to "Back to users", users_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
|
||||||
</div>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<div class="w-full">
|
|
||||||
<% if notice.present? %>
|
|
||||||
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% content_for :title, "Users" %>
|
|
||||||
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<h1 class="font-bold text-4xl">Users</h1>
|
|
||||||
<%= link_to "New user", new_user_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="users" class="min-w-full">
|
|
||||||
<% @users.each do |user| %>
|
|
||||||
<%= render user %>
|
|
||||||
<p>
|
|
||||||
<%= link_to "Show this user", user, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1 +0,0 @@
|
||||||
json.array! @users, partial: "users/user", as: :user
|
|
|
@ -1,7 +0,0 @@
|
||||||
<div class="mx-auto md:w-2/3 w-full">
|
|
||||||
<h1 class="font-bold text-4xl">New user</h1>
|
|
||||||
|
|
||||||
<%= render "form", user: @user %>
|
|
||||||
|
|
||||||
<%= link_to "Back to users", users_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
|
||||||
</div>
|
|
|
@ -1,15 +0,0 @@
|
||||||
<div class="mx-auto md:w-2/3 w-full flex">
|
|
||||||
<div class="mx-auto">
|
|
||||||
<% if notice.present? %>
|
|
||||||
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= render @user %>
|
|
||||||
|
|
||||||
<%= link_to "Edit this user", edit_user_path(@user), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
|
||||||
<%= link_to "Back to users", users_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
|
||||||
<div class="inline-block ml-2">
|
|
||||||
<%= button_to "Destroy this user", @user, method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1 +0,0 @@
|
||||||
json.partial! "users/user", user: @user
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
require "rubygems"
|
|
||||||
require "bundler/setup"
|
|
||||||
|
|
||||||
ARGV.unshift("--ensure-latest")
|
|
||||||
|
|
||||||
load Gem.bin_path("brakeman", "brakeman")
|
|
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
require_relative "../config/boot"
|
|
||||||
require "bundler/audit/cli"
|
|
||||||
|
|
||||||
ARGV.concat %w[ --config config/bundler-audit.yml ] if ARGV.empty? || ARGV.include?("check")
|
|
||||||
Bundler::Audit::CLI.start
|
|
6
bin/ci
6
bin/ci
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
require_relative "../config/boot"
|
|
||||||
require "active_support/continuous_integration"
|
|
||||||
|
|
||||||
CI = ActiveSupport::ContinuousIntegration
|
|
||||||
require_relative "../config/ci.rb"
|
|
10
bin/dev
10
bin/dev
|
@ -1,2 +1,8 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env sh
|
||||||
exec "./bin/rails", "server", *ARGV
|
|
||||||
|
if ! gem list foreman -i --silent; then
|
||||||
|
echo "Installing foreman..."
|
||||||
|
gem install foreman
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec foreman start -f Procfile.dev "$@"
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
#!/bin/bash -e
|
#!/bin/bash -e
|
||||||
|
|
||||||
# Enable jemalloc for reduced memory usage and latency.
|
|
||||||
if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
|
|
||||||
export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If running the rails server then create or migrate existing database
|
# If running the rails server then create or migrate existing database
|
||||||
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
|
if [ "${*}" == "./bin/rails server" ]; then
|
||||||
./bin/rails db:prepare
|
./bin/rails db:prepare
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
6
bin/jobs
6
bin/jobs
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require_relative "../config/environment"
|
|
||||||
require "solid_queue/cli"
|
|
||||||
|
|
||||||
SolidQueue::Cli.start(ARGV)
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
require "rubygems"
|
|
||||||
require "bundler/setup"
|
|
||||||
|
|
||||||
# Explicit RuboCop config increases performance slightly while avoiding config confusion.
|
|
||||||
ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
|
|
||||||
|
|
||||||
load Gem.bin_path("rubocop", "rubocop")
|
|
10
bin/setup
10
bin/setup
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
require "fileutils"
|
require "fileutils"
|
||||||
|
|
||||||
|
# path to your application root.
|
||||||
APP_ROOT = File.expand_path("..", __dir__)
|
APP_ROOT = File.expand_path("..", __dir__)
|
||||||
|
|
||||||
def system!(*args)
|
def system!(*args)
|
||||||
|
@ -13,6 +14,7 @@ FileUtils.chdir APP_ROOT do
|
||||||
# Add necessary setup steps to this file.
|
# Add necessary setup steps to this file.
|
||||||
|
|
||||||
puts "== Installing dependencies =="
|
puts "== Installing dependencies =="
|
||||||
|
system! "gem install bundler --conservative"
|
||||||
system("bundle check") || system!("bundle install")
|
system("bundle check") || system!("bundle install")
|
||||||
|
|
||||||
# puts "\n== Copying sample files =="
|
# puts "\n== Copying sample files =="
|
||||||
|
@ -22,14 +24,10 @@ FileUtils.chdir APP_ROOT do
|
||||||
|
|
||||||
puts "\n== Preparing database =="
|
puts "\n== Preparing database =="
|
||||||
system! "bin/rails db:prepare"
|
system! "bin/rails db:prepare"
|
||||||
system! "bin/rails db:reset" if ARGV.include?("--reset")
|
|
||||||
|
|
||||||
puts "\n== Removing old logs and tempfiles =="
|
puts "\n== Removing old logs and tempfiles =="
|
||||||
system! "bin/rails log:clear tmp:clear"
|
system! "bin/rails log:clear tmp:clear"
|
||||||
|
|
||||||
unless ARGV.include?("--skip-server")
|
puts "\n== Restarting application server =="
|
||||||
puts "\n== Starting development server =="
|
system! "bin/rails restart"
|
||||||
STDOUT.flush # flush the output before exec(2) so that it displays
|
|
||||||
exec "bin/dev"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,7 +9,7 @@ Bundler.require(*Rails.groups)
|
||||||
module FamilyBudget
|
module FamilyBudget
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
# Initialize configuration defaults for originally generated Rails version.
|
# Initialize configuration defaults for originally generated Rails version.
|
||||||
config.load_defaults 8.1
|
config.load_defaults 7.1
|
||||||
|
|
||||||
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
||||||
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
# Audit all gems listed in the Gemfile for known security problems by running bin/bundler-audit.
|
|
||||||
# CVEs that are not relevant to the application can be enumerated on the ignore list below.
|
|
||||||
|
|
||||||
ignore:
|
|
||||||
- CVE-THAT-DOES-NOT-APPLY
|
|
|
@ -1,22 +1,10 @@
|
||||||
# Async adapter only works within the same process, so for manually triggering cable updates from a console,
|
|
||||||
# and seeing results in the browser, you must do so from the web console (running inside the dev process),
|
|
||||||
# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view
|
|
||||||
# to make the web console appear.
|
|
||||||
development:
|
development:
|
||||||
adapter: solid_cable
|
adapter: async
|
||||||
connects_to:
|
|
||||||
database:
|
|
||||||
writing: cable
|
|
||||||
polling_interval: 0.1.seconds
|
|
||||||
message_retention: 1.day
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
adapter: test
|
adapter: test
|
||||||
|
|
||||||
production:
|
production:
|
||||||
adapter: solid_cable
|
adapter: redis
|
||||||
connects_to:
|
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
||||||
database:
|
channel_prefix: family_budget_production
|
||||||
writing: cable
|
|
||||||
polling_interval: 0.1.seconds
|
|
||||||
message_retention: 1.day
|
|
||||||
|
|
23
config/ci.rb
23
config/ci.rb
|
@ -1,23 +0,0 @@
|
||||||
# Run using bin/ci
|
|
||||||
|
|
||||||
CI.run do
|
|
||||||
step "Setup", "bin/setup --skip-server"
|
|
||||||
|
|
||||||
step "Style: Ruby", "bin/rubocop"
|
|
||||||
|
|
||||||
step "Security: Gem audit", "bin/bundler-audit"
|
|
||||||
step "Security: Importmap vulnerability audit", "bin/importmap audit"
|
|
||||||
step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
|
|
||||||
|
|
||||||
step "Tests: Rails", "bin/rails test"
|
|
||||||
step "Tests: System", "bin/rails test:system"
|
|
||||||
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
|
|
||||||
|
|
||||||
# Optional: set a green GitHub commit status to unblock PR merge.
|
|
||||||
# Requires the `gh` CLI and and `gh extension install basecamp/gh-signoff`.
|
|
||||||
# if success?
|
|
||||||
# step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
|
|
||||||
# else
|
|
||||||
# failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
|
|
||||||
# end
|
|
||||||
end
|
|
|
@ -10,43 +10,16 @@ default: &default
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
|
|
||||||
development:
|
development:
|
||||||
primary:
|
|
||||||
<<: *default
|
<<: *default
|
||||||
database: storage/development.sqlite3
|
database: db/development.sqlite3
|
||||||
cache:
|
|
||||||
<<: *default
|
|
||||||
database: storage/development_cache.sqlite3
|
|
||||||
migrations_paths: db/cache_migrate
|
|
||||||
queue:
|
|
||||||
<<: *default
|
|
||||||
database: storage/development_queue.sqlite3
|
|
||||||
migrations_paths: db/queue_migrate
|
|
||||||
cable:
|
|
||||||
<<: *default
|
|
||||||
database: storage/development_cable.sqlite3
|
|
||||||
migrations_paths: db/cable_migrate
|
|
||||||
|
|
||||||
|
|
||||||
# Warning: The database defined as "test" will be erased and
|
# Warning: The database defined as "test" will be erased and
|
||||||
# re-generated from your development database when you run "rake".
|
# re-generated from your development database when you run "rake".
|
||||||
# Do not set this db to the same as development or production.
|
# Do not set this db to the same as development or production.
|
||||||
test:
|
test:
|
||||||
<<: *default
|
<<: *default
|
||||||
database: storage/test.sqlite3
|
database: db/test.sqlite3
|
||||||
|
|
||||||
production:
|
production:
|
||||||
primary:
|
|
||||||
<<: *default
|
<<: *default
|
||||||
database: storage/production.sqlite3
|
database: db/production.sqlite3
|
||||||
cache:
|
|
||||||
<<: *default
|
|
||||||
database: storage/production_cache.sqlite3
|
|
||||||
migrations_paths: db/cache_migrate
|
|
||||||
queue:
|
|
||||||
<<: *default
|
|
||||||
database: storage/production_queue.sqlite3
|
|
||||||
migrations_paths: db/queue_migrate
|
|
||||||
cable:
|
|
||||||
<<: *default
|
|
||||||
database: storage/production_cable.sqlite3
|
|
||||||
migrations_paths: db/cable_migrate
|
|
||||||
|
|
|
@ -3,7 +3,9 @@ require "active_support/core_ext/integer/time"
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
# Settings specified here will take precedence over those in config/application.rb.
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
# Make code changes take effect immediately without server restart.
|
# In the development environment your application's code is reloaded any time
|
||||||
|
# it changes. This slows down response time but is perfect for development
|
||||||
|
# since you don't have to restart the web server when you make code changes.
|
||||||
config.enable_reloading = true
|
config.enable_reloading = true
|
||||||
|
|
||||||
# Do not eager load code on boot.
|
# Do not eager load code on boot.
|
||||||
|
@ -12,21 +14,24 @@ Rails.application.configure do
|
||||||
# Show full error reports.
|
# Show full error reports.
|
||||||
config.consider_all_requests_local = true
|
config.consider_all_requests_local = true
|
||||||
|
|
||||||
# Enable server timing.
|
# Enable server timing
|
||||||
config.server_timing = true
|
config.server_timing = true
|
||||||
|
|
||||||
# Enable/disable Action Controller caching. By default Action Controller caching is disabled.
|
# Enable/disable caching. By default caching is disabled.
|
||||||
# Run rails dev:cache to toggle Action Controller caching.
|
# Run rails dev:cache to toggle caching.
|
||||||
if Rails.root.join("tmp/caching-dev.txt").exist?
|
if Rails.root.join("tmp/caching-dev.txt").exist?
|
||||||
config.action_controller.perform_caching = true
|
config.action_controller.perform_caching = true
|
||||||
config.action_controller.enable_fragment_cache_logging = true
|
config.action_controller.enable_fragment_cache_logging = true
|
||||||
config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" }
|
|
||||||
|
config.cache_store = :memory_store
|
||||||
|
config.public_file_server.headers = {
|
||||||
|
"Cache-Control" => "public, max-age=#{2.days.to_i}"
|
||||||
|
}
|
||||||
else
|
else
|
||||||
config.action_controller.perform_caching = false
|
config.action_controller.perform_caching = false
|
||||||
end
|
|
||||||
|
|
||||||
# Change to :null_store to avoid any caching.
|
config.cache_store = :null_store
|
||||||
config.cache_store = :solid_cache_store
|
end
|
||||||
|
|
||||||
# Store uploaded files on the local file system (see config/storage.yml for options).
|
# Store uploaded files on the local file system (see config/storage.yml for options).
|
||||||
config.active_storage.service = :local
|
config.active_storage.service = :local
|
||||||
|
@ -34,24 +39,23 @@ Rails.application.configure do
|
||||||
# Don't care if the mailer can't send.
|
# Don't care if the mailer can't send.
|
||||||
config.action_mailer.raise_delivery_errors = false
|
config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
# Make template changes take effect immediately.
|
|
||||||
config.action_mailer.perform_caching = false
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
# Set localhost to be used by links generated in mailer templates.
|
|
||||||
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
|
|
||||||
|
|
||||||
# Print deprecation notices to the Rails logger.
|
# Print deprecation notices to the Rails logger.
|
||||||
config.active_support.deprecation = :log
|
config.active_support.deprecation = :log
|
||||||
|
|
||||||
|
# Raise exceptions for disallowed deprecations.
|
||||||
|
config.active_support.disallowed_deprecation = :raise
|
||||||
|
|
||||||
|
# Tell Active Support which deprecation messages to disallow.
|
||||||
|
config.active_support.disallowed_deprecation_warnings = []
|
||||||
|
|
||||||
# Raise an error on page load if there are pending migrations.
|
# Raise an error on page load if there are pending migrations.
|
||||||
config.active_record.migration_error = :page_load
|
config.active_record.migration_error = :page_load
|
||||||
|
|
||||||
# Highlight code that triggered database queries in logs.
|
# Highlight code that triggered database queries in logs.
|
||||||
config.active_record.verbose_query_logs = true
|
config.active_record.verbose_query_logs = true
|
||||||
|
|
||||||
# Append comments with runtime information tags to SQL queries in logs.
|
|
||||||
config.active_record.query_log_tags_enabled = true
|
|
||||||
|
|
||||||
# Highlight code that enqueued background job in logs.
|
# Highlight code that enqueued background job in logs.
|
||||||
config.active_job.verbose_enqueue_logs = true
|
config.active_job.verbose_enqueue_logs = true
|
||||||
|
|
||||||
|
@ -62,14 +66,11 @@ Rails.application.configure do
|
||||||
# config.i18n.raise_on_missing_translations = true
|
# config.i18n.raise_on_missing_translations = true
|
||||||
|
|
||||||
# Annotate rendered view with file names.
|
# Annotate rendered view with file names.
|
||||||
config.action_view.annotate_rendered_view_with_filenames = true
|
# config.action_view.annotate_rendered_view_with_filenames = true
|
||||||
|
|
||||||
# Uncomment if you wish to allow Action Cable access from any origin.
|
# Uncomment if you wish to allow Action Cable access from any origin.
|
||||||
# config.action_cable.disable_request_forgery_protection = true
|
# config.action_cable.disable_request_forgery_protection = true
|
||||||
|
|
||||||
# Raise error when a before_action's only/except options reference missing actions.
|
# Raise error when a before_action's only/except options reference missing actions
|
||||||
config.action_controller.raise_on_missing_callback_actions = true
|
config.action_controller.raise_on_missing_callback_actions = true
|
||||||
|
|
||||||
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
|
||||||
# config.generators.apply_rubocop_autocorrect_after_generate!
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,84 +6,92 @@ Rails.application.configure do
|
||||||
# Code is not reloaded between requests.
|
# Code is not reloaded between requests.
|
||||||
config.enable_reloading = false
|
config.enable_reloading = false
|
||||||
|
|
||||||
# Eager load code on boot for better performance and memory savings (ignored by Rake tasks).
|
# Eager load code on boot. This eager loads most of Rails and
|
||||||
|
# your application in memory, allowing both threaded web servers
|
||||||
|
# and those relying on copy on write to perform better.
|
||||||
|
# Rake tasks automatically ignore this option for performance.
|
||||||
config.eager_load = true
|
config.eager_load = true
|
||||||
|
|
||||||
# Full error reports are disabled.
|
# Full error reports are disabled and caching is turned on.
|
||||||
config.consider_all_requests_local = false
|
config.consider_all_requests_local = false
|
||||||
|
|
||||||
# Turn on fragment caching in view templates.
|
|
||||||
config.action_controller.perform_caching = true
|
config.action_controller.perform_caching = true
|
||||||
|
|
||||||
# Cache assets for far-future expiry since they are all digest stamped.
|
# Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
|
||||||
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }
|
# key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
|
||||||
|
# config.require_master_key = true
|
||||||
|
|
||||||
|
# Enable static file serving from the `/public` folder (turn off if using NGINX/Apache for it).
|
||||||
|
config.public_file_server.enabled = true
|
||||||
|
|
||||||
|
# Compress CSS using a preprocessor.
|
||||||
|
# config.assets.css_compressor = :sass
|
||||||
|
|
||||||
|
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||||
|
config.assets.compile = false
|
||||||
|
|
||||||
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
||||||
# config.asset_host = "http://assets.example.com"
|
# config.asset_host = "http://assets.example.com"
|
||||||
|
|
||||||
|
# Specifies the header that your server uses for sending files.
|
||||||
|
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
|
||||||
|
# config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
|
||||||
|
|
||||||
# Store uploaded files on the local file system (see config/storage.yml for options).
|
# Store uploaded files on the local file system (see config/storage.yml for options).
|
||||||
config.active_storage.service = :local
|
config.active_storage.service = :local
|
||||||
|
|
||||||
|
# Mount Action Cable outside main process or domain.
|
||||||
|
# config.action_cable.mount_path = nil
|
||||||
|
# config.action_cable.url = "wss://example.com/cable"
|
||||||
|
# config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
|
||||||
|
|
||||||
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
|
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
|
||||||
|
# Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
|
||||||
# config.assume_ssl = true
|
# config.assume_ssl = true
|
||||||
|
|
||||||
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
||||||
config.force_ssl = false
|
config.force_ssl = false
|
||||||
|
|
||||||
# Skip http-to-https redirect for the default health check endpoint.
|
# Log to STDOUT by default
|
||||||
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
|
config.logger = ActiveSupport::Logger.new($stdout)
|
||||||
|
.tap { |logger| logger.formatter = ::Logger::Formatter.new }
|
||||||
|
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }
|
||||||
|
|
||||||
# Log to STDOUT with the current request id as a default log tag.
|
# Prepend all log lines with the following tags.
|
||||||
config.log_tags = [ :request_id ]
|
config.log_tags = [:request_id]
|
||||||
config.logger = ActiveSupport::TaggedLogging.logger(STDOUT)
|
|
||||||
|
|
||||||
# Change to "debug" to log everything (including potentially personally-identifiable information!).
|
# Info include generic and useful information about system operation, but avoids logging too much
|
||||||
|
# information to avoid inadvertent exposure of personally identifiable information (PII). If you
|
||||||
|
# want to log everything, set the level to "debug".
|
||||||
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
|
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
|
||||||
|
|
||||||
# Prevent health checks from clogging up the logs.
|
# Use a different cache store in production.
|
||||||
config.silence_healthcheck_path = "/up"
|
|
||||||
|
|
||||||
# Don't log any deprecations.
|
|
||||||
config.active_support.report_deprecations = false
|
|
||||||
|
|
||||||
# Replace the default in-process memory cache store with a durable alternative.
|
|
||||||
# config.cache_store = :mem_cache_store
|
# config.cache_store = :mem_cache_store
|
||||||
|
|
||||||
# Replace the default in-process and non-durable queuing backend for Active Job.
|
# Use a real queuing backend for Active Job (and separate queues per environment).
|
||||||
config.cache_store = :solid_cache_store
|
# config.active_job.queue_adapter = :resque
|
||||||
|
# config.active_job.queue_name_prefix = "family_budget_production"
|
||||||
|
|
||||||
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
# Ignore bad email addresses and do not raise email delivery errors.
|
# Ignore bad email addresses and do not raise email delivery errors.
|
||||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||||
# config.action_mailer.raise_delivery_errors = false
|
# config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
# Set host to be used by links generated in mailer templates.
|
|
||||||
config.action_mailer.default_url_options = { host: "example.com" }
|
|
||||||
|
|
||||||
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
|
|
||||||
# config.action_mailer.smtp_settings = {
|
|
||||||
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
|
|
||||||
# password: Rails.application.credentials.dig(:smtp, :password),
|
|
||||||
# address: "smtp.example.com",
|
|
||||||
# port: 587,
|
|
||||||
# authentication: :plain
|
|
||||||
# }
|
|
||||||
|
|
||||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||||
# the I18n.default_locale when a translation cannot be found).
|
# the I18n.default_locale when a translation cannot be found).
|
||||||
config.i18n.fallbacks = true
|
config.i18n.fallbacks = true
|
||||||
|
|
||||||
|
# Don't log any deprecations.
|
||||||
|
config.active_support.report_deprecations = false
|
||||||
|
|
||||||
# Do not dump schema after migrations.
|
# Do not dump schema after migrations.
|
||||||
config.active_record.dump_schema_after_migration = false
|
config.active_record.dump_schema_after_migration = false
|
||||||
|
|
||||||
# Only use :id for inspections in production.
|
|
||||||
config.active_record.attributes_for_inspect = [ :id ]
|
|
||||||
|
|
||||||
# Enable DNS rebinding protection and other `Host` header attacks.
|
# Enable DNS rebinding protection and other `Host` header attacks.
|
||||||
# config.hosts = [
|
# config.hosts = [
|
||||||
# "example.com", # Allow requests from example.com
|
# "example.com", # Allow requests from example.com
|
||||||
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
|
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
|
||||||
# ]
|
# ]
|
||||||
#
|
|
||||||
# Skip DNS rebinding protection for the default health check endpoint.
|
# Skip DNS rebinding protection for the default health check endpoint.
|
||||||
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
|
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require "active_support/core_ext/integer/time"
|
||||||
|
|
||||||
# The test environment is used exclusively to run your application's
|
# The test environment is used exclusively to run your application's
|
||||||
# test suite. You never need to work with it otherwise. Remember that
|
# test suite. You never need to work with it otherwise. Remember that
|
||||||
# your test database is "scratch space" for the test suite and is wiped
|
# your test database is "scratch space" for the test suite and is wiped
|
||||||
|
@ -15,14 +17,18 @@ Rails.application.configure do
|
||||||
# loading is working properly before deploying your code.
|
# loading is working properly before deploying your code.
|
||||||
config.eager_load = ENV["CI"].present?
|
config.eager_load = ENV["CI"].present?
|
||||||
|
|
||||||
# Configure public file server for tests with cache-control for performance.
|
# Configure public file server for tests with Cache-Control for performance.
|
||||||
config.public_file_server.headers = { "cache-control" => "public, max-age=3600" }
|
config.public_file_server.enabled = true
|
||||||
|
config.public_file_server.headers = {
|
||||||
|
"Cache-Control" => "public, max-age=#{1.hour.to_i}"
|
||||||
|
}
|
||||||
|
|
||||||
# Show full error reports.
|
# Show full error reports and disable caching.
|
||||||
config.consider_all_requests_local = true
|
config.consider_all_requests_local = true
|
||||||
|
config.action_controller.perform_caching = false
|
||||||
config.cache_store = :null_store
|
config.cache_store = :null_store
|
||||||
|
|
||||||
# Render exception templates for rescuable exceptions and raise for other exceptions.
|
# Raise exceptions instead of rendering exception templates.
|
||||||
config.action_dispatch.show_exceptions = :rescuable
|
config.action_dispatch.show_exceptions = :rescuable
|
||||||
|
|
||||||
# Disable request forgery protection in test environment.
|
# Disable request forgery protection in test environment.
|
||||||
|
@ -31,23 +37,28 @@ Rails.application.configure do
|
||||||
# Store uploaded files on the local file system in a temporary directory.
|
# Store uploaded files on the local file system in a temporary directory.
|
||||||
config.active_storage.service = :test
|
config.active_storage.service = :test
|
||||||
|
|
||||||
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
# Tell Action Mailer not to deliver emails to the real world.
|
# Tell Action Mailer not to deliver emails to the real world.
|
||||||
# The :test delivery method accumulates sent emails in the
|
# The :test delivery method accumulates sent emails in the
|
||||||
# ActionMailer::Base.deliveries array.
|
# ActionMailer::Base.deliveries array.
|
||||||
config.action_mailer.delivery_method = :test
|
config.action_mailer.delivery_method = :test
|
||||||
|
|
||||||
# Set host to be used by links generated in mailer templates.
|
|
||||||
config.action_mailer.default_url_options = { host: "example.com" }
|
|
||||||
|
|
||||||
# Print deprecation notices to the stderr.
|
# Print deprecation notices to the stderr.
|
||||||
config.active_support.deprecation = :stderr
|
config.active_support.deprecation = :stderr
|
||||||
|
|
||||||
|
# Raise exceptions for disallowed deprecations.
|
||||||
|
config.active_support.disallowed_deprecation = :raise
|
||||||
|
|
||||||
|
# Tell Active Support which deprecation messages to disallow.
|
||||||
|
config.active_support.disallowed_deprecation_warnings = []
|
||||||
|
|
||||||
# Raises error for missing translations.
|
# Raises error for missing translations.
|
||||||
# config.i18n.raise_on_missing_translations = true
|
# config.i18n.raise_on_missing_translations = true
|
||||||
|
|
||||||
# Annotate rendered view with file names.
|
# Annotate rendered view with file names.
|
||||||
# config.action_view.annotate_rendered_view_with_filenames = true
|
# config.action_view.annotate_rendered_view_with_filenames = true
|
||||||
|
|
||||||
# Raise error when a before_action's only/except options reference missing actions.
|
# Raise error when a before_action's only/except options reference missing actions
|
||||||
config.action_controller.raise_on_missing_callback_actions = true
|
config.action_controller.raise_on_missing_callback_actions = true
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Use a real queuing backend for Active Job (and separate queues per environment).
|
|
||||||
Rails.application.config.active_job.queue_adapter = :solid_queue
|
|
||||||
Rails.application.config.solid_queue.connects_to = { database: { writing: :queue } }
|
|
|
@ -5,3 +5,8 @@ Rails.application.config.assets.version = "1.0"
|
||||||
|
|
||||||
# Add additional assets to the asset load path.
|
# Add additional assets to the asset load path.
|
||||||
# Rails.application.config.assets.paths << Emoji.images_path
|
# Rails.application.config.assets.paths << Emoji.images_path
|
||||||
|
|
||||||
|
# Precompile additional assets.
|
||||||
|
# application.js, application.css, and all non-JS/CSS in the app/assets
|
||||||
|
# folder are already added.
|
||||||
|
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
|
||||||
|
|
|
@ -20,10 +20,6 @@
|
||||||
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
|
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
|
||||||
# config.content_security_policy_nonce_directives = %w(script-src style-src)
|
# config.content_security_policy_nonce_directives = %w(script-src style-src)
|
||||||
#
|
#
|
||||||
# # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`
|
|
||||||
# # if the corresponding directives are specified in `content_security_policy_nonce_directives`.
|
|
||||||
# # config.content_security_policy_nonce_auto = true
|
|
||||||
#
|
|
||||||
# # Report violations without enforcing the policy.
|
# # Report violations without enforcing the policy.
|
||||||
# # config.content_security_policy_report_only = true
|
# # config.content_security_policy_report_only = true
|
||||||
# end
|
# end
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Be sure to restart your server when you modify this file.
|
|
||||||
|
|
||||||
# Avoid CORS issues when API is called from the frontend app.
|
|
||||||
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin Ajax requests.
|
|
||||||
|
|
||||||
# Read more: https://github.com/cyu/rack-cors
|
|
||||||
|
|
||||||
# Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
|
||||||
# allow do
|
|
||||||
# origins "example.com"
|
|
||||||
#
|
|
||||||
# resource "*",
|
|
||||||
# headers: :any,
|
|
||||||
# methods: [:get, :post, :put, :patch, :delete, :options, :head]
|
|
||||||
# end
|
|
||||||
# end
|
|
|
@ -4,5 +4,5 @@
|
||||||
# Use this to limit dissemination of sensitive information.
|
# Use this to limit dissemination of sensitive information.
|
||||||
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
|
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
|
||||||
Rails.application.config.filter_parameters += [
|
Rails.application.config.filter_parameters += [
|
||||||
:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc
|
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Be sure to restart your server when you modify this file.
|
|
||||||
#
|
|
||||||
# This file eases your Rails 8.1 framework defaults upgrade.
|
|
||||||
#
|
|
||||||
# Uncomment each configuration one by one to switch to the new default.
|
|
||||||
# Once your application is ready to run with all new defaults, you can remove
|
|
||||||
# this file and set the `config.load_defaults` to `8.1`.
|
|
||||||
#
|
|
||||||
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
|
|
||||||
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
|
|
||||||
|
|
||||||
###
|
|
||||||
# Skips escaping HTML entities and line separators. When set to `false`, the
|
|
||||||
# JSON renderer no longer escapes these to improve performance.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# class PostsController < ApplicationController
|
|
||||||
# def index
|
|
||||||
# render json: { key: "\u2028\u2029<>&" }
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Renders `{"key":"\u2028\u2029\u003c\u003e\u0026"}` with the previous default, but `{"key":"
<>&"}` with the config
|
|
||||||
# set to `false`.
|
|
||||||
#
|
|
||||||
# Applications that want to keep the escaping behavior can set the config to `true`.
|
|
||||||
#++
|
|
||||||
# Rails.configuration.action_controller.escape_json_responses = false
|
|
|
@ -1,12 +0,0 @@
|
||||||
# Automatically enable YJIT if running Ruby 3.3 or newer,
|
|
||||||
# as it brings very sizeable performance improvements.
|
|
||||||
# Many users reported 15-25% improved latency.
|
|
||||||
|
|
||||||
# If you are deploying to a memory-constrained environment,
|
|
||||||
# you may want to delete this file, but otherwise, it's free
|
|
||||||
# performance.
|
|
||||||
if defined? RubyVM::YJIT.enable
|
|
||||||
Rails.application.config.after_initialize do
|
|
||||||
RubyVM::YJIT.enable
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,44 +1,43 @@
|
||||||
# This configuration file will be evaluated by Puma. The top-level methods that
|
# Puma can serve each request in a thread from an internal thread pool.
|
||||||
# are invoked here are part of Puma's configuration DSL. For more information
|
# The `threads` method setting takes two numbers: a minimum and maximum.
|
||||||
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
|
# Any libraries that use thread pools should be configured to match
|
||||||
|
# the maximum value specified for Puma. Default is set to 5 threads for minimum
|
||||||
|
# and maximum; this matches the default thread size of Active Record.
|
||||||
#
|
#
|
||||||
# Puma starts a configurable number of processes (workers) and each process
|
max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
|
||||||
# serves each request in a thread from an internal thread pool.
|
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
|
||||||
|
threads min_threads_count, max_threads_count
|
||||||
|
|
||||||
|
# Specifies the `worker_timeout` threshold that Puma will use to wait before
|
||||||
|
# terminating a worker in development environments.
|
||||||
#
|
#
|
||||||
# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You
|
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
|
||||||
# should only set this value when you want to run 2 or more workers. The
|
|
||||||
# default is already 1. You can set it to `auto` to automatically start a worker
|
|
||||||
# for each available processor.
|
|
||||||
#
|
|
||||||
# The ideal number of threads per worker depends both on how much time the
|
|
||||||
# application spends waiting for IO operations and on how much you wish to
|
|
||||||
# prioritize throughput over latency.
|
|
||||||
#
|
|
||||||
# As a rule of thumb, increasing the number of threads will increase how much
|
|
||||||
# traffic a given process can handle (throughput), but due to CRuby's
|
|
||||||
# Global VM Lock (GVL) it has diminishing returns and will degrade the
|
|
||||||
# response time (latency) of the application.
|
|
||||||
#
|
|
||||||
# The default is set to 3 threads as it's deemed a decent compromise between
|
|
||||||
# throughput and latency for the average Rails application.
|
|
||||||
#
|
|
||||||
# Any libraries that use a connection pool or another resource pool should
|
|
||||||
# be configured to provide at least as many connections as the number of
|
|
||||||
# threads. This includes Active Record's `pool` parameter in `database.yml`.
|
|
||||||
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
|
|
||||||
threads threads_count, threads_count
|
|
||||||
|
|
||||||
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
||||||
|
#
|
||||||
port ENV.fetch("PORT", 3000)
|
port ENV.fetch("PORT", 3000)
|
||||||
|
|
||||||
|
# Specifies the `environment` that Puma will run in.
|
||||||
|
#
|
||||||
|
environment ENV.fetch("RAILS_ENV") { "development" }
|
||||||
|
|
||||||
|
# Specifies the `pidfile` that Puma will use.
|
||||||
|
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
|
||||||
|
|
||||||
|
# Specifies the number of `workers` to boot in clustered mode.
|
||||||
|
# Workers are forked web server processes. If using threads and workers together
|
||||||
|
# the concurrency of the application would be max `threads` * `workers`.
|
||||||
|
# Workers do not work on JRuby or Windows (both of which do not support
|
||||||
|
# processes).
|
||||||
|
#
|
||||||
|
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
||||||
|
|
||||||
|
# Use the `preload_app!` method when specifying a `workers` number.
|
||||||
|
# This directive tells Puma to first boot the application and load code
|
||||||
|
# before forking the application. This takes advantage of Copy On Write
|
||||||
|
# process behavior so workers use less memory.
|
||||||
|
#
|
||||||
|
# preload_app!
|
||||||
|
|
||||||
# Allow puma to be restarted by `bin/rails restart` command.
|
# Allow puma to be restarted by `bin/rails restart` command.
|
||||||
plugin :tmp_restart
|
plugin :tmp_restart
|
||||||
|
|
||||||
# Run the Solid Queue supervisor inside of Puma for single-server deployments.
|
|
||||||
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]
|
|
||||||
|
|
||||||
# Specify the PID file. Defaults to tmp/pids/server.pid in development.
|
|
||||||
# In other environments, only set the PID file if requested.
|
|
||||||
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
|
|
||||||
|
|
||||||
plugin :tailwindcss if ENV.fetch("RAILS_ENV", "development") == "development"
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
default: &default
|
|
||||||
dispatchers:
|
|
||||||
- polling_interval: 1
|
|
||||||
batch_size: 500
|
|
||||||
workers:
|
|
||||||
- queues: "*"
|
|
||||||
threads: 3
|
|
||||||
processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %>
|
|
||||||
polling_interval: 0.1
|
|
||||||
|
|
||||||
development:
|
|
||||||
<<: *default
|
|
||||||
|
|
||||||
test:
|
|
||||||
<<: *default
|
|
||||||
|
|
||||||
production:
|
|
||||||
<<: *default
|
|
|
@ -1,10 +0,0 @@
|
||||||
# production:
|
|
||||||
# periodic_cleanup:
|
|
||||||
# class: CleanSoftDeletedRecordsJob
|
|
||||||
# queue: background
|
|
||||||
# args: [ 1000, { batch_size: 500 } ]
|
|
||||||
# schedule: every hour
|
|
||||||
# periodic_command:
|
|
||||||
# command: "SoftDeletedRecord.due.delete_all"
|
|
||||||
# priority: 2
|
|
||||||
# schedule: at 5am every day
|
|
|
@ -1,7 +1,4 @@
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
resources :sessions, only: %i[new create]
|
|
||||||
resource :session, only: :destroy
|
|
||||||
resources :users
|
|
||||||
resources :credit_card_bills
|
resources :credit_card_bills
|
||||||
resource :dashboard, only: :show
|
resource :dashboard, only: :show
|
||||||
resources :incomes
|
resources :incomes
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
default: &default
|
|
||||||
database: cache
|
|
||||||
store_options:
|
|
||||||
# Cap age of oldest cache entry to fulfill retention policies
|
|
||||||
# max_age: <%= 60.days.to_i %>
|
|
||||||
max_size: <%= 256.megabytes %>
|
|
||||||
namespace: <%= Rails.env %>
|
|
||||||
|
|
||||||
development:
|
|
||||||
<<: *default
|
|
||||||
|
|
||||||
test:
|
|
||||||
<<: *default
|
|
||||||
|
|
||||||
production:
|
|
||||||
<<: *default
|
|
|
@ -1,23 +0,0 @@
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
|
||||||
#
|
|
||||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
||||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
||||||
# be faster and is potentially less error prone than running all of your
|
|
||||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
||||||
# migrations use external dependencies or application code.
|
|
||||||
#
|
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
|
||||||
|
|
||||||
ActiveRecord::Schema[8.1].define(version: 1) do
|
|
||||||
create_table "solid_cable_messages", force: :cascade do |t|
|
|
||||||
t.binary "channel", limit: 1024, null: false
|
|
||||||
t.integer "channel_hash", limit: 8, null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.binary "payload", limit: 536870912, null: false
|
|
||||||
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
|
|
||||||
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
|
|
||||||
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,24 +0,0 @@
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
|
||||||
#
|
|
||||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
||||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
||||||
# be faster and is potentially less error prone than running all of your
|
|
||||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
||||||
# migrations use external dependencies or application code.
|
|
||||||
#
|
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
|
||||||
|
|
||||||
ActiveRecord::Schema[8.1].define(version: 1) do
|
|
||||||
create_table "solid_cache_entries", force: :cascade do |t|
|
|
||||||
t.integer "byte_size", limit: 4, null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.binary "key", limit: 1024, null: false
|
|
||||||
t.integer "key_hash", limit: 8, null: false
|
|
||||||
t.binary "value", limit: 536870912, null: false
|
|
||||||
t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size"
|
|
||||||
t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
|
|
||||||
t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,101 +0,0 @@
|
||||||
# This migration comes from solid_queue (originally 20231211200639)
|
|
||||||
class CreateSolidQueueTables < ActiveRecord::Migration[7.0]
|
|
||||||
def change
|
|
||||||
create_table :solid_queue_jobs do |t|
|
|
||||||
t.string :queue_name, null: false
|
|
||||||
t.string :class_name, null: false, index: true
|
|
||||||
t.text :arguments
|
|
||||||
t.integer :priority, default: 0, null: false
|
|
||||||
t.string :active_job_id, index: true
|
|
||||||
t.datetime :scheduled_at
|
|
||||||
t.datetime :finished_at, index: true
|
|
||||||
t.string :concurrency_key
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
|
|
||||||
t.index [ :queue_name, :finished_at ], name: "index_solid_queue_jobs_for_filtering"
|
|
||||||
t.index [ :scheduled_at, :finished_at ], name: "index_solid_queue_jobs_for_alerting"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_scheduled_executions do |t|
|
|
||||||
t.references :job, index: { unique: true }, null: false
|
|
||||||
t.string :queue_name, null: false
|
|
||||||
t.integer :priority, default: 0, null: false
|
|
||||||
t.datetime :scheduled_at, null: false
|
|
||||||
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
|
|
||||||
t.index [ :scheduled_at, :priority, :job_id ], name: "index_solid_queue_dispatch_all"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_ready_executions do |t|
|
|
||||||
t.references :job, index: { unique: true }, null: false
|
|
||||||
t.string :queue_name, null: false
|
|
||||||
t.integer :priority, default: 0, null: false
|
|
||||||
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
|
|
||||||
t.index [ :priority, :job_id ], name: "index_solid_queue_poll_all"
|
|
||||||
t.index [ :queue_name, :priority, :job_id ], name: "index_solid_queue_poll_by_queue"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_claimed_executions do |t|
|
|
||||||
t.references :job, index: { unique: true }, null: false
|
|
||||||
t.bigint :process_id
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
|
|
||||||
t.index [ :process_id, :job_id ]
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_blocked_executions do |t|
|
|
||||||
t.references :job, index: { unique: true }, null: false
|
|
||||||
t.string :queue_name, null: false
|
|
||||||
t.integer :priority, default: 0, null: false
|
|
||||||
t.string :concurrency_key, null: false
|
|
||||||
t.datetime :expires_at, null: false
|
|
||||||
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
|
|
||||||
t.index [ :expires_at, :concurrency_key ], name: "index_solid_queue_blocked_executions_for_maintenance"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_failed_executions do |t|
|
|
||||||
t.references :job, index: { unique: true }, null: false
|
|
||||||
t.text :error
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_pauses do |t|
|
|
||||||
t.string :queue_name, null: false, index: { unique: true }
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_processes do |t|
|
|
||||||
t.string :kind, null: false
|
|
||||||
t.datetime :last_heartbeat_at, null: false, index: true
|
|
||||||
t.bigint :supervisor_id, index: true
|
|
||||||
|
|
||||||
t.integer :pid, null: false
|
|
||||||
t.string :hostname
|
|
||||||
t.text :metadata
|
|
||||||
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :solid_queue_semaphores do |t|
|
|
||||||
t.string :key, null: false, index: { unique: true }
|
|
||||||
t.integer :value, default: 1, null: false
|
|
||||||
t.datetime :expires_at, null: false, index: true
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
|
|
||||||
t.index [ :key, :value ], name: "index_solid_queue_semaphores_on_key_and_value"
|
|
||||||
end
|
|
||||||
|
|
||||||
add_foreign_key :solid_queue_blocked_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
|
|
||||||
add_foreign_key :solid_queue_claimed_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
|
|
||||||
add_foreign_key :solid_queue_failed_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
|
|
||||||
add_foreign_key :solid_queue_ready_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
|
|
||||||
add_foreign_key :solid_queue_scheduled_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# This migration comes from solid_queue (originally 20240110143450)
|
|
||||||
class AddMissingIndexToBlockedExecutions < ActiveRecord::Migration[7.1]
|
|
||||||
def change
|
|
||||||
add_index :solid_queue_blocked_executions, [ :concurrency_key, :priority, :job_id ], name: "index_solid_queue_blocked_executions_for_release"
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,15 +0,0 @@
|
||||||
# This migration comes from solid_queue (originally 20240218110712)
|
|
||||||
class CreateRecurringExecutions < ActiveRecord::Migration[7.1]
|
|
||||||
def change
|
|
||||||
create_table :solid_queue_recurring_executions do |t|
|
|
||||||
t.references :job, index: { unique: true }, null: false
|
|
||||||
t.string :task_key, null: false
|
|
||||||
t.datetime :run_at, null: false
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
|
|
||||||
t.index [ :task_key, :run_at ], unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
add_foreign_key :solid_queue_recurring_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,11 +0,0 @@
|
||||||
class CreateUsers < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
create_table :users do |t|
|
|
||||||
t.string :email
|
|
||||||
t.string :password_digest
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
add_index :users, :email, unique: true
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,21 +0,0 @@
|
||||||
# This migration comes from solid_queue (originally 20240719134516)
|
|
||||||
class CreateRecurringTasks < ActiveRecord::Migration[7.1]
|
|
||||||
def change
|
|
||||||
create_table :solid_queue_recurring_tasks do |t|
|
|
||||||
t.string :key, null: false, index: { unique: true }
|
|
||||||
t.string :schedule, null: false
|
|
||||||
t.string :command, limit: 2048
|
|
||||||
t.string :class_name
|
|
||||||
t.text :arguments
|
|
||||||
|
|
||||||
t.string :queue_name
|
|
||||||
t.integer :priority, default: 0
|
|
||||||
|
|
||||||
t.boolean :static, default: true, index: true
|
|
||||||
|
|
||||||
t.text :description
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# This migration comes from solid_queue (originally 20240811173327)
|
|
||||||
class AddNameToProcesses < ActiveRecord::Migration[7.1]
|
|
||||||
def change
|
|
||||||
add_column :solid_queue_processes, :name, :string
|
|
||||||
end
|
|
||||||
end
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue