feste 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +155 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/feste/application.js +1 -0
- data/app/assets/javascripts/feste/subscription-verification.js +72 -0
- data/app/assets/stylesheets/feste/application.css +4 -0
- data/app/assets/stylesheets/feste/base.css +150 -0
- data/app/assets/stylesheets/feste/modal.css +40 -0
- data/app/assets/stylesheets/feste/subscriptions.css +40 -0
- data/app/controllers/concerns/feste/authenticatable.rb +41 -0
- data/app/controllers/feste/subscriptions_controller.rb +43 -0
- data/app/models/feste/subscription.rb +64 -0
- data/app/views/feste/subscriptions/index.html.erb +42 -0
- data/app/views/layouts/feste/application.html.erb +28 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config/routes.rb +5 -0
- data/feste.gemspec +38 -0
- data/lib/feste/authentication/authentication.rb +11 -0
- data/lib/feste/authentication/clearance.rb +16 -0
- data/lib/feste/authentication/custom.rb +11 -0
- data/lib/feste/authentication/devise.rb +11 -0
- data/lib/feste/engine.rb +18 -0
- data/lib/feste/mailer.rb +127 -0
- data/lib/feste/template_helper.rb +16 -0
- data/lib/feste/user.rb +23 -0
- data/lib/feste/version.rb +3 -0
- data/lib/feste.rb +51 -0
- data/lib/generators/feste/install_generator.rb +40 -0
- data/lib/generators/feste/templates/install.rb +11 -0
- metadata +261 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 780ca4e3f5539ccc209639ad6c74c73b13c273a5a7caf22b1775c36339606445
|
4
|
+
data.tar.gz: a4b6d5c3ffbc86aaf53d08c5ee767121a66b26d4959b1036b35d3c90ce8338f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8bb1dcf62ce31f4594b065cfd9ac5c8e4fc0df1fa7c83a345a2bf363432652469f84963f92b90fffe371ab429c760af824747889cf08601d45fade0b3b82f76f
|
7
|
+
data.tar.gz: 264e8a84f385e74f8f5cc0333546b6f2fa90b6bd30dab3ab72b0354ef8606124642bba0b314a73ccbdd7604d11adf0e999dd6190e4f8adaf6463fae811be0aa6
|
data/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/Gemfile.lock
|
4
|
+
/_yardoc/
|
5
|
+
/coverage/
|
6
|
+
/doc/
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/tmp/
|
10
|
+
.byebug_history
|
11
|
+
/*.gem
|
12
|
+
|
13
|
+
# rspec failure tracking
|
14
|
+
.rspec_status
|
15
|
+
|
16
|
+
# dummy app
|
17
|
+
/spec/dummy/.bundle
|
18
|
+
/spec/dummy/.byebug_history
|
19
|
+
/spec/dummy/.env
|
20
|
+
/spec/dummy/.yardoc
|
21
|
+
/spec/dummy/coverage/
|
22
|
+
/spec/dummy/doc/
|
23
|
+
/spec/dummy/pkg/
|
24
|
+
/spec/dummy/spec/reports/
|
25
|
+
/spec/dummy/tags
|
26
|
+
/spec/dummy/*.DS_STORE
|
27
|
+
/spec/dummy/tmp/*
|
28
|
+
/spec/dummy/log/*
|
29
|
+
!/spec/dummy/log/.keep
|
30
|
+
!/spec/dummy/tmp/.keep
|
31
|
+
/spec/dummy/node_modules
|
32
|
+
/spec/dummy/yarn-error.log
|
33
|
+
/spec/dummy/public/system
|
34
|
+
/spec/dummy/public/assets
|
35
|
+
/spec/dummy/public/uploads
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Josh Reinhardt
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
:email: Feste is an easy way to give your users the ability to manage email subscriptions in your Rails application.
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'feste'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle install
|
14
|
+
$ rails generate feste:install
|
15
|
+
$ rake db:migrate
|
16
|
+
|
17
|
+
Once installed, you will need to mount Feste in your application.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
# config/routes.rb
|
21
|
+
mount Feste::Engine => "/email-subscriptions", as: "feste"
|
22
|
+
```
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
|
26
|
+
Feste organizes subscribable emails by separating them into categories that you define (see <a href="#mailer">Mailer</a> for more details). This requires an array of available categories (represented by symbols) to be provided to the `categories` configuration.
|
27
|
+
|
28
|
+
Out of the box, Feste allows your users to manage their subscriptions from a url in their email, which includes an identifying token. If you would like for them to be able to do so from within your application, you will need to provide a method for identifying the currently logged in user. Luckily, Feste provides authentication adapters for applications that use Devise and Clearance to manage user sessions. Otherwise, you can provide a Proc to the `authenticate_with` option.
|
29
|
+
|
30
|
+
Optionally, you can set the attribute your user model(s) use(s) to reference a user's email address (`email_source`) and the host that is used by the `subscriptions_url` helper (`host`).
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# initializers/feste.rb
|
34
|
+
authentication_method = Proc.new do |controller|
|
35
|
+
::User.find_by(id: controller.session[:user_id])
|
36
|
+
end
|
37
|
+
|
38
|
+
Feste.configure do |config|
|
39
|
+
# set your category names
|
40
|
+
config.categories = [:marketing_emails, :reminder_emails]
|
41
|
+
# for applications that use clearance
|
42
|
+
config.authenticate_with = :clearance
|
43
|
+
# for applications that use devise
|
44
|
+
config.authenticate_with = :devise
|
45
|
+
# for applications that use custom authentication
|
46
|
+
config.authenticate_with = authentication_method
|
47
|
+
# set the email attribute of your user model
|
48
|
+
config.email_source = :email
|
49
|
+
# set the host for subscription_url
|
50
|
+
config.host = ActionMailer::Base.default_url_options[:host]
|
51
|
+
end
|
52
|
+
```
|
53
|
+
## Usage
|
54
|
+
|
55
|
+
### Model
|
56
|
+
|
57
|
+
In the model that holds your users' data, include `Feste::User`.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class User < ApplicationRecord
|
61
|
+
include Feste::User
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
This will give your user model a `has_many` relationship to `subscriptions`. Since this relationship is polymorphic, your can include the `Feste::User` module in multiple models.
|
66
|
+
|
67
|
+
### Mailer
|
68
|
+
|
69
|
+
In your mailer, include the `Feste::Mailer` module.
|
70
|
+
|
71
|
+
Feste keeps track of email subscriptions by grouping mailer actions into categories that you define. Your users will not be able to subscribe or unsubscribe to emails until you assign specific actions to a category. In order to do this, you can call the `categorize` method within your mailer. Doing so will automatically assign all actions in that mailer to the category you provide through the `as` option.
|
72
|
+
|
73
|
+
When calling the `mail` method within an action, make sure to explicitly state which user the subscription should be applied to using the `subscriber` option.
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class CouponMailer < ApplicationMailer
|
77
|
+
include Feste::Mailer
|
78
|
+
|
79
|
+
categorize as: :marketing_emails
|
80
|
+
|
81
|
+
def send_coupon(user)
|
82
|
+
mail(to: user.email, from: "support@here.com", subscriber: user)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
If you only want to categorize specific actions in a mailer, you can do so by listing those actions in an array as your first arguement.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class CouponMailer < ApplicationMailer
|
91
|
+
include Feste::Mailer
|
92
|
+
|
93
|
+
categorize [:send_coupon], as: :marketing_emails
|
94
|
+
categorize [:send_coupon_reminder], as: :reminder_emails
|
95
|
+
|
96
|
+
def send_coupon(user)
|
97
|
+
mail(to: user.email, from: "support@here.com", subscriber: user)
|
98
|
+
end
|
99
|
+
|
100
|
+
def send_coupon_reminder(user)
|
101
|
+
mail(to: user.email, from: "support@here.com", subscriber: user)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
### View
|
107
|
+
|
108
|
+
#### Mailer View
|
109
|
+
|
110
|
+
In our view file, you can use the helper method `subscription_url` to link to the page where users can manage their subscriptions.
|
111
|
+
|
112
|
+
```html
|
113
|
+
<a href="<%= subscription_url %>">click here to unsubscribe</a>
|
114
|
+
```
|
115
|
+
|
116
|
+
When a user clicks this link, they are taken to a page that allows them to choose which emails (by category) they would like to keep receiving, and which ones they would like to unsubscribe to.
|
117
|
+
|
118
|
+
#### Application View
|
119
|
+
|
120
|
+
The route to the subscriptions page is the root of the feste engine. You can link to this page from anywhere in your app using the `feste.subscriptions_url` helper (assuming the engine is mounted as 'feste'). When a logged in user visits this page from your application, they will be authenticated through the method which you provide in the configuration, and shown their email subscriptions.
|
121
|
+
|
122
|
+
### Human Readable Category Names
|
123
|
+
|
124
|
+
In order to create category names that are human readable, add a `feste.categories`section to your i18n `locales` files. Create keys in this section that correspond to the `categories` configuration.
|
125
|
+
|
126
|
+
```yml
|
127
|
+
# config/locales/en.yml
|
128
|
+
|
129
|
+
en:
|
130
|
+
feste:
|
131
|
+
categories:
|
132
|
+
marketing_emails: Marketing Emails
|
133
|
+
reminder_emails: Reminder Emails
|
134
|
+
```
|
135
|
+
|
136
|
+
### When not to use
|
137
|
+
|
138
|
+
It is recommended you DO NOT include any important emails, such as password reset emails, into a subscribable category. It is also recommended you do not include the subscription link in any email that is sent to multiple recipients. Though Feste comes with some security measures, it is assumed that each email is intended for only one recipient, and the `subscription_url` helper leads to a subsciption page meant only for that recipient. Exposing this page to other users may allow them to change subscription preferences for someone else's account.
|
139
|
+
|
140
|
+
## Development
|
141
|
+
|
142
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
143
|
+
|
144
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
145
|
+
|
146
|
+
## Contributing
|
147
|
+
|
148
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jereinhardt/feste.
|
149
|
+
|
150
|
+
|
151
|
+
## License
|
152
|
+
|
153
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
154
|
+
|
155
|
+
l create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require "bundler/setup"
|
3
|
+
rescue LoadError
|
4
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
9
|
+
load 'rails/tasks/engine.rake'
|
10
|
+
|
11
|
+
Bundler::GemHelper.install_tasks
|
12
|
+
|
13
|
+
require "bundler/gem_tasks"
|
14
|
+
require "rspec/core"
|
15
|
+
require "rspec/core/rake_task"
|
16
|
+
require "pg"
|
17
|
+
|
18
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
19
|
+
RSpec::Core::RakeTask.new(spec: "app:db:test:prepare")
|
20
|
+
|
21
|
+
task :default => :spec
|
@@ -0,0 +1 @@
|
|
1
|
+
//= require_tree .
|
@@ -0,0 +1,72 @@
|
|
1
|
+
subscriptionVerification = (function () {
|
2
|
+
return function () {
|
3
|
+
var $form = document.getElementById("edit-subscriptions-form");
|
4
|
+
var $modal = document.getElementById("confirmation-modal");
|
5
|
+
|
6
|
+
$form.addEventListener("submit", handleMainFormSubmission);
|
7
|
+
|
8
|
+
function handleMainFormSubmission(event) {
|
9
|
+
event.preventDefault();
|
10
|
+
openModal($modal);
|
11
|
+
}
|
12
|
+
|
13
|
+
function openModal($modalContainer) {
|
14
|
+
$modalContainer.classList.remove("hidden");
|
15
|
+
bindModalEvents($modalContainer);
|
16
|
+
}
|
17
|
+
|
18
|
+
function closeModal($modalContainer) {
|
19
|
+
$modalContainer.className += " hidden";
|
20
|
+
unbindModalEvents($modalContainer);
|
21
|
+
}
|
22
|
+
|
23
|
+
function bindModalEvents($modalContainer) {
|
24
|
+
var $closeButtons = $modalContainer.querySelectorAll("[data-js-modal-close]");
|
25
|
+
for (var i = 0; i < $closeButtons.length; i ++) {
|
26
|
+
var $button = $closeButtons[i];
|
27
|
+
$button.addEventListener("click", function(event) {
|
28
|
+
if ( event.target == event.currentTarget ) {
|
29
|
+
event.preventDefault();
|
30
|
+
closeModal($modalContainer);
|
31
|
+
}
|
32
|
+
});
|
33
|
+
}
|
34
|
+
|
35
|
+
var $confirmForm = document.getElementById("cancel-subscription-form");
|
36
|
+
$confirmForm.addEventListener("submit", handleConfirmFormSubmission);
|
37
|
+
}
|
38
|
+
|
39
|
+
function unbindModalEvents($modalContainer) {
|
40
|
+
var $closeButtons = $modalContainer.querySelectorAll("[data-js-modal-close]");
|
41
|
+
for (var i = 0; i < $closeButtons.length; i ++) {
|
42
|
+
var $button = $closeButtons[i];
|
43
|
+
$button.removeEventListener("click", function(event) {
|
44
|
+
if ( event.target == event.currentTarget ) {
|
45
|
+
event.preventDefault();
|
46
|
+
closeModal($modalContainer);
|
47
|
+
}
|
48
|
+
});
|
49
|
+
}
|
50
|
+
|
51
|
+
var $confirmForm = document.getElementById("cancel-subscription-form");
|
52
|
+
$confirmForm.removeEventListener("submit", handleConfirmFormSubmission);
|
53
|
+
}
|
54
|
+
|
55
|
+
function handleConfirmFormSubmission(event) {
|
56
|
+
event.preventDefault();
|
57
|
+
var $form = event.target;
|
58
|
+
var $input = document.getElementById("email-confirmation-input");
|
59
|
+
var $required = $form.querySelectorAll("[data-js-required]")[0];
|
60
|
+
var $errorMessage = $required.querySelectorAll("[data-js-error]")[0];
|
61
|
+
var val = $required.querySelectorAll("input")[0].value;
|
62
|
+
var email = document.getElementById("subscriber-email").value
|
63
|
+
$errorMessage.className += " hidden";
|
64
|
+
if (!val || val != email) {
|
65
|
+
$errorMessage.classList.remove("hidden");
|
66
|
+
} else {
|
67
|
+
var $mainForm = document.getElementById("edit-subscriptions-form");
|
68
|
+
$mainForm.submit();
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
})();
|
@@ -0,0 +1,150 @@
|
|
1
|
+
body {
|
2
|
+
background: transparent;
|
3
|
+
color: #4f4d4b;
|
4
|
+
font-family: "Whitney A", "Whitney B", "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif;
|
5
|
+
margin: 0;
|
6
|
+
}
|
7
|
+
|
8
|
+
.container {
|
9
|
+
padding: 0 1.25rem;
|
10
|
+
}
|
11
|
+
|
12
|
+
.main-body {
|
13
|
+
max-width: 750px;
|
14
|
+
}
|
15
|
+
|
16
|
+
.hidden {
|
17
|
+
display: none;
|
18
|
+
}
|
19
|
+
|
20
|
+
.flash-message {
|
21
|
+
border-radius: 30px;
|
22
|
+
color: white;
|
23
|
+
font-size: 1rem;
|
24
|
+
font-weight: 300;
|
25
|
+
margin-bottom: 3em;
|
26
|
+
padding: 15px;
|
27
|
+
text-align: center;
|
28
|
+
}
|
29
|
+
|
30
|
+
.flash-success {
|
31
|
+
background-color: #e6efc2;
|
32
|
+
color: #56651a;
|
33
|
+
display: block;
|
34
|
+
font-weight: 600;
|
35
|
+
margin-bottom: 0.625rem;
|
36
|
+
padding: 0.625rem;
|
37
|
+
text-align: center;
|
38
|
+
}
|
39
|
+
|
40
|
+
.flash-notice {
|
41
|
+
background-color: #e5edf8;
|
42
|
+
color: #244e87;
|
43
|
+
display: block;
|
44
|
+
font-weight: 600;
|
45
|
+
margin-bottom: 0.625rem;
|
46
|
+
padding: 0.625rem;
|
47
|
+
text-align: center;
|
48
|
+
}
|
49
|
+
|
50
|
+
.button {
|
51
|
+
-webkit-appearance: none;
|
52
|
+
-moz-appearance: none;
|
53
|
+
-ms-appearance: none;
|
54
|
+
-o-appearance: none;
|
55
|
+
appearance: none;
|
56
|
+
-webkit-font-smoothing: antialiased;
|
57
|
+
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #ffffff), to(#f7f7f7));
|
58
|
+
background-image: linear-gradient(#ffffff 20%, #f7f7f7);
|
59
|
+
border-radius: 2px;
|
60
|
+
border: 1px solid #e4e4e3;
|
61
|
+
border-top-color: rgb(228, 228, 227);
|
62
|
+
border-top-style: solid;
|
63
|
+
border-top-width: 1px;
|
64
|
+
border-right-color: rgb(228, 228, 227);
|
65
|
+
border-right-style: solid;
|
66
|
+
border-right-width: 1px;
|
67
|
+
border-bottom-color: rgb(228, 228, 227);
|
68
|
+
border-bottom-style: solid;
|
69
|
+
border-bottom-width: 1px;
|
70
|
+
border-left-color: rgb(228, 228, 227);
|
71
|
+
border-left-style: solid;
|
72
|
+
border-left-width: 1px;
|
73
|
+
border-image-source: initial;
|
74
|
+
border-image-slice: initial;
|
75
|
+
border-image-width: initial;
|
76
|
+
border-image-outset: initial;
|
77
|
+
border-image-repeat: initial;
|
78
|
+
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
79
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
80
|
+
color: #4383c5;
|
81
|
+
cursor: pointer;
|
82
|
+
display: inline-block;
|
83
|
+
font-family: "Whitney A", "Whitney B", "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif;
|
84
|
+
font-size: 0.875rem;
|
85
|
+
font-weight: 600;
|
86
|
+
line-height: 2.3;
|
87
|
+
padding: 0 1.25rem;
|
88
|
+
text-align: center;
|
89
|
+
text-decoration: none;
|
90
|
+
text-decoration-line: none;
|
91
|
+
text-decoration-style: initial;
|
92
|
+
text-decoration-color: initial;
|
93
|
+
-webkit-transition: all 0.2s cubic-bezier(0.39, 0.575, 0.565, 1);
|
94
|
+
transition: all 0.2s cubic-bezier(0.39, 0.575, 0.565, 1);
|
95
|
+
-webkit-user-select: none;
|
96
|
+
-moz-user-select: none;
|
97
|
+
-ms-user-select: none;
|
98
|
+
user-select: none;
|
99
|
+
vertical-align: middle;
|
100
|
+
white-space: nowrap;
|
101
|
+
}
|
102
|
+
|
103
|
+
.button.button--submit {
|
104
|
+
background: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #afca7d), to(#99bb59)), #99bb59;
|
105
|
+
background-image: linear-gradient(rgb(175, 202, 125) 20%, rgb(153, 187, 89)), initial;
|
106
|
+
background-position-x: initial, initial;
|
107
|
+
background-position-y: initial, initial;
|
108
|
+
background-size: initial, initial;
|
109
|
+
background-repeat-x: initial, initial;
|
110
|
+
background-repeat-y: initial, initial;
|
111
|
+
background-attachment: initial, initial;
|
112
|
+
background-origin: initial, initial;
|
113
|
+
background-clip: initial, initial;
|
114
|
+
background-color: rgb(153, 187, 89);
|
115
|
+
background: linear-gradient(#afca7d 20%, #99bb59), #99bb59;
|
116
|
+
background-image: linear-gradient(rgb(175, 202, 125) 20%, rgb(153, 187, 89)), initial;
|
117
|
+
background-position-x: initial, initial;
|
118
|
+
background-position-y: initial, initial;
|
119
|
+
background-size: initial, initial;
|
120
|
+
background-repeat-x: initial, initial;
|
121
|
+
background-repeat-y: initial, initial;
|
122
|
+
background-attachment: initial, initial;
|
123
|
+
background-origin: initial, initial;
|
124
|
+
background-clip: initial, initial;
|
125
|
+
background-color: rgb(153, 187, 89);
|
126
|
+
border: 1px solid #748e42;
|
127
|
+
border-top-color: rgb(116, 142, 66);
|
128
|
+
border-top-style: solid;
|
129
|
+
border-top-width: 1px;
|
130
|
+
border-right-color: rgb(116, 142, 66);
|
131
|
+
border-right-style: solid;
|
132
|
+
border-right-width: 1px;
|
133
|
+
border-bottom-color: rgb(116, 142, 66);
|
134
|
+
border-bottom-style: solid;
|
135
|
+
border-bottom-width: 1px;
|
136
|
+
border-left-color: rgb(116, 142, 66);
|
137
|
+
border-left-style: solid;
|
138
|
+
border-left-width: 1px;
|
139
|
+
border-image-source: initial;
|
140
|
+
border-image-slice: initial;
|
141
|
+
border-image-width: initial;
|
142
|
+
border-image-outset: initial;
|
143
|
+
border-image-repeat: initial;
|
144
|
+
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
|
145
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
|
146
|
+
color: #ffffff;
|
147
|
+
display: inline-block;
|
148
|
+
font-size: 1rem;
|
149
|
+
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
|
150
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
.modal-overlay {
|
2
|
+
background-color: #ffffff;
|
3
|
+
height: 100%;
|
4
|
+
width: 100%;
|
5
|
+
}
|
6
|
+
|
7
|
+
.confirmation-modal-container {
|
8
|
+
height: 100%;
|
9
|
+
left: 0;
|
10
|
+
position: fixed;
|
11
|
+
top: 0;
|
12
|
+
width: 100%;
|
13
|
+
}
|
14
|
+
|
15
|
+
.modal-content {
|
16
|
+
background-color: #ffffff;
|
17
|
+
border: 1px solid #cbcbc9;
|
18
|
+
border-radius: 2px;
|
19
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.07);
|
20
|
+
box-sizing: border-box;
|
21
|
+
max-width: 500px;
|
22
|
+
margin: 0 auto;
|
23
|
+
padding: 1.875rem;
|
24
|
+
text-align: center;
|
25
|
+
position: relative;
|
26
|
+
top: 50px;
|
27
|
+
width: 100%;
|
28
|
+
-webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.07);
|
29
|
+
}
|
30
|
+
|
31
|
+
.modal-header {
|
32
|
+
font-size: 1.25rem;
|
33
|
+
margin: 0;
|
34
|
+
}
|
35
|
+
|
36
|
+
.modal-body {
|
37
|
+
color: #4f4d4b;
|
38
|
+
font-size: 1rem;
|
39
|
+
font-weight: 400;
|
40
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
input {
|
2
|
+
font-size: 1rem;
|
3
|
+
}
|
4
|
+
|
5
|
+
.subscription-category-container {
|
6
|
+
padding: 0.325rem 0;
|
7
|
+
width: 100%;
|
8
|
+
}
|
9
|
+
|
10
|
+
.subscription-submit-button {
|
11
|
+
margin-top: 1.25rem;
|
12
|
+
}
|
13
|
+
|
14
|
+
.error-label {
|
15
|
+
color: #aa1111;
|
16
|
+
font-weight: 600;
|
17
|
+
}
|
18
|
+
|
19
|
+
.form-submit-button {
|
20
|
+
background: #aa1111;
|
21
|
+
border: 0;
|
22
|
+
border-radius: 30px;
|
23
|
+
color: white;
|
24
|
+
font-size: 1.2em;
|
25
|
+
font-weight: 600;
|
26
|
+
padding: 15px;
|
27
|
+
}
|
28
|
+
|
29
|
+
.email-field-container {
|
30
|
+
margin-bottom: 1.2em;
|
31
|
+
}
|
32
|
+
|
33
|
+
.email-field-input {
|
34
|
+
box-sizing: border-box;
|
35
|
+
width: 100%;
|
36
|
+
padding: 5px;
|
37
|
+
font-size: 1em;
|
38
|
+
text-align: center;
|
39
|
+
font-weight: 300;
|
40
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Feste
|
2
|
+
module Authenticatable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
if Feste.options[:authenticate_with] == :clearance
|
7
|
+
include Feste::Authentication::Clearance
|
8
|
+
elsif Feste.options[:authenticate_with] == :devise
|
9
|
+
include Feste::Authentication::Devise
|
10
|
+
elsif Feste.options[:authenticate_with].is_a?(Proc)
|
11
|
+
include Feste::Authentication::Custom
|
12
|
+
else
|
13
|
+
include Feste::Authentication::DefaultInstanceMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
before_action :get_user_data
|
17
|
+
end
|
18
|
+
|
19
|
+
def subscriber
|
20
|
+
@_subscriber ||= Feste::Subscription.find_by(token: params[:token])&.subscriber ||
|
21
|
+
current_user
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_user_data
|
25
|
+
if subscriber.present?
|
26
|
+
find_or_create_subscriptions
|
27
|
+
else
|
28
|
+
render file: "#{Rails.root}/public/404.html", status: 404
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_or_create_subscriptions
|
33
|
+
Feste.options[:categories].each do |category|
|
34
|
+
Feste::Subscription.find_or_create_by(
|
35
|
+
subscriber: subscriber,
|
36
|
+
category: category
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Feste
|
2
|
+
class SubscriptionsController < ActionController::Base
|
3
|
+
include Feste::Authenticatable
|
4
|
+
|
5
|
+
protect_from_forgery with: :exception
|
6
|
+
|
7
|
+
layout "feste/application"
|
8
|
+
|
9
|
+
def index
|
10
|
+
@subscriber = subscriber
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
if update_subscriptions
|
15
|
+
flash[:success] = "You have successfully updated your subscriptions!"
|
16
|
+
else
|
17
|
+
flash[:notice] = "Something went wrong! Please try again later."
|
18
|
+
end
|
19
|
+
redirect_back(
|
20
|
+
fallback_location: subscriptions_path(token: params[:token])
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def user_params
|
27
|
+
params.require(subscriber.class.to_s.downcase.to_sym).
|
28
|
+
permit(subscriptions: [])
|
29
|
+
end
|
30
|
+
|
31
|
+
def subscriptions_params
|
32
|
+
user_params[:subscriptions]
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_subscriptions
|
36
|
+
subscribed = subscriber.subscriptions.where(id: subscriptions_params)
|
37
|
+
unsubscribed = subscriber.subscriptions.where.
|
38
|
+
not(id: subscriptions_params)
|
39
|
+
subscribed.update_all(canceled: false) &&
|
40
|
+
unsubscribed.update_all(canceled: true)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|