diff --git a/app/assets/images/background-photo.jpg b/app/assets/images/background-photo.jpg new file mode 100644 index 000000000..3f2eeb121 Binary files /dev/null and b/app/assets/images/background-photo.jpg differ diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 552356a22..c7b8814f5 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -7,15 +7,73 @@ $darker-background-color: #e3dede; $text-color: #333030; $lighter-text-color: #8b8687; -@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700,400italic"); +@import url(https://fonts.googleapis.com/css?family=Roboto:400,500,400italic); +@import url(https://fonts.googleapis.com/css?family=Roboto+Mono); @import "font-awesome-sprockets"; @import "font-awesome"; +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +body { + line-height: 1; +} + +ol, ul { + list-style: none; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + body { - font-family: 'Noto Sans', sans-serif; - background: $background-color image-url('background-pattern.png'); + font-family: 'Roboto', sans-serif; + background: $background-color image-url('background-photo.jpeg'); + background-size: cover; font-size: 13px; line-height: 18px; + font-weight: 400; color: $text-color; } @@ -25,19 +83,143 @@ body { margin-top: 40px; } -.footer { - text-align: center; - padding: 100px 0; - font-size: 12px; - color: $text-color; +.logo-container { + width: 400px; + margin: 100px auto; + cursor: default; + + h1 { + display: block; + text-align: center; + color: #fff; + font-size: 48px; + line-height: 48px; + font-weight: 500; + + small { + display: block; + font-size: 12px; + font-weight: 400; + font-family: 'Roboto Mono', monospace; + } + } +} + +.form-container { + width: 400px; + margin: 0 auto; + + .field { + margin-bottom: 15px; + } + + input[type=text], input[type=email], input[type=password] { + background: transparent; + border: 0; + border-bottom: 2px solid #9baec8; + padding: 7px 0; + font-size: 16px; + color: #fff; + display: block; + width: 100%; + outline: 0; + + &:invalid { + box-shadow: none; + } + + &:focus:invalid { + border-bottom-color: #df405a; + } + + &:required:valid { + border-bottom-color: #79bd9a; + } + + &:active, &:focus { + border-bottom-color: #2b90d9; + } + } + + .field_with_error { + input[type=text], input[type=email], input[type=password] { + border-bottom-color: #df405a; + } + } + + .actions { + margin-top: 30px; + + button { + display: block; + width: 100%; + border: 0; + border-radius: 4px; + background: #2b90d9; + color: #fff; + font-size: 18px; + padding: 10px; + text-transform: uppercase; + cursor: pointer; + font-weight: 500; + outline: 0; + + &:hover { + background-color: lighten(#2b90d9, 5%); + } - .mastodon-link { - color: $quaternary-color; - text-decoration: none; - font-weight: bold; + &:active, &:focus { + position: relative; + top: 1px; + background-color: darken(#2b90d9, 5%); + } + } + } + + .form-footer { + margin-top: 30px; + text-align: center; + + + a { + color: #9baec8; + text-decoration: none; + + &:hover { + color: #d9e1e8; + text-decoration: underline; + } + } + } + + #error_explanation { + background: #282c37; + color: #9baec8; + border-radius: 4px; + padding: 15px 10px; + margin-bottom: 30px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); + + h2 { + font-weight: 500; + margin-bottom: 5px; + } + + li { + margin-left: 15px; + list-style: circle; + } } } +.no-list { + list-style: none; + + li { + display: inline-block; + margin: 0 5px; + } +} @import 'home'; @import 'accounts'; diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb new file mode 100644 index 000000000..5c964bda0 --- /dev/null +++ b/app/controllers/auth/passwords_controller.rb @@ -0,0 +1,34 @@ +class Auth::PasswordsController < Devise::PasswordsController + layout 'auth' + + # GET /resource/password/new + # def new + # super + # end + + # POST /resource/password + # def create + # super + # end + + # GET /resource/password/edit?reset_password_token=abcdef + # def edit + # super + # end + + # PUT /resource/password + # def update + # super + # end + + # protected + + # def after_resetting_password_path_for(resource) + # super(resource) + # end + + # The path used after sending reset password instructions + # def after_sending_reset_password_instructions_path_for(resource_name) + # super(resource_name) + # end +end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb new file mode 100644 index 000000000..3114ae60a --- /dev/null +++ b/app/controllers/auth/registrations_controller.rb @@ -0,0 +1,22 @@ +class Auth::RegistrationsController < Devise::RegistrationsController + layout 'auth' + + before_filter :configure_sign_up_params, only: [:create] + + protected + + def build_resource(hash = nil) + super(hash) + self.resource.build_account if self.resource.account.nil? + end + + def configure_sign_up_params + devise_parameter_sanitizer.for(:sign_up) do |u| + u.permit(:email, :password, :password_confirmation, account_attributes: [:username]) + end + end + + def after_sign_up_path_for(resource) + account_path(resource.account) + end +end diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb new file mode 100644 index 000000000..d1bcc7c17 --- /dev/null +++ b/app/controllers/auth/sessions_controller.rb @@ -0,0 +1,27 @@ +class Auth::SessionsController < Devise::SessionsController + layout 'auth' + + # before_filter :configure_sign_in_params, only: [:create] + + # GET /resource/sign_in + # def new + # super + # end + + # POST /resource/sign_in + # def create + # super + # end + + # DELETE /resource/sign_out + # def destroy + # super + # end + + # protected + + # If you have extra params to permit, append them to the sanitizer. + # def configure_sign_in_params + # devise_parameter_sanitizer.for(:sign_in) << :attribute + # end +end diff --git a/app/models/account.rb b/app/models/account.rb index 6a4aa16b4..47e43f0ac 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -1,6 +1,7 @@ class Account < ActiveRecord::Base # Local users has_one :user, inverse_of: :account + validates :username, uniqueness: { scope: :domain } # Avatar upload attr_reader :avatar_remote_url diff --git a/app/models/user.rb b/app/models/user.rb index 999aa0d39..038ff21c6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,7 @@ class User < ActiveRecord::Base devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable belongs_to :account, inverse_of: :user + accepts_nested_attributes_for :account validates :account, presence: true end diff --git a/app/views/auth/mailer/password_change.html.erb b/app/views/auth/mailer/password_change.html.erb new file mode 100644 index 000000000..b41daf476 --- /dev/null +++ b/app/views/auth/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/auth/mailer/reset_password_instructions.html.erb b/app/views/auth/mailer/reset_password_instructions.html.erb new file mode 100644 index 000000000..f667dc12f --- /dev/null +++ b/app/views/auth/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/auth/passwords/edit.html.erb b/app/views/auth/passwords/edit.html.erb new file mode 100644 index 000000000..6a796b050 --- /dev/null +++ b/app/views/auth/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= devise_error_messages! %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "off" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "off" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/auth/passwords/new.html.haml b/app/views/auth/passwords/new.html.haml new file mode 100644 index 000000000..2677feea0 --- /dev/null +++ b/app/views/auth/passwords/new.html.haml @@ -0,0 +1,9 @@ += form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| + = devise_error_messages! + + .field + = f.email_field :email, autofocus: true, required: true, placeholder: 'E-mail address' + .actions + = f.button "Reset password", type: 'submit' + +.form-footer= render "auth/shared/links" diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml new file mode 100644 index 000000000..943230b34 --- /dev/null +++ b/app/views/auth/registrations/edit.html.haml @@ -0,0 +1,11 @@ += form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| + = devise_error_messages! + + .field + = f.password_field :password, autocomplete: "off", placeholder: 'New password' + .field + = f.password_field :password_confirmation, autocomplete: "off", placeholder: 'Confirm new password' + .field + = f.password_field :current_password, autocomplete: "off", placeholder: 'Current password' + .actions + = f.button "Save changes", type: 'submit' diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml new file mode 100644 index 000000000..c8532ec38 --- /dev/null +++ b/app/views/auth/registrations/new.html.haml @@ -0,0 +1,17 @@ += form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| + = devise_error_messages! + + = f.fields_for :account do |ff| + .field + = ff.text_field :username, autofocus: true, placeholder: 'Username', required: true + + .field + = f.email_field :email, placeholder: 'E-mail address', required: true + .field + = f.password_field :password, autocomplete: "off", placeholder: 'Password', required: true + .field + = f.password_field :password_confirmation, autocomplete: "off", placeholder: 'Confirm password', required: true + .actions + = f.button "Sign up", type: 'submit' + +.form-footer= render "auth/shared/links" diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml new file mode 100644 index 000000000..220d0ec79 --- /dev/null +++ b/app/views/auth/sessions/new.html.haml @@ -0,0 +1,9 @@ += form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| + .field + = f.email_field :email, autofocus: true, placeholder: 'E-mail address', required: true + .field + = f.password_field :password, autocomplete: "off", placeholder: 'Password', required: true + .actions + = f.button "Log in", type: 'submit' + +.form-footer= render "auth/shared/links" diff --git a/app/views/auth/shared/_links.html.haml b/app/views/auth/shared/_links.html.haml new file mode 100644 index 000000000..6f89eed75 --- /dev/null +++ b/app/views/auth/shared/_links.html.haml @@ -0,0 +1,19 @@ +%ul.no-list + - if controller_name != 'sessions' + %li= link_to "Log in", new_session_path(resource_name) + + - if devise_mapping.registerable? && controller_name != 'registrations' + %li= link_to "Sign up", new_registration_path(resource_name) + + - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' + %li= link_to "Forgot your password?", new_password_path(resource_name) + + - if devise_mapping.confirmable? && controller_name != 'confirmations' + %li= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) + + - if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' + %li= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) + + - if devise_mapping.omniauthable? + - resource_class.omniauth_providers.each do |provider| + %li= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 862374a98..11894d72c 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1 +1,3 @@ Mastodon + += link_to 'Logout', destroy_user_session_path, method: :delete diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index f15217203..25b3b5b49 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -9,7 +9,4 @@ = yield :header_tags %body .container - = yield - .footer - Powered by - = link_to 'Mastodon', 'https://github.com/Gargron/mastodon', class: 'mastodon-link' + = content_for?(:content) ? yield(:content) : yield diff --git a/app/views/layouts/auth.html.haml b/app/views/layouts/auth.html.haml new file mode 100644 index 000000000..dd0e7f166 --- /dev/null +++ b/app/views/layouts/auth.html.haml @@ -0,0 +1,10 @@ +- content_for :content do + .logo-container + %h1 + Mastodon + %small= Rails.configuration.x.local_domain + + .form-container + = yield + += render template: "layouts/application" diff --git a/config/routes.rb b/config/routes.rb index e74f8c368..8014a2fb2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,11 @@ Rails.application.routes.draw do get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger - devise_for :users, path: 'auth' + devise_for :users, path: 'auth', controllers: { + sessions: 'auth/sessions', + registrations: 'auth/registrations', + passwords: 'auth/passwords' + } resources :accounts, path: 'users', only: [:show], param: :username do resources :stream_entries, path: 'updates', only: [:show]