-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7533d49
commit df47258
Showing
12 changed files
with
832 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -258,6 +258,7 @@ GEM | |
|
||
PLATFORMS | ||
arm64-darwin-23 | ||
arm64-darwin-24 | ||
x86_64-darwin-21 | ||
x86_64-darwin-22 | ||
x86_64-linux | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
131 changes: 131 additions & 0 deletions
131
examples/playbook-rails/app/assets/stylesheets/feedback_form.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
@import "tokens/colors"; | ||
@import "tokens/typography"; | ||
@import "tokens/spacing"; | ||
@import "tokens/shadows"; | ||
@import "tokens/line_height"; | ||
|
||
.feedback-form { | ||
max-width: 500px; | ||
padding: 24px; | ||
background-color: $card_light; | ||
border: 1px solid $border_light; | ||
border-radius: 8px; | ||
box-shadow: $shadow_deep; | ||
|
||
&__title { | ||
font-family: $font_family_base; | ||
font-size: $heading_3; | ||
font-weight: $bold; | ||
color: $text_lt_default; | ||
margin-bottom: 24px; | ||
line-height: $lh_tight; | ||
} | ||
|
||
&__field { | ||
margin-bottom: 16px; | ||
} | ||
|
||
&__label { | ||
display: block; | ||
font-family: $font_family_base; | ||
font-size: $font_base; | ||
font-weight: $bold; | ||
color: $text_lt_default; | ||
margin-bottom: 8px; | ||
} | ||
|
||
&__input { | ||
width: 100%; | ||
padding: 12px; | ||
font-family: $font_family_base; | ||
font-size: $font_base; | ||
color: $text_lt_default; | ||
border: 1px solid $border_light; | ||
border-radius: 4px; | ||
background-color: $white; | ||
transition: border-color 0.2s ease, box-shadow 0.2s ease; | ||
|
||
&:focus { | ||
outline: none; | ||
border-color: $focus_color; | ||
box-shadow: 0 0 0 3px $focus_input_light; | ||
} | ||
|
||
&--error { | ||
border-color: $error; | ||
|
||
&:focus { | ||
box-shadow: 0 0 0 3px $error_subtle; | ||
} | ||
} | ||
} | ||
|
||
&__textarea { | ||
@extend .feedback-form__input; | ||
min-height: 120px; | ||
resize: vertical; | ||
} | ||
|
||
&__error { | ||
display: none; | ||
color: $error; | ||
font-size: $font_small; | ||
margin-top: 4px; | ||
} | ||
|
||
&__error--visible { | ||
display: block; | ||
} | ||
|
||
&__submit { | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
padding: 12px 24px; | ||
font-family: $font_family_base; | ||
font-size: $font_base; | ||
font-weight: $bold; | ||
color: $white; | ||
background-color: $primary; | ||
border: none; | ||
border-radius: 4px; | ||
cursor: pointer; | ||
transition: background-color 0.2s ease; | ||
|
||
&:hover { | ||
background-color: darken($primary, 5%); | ||
} | ||
|
||
&:focus { | ||
outline: none; | ||
box-shadow: 0 0 0 3px $focus_input_light; | ||
} | ||
|
||
&:disabled { | ||
opacity: 0.7; | ||
cursor: not-allowed; | ||
} | ||
|
||
&--loading { | ||
position: relative; | ||
color: transparent; | ||
|
||
&::after { | ||
content: ""; | ||
position: absolute; | ||
width: 16px; | ||
height: 16px; | ||
border: 2px solid $white; | ||
border-radius: 50%; | ||
border-top-color: transparent; | ||
animation: spin 0.8s linear infinite; | ||
} | ||
} | ||
} | ||
} | ||
|
||
@keyframes spin { | ||
to { | ||
transform: rotate(360deg); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
examples/playbook-rails/app/controllers/feedback_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
class FeedbackController < ApplicationController | ||
skip_before_action :verify_authenticity_token | ||
|
||
def create | ||
@feedback = Feedback.new(feedback_params) | ||
|
||
if @feedback.save | ||
render json: { message: "Thank you for your feedback!" }, status: :ok | ||
else | ||
render json: { | ||
errors: @feedback.errors.messages.transform_values(&:first), | ||
}, status: :unprocessable_entity | ||
end | ||
rescue | ||
render json: { error: "An unexpected error occurred" }, status: :internal_server_error | ||
end | ||
|
||
private | ||
|
||
def feedback_params | ||
params.require(:feedback).permit(:name, :comments) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# frozen_string_literal: true | ||
|
||
class Feedback < ApplicationRecord | ||
# Validations | ||
validates :name, presence: true, | ||
length: { minimum: 2, maximum: 100 } | ||
|
||
validates :comments, presence: true, | ||
length: { minimum: 10, maximum: 1000 } | ||
|
||
# Sanitization | ||
before_validation :sanitize_fields | ||
|
||
private | ||
|
||
def sanitize_fields | ||
self.name = name.to_s.strip | ||
self.comments = comments.to_s.strip | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,108 @@ | ||
<%= pb_rails("circle_icon_button", props: { | ||
variant: "primary", | ||
icon: "plus" | ||
}) %> | ||
<%= pb_rails("title", props: { | ||
}) do %> | ||
Welcome to Playbook | ||
<% end %> | ||
<div class="feedback-form"> | ||
<h2 class="feedback-form__title">Share Your Feedback</h2> | ||
|
||
<form action="/feedback" method="post" class="js-feedback-form"> | ||
<div class="feedback-form__field"> | ||
<label class="feedback-form__label" for="feedback_name">Name</label> | ||
<input type="text" id="feedback_name" name="feedback[name]" class="feedback-form__input" required> | ||
<div class="feedback-form__error" data-error="name">Please enter your name</div> | ||
</div> | ||
|
||
<div class="feedback-form__field"> | ||
<label class="feedback-form__label" for="feedback_comments">Comments</label> | ||
<textarea id="feedback_comments" name="feedback[comments]" class="feedback-form__textarea" required></textarea> | ||
<div class="feedback-form__error" data-error="comments">Please enter your feedback</div> | ||
</div> | ||
|
||
<button type="submit" class="feedback-form__submit"> | ||
Submit Feedback | ||
</button> | ||
</form> | ||
</div> | ||
|
||
<script> | ||
document.addEventListener('DOMContentLoaded', function() { | ||
const form = document.querySelector('.js-feedback-form'); | ||
const submitButton = form.querySelector('.feedback-form__submit'); | ||
|
||
form.addEventListener('submit', function(e) { | ||
e.preventDefault(); | ||
|
||
// Reset errors | ||
form.querySelectorAll('.feedback-form__error').forEach(error => { | ||
error.classList.remove('feedback-form__error--visible'); | ||
}); | ||
form.querySelectorAll('.feedback-form__input, .feedback-form__textarea').forEach(input => { | ||
input.classList.remove('feedback-form__input--error'); | ||
}); | ||
|
||
// Add loading state | ||
submitButton.disabled = true; | ||
submitButton.classList.add('feedback-form__submit--loading'); | ||
|
||
// Get the form data and structure it properly | ||
const formData = new FormData(form); | ||
const feedbackData = { | ||
feedback: { | ||
name: formData.get('feedback[name]'), | ||
comments: formData.get('feedback[comments]') | ||
} | ||
}; | ||
|
||
console.log('Sending data:', feedbackData); | ||
|
||
// Submit form | ||
fetch(form.action, { | ||
method: 'POST', | ||
headers: { | ||
'Accept': 'application/json', | ||
'Content-Type': 'application/json', | ||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content | ||
}, | ||
body: JSON.stringify(feedbackData) | ||
}) | ||
.then(response => { | ||
console.log('Response status:', response.status); | ||
if (!response.ok) { | ||
return response.json().then(data => { | ||
console.log('Error data:', data); | ||
return Promise.reject(data); | ||
}); | ||
} | ||
return response.json(); | ||
}) | ||
.then(data => { | ||
console.log('Success data:', data); | ||
if (data.message) { | ||
alert(data.message); | ||
form.reset(); | ||
} | ||
}) | ||
.catch(error => { | ||
console.log('Caught error:', error); | ||
if (error.errors) { | ||
Object.entries(error.errors).forEach(([field, message]) => { | ||
if (message) { | ||
const input = form.querySelector(`[name="feedback[${field}]"]`); | ||
if (input) { | ||
input.classList.add('feedback-form__input--error'); | ||
const errorElement = form.querySelector(`[data-error="${field}"]`); | ||
if (errorElement) { | ||
errorElement.textContent = message; | ||
errorElement.classList.add('feedback-form__error--visible'); | ||
} | ||
} | ||
} | ||
}); | ||
} else { | ||
alert('An error occurred. Please try again.'); | ||
} | ||
}) | ||
.finally(() => { | ||
submitButton.disabled = false; | ||
submitButton.classList.remove('feedback-form__submit--loading'); | ||
}); | ||
}); | ||
}); | ||
</script> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
# frozen_string_literal: true | ||
|
||
Rails.application.routes.draw do | ||
root 'pages#index' | ||
|
||
get '/pages', to: 'pages#index' | ||
root "pages#index" | ||
|
||
get "/pages", to: "pages#index" | ||
post "/feedback", to: "feedback#create" | ||
end |
Binary file not shown.
11 changes: 11 additions & 0 deletions
11
examples/playbook-rails/db/migrate/20240119000000_create_feedbacks.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# frozen_string_literal: true | ||
|
||
class CreateFeedbacks < ActiveRecord::Migration[7.0] | ||
def change | ||
create_table :feedbacks do |t| | ||
t.string :name, null: false | ||
t.text :comments, null: false | ||
t.timestamps | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# frozen_string_literal: true | ||
|
||
# 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[7.0].define(version: 2024_01_19_000000) do | ||
create_table "feedbacks", force: :cascade do |t| | ||
t.string "name", null: false | ||
t.text "comments", null: false | ||
t.datetime "created_at", null: false | ||
t.datetime "updated_at", null: false | ||
end | ||
end |
Oops, something went wrong.