Merge branch 'staging'

This commit is contained in:
Torsten 2023-01-16 21:53:56 +02:00
commit 8adbb6d4a4
64 changed files with 778 additions and 164 deletions

View File

@ -16,10 +16,11 @@ gem 'html2haml'
gem 'devise'
gem 'carrierwave', '>= 3.0.0.beta', '< 4.0'
gem 'kaminari'
gem "ruby2js" , git: "https://github.com/ruby2js/ruby2js/" , branch: "haml_fix"
gem 'thredded', '~> 1.0'
gem "rest-client"
gem "pundit"
gem "simple_form" , "5.1.0"
gem "simple_form_tailwind_css"

View File

@ -439,10 +439,12 @@ DEPENDENCIES
haml-rails
html2haml
importmap-rails
kaminari
merged!
mina
passenger
pg (~> 1.1)
pundit
rails (~> 7.0)
rest-client
ruby2js!

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,9 +1,13 @@
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
include Pundit::Authorization
alias :current_user :current_member #for pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end
protected
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_back(fallback_location: root_path)
end
end

View File

@ -0,0 +1,65 @@
class EventsController < ApplicationController
before_action :set_event, only: %i[ show edit update destroy ]
# GET /events
def index
@events = Event.all
end
# GET /events/1
def show
end
# GET /events/new
def new
kind = params[:profile]
kind = Profile.types.first unless Profile.types.include?(kind)
profile = current_member.profile(kind)
if(profile)
@event = Event.new profile: profile
else
redirect_to member_path(current_member), notice: "No such profile #{kind}."
end
end
# GET /events/1/edit
def edit
end
# POST /events
def create
@event = Event.new(event_params)
if @event.save
redirect_to @event, notice: "Event was successfully created."
else
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /events/1
def update
if @event.update(event_params)
redirect_to @event, notice: "Event was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /events/1
def destroy
@event.destroy
redirect_to events_url, notice: "Event was successfully destroyed."
end
private
# Use callbacks to share common setup or constraints between actions.
def set_event
@event = Event.find(params[:id])
end
# Only allow a list of trusted parameters through.
def event_params
params.require(:event).permit(:name, :text, :start_date, :end_date, :profile_id, :picture)
end
end

View File

@ -3,7 +3,7 @@ class MembersController < ApplicationController
# GET /members
def index
@members = Member.all
@members = Member.page params[:page]
end
# GET /members/1

View File

@ -0,0 +1,64 @@
class ProfilesController < ApplicationController
before_action :set_profile, only: %i[ show edit update destroy ]
# GET /profiles
def index
@profiles = Profile.page params[:page]
end
# GET /profiles/1
def show
end
# GET /profiles/new
def new
kind = params[:kind]
kind = Profile.types.first unless Profile.types.include?(kind)
@profile = Profile.new kind: kind
end
# GET /profiles/1/edit
def edit
authorize @profile
end
# POST /profiles
def create
@profile = Profile.new(profile_params)
@profile.member = current_member
if @profile.save
redirect_to member_path(current_member), notice: "Successfully created #{@profile.Kind} profile"
else
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /profiles/1
def update
authorize @profile
if @profile.update(profile_params)
redirect_to member_path(current_member), notice: "#{@profile.Kind} profile was updated."
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /profiles/1
def destroy
authorize @profile
@profile.destroy
redirect_to member_path(current_member), notice: "#{@profile.Kind} profile was successfully destroyed."
end
private
# Use callbacks to share common setup or constraints between actions.
def set_profile
@profile = Profile.find(params[:id])
end
# Only allow a list of trusted parameters through.
def profile_params
params.require(:profile).permit(:name, :bio, :picture, :kind)
end
end

View File

