use devise instead auth0

This commit is contained in:
João Geonizeli
2021-08-09 22:07:48 -03:00
parent 15abf28e80
commit 645b818f6c
48 changed files with 519 additions and 399 deletions

View File

@@ -1,4 +1,14 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base
include Pundit
before_action :configure_devise_permitted_parameters, if: :devise_controller?
protected
def configure_devise_permitted_parameters
attributes = [:first_name, :last_name]
devise_parameter_sanitizer.permit(:sign_up, keys: attributes)
devise_parameter_sanitizer.permit(:account_update, keys: attributes)
end
end

View File

@@ -1,12 +0,0 @@
# frozen_string_literal: true
module Authenticable
def current_auth
@current_auth ||= Auth::Authenticate.new(bearer_token).profile
end
def bearer_token
pattern = /^Bearer /
header = request.headers["Authorization"]
header.gsub(pattern, "") if header&.match(pattern)
end
end

View File

@@ -1,16 +1,11 @@
# frozen_string_literal: true
class GraphqlController < ApplicationController
include Authenticable
protect_from_forgery with: :null_session
def execute
variables = prepare_variables(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
current_auth: current_auth,
current_user: current_auth&.user,
current_user: current_user,
}
result = XStakeSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render(json: result)

View File

@@ -12,5 +12,6 @@
// Turbolinks.start()
// ActiveStorage.start()
import "regenerator-runtime";
import "stylesheets/application";
import "regenerator-runtime";
import "../src/index";

View File

@@ -1 +0,0 @@
import "../src/index";

View File

@@ -1,15 +1,21 @@
import type { Variables, RequestParameters, CacheConfig } from "relay-runtime";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
async function fetchRelay(
export const fetchRelay = async (
params: RequestParameters,
variables: Variables,
_cacheConfig: CacheConfig
) {
) => {
const csrfToken =
document
.querySelector('meta[name="csrf-token"]')
?.getAttribute("content") ?? "";
const response = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken,
},
body: JSON.stringify({
query: params.text,
@@ -30,7 +36,7 @@ async function fetchRelay(
}
return json;
}
};
export const environment = new Environment({
network: Network.create(fetchRelay),

View File

@@ -1,33 +1,37 @@
import { graphql } from "babel-plugin-relay/macro";
import type { FC } from "react";
import React, { Suspense } from "react";
import { RelayEnvironmentProvider } from "react-relay";
import { BrowserRouter as Router } from "react-router-dom";
import React from "react";
import { useLazyLoadQuery } from "react-relay";
import { environment } from "../relay/environment";
import { Navbar } from "./components/Navbar";
import { SideNav } from "./components/SideNav";
import { AppContext } from "./contexts/AppContext";
import { AuthProvider } from "./contexts/AuthProvider";
import { Navbar, SideNav } from "./components";
import { AppProvider } from "./contexts/AppProvider";
import { UserProvider } from "./contexts/UserProvider";
import { Routes } from "./Routes";
import type { AppQuery } from "./__generated__/AppQuery.graphql";
export const App: FC = () => {
const { currentUser } = useLazyLoadQuery<AppQuery>(
graphql`
query AppQuery {
currentUser {
firstName
}
}
`,
{}
);
return (
<RelayEnvironmentProvider environment={environment}>
<Suspense fallback="Carregando...">
<Router>
<AuthProvider>
<AppContext>
<main className="min-h-screen w-full bg-gray-50 flex flex-col">
<Navbar />
<div className="flex flex-grow">
<SideNav />
<Routes />
</div>
</main>
</AppContext>
</AuthProvider>
</Router>
</Suspense>
</RelayEnvironmentProvider>
<AppProvider>
<UserProvider user={currentUser}>
<main className="min-h-screen w-full bg-gray-50 flex flex-col">
<Navbar />
<div className="flex flex-grow">
<SideNav />
<Routes />
</div>
</main>
</UserProvider>
</AppProvider>
);
};

View File

@@ -0,0 +1,97 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from "relay-runtime";
export type AppQueryVariables = {};
export type AppQueryResponse = {
readonly currentUser: {
readonly firstName: string;
} | null;
};
export type AppQuery = {
readonly response: AppQueryResponse;
readonly variables: AppQueryVariables;
};
/*
query AppQuery {
currentUser {
firstName
id
}
}
*/
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "firstName",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "AppQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "currentUser",
"plural": false,
"selections": [
(v0/*: any*/)
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "AppQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "currentUser",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "ecf2bf1a08ead5c3e2edb202242ed8cf",
"id": null,
"metadata": {},
"name": "AppQuery",
"operationKind": "query",
"text": "query AppQuery {\n currentUser {\n firstName\n id\n }\n}\n"
}
};
})();
(node as any).hash = 'ea65c9caf86d93b8ddded2ae3eaa7b0e';
export default node;

