bug fixes, add logo, mobile styling
This commit is contained in:
parent
32cefda0a0
commit
2ccb3d0053
|
@ -12,15 +12,15 @@
|
|||
transition: opacity 1s ease-out;
|
||||
}
|
||||
|
||||
.phx-disconnected{
|
||||
.phx-disconnected {
|
||||
cursor: wait;
|
||||
}
|
||||
.phx-disconnected *{
|
||||
.phx-disconnected * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.phx-modal {
|
||||
opacity: 1!important;
|
||||
opacity: 1 !important;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
|
@ -28,8 +28,8 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgb(0,0,0);
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.phx-modal-content {
|
||||
|
@ -54,7 +54,6 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* Alerts and form errors */
|
||||
.alert {
|
||||
padding: 15px;
|
||||
|
@ -88,3 +87,33 @@
|
|||
display: block;
|
||||
margin: -1rem 0 2rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 200px;
|
||||
padding: 20px;
|
||||
border-right: 1px #efefef solid;
|
||||
}
|
||||
|
||||
.centered-container {
|
||||
margin-top: 3rem;
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.logo {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-right: none;
|
||||
border-bottom: 1px #efefef solid;
|
||||
}
|
||||
|
||||
.splashHeader {
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
|
||||
.splashSubheader {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 200;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,7 @@ type OAuthEmail = {
|
|||
|
||||
type IntendedLink = {
|
||||
filename: string | null,
|
||||
filetype: string | null
|
||||
filetype: string | null,
|
||||
text_content: string | null,
|
||||
file_content: string | null
|
||||
};
|
||||
|
|
|
@ -40,12 +40,12 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
|
||||
const [secretFileUrl, setSecretFileUrl] = useState<string>("#");
|
||||
const [secretFileName, setSecretFileName] = useState<string>("");
|
||||
const [secretMessage, setSecretMessage] = useState<string>("Decrypting...");
|
||||
const [secretMessage, setSecretMessage] = useState<string>("");
|
||||
const [messageRevealed, setMessageRevealed] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
init().catch((reason) => {
|
||||
alert(reason);
|
||||
console.log(reason);
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
@ -55,7 +55,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
const init = async (): Promise<void> => {
|
||||
const link: LinkFiles | null = await retrieveLink();
|
||||
const keys: Keys | null = await retrieveKeys();
|
||||
if (link && keys) {
|
||||
if (link && keys && user) {
|
||||
await decrypt(link, keys);
|
||||
}
|
||||
};
|
||||
|
@ -77,25 +77,36 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
}
|
||||
|
||||
const linkResponse = await fetch(`/links/${linkId}`);
|
||||
const linkData: IntendedLink = await linkResponse.json();
|
||||
const textResponse = await fetch(
|
||||
`/uploads/links/${linkId}/secret_message.txt`
|
||||
);
|
||||
const textData = await textResponse.blob();
|
||||
const fileResponse = await fetch(
|
||||
`/uploads/links/${linkId}/${linkData.filename}`
|
||||
);
|
||||
const fileData = await fileResponse.blob();
|
||||
let linkData: IntendedLink | null;
|
||||
let textData = null;
|
||||
let fileData = null;
|
||||
if (linkResponse.status !== 200) {
|
||||
throw new Error(linkResponse.statusText);
|
||||
return null;
|
||||
}
|
||||
linkData = await linkResponse.json();
|
||||
|
||||
if (linkData.filename) {
|
||||
await setSecretFileName(linkData.filename);
|
||||
if (linkData) {
|
||||
const textResponse = linkData.text_content
|
||||
? await fetch(`/uploads/links/${linkId}/secret_message.txt`)
|
||||
: null;
|
||||
textData = textResponse ? await textResponse.blob() : null;
|
||||
|
||||
const fileResponse = linkData.file_content
|
||||
? await fetch(`/uploads/links/${linkId}/${linkData.filename}`)
|
||||
: null;
|
||||
fileData = fileResponse ? await fileResponse.blob() : null;
|
||||
|
||||
if (linkData.filename) {
|
||||
await setSecretFileName(linkData.filename);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
text: textData.size > 0 ? textData : null,
|
||||
file: fileData.size > 0 ? fileData : null,
|
||||
filename: linkData.filename,
|
||||
filetype: linkData.filetype,
|
||||
text: textData,
|
||||
file: fileData,
|
||||
filename: linkData ? linkData.filename : null,
|
||||
filetype: linkData ? linkData.filetype : null,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -107,13 +118,13 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
fragmentData[0] = fragmentData[0].slice(1);
|
||||
|
||||
if (fragmentData.length <= 1) {
|
||||
key = sessionStorage.getItem("link_key");
|
||||
iv = sessionStorage.getItem("link_iv");
|
||||
key = sessionStorage.getItem("key_hex");
|
||||
iv = sessionStorage.getItem("iv_hex");
|
||||
} else {
|
||||
key = fragmentData[0];
|
||||
iv = fragmentData[1];
|
||||
sessionStorage.setItem("link_key", key);
|
||||
sessionStorage.setItem("link_iv", iv);
|
||||
sessionStorage.setItem("key_hex", key);
|
||||
sessionStorage.setItem("iv_hex", iv);
|
||||
}
|
||||
|
||||
if (key && iv) {
|
||||
|
@ -134,6 +145,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
|
||||
if (link?.text) {
|
||||
const textFile = await link.text.arrayBuffer();
|
||||
const encodedText = await window.crypto.subtle.decrypt(
|
||||
|
@ -201,7 +213,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
const renderHeader = (): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
<Header2 style={{ margin: ".4rem" }}>
|
||||
<Header2 style={{ marginBottom: ".4rem" }}>
|
||||
{user ? "You have been identified!" : "Someone sent you a secret"}
|
||||
</Header2>
|
||||
{user ? (
|
||||
|
@ -226,7 +238,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
|
||||
const renderAuth = (): JSX.Element => {
|
||||
return (
|
||||
<CenteredContainer fullscreen>
|
||||
<CenteredContainer fullscreen className="centered-container">
|
||||
<CenteredContainer>
|
||||
{renderHeader()}
|
||||
<Spacer space="3rem" />
|
||||
|
@ -256,7 +268,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||
|
||||
const renderReveal = (): JSX.Element => {
|
||||
return (
|
||||
<CenteredContainer fullscreen>
|
||||
<CenteredContainer fullscreen className="centered-container">
|
||||
<CenteredContainer>
|
||||
{renderHeader()}
|
||||
<Spacer space="3rem" />
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
import React, { useState } from "react";
|
||||
|
||||
import { ProgressIndicator, Header2, Button, IconArrow, Label, Input, Select, CenteredContainer, SpaceBetweenContainer, Spacer, TextAlignWrapper, GlobalStyle } from "@intended/intended-ui";
|
||||
|
||||
import {
|
||||
ProgressIndicator,
|
||||
Header2,
|
||||
Button,
|
||||
IconArrow,
|
||||
Label,
|
||||
Input,
|
||||
Select,
|
||||
CenteredContainer,
|
||||
SpaceBetweenContainer,
|
||||
Spacer,
|
||||
TextAlignWrapper,
|
||||
GlobalStyle,
|
||||
} from "@intended/intended-ui";
|
||||
|
||||
type ForPageProps = {
|
||||
csrf: string
|
||||
}
|
||||
csrf: string;
|
||||
};
|
||||
|
||||
const ForPage = (props: ForPageProps) => {
|
||||
const [recipientInput, setRecipientInput] = useState("");
|
||||
|
@ -35,20 +47,20 @@ const ForPage = (props: ForPageProps) => {
|
|||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('recipient', recipientInput);
|
||||
formData.append('service', serviceSelect);
|
||||
formData.append("recipient", recipientInput);
|
||||
formData.append("service", serviceSelect);
|
||||
formData.append("link_id", linkId);
|
||||
|
||||
try {
|
||||
const results = await fetch(`${window.location.origin}/just/for`, {
|
||||
headers: {
|
||||
"X-CSRF-Token": props.csrf
|
||||
"X-CSRF-Token": props.csrf,
|
||||
},
|
||||
body: formData,
|
||||
method: "POST"
|
||||
method: "POST",
|
||||
});
|
||||
if (!results.ok) {
|
||||
throw new Error('Network response was not OK');
|
||||
throw new Error("Network response was not OK");
|
||||
}
|
||||
|
||||
await results.json();
|
||||
|
@ -61,7 +73,7 @@ const ForPage = (props: ForPageProps) => {
|
|||
return (
|
||||
<React.StrictMode>
|
||||
<GlobalStyle />
|
||||
<CenteredContainer fullscreen>
|
||||
<CenteredContainer fullscreen className="centered-container">
|
||||
<CenteredContainer>
|
||||
<ProgressIndicator currentProgress={2} />
|
||||
<Header2>Tell Someone</Header2>
|
||||
|
@ -79,7 +91,8 @@ const ForPage = (props: ForPageProps) => {
|
|||
<Spacer space="2.5rem" />
|
||||
<TextAlignWrapper align="left">
|
||||
<Label htmlFor="serviceSelector">
|
||||
What type of account is the above username or email associated with?
|
||||
What type of account is the above username or email associated
|
||||
with?
|
||||
</Label>
|
||||
</TextAlignWrapper>
|
||||
<Select
|
||||
|
@ -87,11 +100,14 @@ const ForPage = (props: ForPageProps) => {
|
|||
onChange={handleServiceChange}
|
||||
value={serviceSelect}
|
||||
>
|
||||
<option value='github'>Github</option>
|
||||
</Select>
|
||||
<option value="github">Github</option>
|
||||
</Select>
|
||||
<Spacer space="3rem" />
|
||||
<SpaceBetweenContainer>
|
||||
<Button variant="secondary" onClick={() => window.location.href = "/just"}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => (window.location.href = "/just")}
|
||||
>
|
||||
<IconArrow arrowDirection="left" />
|
||||
</Button>
|
||||
<Button onClick={postContacts}>Generate Secret Code</Button>
|
||||
|
|
|
@ -74,7 +74,11 @@ const JustPage = (props: JustPageProps) => {
|
|||
}
|
||||
};
|
||||
|
||||
const fileFormData = async (form: FormData, aesKey: AESKey) => {
|
||||
const fileFormData = async (
|
||||
form: FormData,
|
||||
aesKey: AESKey
|
||||
): Promise<FormData> => {
|
||||
if (!fileInput) return form;
|
||||
const encoded = HexMix.stringToArrayBuffer(fileInput as string);
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{
|
||||
|
@ -91,9 +95,14 @@ const JustPage = (props: JustPageProps) => {
|
|||
HexMix.arrayBufferToString(encrypted, (result: string) => {
|
||||
sessionStorage.setItem("encoded_file", result);
|
||||
});
|
||||
return form;
|
||||
};
|
||||
|
||||
const textFormData = async (form: FormData, aesKey: AESKey) => {
|
||||
const textFormData = async (
|
||||
form: FormData,
|
||||
aesKey: AESKey
|
||||
): Promise<FormData> => {
|
||||
if (!secretInput) return form;
|
||||
const encoded = HexMix.stringToArrayBuffer(secretInput);
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{
|
||||
|
@ -108,6 +117,7 @@ const JustPage = (props: JustPageProps) => {
|
|||
HexMix.arrayBufferToString(encrypted, (result: string) => {
|
||||
sessionStorage.setItem("encoded_message", result);
|
||||
});
|
||||
return form;
|
||||
};
|
||||
|
||||
const createKey = async (): Promise<AESKey> => {
|
||||
|
@ -141,9 +151,9 @@ const JustPage = (props: JustPageProps) => {
|
|||
}
|
||||
|
||||
const key = await createKey();
|
||||
const formData = new FormData();
|
||||
await fileFormData(formData, key);
|
||||
await textFormData(formData, key);
|
||||
let formData = new FormData();
|
||||
formData = await fileFormData(formData, key);
|
||||
formData = await textFormData(formData, key);
|
||||
|
||||
try {
|
||||
const link: Response = await fetch(`${window.location.origin}/just`, {
|
||||
|
@ -167,7 +177,7 @@ const JustPage = (props: JustPageProps) => {
|
|||
return (
|
||||
<React.StrictMode>
|
||||
<GlobalStyle />
|
||||
<CenteredContainer fullscreen>
|
||||
<CenteredContainer fullscreen className="centered-container">
|
||||
<CenteredContainer>
|
||||
<ProgressIndicator currentProgress={1} />
|
||||
<Header2>Create a secret</Header2>
|
||||
|
|
|
@ -28,13 +28,26 @@ const SplashPage = (props: SplashPageProps) => {
|
|||
return (
|
||||
<React.StrictMode>
|
||||
<GlobalStyle />
|
||||
<CenteredContainer fullscreen>
|
||||
<CenteredContainer
|
||||
fullscreen
|
||||
style={{
|
||||
background: "none",
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
transform: "translate(0, -50%)",
|
||||
}}
|
||||
className="centered-container"
|
||||
>
|
||||
<CenteredContainer wide>
|
||||
<SplashIconHeader />
|
||||
<Header1>Securely Share Your Secrets</Header1>
|
||||
<SplashIconHeader style={{ width: "100%", maxWidth: "440px" }} />
|
||||
<Header1>
|
||||
<span className="splashHeader">Securely Share Your Secrets</span>
|
||||
</Header1>
|
||||
<Header3>
|
||||
With Intended Link you can easily share messages and files securely
|
||||
and secretly.
|
||||
<span className="splashSubheader">
|
||||
With Intended Link you can easily share messages and files
|
||||
securely and secretly.
|
||||
</span>
|
||||
</Header3>
|
||||
<Spacer />
|
||||
<Button
|
||||
|
|
|
@ -49,7 +49,7 @@ const YouPage = () => {
|
|||
return (
|
||||
<React.StrictMode>
|
||||
<GlobalStyle />
|
||||
<CenteredContainer fullscreen>
|
||||
<CenteredContainer fullscreen className="centered-container">
|
||||
<CenteredContainer>
|
||||
<ProgressIndicator currentProgress={3} />
|
||||
<Header2>Share the secret</Header2>
|
||||
|
|
|
@ -1143,9 +1143,9 @@
|
|||
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
||||
},
|
||||
"@intended/intended-ui": {
|
||||
"version": "0.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@intended/intended-ui/-/intended-ui-0.1.21.tgz",
|
||||
"integrity": "sha512-rNyJOGLOw8iKP0AaSYSytZXFssPhlE1FbUjxEze8F4iOgXxs6iu7B9+gSIF4QJg/IrQUVu76tkevBGDk2nTQNg==",
|
||||
"version": "0.1.26",
|
||||
"resolved": "https://registry.npmjs.org/@intended/intended-ui/-/intended-ui-0.1.26.tgz",
|
||||
"integrity": "sha512-+fSZctq4ywDUN6IJ+SbQyGhcx1fZmdyq/zsDqveJShFOGLOU39l5tT34/QHBScXlSLwgAkosCycldWyfW/olUg==",
|
||||
"requires": {
|
||||
"polished": "^4.1.3",
|
||||
"react": "^17.0.2",
|
||||
|
@ -1720,14 +1720,15 @@
|
|||
}
|
||||
},
|
||||
"babel-plugin-styled-components": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz",
|
||||
"integrity": "sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw==",
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.5.tgz",
|
||||
"integrity": "sha512-A7kfST5odbf8Ev42OQbj5teEiT8DskpRoQ/iPYePLLdcTCAsodpYKqtoy4SJthpsGzQKc2vvnrtlUgdmJq6WKQ==",
|
||||
"requires": {
|
||||
"@babel/helper-annotate-as-pure": "^7.16.0",
|
||||
"@babel/helper-module-imports": "^7.16.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"lodash": "^4.17.11"
|
||||
"lodash": "^4.17.11",
|
||||
"picomatch": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-syntax-jsx": {
|
||||
|
@ -5863,8 +5864,7 @@
|
|||
"picomatch": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
|
||||
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw=="
|
||||
},
|
||||
"pify": {
|
||||
"version": "4.0.1",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"watch": "webpack --mode development --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intended/intended-ui": "0.1.21",
|
||||
"@intended/intended-ui": "0.1.26",
|
||||
"phoenix": "file:../deps/phoenix",
|
||||
"phoenix_html": "file:../deps/phoenix_html",
|
||||
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
|
@ -28,8 +28,7 @@ defmodule EntenduWeb.AuthController do
|
|||
link = get_session(conn, :intended_link)
|
||||
|
||||
with %{id: link_id, recipient: recipient} <- link,
|
||||
{:ok, user} <- UserFromAuth.find_or_create(auth),
|
||||
true <- UserFromAuth.can_access?(recipient, user) do
|
||||
{:ok, user} <- UserFromAuth.find_or_create(auth) do
|
||||
# TODO: send over encrypted data that the frontend can decrypt
|
||||
|
||||
conn
|
||||
|
@ -42,11 +41,6 @@ defmodule EntenduWeb.AuthController do
|
|||
|> put_flash(:error, "Could not find link to authenticate against")
|
||||
|> redirect(to: "/just/for/you/")
|
||||
|
||||
false ->
|
||||
conn
|
||||
|> put_flash(:error, "#{link.recipient} was not found in your list of verified emails")
|
||||
|> redirect(to: "/just/for/you/#{link.id}")
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_flash(:error, reason)
|
||||
|
|
|
@ -36,6 +36,7 @@ defmodule EntenduWeb.LinkController do
|
|||
with %Link{} = link <- Links.get_link(link_id),
|
||||
Links.update_link(link, %{recipient: recipient, service: service}) do
|
||||
conn
|
||||
|> put_session(:intended_link, %{})
|
||||
|> render("show_authorized.json", %{link: link})
|
||||
end
|
||||
end
|
||||
|
@ -45,9 +46,9 @@ defmodule EntenduWeb.LinkController do
|
|||
end
|
||||
|
||||
def auth_page(conn, %{"id" => link_id}) do
|
||||
with %Link{service: service, recipient: recipient} = link <- Links.get_link(link_id) do
|
||||
with %Link{id: id, service: service, recipient: recipient} = link <- Links.get_link(link_id) do
|
||||
conn
|
||||
|> put_session(:intended_link, %{service: service, recipient: recipient})
|
||||
|> put_session(:intended_link, %{id: id, service: service, recipient: recipient})
|
||||
|> render("auth.html", %{intended_link: %{service: service, recipient: recipient}})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ defmodule EntenduWeb.PageController do
|
|||
use EntenduWeb, :controller
|
||||
|
||||
def index(conn, _params) do
|
||||
render(conn, "index.html", current_user: get_session(conn, :current_user))
|
||||
conn
|
||||
|> clear_session()
|
||||
|> render("index.html")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
<main role="main">
|
||||
<main role="main" style="height: 100vh;">
|
||||
<a href="/">
|
||||
<img src="<%= Routes.static_path(@conn, "/images/logo.png") %>" class="logo" />
|
||||
</a>
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
||||
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||
</head>
|
||||
<body style="background: #060b2e;">
|
||||
<body style="background: linear-gradient(180deg,#060b2e 0%,#051745 100%); height: 100vh;">
|
||||
<%= @inner_content %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,45 +2,4 @@
|
|||
|
||||
<%= react_component("Components.SplashPage", %{ error: get_flash(@conn, :error) }) %>
|
||||
|
||||
<%= if @current_user do %>
|
||||
<h2>Welcome, <%= @current_user.name %>!</h2>
|
||||
<div>
|
||||
<img src="<%= @current_user.avatar %>" />
|
||||
</div>
|
||||
<%= link "Logout", to: Routes.auth_path(@conn, :delete), method: "delete", class: "button" %>
|
||||
<br>
|
||||
<% else %>
|
||||
<ul style="display: none;">
|
||||
<li>
|
||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "github") %>">
|
||||
<i class="fa fa-github"></i>
|
||||
Sign in with GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "facebook") %>">
|
||||
<i class="fa fa-facebook"></i>
|
||||
Sign in with Facebook
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "google") %>">
|
||||
<i class="fa fa-google"></i>
|
||||
Sign in with Google
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "slack") %>">
|
||||
<i class="fa fa-slack"></i>
|
||||
Sign in with Slack
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "twitter") %>">
|
||||
<i class="fa fa-twitter"></i>
|
||||
Sign in with Twitter
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<% end %>
|
||||
</section>
|
||||
|
|
Loading…
Reference in New Issue