@ -1,21 +1,12 @@
require "redcarpet"
module ApplicationHelper
include Merged::SharedHelper
def renderer
options = {hard_wrap: true , autolink: true, no_intra_emphasis: true ,
safe_links_only: true, no_styles: true ,
link_attributes: { target: '_blank' }}
html = Redcarpet::Render::HTML.new(options)
Redcarpet::Markdown.new(html, options)
def shorten(text , to = 100)
return "" if text.blank?
"#{text[0..to]} . . . ".html_safe
end
def markdown(text)
return "" if text.blank?
text = text.text unless text.is_a?(String)
return "" if text.blank?
self.renderer.render(text).html_safe
end
def main_app
Rails.application.routes.url_helpers
@ -25,4 +16,13 @@ module ApplicationHelper
"mr-3 inline-block rounded-lg px-3 py-2 text-md font-medium border border-gray-500"
end
def image_for(someone , classes = "")
if someone.picture.blank?
image = asset_url("no_image.png")
else
image = someone.picture.url
end
image_tag(image , alt: someone.name , class: classes )
end
end

View File

@ -0,0 +1,2 @@
module EventsHelper
end

View File

@ -0,0 +1,2 @@
module ProfilesHelper
end

View File

@ -20,8 +20,8 @@ class Entity < ApplicationRecord
end
def self.response(path = "")
host = Rails.credentials.weather_dev
host = Rails.credentials.weather_pro if Rails.env.production?
host = Rails.application.credentials.weather_dev
host = Rails.application.credentials.weather_pro if Rails.env.production?
token = Rails.application.credentials.weather_token
begin
all = RestClient.get( "#{host}:8123/api/states#{path}" ,

11
app/models/event.rb Normal file
View File

@ -0,0 +1,11 @@
class Event < ApplicationRecord
belongs_to :profile
validates :name , presence: true
validates :text , presence: true
validates :start_date , presence: true
validates :picture , presence: true
mount_uploader :picture, PictureUploader
end

View File

@ -7,11 +7,19 @@ class Member < ApplicationRecord
mount_uploader :picture, PictureUploader
has_many :entities
has_many :profiles
def admin
true
Profile.types.each do |kind|
define_method :kind do
Profile.where( member_id: self.id).where(kind: kind).first
end
end
def profile( kind )
Profile.where( member_id: self.id).where(kind: kind).first
end
def admin?
true
email == "torsten@villataika.fi"
end
alias :admin :admin?
end

19
app/models/profile.rb Normal file
View File

@ -0,0 +1,19 @@
class Profile < ApplicationRecord
belongs_to :member
has_many :events
validates :name , presence: true
validates :kind , presence: true
validates :bio , presence: true
validates :picture , presence: true
mount_uploader :picture, PictureUploader
def self.types
["member" , "artist" , "teacher"]
end
def Kind
self.kind.capitalize
end
end

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :member, :record
def initialize(member, record)
@member = member
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
def initialize(member, scope)
@member = member
@scope = scope
end
def resolve
raise NotImplementedError, "You must define #resolve in #{self.class}"
end
private
attr_reader :member, :scope
end
end

View File

@ -0,0 +1,14 @@
# allows to edit/detroy own data
# which can be viewed by anyone
class EditOwnPolicy < ApplicationPolicy
def edit?
return true member.admin?
owner?
end
def owner?
member == record.member
end
alias :update? :edit?
alias :destroy? :edit?
end

View File

@ -0,0 +1,14 @@
class EntityPolicy < ApplicationPolicy
def owner?
member.admin?
end
alias :owner? :index?
alias :owner? :show?
alias :owner? :create?
alias :owner? :new?
alias :owner? :update?
alias :owner? :edit?
alias :owner? :destroy?
end

View File

@ -0,0 +1,13 @@
class EventPolicy < EditOwnPolicy
def owner?
member == record.profile.member
end
class Scope < Scope
# NOTE: Be explicit about which records you allow access to!
# def resolve
# scope.all
# end
end
end

View File

@ -0,0 +1,6 @@
class MemberPolicy < EditOwnPolicy
class Scope < Scope
end
end

View File

@ -0,0 +1,4 @@
class ProfilePolicy < EditOwnPolicy
end

View File

@ -15,6 +15,6 @@
class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline" |
.mb-4
= f.submit "Resend Confirmation Info", |
class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
class: "button bg-cyan-700 hover:bg-cyan-500 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -27,6 +27,6 @@
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
.mb-4
= f.submit "Change My Password", |
class: "button bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
class: "button bg-cyan-700 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -4,7 +4,7 @@
= form_for(resource, as: resource_name,
html: { method: :post,
class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" } ,
url: password_path(resource_name) ) do |f|
url: password_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm"
@ -12,6 +12,6 @@
class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" |
.mb-4
= f.submit "Send Password Reset Info", |
class: "button bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full" |
class: "button bg-cyan-700 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full" |
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -33,7 +33,7 @@
autocomplete: "current-password", |
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" |
.actions
= f.submit "Update", class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full"
= f.submit "Update", class: "button bg-cyan-700 hover:bg-cyan-500 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full"
%p
Unhappy?
%span= button_to "Delete my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete

View File

@ -5,12 +5,6 @@
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" } ,
url: registration_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :name, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.text_field :name,
autocomplete: "name",
placeholder: "Pekka Juustonen",
class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline"
.mb-4
= f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.email_field :email,
@ -33,6 +27,6 @@
class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" |
.mb-4
= f.submit "Sign Up", |
class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
class: "button bg-cyan-700 hover:bg-cyan-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -21,6 +21,6 @@
class: "align-baseline inline-block text-gray-700 text-sm"
.mb-4
= f.submit "Log in",
class: "button bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
class: "button bg-cyan-700 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -4,7 +4,7 @@
= form_for(resource, as: resource_name,
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" ,
method: :post }
url: unlock_path(resource_name) ) do |f|
url: unlock_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm"
@ -14,6 +14,6 @@
class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" |
.mb-4
= f.submit "Resend unlock instructions", |
class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
class: "button bg-cyan-700 hover:bg-cyan-500 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -1,16 +1,11 @@
= simple_form_for @entity do |f|
- if @entity.errors.any?
#error_explanation
%h2= "#{pluralize(@entity.errors.count, "error")} prohibited this entity from being saved:"
%ul
- @entity.errors.full_messages.each do |message|
%li= message
= f.error_notification
.field
= f.input :name
.field
= f.input :ha_id , collection: Entity.ha_entities
.field
= f.input :member_id , collection: Member.all.collect{|m| [m.name , m.id ]}
.actions{class: button_classes}
= f.submit 'Save'
= f.input :name
= f.input :ha_id , collection: Entity.ha_entities
= f.input :member_id , collection: Member.all.collect{|m| [m.email , m.id ]}
.flex.justify-between.mt-3
.actions{class: button_classes}
= f.submit 'Save'
= link_to 'Back', entities_path , class: "mt-3"

View File

@ -1,7 +1,5 @@
%h1 Editing entity
= render 'form'
= link_to 'Show', @entity
\|
= link_to 'Back', entities_path
.flex.justify-center
.flex.flex-col
.text-xl.m-4
New event
= render 'form'

View File

@ -1,27 +1,30 @@
%h1 Listing entities
.flex.justify-center
%h1.text-3xl Listing entities
.flex.justify-center.mt-10
%table
%thead
%tr
%th Name
%th Ha
%th Type
%th Member
%th
%th
%th
%tbody
- @entities.each do |entity|
%table.table-auto.border-separate
%thead
%tr
%td= entity.name
%td= entity.ha_id
%td= entity.type
%td= entity.member.name
%td= link_to 'Show', entity
%td= link_to 'Edit', edit_entity_path(entity)
%td= link_to 'Destroy', entity, method: :delete, data: { confirm: 'Are you sure?' }
%th Name
%th Ha
%th Type
%th Member
%th
%th
%th
%br
%tbody
- @entities.each do |entity|
%tr
%td.p-2= entity.name
%td.p-2= entity.ha_id
%td.p-2= entity.type
%td.p-2= entity.member.email
%td.p-2= link_to 'Show', entity
%td.p-2= link_to 'Edit', edit_entity_path(entity)
%td.p-2= link_to 'Destroy', entity, method: :delete, data: { confirm: 'Are you sure?' }
= link_to 'New Entity', new_entity_path
.flex.justify-center.mt-10
= link_to 'New Entity', new_entity_path

View File

@ -1,7 +1,5 @@
%h1 New entity
.grid.grid-cols-3
%div
= render 'form'
= link_to 'Back', entities_path
.flex.justify-center
.column
.text-xl.m-4
New entity
= render 'form'

View File

@ -15,7 +15,7 @@
= @entity.value
%p
%b Member:
= @entity.member.name
= @entity.member.email
= link_to 'Edit', edit_entity_path(@entity)
\|

View File

@ -0,0 +1,11 @@
%section.overflow-hidden.grid.grid-cols-1.m-5.md:m-12.lg:m-20.md:grid-cols-2
%div
= image_for( event , "h-56 w-full object-cover sm:h-full")
.p-8.md:p-12.lg:px-16.lg:py-24
.mx-auto.max-w-xl.text-center
%h2.text-2xl.font-bold.md:text-4xl
= event.name
%h4.text-xl.mt-10.md:text-2xl
= event.start_date
.mt-8{ prose_classes }
= markdown(event.text)

View File

@ -0,0 +1,16 @@
= simple_form_for @event do |f|
= f.error_notification
= f.input :name
= f.input :text
.grid.grid-cols-2.gap-10
= f.input :start_date
= f.input :end_date
= f.input :profile_id , as: :hidden
= f.input :picture , as: :file
.flex.justify-between
%button.mt-6.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
= f.submit 'Save'
= link_to member_path(current_member) do
.mt-6.ml-20.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Back

View File

@ -0,0 +1,6 @@
.flex.justify-center
.column
.text-xl.m-4
Editing event
= render 'form'

View File

@ -0,0 +1,12 @@
.flex.justify-center
.grid.grid-cols-4
- @events.each do |event|
.fex.flex-col.overflow-hidden.rounded-lg.border.border-gray-100.shadow-sm.m-10
=link_to event do
= image_for( event , class: "h-60 w-full object-cover")
%h3.py-5.text-2xl.bg-gray-100.text-black.font-bold.text-center
= event.name
%div.h-full
.p-5.text-center
.m-2.text-sm.leading-relaxed.line-clamp-3{ prose_classes }
= shorten markdown(event.text)

View File

@ -0,0 +1,6 @@
.flex.justify-center
.column
.text-xl.m-4
New event
= render 'form'

View File

@ -0,0 +1,3 @@
= render "event" , event: @event
= render "profiles/profile" , profile: @event.profile , header: "About the teacher"

View File

@ -14,8 +14,8 @@
%form.w-full
%label.sr-only{:for => "UserEmail"} Email
.p-2.border.border-gray-100.focus-within:ring.sm:flex.sm:items-center.sm:gap-4
%input#UserEmail.w-full.border-none.focus:border-transparent.focus:ring-transparent.sm:text-sm{:placeholder => "jaakko@iki.fi", :type => "email"}/
%button.w-full.px-6.py-3.mt-1.text-sm.font-bold.tracking-wide.text-white.uppercase.transition-none.bg-teal-500.hover:bg-teal-600.sm:mt-0.sm:w-auto.sm:flex-shrink-0
%input.w-full.rounded.focus:border-transparent.focus:ring-transparent.sm:text-sm{:placeholder => "jaakko@iki.fi", :type => "email"}/
%button.w-full.rounded.px-6.py-3.mt-1.text-sm.font-bold.tracking-wide.text-white.uppercase.bg-cyan-700.hover:bg-cyan-500.sm:mt-0.sm:w-auto.sm:flex-shrink-0
Sign Up
.col-span-2.sm:col-span-1
%p.font-medium.text-gray-900 Main

View File

@ -12,7 +12,7 @@
= image_tag("fb.webp" , class: "h-10 rounded-md")
%li
- unless member_signed_in?
%a.inline-flex.items-center.justify-center.h-12.px-6.font-medium.tracking-wide.text-white.transition.duration-200.rounded-lg.shadow-md.bg-green-800.hover:bg-blue-800.focus:shadow-outline.focus:outline-none{"aria-label" => "Sign up", :href => main_app.member_session_path, :title => "Log in or Sign up"}
%a.inline-flex.items-center.justify-center.h-12.px-6.font-medium.tracking-wide.text-white.transition.duration-200.rounded-lg.shadow-md.bg-cyan-700.hover:bg-cyan-500.focus:shadow-outline.focus:outline-none{"aria-label" => "Sign up", :href => main_app.member_session_path, :title => "Log in or Sign up"}
Login
- else
.inline-flex.items-stretch.rounded-md.border

View File

@ -3,7 +3,7 @@
%a.block.rounded-lg.px-4.py-2.text-sm.text-gray-500.hover:bg-gray-50.hover:text-gray-700{:href => "/forum", :role => "menuitem"}
Forum
%a.block.rounded-lg.px-4.py-2.text-sm.text-gray-500.hover:bg-gray-50.hover:text-gray-700{:href => main_app.member_path(current_member), :role => "menuitem"}
Profile
Settings
- unless Rails.env.production?
- if current_member.admin?
%a.block.rounded-lg.px-4.py-2.text-sm.text-gray-500.hover:bg-gray-50.hover:text-gray-700{:href => merged.pages_path(), :role => "menuitem"}

View File

@ -1,11 +1,5 @@
= form_for @member do |f|
.flex.flex-col
- if @member.errors.any?
#error_explanation
%h2= "#{pluralize(@member.errors.count, "error")} prohibited this member from being saved:"
%ul
- @member.errors.full_messages.each do |message|
%li= message
= simple_form_for @member do |f|
= f.error_notification
.grid.grid-cols-2.m-20.gap-10
.field

View File

@ -1,25 +1,14 @@
= paginate @members
.flex.justify-center
%h1 Listing members
%table
%thead
%tr
%th Name
%th Public
%th
%th
%th
%tbody
- @members.each do |member|
%tr
%td= member.name
%td= member.public
%td= link_to 'Show', member
%td= link_to 'Edit', edit_member_path(member)
%td= link_to 'Destroy', member, method: :delete, data: { confirm: 'Are you sure?' }
%br
= link_to 'New Member', new_member_path
.grid.grid-cols-4
- @members.each do |member|
.fex.flex-col.overflow-hidden.rounded-lg.border.border-gray-100.shadow-sm.m-10
=link_to member do
= image_for( member , class: "h-60 w-full object-cover")
%h3.pt-5.text-2xl.bg-gray-100.text-black.font-bold.text-center
= member.name
%div.h-full
.p-5.text-center
.m-2.text-sm.leading-relaxed.line-clamp-3{ prose_classes }
= shorten markdown(member.bio)

View File

@ -1,33 +1,51 @@
.px-4.py-16.mx-auto.sm:max-w-xl.md:max-w-full.lg:max-w-screen-xl.md:px-24.lg:px-8.lg:py-20
.flex.flex-col.max-w-screen-lg.overflow-hidden.bg-white.border.rounded.shadow-sm.lg:flex-row.sm:mx-auto
.relative{:class => "lg:w-1/2"}
-if @member.picture_url
= image_tag @member.picture_url, class: "object-cover w-full lg:absolute h-80 lg:h-full"
.flex.flex-col.justify-center.p-8.lg:p-16.lg:pl-10{:class => "lg:w-1/2"}
%div
%p.inline-block.px-3.py-px.mb-4.text-xs.font-semibold.tracking-wider.text-teal-900.uppercase.rounded-full.bg-teal-accent-400
Role ?
%h5.mb-3.text-3xl.font-extrabold.leading-none.sm:text-4xl
= @member.name
.mb-8.text-gray-800
.prose= markdown(@member.bio)
.flex.items-center
%a.inline-flex.items-center.font-semibold.transition-colors.duration-200.text-deep-purple-accent-400.hover:text-deep-purple-800{"aria-label" => "", :href => "/"}
Learn More
%svg.inline-block.w-3.ml-2{:fill => "currentColor", :viewbox => "0 0 12 12"}
%path{:d => "M9.707,5.293l-5-5A1,1,0,0,0,3.293,1.707L7.586,6,3.293,10.293a1,1,0,1,0,1.414,1.414l5-5A1,1,0,0,0,9.707,5.293Z"}
%section.mx-20
.flex.justify-center
.px-4.py-4.md:py-10.lg:py-16.mx-5.md:mx-12.lg:mx-20.text-center
%h1.text-2xl.font-bold.tracking-tight.sm:text-4xl
Settings and profiles
%h4.text-xl.mt-4.md:text-2xl
Allowing you to create and edit various profiles
.mt-4.text-lg.pt-4
You can have seperate profiles as teacher, artist, or coop member.
If you choose to create them they will be publicly listed.
Techers and Artists may then create events.
-if @member == current_member or current_member.admin?
.grid.grid-cols-2.mx-20
.first
- Profile.types.each do |kind|
.grid.grid-cols-4
- if profile = @member.profile(kind)
%div
=profile.Kind
=link_to profile.name , profile_path(profile) , class: "underline"
exists
= link_to edit_profile_path(profile) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit
= link_to new_event_path(profile: profile.kind) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
New #{profile.Kind} Event
-if event = profile.events.length >0
%div
=link_to profile.events.first.name , event_path(profile.events.first) , class: "underline"
= link_to edit_event_path(profile.events.first) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit
-else
No events
-else
%div Create new profile as
= link_to new_profile_path(kind: kind) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
= kind.capitalize
%div Create profile first
%div No events, create profile first
.flex.justify-around
= link_to edit_member_path(@member) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit
.grid.grid-cols-3.gap-4
-@member.entities.each do |entity|
%div= entity.type
%div= entity.name
%div= entity.value
= form_tag( destroy_member_session_path , {method: :delete } ) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400{type: :submit}
Sign out
.grid.grid-cols-6.gap-4.mt-10.mx-10
-@member.entities.each do |entity|
%div= entity.type
%div= entity.name
%div= entity.value
= form_tag( destroy_member_session_path , {method: :delete } ) do
%button.mt-10.ml-10.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400{type: :submit}
Sign out

View File

@ -0,0 +1,14 @@
= simple_form_for @profile do |f|
= f.error_notification
= f.input :name
= f.input :bio
= f.input :kind, :as => :hidden
= f.input :picture , as: :file , label: (@profile.picture.blank? ? "Add picture" : "Change picture (optional)")
.flex.justify-between.mt-4
%button.bg-cyan-200{class: button_classes}
= f.submit 'Save'
%button.bg-cyan-200{class: button_classes}
= link_to 'Cancel', member_path(current_member)

View File

@ -0,0 +1,14 @@
.px-4.py-16.mx-auto.sm:max-w-xl.md:max-w-full.lg:max-w-screen-xl.md:px-24.lg:px-8.lg:py-20
.flex.justify-center
.px-4.py-4.text-center
%h1.text-2xl.font-bold.tracking-tight.sm:text-4xl
= header
.flex.mt-6.py-4.flex-col.max-w-screen-lg.overflow-hidden.bg-white.border.rounded.shadow-sm.lg:flex-row.sm:mx-auto
.relative{:class => "lg:w-1/2"}
-if profile.picture_url
= image_tag profile.picture_url, class: "object-cover w-full lg:absolute h-80 lg:h-full"
.flex.flex-col.justify-center.p-8.lg:p-16.lg:pl-10{:class => "lg:w-1/2"}
%h5.mb-3.text-3xl.font-extrabold.leading-none.sm:text-4xl
= profile.name
.mb-8.text-gray-800
.prose= markdown(profile.bio)

View File

@ -0,0 +1,7 @@
.flex.justify-center
.column
.text-xl.m-4
Edit your
=@profile.Kind
profile
= render 'form'

View File

@ -0,0 +1,14 @@
= paginate @profiles
.flex.justify-center
.grid.grid-cols-4
- @profiles.each do |profile|
.fex.flex-col.overflow-hidden.rounded-lg.border.border-gray-100.shadow-sm.m-10
=link_to profile do
= image_for( profile , class: "h-60 w-full object-cover")
%h3.py-5.text-2xl.bg-gray-100.text-black.font-bold.text-center
= profile.name
%div.h-full
.p-5.text-center
.m-2.text-sm.leading-relaxed.line-clamp-3{ prose_classes }
= shorten markdown(profile.bio)

View File

@ -0,0 +1,7 @@
.flex.justify-center
.column
.text-xl.m-4
Create your
=@profile.kind.capitalize
profile
= render 'form'

View File

@ -0,0 +1 @@
= render "profile" , profile: @profile , header: "#{@profile.Kind} profile"

View File

@ -14,7 +14,7 @@ SimpleForm.setup do |config|
# stack. The options given below are used to wrap the
# whole input.
config.wrappers :default, tag: 'div', class: '', error_class: '', valid_class: '' do |b|
config.wrappers :default, tag: 'div', class: 'mt-4', error_class: '', valid_class: '' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength

View File

@ -1,4 +1,6 @@
Rails.application.routes.draw do
resources :events
resources :profiles
resources :entities
devise_for :members

View File

@ -0,0 +1,17 @@
class CreateProfiles < ActiveRecord::Migration[7.0]
def change
create_table :profiles do |t|
t.string :name , null: false
t.string :kind , null: false
t.text :bio , null: false
t.string :picture , null: false
t.references :member, null: false, foreign_key: true
t.timestamps
end
remove_column :members, :name, :string
remove_column :members, :bio, :text
remove_column :members, :picture, :string
remove_column :members, :public, :boolean
end
end

View File

@ -0,0 +1,14 @@
class CreateEvents < ActiveRecord::Migration[7.0]
def change
create_table :events do |t|
t.string :name , null: false
t.text :text , null: false
t.date :start_date , null: false
t.date :end_date
t.references :profile, null: false, foreign_key: true
t.string :picture , null: false
t.timestamps
end
end
end

31
db/schema.rb generated
View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_01_13_170732) do
ActiveRecord::Schema[7.0].define(version: 2023_01_16_120518) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -24,6 +24,18 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_13_170732) do
t.index ["member_id"], name: "index_entities_on_member_id"
end
create_table "events", force: :cascade do |t|
t.string "name", null: false
t.text "text", null: false
t.date "start_date", null: false
t.date "end_date"
t.bigint "profile_id", null: false
t.string "picture", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["profile_id"], name: "index_events_on_profile_id"
end
create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
@ -37,10 +49,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_13_170732) do
end
create_table "members", force: :cascade do |t|
t.string "name"
t.string "bio"
t.string "picture"
t.boolean "public"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
@ -56,6 +64,17 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_13_170732) do
t.index ["reset_password_token"], name: "index_members_on_reset_password_token", unique: true
end
create_table "profiles", force: :cascade do |t|
t.string "name", null: false
t.string "kind", null: false
t.text "bio", null: false
t.string "picture", null: false
t.bigint "member_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["member_id"], name: "index_profiles_on_member_id"
end
create_table "thredded_categories", force: :cascade do |t|
t.bigint "messageboard_id", null: false
t.text "name", null: false
@ -288,6 +307,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_13_170732) do
end
add_foreign_key "entities", "members"
add_foreign_key "events", "profiles"
add_foreign_key "profiles", "members"
add_foreign_key "thredded_messageboard_users", "thredded_messageboards", on_delete: :cascade
add_foreign_key "thredded_messageboard_users", "thredded_user_details", on_delete: :cascade
add_foreign_key "thredded_user_post_notifications", "members", column: "user_id", on_delete: :cascade