View File

@@ -1,63 +0,0 @@
import * as React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import XStakeLogo from "../images/logo.png";
import { useAppContext } from "../contexts/AppContext";
export const Navbar = () => {
const { setSideNavExpanded } = useAppContext();
const handleExpandSideNav = () => {
setSideNavExpanded((prevState) => !prevState);
};
const { loginWithRedirect, logout, isAuthenticated } = useAuth0();
return (
<nav className="fixed w-full h-16 flex bg-white shadow items-center px-4 space-x-2 z-50">
<button
className="w-10 h-10 xl:hidden"
onClick={() => handleExpandSideNav()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-full w-full"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
<img
src={XStakeLogo}
alt="XStake Logo"
width={64}
placeholder="blurred"
/>
<h1 className="text-2xl font-bold">XStake</h1>
<div className="w-full h-full flex items-center justify-end">
{isAuthenticated ? (
<button
className="cursor-pointer hover:bg-gray-100 h-full px-4 font-bold"
onClick={() => logout({ returnTo: window.location.origin })}
>
Sair
</button>
) : (
<button
className="cursor-pointer hover:bg-gray-100 h-full px-4 font-bold"
onClick={loginWithRedirect}
>
Entrar
</button>
)}
</div>
</nav>
);
};

View File

@@ -0,0 +1,75 @@
import * as React from "react";
import XStakeLogo from "../../images/logo.png";
import { useApp } from "../../contexts/AppProvider";
import { useCurrentUser } from "../../contexts/UserProvider";
const linkStyles =
"cursor-pointer hover:bg-gray-100 h-full px-4 font-bold flex items-center";
export const Navbar = () => {
const { setSideNavExpanded } = useApp();
const handleExpandSideNav = () => {
setSideNavExpanded((prevState) => !prevState);
};
const { isAuthenticated } = useCurrentUser();
const csrfToken =
document
.querySelector('meta[name="csrf-token"]')
?.getAttribute("content") ?? "";
return (
<nav className="fixed w-full h-16 flex bg-white shadow items-center px-4 space-x-2 z-50">
<button
className="w-10 h-10 xl:hidden"
onClick={() => handleExpandSideNav()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-full w-full"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
<img
src={XStakeLogo}
alt="XStake Logo"
width={64}
placeholder="blurred"
/>
<h1 className="text-2xl font-bold">XStake</h1>
<div className="w-full h-full flex items-center justify-end">
{isAuthenticated ? (
<form className="h-full" method="post" action="/users/sign_out">
<input className={linkStyles} type="submit" value="Sair" />
<input
type="hidden"
name="authenticity_token"
defaultValue={csrfToken}
/>
<input type="hidden" name="_method" value="delete" />
</form>
) : (
<form className="h-full" method="post" action="/users/sign_in">
<input className={linkStyles} type="submit" value="Entrar" />
<input
type="hidden"
name="authenticity_token"
defaultValue={csrfToken}
/>
</form>
)}
</div>
</nav>
);
};

View File

@@ -0,0 +1 @@
export * from "./Navbar";

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import cx from "classnames";
import { Link } from "react-router-dom";
import { useAppContext } from "../contexts/AppContext";
import { useApp } from "../contexts/AppProvider";
type MenuItem = {
label: string;
@@ -25,7 +25,7 @@ const MenuItems: MenuItem[] = [
];
export const SideNav = () => {
const { sideNavExpanded, setSideNavExpanded } = useAppContext();
const { sideNavExpanded, setSideNavExpanded } = useApp();
const handleCloseSideNav = () => {
setSideNavExpanded(false);

View File

@@ -0,0 +1,2 @@
export * from "./Navbar";
export * from "./SideNav";

View File

@@ -8,7 +8,7 @@ export type AppContext = {
const Context = createContext<AppContext | null>(null);
export const useAppContext = (): AppContext => {
export const useApp = (): AppContext => {
const context = useContext(Context);
if (context === null) {
@@ -18,7 +18,7 @@ export const useAppContext = (): AppContext => {
return context;
};
export const AppContext: FC = ({ children }) => {
export const AppProvider: FC = ({ children }) => {
const [sideNavExpanded, setSideNavExpanded] = useState(false);
return (

View File

@@ -1,24 +0,0 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Auth0Provider } from "@auth0/auth0-react";
import type { FC } from "react";
import React from "react";
export const AuthProvider: FC = ({ children }) => {
// @ts-ignore
const domain = window.AUTH_DOMAIN;
// @ts-ignore
const clientId = window.AUTH_CLIENT_ID;
// @ts-ignore
const audience = window.AUTH_AUDIENCE;
return (
<Auth0Provider
domain={domain}
clientId={clientId}
audience={audience}
redirectUri={window.location.origin}
>
{children}
</Auth0Provider>
);
};

View File

@@ -0,0 +1,32 @@
import type { FC } from "react";
import React, { createContext, useContext } from "react";
type CurrentUserContext = {
user: {
readonly firstName: string;
} | null;
isAuthenticated: boolean;
};
const Context = createContext<CurrentUserContext>({
user: null,
isAuthenticated: false,
});
export const useCurrentUser = (): CurrentUserContext => {
const context = useContext(Context);
return context;
};
type Props = {
user: {
readonly firstName: string;
} | null;
};
export const UserProvider: FC<Props> = ({ user, children }) => (
<Context.Provider value={{ user, isAuthenticated: !!user }}>
{children}
</Context.Provider>
);

View File

@@ -1,12 +1,21 @@
import React from "react";
import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import { RelayEnvironmentProvider } from "react-relay";
import { BrowserRouter as Router } from "react-router-dom";
import { environment } from "../relay/environment";
import { App } from "./App";
document.addEventListener("DOMContentLoaded", () => {
ReactDOM.render(
<React.StrictMode>
<App />
<RelayEnvironmentProvider environment={environment}>
<Suspense fallback="Carregando...">
<Router>
<App />
</Router>
</Suspense>
</RelayEnvironmentProvider>
</React.StrictMode>,
document.getElementById("root")
);

View File

@@ -19,8 +19,6 @@
# index_admin_users_on_reset_password_token (reset_password_token) UNIQUE
#
class AdminUser < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :recoverable,
:rememberable, :validatable
end

View File

@@ -4,18 +4,26 @@
#
# Table name: users
#
# id :bigint not null, primary key
# email :string not null
# first_name :string not null
# last_name :string not null
# created_at :datetime not null
# updated_at :datetime not null
# id :bigint not null, primary key
# email :string default(""), not null
# encrypted_password :string default(""), not null
# first_name :string not null
# last_name :string not null
# remember_created_at :datetime
# reset_password_sent_at :datetime
# reset_password_token :string
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_users_on_email (email) UNIQUE
# index_users_on_email (email) UNIQUE
# index_users_on_reset_password_token (reset_password_token) UNIQUE
#
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :documents, class_name: "UserDocument", dependent: :destroy
validates :first_name, :last_name, :email, presence: true

View File

@@ -1,20 +0,0 @@
# frozen_string_literal: true
module Auth
class Auth0Client
class << self
def find_profile(token)
Profile.new(user_profile_attributes(token))
end
def user_profile_attributes(token)
HTTParty.get(
"https://#{ENV["AUTH_DOMAIN"]}/userinfo",
headers: {
"Content-Type" => "application/json",
"Authorization": "Bearer #{token}",
}
).with_indifferent_access
end
end
end
end

View File

@@ -1,16 +0,0 @@
# frozen_string_literal: true
module Auth
class Authenticate
attr_reader :jwt_token
def initialize(jwt_token)
@jwt_token = jwt_token
end
def profile
return nil if jwt_token.blank?
Auth0Client.find_profile(jwt_token)
end
end
end

View File

@@ -1,15 +0,0 @@
# frozen_string_literal: true
module Auth
class Profile
attr_reader :id, :email
def initialize(attributes)
@id = attributes[:sub]
@email = attributes[:email]
end
def user
@user ||= User.find_by(email: email)
end
end
end

View File

@@ -1,16 +1,16 @@
<h1><%= t('.resend_confirmation_instructions') %></h1>
<h2><%= t('.resend_confirmation_instructions') %></h2>
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= bootstrap_devise_error_messages! %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), class: 'form-control' %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
</div>
<div class="form-group">
<%= f.submit t('.resend_confirmation_instructions'), class: 'btn btn-primary' %>
<div class="actions">
<%= f.submit t('.resend_confirmation_instructions') %>
</div>
<% end %>
<%= render 'devise/shared/links' %>
<%= render "devise/shared/links" %>

View File

@@ -0,0 +1,5 @@
<p><%= t('.greeting', recipient: @email) %></p>
<p><%= t('.instruction') %></p>
<p><%= link_to t('.action'), confirmation_url(@resource, confirmation_token: @token) %></p>

View File

@@ -0,0 +1,7 @@
<p><%= t('.greeting', recipient: @email) %></p>
<% if @resource.try(:unconfirmed_email?) %>
<p><%= t('.message_unconfirmed', email: @resource.unconfirmed_email) %></p>
<% else %>
<p><%= t('.message', email: @resource.email) %></p>
<% end %>

View File

@@ -0,0 +1,3 @@
<p><%= t('.greeting', recipient: @resource.email) %></p>
<p><%= t('.message') %></p>

View File

@@ -0,0 +1,8 @@
<p><%= t('.greeting', recipient: @resource.email) %></p>
<p><%= t('.instruction') %></p>
<p><%= link_to t('.action'), edit_password_url(@resource, reset_password_token: @token) %></p>
<p><%= t('.instruction_2') %></p>
<p><%= t('.instruction_3') %></p>

View File

@@ -0,0 +1,7 @@
<p><%= t('.greeting', recipient: @resource.email) %></p>
<p><%= t('.message') %></p>
<p><%= t('.instruction') %></p>
<p><%= link_to t('.action'), unlock_url(@resource, unlock_token: @token) %></p>

View File

@@ -1,26 +1,25 @@
<h1><%= t('.change_your_password') %></h1>
<h2><%= t('.change_your_password') %></h2>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<%= bootstrap_devise_error_messages! %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>
<div class="form-group">
<%= f.label :password, t('.new_password') %>
<%= f.password_field :password, autofocus: true, class: 'form-control' %>
<div class="field">
<%= f.label :password, t('.new_password') %><br />
<% if @minimum_password_length %>
<small class="form-text text-muted"><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></small>
<em><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></em><br />
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
</div>
<div class="form-group">
<%= f.label :password_confirmation, t('.confirm_new_password') %>
<%= f.password_field :password_confirmation, autocomplete: 'off', class: 'form-control' %>
<div class="field">
<%= f.label :password_confirmation, t('.confirm_new_password') %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="form-group">
<%= f.submit t('.change_my_password'), class: 'btn btn-primary' %>
<div class="actions">
<%= f.submit t('.change_my_password') %>
</div>
<% end %>
<%= render 'devise/shared/links' %>
<%= render "devise/shared/links" %>

View File

@@ -1,16 +1,16 @@
<h1><%= t('.forgot_your_password') %></h1>
<h2><%= t('.forgot_your_password') %></h2>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
<%= bootstrap_devise_error_messages! %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="form-group">
<%= f.submit t('.send_me_reset_password_instructions'), class: 'btn btn-primary' %>
<div class="actions">
<%= f.submit t('.send_me_reset_password_instructions') %>
</div>
<% end %>
<%= render 'devise/shared/links' %>
<%= render "devise/shared/links" %>

View File

@@ -1,37 +1,43 @@
<h1><%= t('.title', resource: resource_name.to_s.humanize) %></h1>
<h2><%= t('.title', resource: resource.model_name.human) %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= bootstrap_devise_error_messages! %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, autocomplete: 'new-password', class: 'form-control' %>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div><%= t('.currently_waiting_confirmation_for_email', email: resource.unconfirmed_email) %></div>
<% end %>
<small class="form-text text-muted"><%= t('.leave_blank_if_you_don_t_want_to_change_it') %></small>
<div class="field">
<%= f.label :password %> <i>(<%= t('.leave_blank_if_you_don_t_want_to_change_it') %>)</i><br />
<%= f.password_field :password, autocomplete: "new-password" %>
<% if @minimum_password_length %>
<br />
<em><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></em>
<% end %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form-control' %>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="form-group">
<%= f.label :current_password %>
<%= f.password_field :current_password, autocomplete: 'current-password', class: 'form-control' %>
<small class="form-text text-muted"><%= t('.we_need_your_current_password_to_confirm_your_changes') %></small>
<div class="field">
<%= f.label :current_password %> <i>(<%= t('.we_need_your_current_password_to_confirm_your_changes') %>)</i><br />
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
<div class="form-group">
<%= f.submit t('.update'), class: 'btn btn-primary' %>
<div class="actions">
<%= f.submit t('.update') %>
</div>
<% end %>
<p><%= t('.unhappy') %>? <%= link_to t('.cancel_my_account'), registration_path(resource_name), data: { confirm: t('.are_you_sure') }, method: :delete %>.</p>
<h3><%= t('.cancel_my_account') %></h3>
<%= link_to t('.back'), :back %>
<p><%= t('.unhappy') %> <%= button_to t('.cancel_my_account'), registration_path(resource_name), data: { confirm: t('.are_you_sure') }, method: :delete %></p>
<%= link_to t('devise.shared.links.back'), :back %>

View File

@@ -1,30 +1,39 @@
<h1><%= t('.sign_up') %></h1>
<h2><%= t('.sign_up') %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= bootstrap_devise_error_messages! %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %>
<div class="field">
<%= f.label :first_name %><br />
<%= f.text_field :first_name, autofocus: true %>
</div>
<div class="form-group">
<div class="field">
<%= f.label :last_name %><br />
<%= f.text_field :last_name %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password, autocomplete: 'current-password', class: 'form-control' %>
<% if @minimum_password_length %>
<small class="form-text text-muted"><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></small>
<% end %>
<em><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, autocomplete: 'current-password', class: 'form-control' %>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="form-group">
<%= f.submit t('.sign_up'), class: 'btn btn-primary' %>
<div class="actions">
<%= f.submit t('.sign_up') %>
</div>
<% end %>
<%= render 'devise/shared/links' %>
<%= render "devise/shared/links" %>

View File

@@ -1,28 +1,26 @@
<h1><%= t('.sign_in') %></h1>
<h2><%= t('.sign_in') %></h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, autocomplete: 'current-password', class: 'form-control' %>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "current-password" %>
</div>
<% if devise_mapping.rememberable? %>
<div class="form-group form-check">
<%= f.check_box :remember_me, class: 'form-check-input' %>
<%= f.label :remember_me, class: 'form-check-label' do %>
<%= resource.class.human_attribute_name('remember_me') %>
<% end %>
<div class="field">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<% end %>
<div class="form-group">
<%= f.submit t('.sign_in'), class: 'btn btn-primary' %>
<div class="actions">
<%= f.submit t('.sign_in') %>
</div>
<% end %>
<%= render 'devise/shared/links' %>
<%= render "devise/shared/links" %>

View File

@@ -0,0 +1,15 @@
<% if resource.errors.any? %>
<div id="error_explanation">
<h2>
<%= I18n.t("errors.messages.not_saved",
count: resource.errors.count,
resource: resource.class.model_name.human.downcase)
%>
</h2>
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>

View File

@@ -1,27 +1,25 @@
<div class="form-group">
<%- if controller_name != 'sessions' %>
<%= link_to t(".sign_in"), new_session_path(resource_name) %><br />
<% end -%>
<%- if controller_name != 'sessions' %>
<%= link_to t(".sign_in"), new_session_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to t(".sign_up"), new_registration_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to t(".sign_up"), new_registration_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to t(".forgot_your_password"), new_password_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to t(".forgot_your_password"), new_password_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to t('.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to t('.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to t('.didn_t_receive_unlock_instructions'), new_unlock_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to t('.didn_t_receive_unlock_instructions'), new_unlock_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to t('.sign_in_with_provider', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider) %><br />
<% end -%>
<% end -%>
</div>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to t('.sign_in_with_provider', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider), method: :post %><br />
<% end %>
<% end %>

View File

@@ -1,16 +1,16 @@
<h1><%= t('.resend_unlock_instructions') %></h1>
<h2><%= t('.resend_unlock_instructions') %></h2>
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= bootstrap_devise_error_messages! %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="form-group">
<%= f.submit t('.resend_unlock_instructions'), class: 'btn btn-primary'%>
<div class="actions">
<%= f.submit t('.resend_unlock_instructions') %>
</div>
<% end %>
<%= render 'devise/shared/links' %>
<%= render "devise/shared/links" %>

View File

@@ -7,15 +7,7 @@
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<script>
window.AUTH_DOMAIN = "<%= ENV['AUTH_DOMAIN'] %>"
window.AUTH_CLIENT_ID = "<%= ENV['AUTH_CLIENT_ID'] %>"
window.AUTH_AUDIENCE = "<%= ENV['AUTH_AUDIENCE'] %>"
</script>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'react', 'data-turbolinks-track': 'reload' %>
</head>
<body>