Merge branch 'master' into forms-index-page
This commit is contained in:
@@ -14,22 +14,32 @@
|
||||
*= require_self
|
||||
*/
|
||||
@import "bulma/sass/utilities/initial-variables";
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Lobster&family=Comfortaa:wght@400;500;600;700&display=swap');
|
||||
|
||||
$family-sans-serif: "Helvetica",
|
||||
"Arial",
|
||||
sans-serif;
|
||||
// https://coolors.co/06aed5-086788-f0c808-fff1d0-dd1c1a
|
||||
$blue: #083d77;
|
||||
$red: #dd1c1a;
|
||||
$orange: #ee964b;
|
||||
$yellow: #f4d35e;
|
||||
$light: #f5fafe; // #ebebd3;
|
||||
$family-sans-serif: 'Roboto', sans-serif;
|
||||
$family-secondary: 'Comfortaa', cursive;
|
||||
// // https://coolors.co/06aed5-086788-f0c808-fff1d0-dd1c1a
|
||||
// $blue: #083d77;
|
||||
// $red: #dd1c1a;
|
||||
// $orange: #ee964b;
|
||||
// $yellow: #f4d35e;
|
||||
// $light: #f5fafe; // #ebebd3;
|
||||
// $primary: $blue;
|
||||
// $green: #007932; // hsl(141, 53%, 53%);
|
||||
// $footer-background-color: $light;
|
||||
|
||||
$blue: #4c82fc;
|
||||
$primary: $blue;
|
||||
$green: #007932; // hsl(141, 53%, 53%);
|
||||
$footer-background-color: $light;
|
||||
|
||||
$text: $grey-dark;
|
||||
$body-background-color: #FAFCFE;
|
||||
|
||||
@import 'bulma/bulma';
|
||||
|
||||
.card-height{
|
||||
min-height: 200px;
|
||||
.is-font-logo {
|
||||
font-family: 'Lobster', cursive;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
helper_method :current_user, :logged_in?
|
||||
|
||||
def require_login
|
||||
redirect_to login_url unless current_user.present?
|
||||
|
||||
8
app/controllers/file_uploads_controller.rb
Normal file
8
app/controllers/file_uploads_controller.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class FileUploadsController < ApplicationController
|
||||
def show
|
||||
@form = Form.find_by!(token: params[:form_id])
|
||||
@submission = @form.submissions.find(params[:submission_id])
|
||||
@file_upload = @submission.files_attachments.find(params[:id])
|
||||
redirect_to url_for(@file_upload)
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
require 'google/apis/sheets_v4'
|
||||
require 'google/api_client/client_secrets'
|
||||
class FormsController < ApplicationController
|
||||
before_action :require_login
|
||||
before_action :require_login, except: [:form]
|
||||
|
||||
def new
|
||||
@form = current_user.forms.build
|
||||
@@ -25,6 +25,10 @@ class FormsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def form
|
||||
@form = Form.find_by!(token: params[:id])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_params
|
||||
|
||||
@@ -5,14 +5,18 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
def create
|
||||
@form = Form.find_by!(token: params[:form_id])
|
||||
@submission = @form.submissions.build(data: data_params)
|
||||
# create a new submission object. we need a persisted submission to be able to process
|
||||
# potential the data - to be able to create URLs to uploads which is added as link to the table
|
||||
@submission = @form.submissions.create(remote_ip: request.remote_ip, referrer: request.referer)
|
||||
# processes the submitted data and saves the submission
|
||||
@submission.process_data(data_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @submission.save
|
||||
format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? }
|
||||
format.json { render(json: { success: true, data: @submission.data }) }
|
||||
else
|
||||
format.html
|
||||
format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? }
|
||||
format.json { render(json: { error: @submission.errors }, status: 422) }
|
||||
end
|
||||
end
|
||||
|
||||
17
app/javascript/burger_menu.js
Normal file
17
app/javascript/burger_menu.js
Normal file
@@ -0,0 +1,17 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $navbarBurgers = document.querySelectorAll('.navbar-burger');
|
||||
// Check if there are any navbar burgers
|
||||
if ($navbarBurgers.length > 0) {
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
// Get the target from the "data-target" attribute
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -8,10 +8,11 @@ require("turbolinks").start()
|
||||
require("@rails/activestorage").start()
|
||||
require("channels")
|
||||
|
||||
require('burger_menu');
|
||||
|
||||
// Uncomment to copy all static images under ../images to the output folder and reference
|
||||
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
|
||||
// or the `imagePath` JavaScript helper below.
|
||||
//
|
||||
// const images = require.context('../images', true)
|
||||
// const imagePath = (name) => images(name, true)
|
||||
// const imagePath = (name) => images(name, true)
|
||||
16
app/jobs/submission_append_job.rb
Normal file
16
app/jobs/submission_append_job.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
class SubmissionAppendJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
rescue_from(Signet::AuthorizationError, Google::Apis::AuthorizationError) do |exception|
|
||||
submission_id = self.arguments.first
|
||||
Rails.logger.error("AuthorizationError during SubmissionAppend: submission_id=#{submission_id}")
|
||||
submission = Submission.find(submission_id)
|
||||
submission.form.deactivate!('AuthorizationError')
|
||||
end
|
||||
|
||||
def perform(*args)
|
||||
submission_id = args.first
|
||||
submission = Submission.find(submission_id)
|
||||
submission.append_to_spreadsheet
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,9 @@
|
||||
class Authentication < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
encrypts :access_token
|
||||
encrypts :refresh_token
|
||||
|
||||
def expired?
|
||||
expires_at <= Time.current
|
||||
end
|
||||
|
||||
@@ -11,6 +11,14 @@ class Form < ApplicationRecord
|
||||
|
||||
validates_presence_of :title
|
||||
|
||||
def deactivate!(reason = nil)
|
||||
self.user.deactivate!(reason)
|
||||
end
|
||||
|
||||
def active?
|
||||
self.user.active?
|
||||
end
|
||||
|
||||
def google_spreadsheet_url
|
||||
"https://docs.google.com/spreadsheets/d/#{google_spreadsheet_id}/edit" if google_spreadsheet_id.present?
|
||||
end
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
class Submission < ApplicationRecord
|
||||
belongs_to :form
|
||||
after_create :append_to_spreadsheet
|
||||
validates_presence_of :data
|
||||
has_many_attached :files
|
||||
|
||||
def data=(value)
|
||||
sanitized_data = {}
|
||||
value.each do |key, value|
|
||||
sanitized_data[key] = submission_value(value)
|
||||
validates_presence_of :data, if: :appended_at?
|
||||
|
||||
def process_data(submitted_data)
|
||||
processed_data = {}
|
||||
submitted_data.each do |key, value|
|
||||
processed_data[key] = submission_value_for(value)
|
||||
end
|
||||
write_attribute(:data, sanitized_data)
|
||||
update_attribute(:data, processed_data)
|
||||
SubmissionAppendJob.perform_later(self.id)
|
||||
end
|
||||
|
||||
def submission_value(value)
|
||||
if value.to_s.downcase == 'tinyforms_now'
|
||||
def submission_value_for(value)
|
||||
case value
|
||||
when Array
|
||||
value.join(', ')
|
||||
when Hash
|
||||
JSON.dump(value)
|
||||
when 'tinyforms_now'
|
||||
Time.now.utc.to_formatted_s(:rfc822)
|
||||
when ActionDispatch::Http::UploadedFile
|
||||
# manually create the ActiveStorage attachment because we need the ID of the Attachment to create the URL
|
||||
# first the file needs to be uplaoded then we can create an Attachment
|
||||
# The CreateOne mainly handles the uplaod and the creation of the blob for us
|
||||
# `files` is the name from `has_many_attached :files`
|
||||
create_one = ActiveStorage::Attached::Changes::CreateOne.new('files', self, value)
|
||||
create_one.upload
|
||||
attachment = ActiveStorage::Attachment.new(record: self, name: 'files', blob: create_one.blob)
|
||||
attachment.save
|
||||
# return the URL that we use to show in the Spreadsheet
|
||||
Rails.application.routes.url_helpers.file_upload_url(form_id: form, submission_id: self, id: attachment.id, host: DEFAULT_HOST)
|
||||
else
|
||||
value.to_s
|
||||
end
|
||||
|
||||
@@ -8,7 +8,12 @@ class User < ApplicationRecord
|
||||
user_info = oauth.get_userinfo
|
||||
|
||||
if user = User.find_by(google_id: user_info.id)
|
||||
return user, user.authentications.last
|
||||
authentication = user.authentications.last
|
||||
authentication.access_token = auth_client.access_token if auth_client.access_token.present?
|
||||
authentication.refresh_token = auth_client.refresh_token if auth_client.refresh_token.present?
|
||||
authentication.expires_at = Time.at(auth_client.expires_at) if auth_client.expires_at.present?
|
||||
authentication.save
|
||||
return user, authentication
|
||||
else
|
||||
user = User.create(name: user_info.name, email: user_info.email, google_id: user_info.id)
|
||||
authentication = user.authentications.create(
|
||||
@@ -20,6 +25,15 @@ class User < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def deactivate!(reason = nil)
|
||||
# currently we only use deactivate if we get an authentication exception appending data to a spreadsheet
|
||||
authentications.last&.update(expires_at: Time.current)
|
||||
end
|
||||
|
||||
def active?
|
||||
authentications.last.present? && !authentications.last.expired?
|
||||
end
|
||||
|
||||
def google_authorization
|
||||
authentications.last.google_authorization
|
||||
end
|
||||
|
||||
12
app/views/forms/form.html.erb
Normal file
12
app/views/forms/form.html.erb
Normal file
@@ -0,0 +1,12 @@
|
||||
<h1><%= @form.title %></h1>
|
||||
|
||||
<%= form_with url: submission_url(@form), action: 'post', authenticity_token: false, local: true, html: { enctype: 'multipart/form-data' } do %>
|
||||
<% @form.header_values.each do |header| %>
|
||||
<p>
|
||||
<label><%= header %>
|
||||
<%= text_field_tag header %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= submit_tag 'Send', name: nil %>
|
||||
<% end %>
|
||||
@@ -1,3 +1,12 @@
|
||||
<h1>Welcome</h1>
|
||||
|
||||
<%= link_to "Login", login_url %>
|
||||
<section class="hero is-medium is-dark is-bold">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h1 class="title is-family-secondary has-text-weight-bold">
|
||||
Welcome
|
||||
</h1>
|
||||
<p class="subtitle is-size-6 has-text-white">
|
||||
Generate forms instantly
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<<<<<<< HEAD
|
||||
<head>
|
||||
<title>Tinyform</title>
|
||||
<%= csrf_meta_tags %>
|
||||
@@ -15,3 +16,64 @@
|
||||
<script src="https://kit.fontawesome.com/221ac0e2fd.js" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
=======
|
||||
|
||||
<head>
|
||||
<title>Tinyform</title>
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Wrapper -->
|
||||
<div id="wrapper" class="has-text-centered-mobile">
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="hero is-medium">
|
||||
<div class="hero-head">
|
||||
<div class="container">
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-size-4 has-text-black has-text-weight-bold is-font-logo" href="/">
|
||||
TinyForms
|
||||
</a>
|
||||
|
||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||
data-target="navbar">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbar" class="navbar-menu">
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<% if !logged_in? -%>
|
||||
<%= link_to "Login", login_url, { :class => "button is-primary"} %>
|
||||
<% else -%>
|
||||
<%= link_to "Logout", logout_url, { :class => "button is-light"} %>
|
||||
<% end -%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main>
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="https://kit.fontawesome.com/221ac0e2fd.js" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
>>>>>>> master
|
||||
|
||||
Reference in New Issue
Block a user