17
test/fixtures/events.yml vendored Normal file
View File

@ -0,0 +1,17 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
text: MyText
start_date: 2023-01-16
end_date: 2023-01-16
profile: one
picture: MyString
two:
name: MyString
text: MyText
start_date: 2023-01-16
end_date: 2023-01-16
profile: two
picture: MyString

13
test/fixtures/profiles.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
bio: MyString
picture: MyString
member: one
two:
name: MyString
bio: MyString
picture: MyString
member: two

View File

@ -0,0 +1,7 @@
require "test_helper"
class EventTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require "test_helper"
class ProfileTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,18 @@
require 'test_helper'
class EventPolicyTest < ActiveSupport::TestCase
def test_scope
end
def test_show
end
def test_create
end
def test_update
end
def test_destroy
end
end

View File

@ -0,0 +1,18 @@
require 'test_helper'
class MemberPolicyTest < ActiveSupport::TestCase
def test_scope
end
def test_show
end
def test_create
end
def test_update
end
def test_destroy
end
end

View File

@ -0,0 +1,51 @@
require "application_system_test_case"
class EventsTest < ApplicationSystemTestCase
setup do
@event = events(:one)
end
test "visiting the index" do
visit events_url
assert_selector "h1", text: "Events"
end
test "should create event" do
visit events_url
click_on "New event"
fill_in "End date", with: @event.end_date
fill_in "Name", with: @event.name
fill_in "Picture", with: @event.picture
fill_in "Profile", with: @event.profile_id
fill_in "Start date", with: @event.start_date
fill_in "Text", with: @event.text
click_on "Create Event"
assert_text "Event was successfully created"
click_on "Back"
end
test "should update Event" do
visit event_url(@event)
click_on "Edit this event", match: :first
fill_in "End date", with: @event.end_date
fill_in "Name", with: @event.name
fill_in "Picture", with: @event.picture
fill_in "Profile", with: @event.profile_id
fill_in "Start date", with: @event.start_date
fill_in "Text", with: @event.text
click_on "Update Event"
assert_text "Event was successfully updated"
click_on "Back"
end
test "should destroy Event" do
visit event_url(@event)
click_on "Destroy this event", match: :first
assert_text "Event was successfully destroyed"
end
end

View File

@ -0,0 +1,47 @@
require "application_system_test_case"
class ProfilesTest < ApplicationSystemTestCase
setup do
@profile = profiles(:one)
end
test "visiting the index" do
visit profiles_url
assert_selector "h1", text: "Profiles"
end
test "should create profile" do
visit profiles_url
click_on "New profile"
fill_in "Bio", with: @profile.bio
fill_in "Member", with: @profile.member_id
fill_in "Name", with: @profile.name
fill_in "Picture", with: @profile.picture
click_on "Create Profile"
assert_text "Profile was successfully created"
click_on "Back"
end
test "should update Profile" do
visit profile_url(@profile)
click_on "Edit this profile", match: :first
fill_in "Bio", with: @profile.bio
fill_in "Member", with: @profile.member_id
fill_in "Name", with: @profile.name
fill_in "Picture", with: @profile.picture
click_on "Update Profile"
assert_text "Profile was successfully updated"
click_on "Back"
end
test "should destroy Profile" do
visit profile_url(@profile)
click_on "Destroy this profile", match: :first
assert_text "Profile was successfully destroyed"
end
end