From edcdc9b18dddb30f2726da282efcf77ab5ad3c1a Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sat, 13 Feb 2021 00:27:03 +0300 Subject: Cleans Up OAuth Button Removes OAuth button from home page, and redesigns it. Uses new authorization functionality in auth. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/OAuth2Button.tsx | 91 ++++++++++------------------------------- 1 file changed, 21 insertions(+), 70 deletions(-) (limited to 'src/components') diff --git a/src/components/OAuth2Button.tsx b/src/components/OAuth2Button.tsx index 4fa3f61..231e560 100644 --- a/src/components/OAuth2Button.tsx +++ b/src/components/OAuth2Button.tsx @@ -1,88 +1,39 @@ /** @jsx jsx */ import { css, jsx } from "@emotion/react"; +import { useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faDiscord } from "@fortawesome/free-brands-svg-icons"; -import colors from "../colors"; -import { useState } from "react"; - -const OAUTH2_CLIENT_ID = process.env.REACT_APP_OAUTH2_CLIENT_ID; +import authenticate, { OAuthScopes } from "../api/auth"; -const buttonStyling = css` -display: flex; -background-color: ${colors.blurple}; -border: none; -color: white; -font-family: "Hind", "Helvetica", "Arial", sans-serif; -border-radius: 5px; -padding-top: 10px; -padding-bottom: 10px; -padding-right: 20px; -padding-left: 20px; -outline: none; -transition: filter 100ms; -font-size: 1.2em; -align-items: center; - -span { - vertical-align: middle; -} -&:hover:enabled { - filter: brightness(110%); - cursor: pointer; +interface OAuth2ButtonProps { + scopes?: OAuthScopes[], + path?: string } -&:disabled { - background-color: ${colors.greyple}; -} +const iconStyles = css` + position: relative; + top: 0.3rem; + padding-left: 0.65rem; + font-size: 1.2em; `; -function doLogin(disableFunction: (newState: boolean) => void) { - disableFunction(true); - - const redirectURI = encodeURIComponent(document.location.protocol + "//" + document.location.host + "/callback"); - - const windowRef = window.open( - `https://discord.com/api/oauth2/authorize?client_id=${OAUTH2_CLIENT_ID}&response_type=code&scope=identify&redirect_uri=${redirectURI}&prompt=none`, - "Discord_OAuth2", - "height=700,width=500,location=no,menubar=no,resizable=no,status=no,titlebar=no,left=300,top=300" - ); - - const interval = setInterval(() => { - if (windowRef?.closed) { - clearInterval(interval); - disableFunction(false); - } - }, 500); - - window.onmessage = (code: MessageEvent) => { - if (code.data.source) { - // React DevTools has a habit of sending messages, ignore them. - return; - } - - if (code.isTrusted) { - windowRef?.close(); - - console.log("Code received:", code.data); - - disableFunction(false); - clearInterval(interval); - - window.onmessage = null; - } - }; -} +const textStyles = css` + display: inline-block; + padding: 0.5rem 0.75rem 0.5rem 0.5rem; +`; -function OAuth2Button(): JSX.Element { +function OAuth2Button(props: OAuth2ButtonProps): JSX.Element { const [disabled, setDisabled] = useState(false); - return ; + return ( + + ); } export default OAuth2Button; -- cgit v1.2.3 From 451c825c77cd68eafeb262eb1ea5cbfc21dce550 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sat, 13 Feb 2021 01:21:36 +0300 Subject: Dynamically Show Discord OAuth Button Dynamically displays an auth button in place of the submit button if needed, and adds full authorization flow. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/commonStyles.tsx | 32 ++++++++++++++++++++ src/components/OAuth2Button.tsx | 14 +++++++-- src/pages/FormPage.tsx | 65 ++++++++++++++++++++--------------------- 3 files changed, 74 insertions(+), 37 deletions(-) (limited to 'src/components') diff --git a/src/commonStyles.tsx b/src/commonStyles.tsx index 89a2746..eb3e319 100644 --- a/src/commonStyles.tsx +++ b/src/commonStyles.tsx @@ -1,4 +1,5 @@ import { css } from "@emotion/react"; +import colors from "./colors"; const selectable = css` -moz-user-select: text; @@ -50,6 +51,36 @@ const textInputs = css` border-radius: 8px; `; +const submitStyles = css` + text-align: right; + + button:disabled { + background-color: ${colors.greyple}; + cursor: default; + } + + button { + cursor: pointer; + + border: none; + border-radius: 8px; + + color: white; + font: inherit; + + background-color: ${colors.blurple}; + transition: background-color 300ms; + } + + button[type="submit"] { + padding: 0.55rem 4.25rem; + } + + button:enabled:hover { + background-color: ${colors.darkerBlurple}; + } +`; + export { selectable, @@ -57,4 +88,5 @@ export { hiddenInput, multiSelectInput, textInputs, + submitStyles }; diff --git a/src/components/OAuth2Button.tsx b/src/components/OAuth2Button.tsx index 231e560..90a25fa 100644 --- a/src/components/OAuth2Button.tsx +++ b/src/components/OAuth2Button.tsx @@ -5,12 +5,13 @@ import { useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faDiscord } from "@fortawesome/free-brands-svg-icons"; -import authenticate, { OAuthScopes } from "../api/auth"; +import authenticate, {checkScopes, OAuthScopes} from "../api/auth"; interface OAuth2ButtonProps { scopes?: OAuthScopes[], - path?: string + path?: string, + rerender: () => void } const iconStyles = css` @@ -27,9 +28,16 @@ const textStyles = css` function OAuth2Button(props: OAuth2ButtonProps): JSX.Element { const [disabled, setDisabled] = useState(false); + async function login() { + await authenticate(props.scopes, setDisabled, props.path); + + if (checkScopes(props.scopes, props.path)) { + props.rerender(); + } + } return ( - diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx index c49b9fd..4237e86 100644 --- a/src/pages/FormPage.tsx +++ b/src/pages/FormPage.tsx @@ -9,10 +9,12 @@ import HeaderBar from "../components/HeaderBar"; import RenderedQuestion from "../components/Question"; import Loading from "../components/Loading"; import ScrollToTop from "../components/ScrollToTop"; +import OAuth2Button from "../components/OAuth2Button"; import { Form, FormFeatures, getForm } from "../api/forms"; +import { OAuthScopes, checkScopes } from "../api/auth"; import colors from "../colors"; -import { unselectable } from "../commonStyles"; +import { submitStyles, unselectable } from "../commonStyles"; interface PathParams { @@ -20,10 +22,13 @@ interface PathParams { } interface NavigationProps { - form_state: boolean // Whether the form is open or not + form_state: boolean, // Whether the form is open or not + scopes: OAuthScopes[] } class Navigation extends React.Component { + PAGE_PATH = "/form" + containerStyles = css` margin: auto; width: 50%; @@ -38,7 +43,7 @@ class Navigation extends React.Component { width: 50%; } - @media (max-width: 850px) { + @media (max-width: 870px) { width: 100%; > div { @@ -62,13 +67,13 @@ class Navigation extends React.Component { height: 0; display: none; - @media (max-width: 850px) { + @media (max-width: 870px) { display: block; } `; returnStyles = css` - padding: 0.5rem 2rem; + padding: 0.55rem 2.2rem; border-radius: 8px; color: white; @@ -83,36 +88,21 @@ class Navigation extends React.Component { } `; - submitStyles = css` - text-align: right; - - button { - padding: 0.5rem 4rem; - cursor: pointer; - - border: none; - border-radius: 8px; - - color: white; - font: inherit; - - background-color: ${colors.blurple}; - transition: background-color 300ms; - } - - button:hover { - background-color: ${colors.darkerBlurple}; - } - `; + constructor(props: NavigationProps) { + super(props); + this.setState({"logged_in": false}); + } render(): JSX.Element { let submit = null; + if (this.props.form_state) { - submit = ( -
- -
- ); + if (this.props.scopes.includes(OAuthScopes.Identify) && !checkScopes(this.props.scopes, this.PAGE_PATH)) { + // Render OAuth button if login is required, and the scopes needed are not available + submit = this.setState({"logged_in": true})}/>; + } else { + submit = ; + } } return ( @@ -121,7 +111,7 @@ class Navigation extends React.Component { Return Home
- { submit } +
{ submit }
); } @@ -146,7 +136,7 @@ const closedHeaderStyles = css` font-size: 1.5rem; background-color: ${colors.error}; - + @media (max-width: 500px) { padding: 1rem 1.5rem; } @@ -186,6 +176,13 @@ function FormPage(): JSX.Element { } const open: boolean = form.features.includes(FormFeatures.Open); + const require_auth: boolean = form.features.includes(FormFeatures.RequiresLogin); + + const scopes = []; + if (require_auth) { + scopes.push(OAuthScopes.Identify); + if (form.features.includes(FormFeatures.CollectEmail)) { scopes.push(OAuthScopes.Email); } + } let closed_header = null; if (!open) { @@ -201,7 +198,7 @@ function FormPage(): JSX.Element { { closed_header } { questions } - +
-- cgit v1.2.3 From 2229ba931b6eadafc0c999d306fce9e6e3dc7339 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 16 Feb 2021 23:29:24 +0300 Subject: Adds Error Handler For OAuth Button Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/OAuth2Button.tsx | 64 ++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 11 deletions(-) (limited to 'src/components') diff --git a/src/components/OAuth2Button.tsx b/src/components/OAuth2Button.tsx index 90a25fa..61bcd99 100644 --- a/src/components/OAuth2Button.tsx +++ b/src/components/OAuth2Button.tsx @@ -1,11 +1,12 @@ /** @jsx jsx */ import { css, jsx } from "@emotion/react"; -import { useState } from "react"; +import React, { useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faDiscord } from "@fortawesome/free-brands-svg-icons"; -import authenticate, {checkScopes, OAuthScopes} from "../api/auth"; +import authenticate, { APIErrors, checkScopes, OAuthScopes } from "../api/auth"; +import { selectable } from "../commonStyles"; interface OAuth2ButtonProps { @@ -26,22 +27,63 @@ const textStyles = css` padding: 0.5rem 0.75rem 0.5rem 0.5rem; `; -function OAuth2Button(props: OAuth2ButtonProps): JSX.Element { - const [disabled, setDisabled] = useState(false); - async function login() { - await authenticate(props.scopes, setDisabled, props.path); +const errorStyles = css` + position: absolute; + visibility: hidden; + width: 100%; + left: 0; + + text-align: center; + white-space: normal; + box-sizing: border-box; + padding: 0 5rem; + + color: red; + margin-top: 2.5rem; +`; + +async function login(props: OAuth2ButtonProps, errorDialog: React.RefObject, setDisabled: (newState: boolean) => void) { + await authenticate(props.scopes, setDisabled, props.path).catch((reason: APIErrors) => { + // Display Error Message + if (errorDialog.current) { + errorDialog.current.style.visibility = "visible"; + errorDialog.current.textContent = reason.Message; + errorDialog.current.scrollIntoView({behavior: "smooth"}); + } + + // Propagate to sentry + const error = reason.Error.toJSON(); + error["Custom Error Message"] = reason.Message; + + // Filter Discord code + if (error?.config?.data) { + const data = JSON.parse(error.config.data); + if (data["token"]) { + data["token"] = "[FILTERED]"; + } - if (checkScopes(props.scopes, props.path)) { - props.rerender(); + error.config.data = data; } + + throw error; + }); + + if (checkScopes(props.scopes, props.path)) { + props.rerender(); } +} + +function OAuth2Button(props: OAuth2ButtonProps): JSX.Element { + const [disabled, setDisabled] = useState(false); + const errorDialog: React.RefObject = React.useRef(null); - return ( - - ); +
+ ; } export default OAuth2Button; -- cgit v1.2.3 From 020b794ef1c1b3ba67711e19b0baee7687c92c96 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Tue, 16 Feb 2021 23:48:25 +0300 Subject: Cleans Up Error Display Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/OAuth2Button.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/components') diff --git a/src/components/OAuth2Button.tsx b/src/components/OAuth2Button.tsx index 61bcd99..b084571 100644 --- a/src/components/OAuth2Button.tsx +++ b/src/components/OAuth2Button.tsx @@ -36,7 +36,11 @@ const errorStyles = css` text-align: center; white-space: normal; box-sizing: border-box; - padding: 0 5rem; + + padding: 0 15rem; + @media (max-width: 750px) { + padding: 0 5rem; + } color: red; margin-top: 2.5rem; @@ -52,7 +56,7 @@ async function login(props: OAuth2ButtonProps, errorDialog: React.RefObject Date: Wed, 17 Feb 2021 00:03:01 +0300 Subject: Cleans Up OAuth Error Logging Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/OAuth2Button.tsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'src/components') diff --git a/src/components/OAuth2Button.tsx b/src/components/OAuth2Button.tsx index b084571..1ee456c 100644 --- a/src/components/OAuth2Button.tsx +++ b/src/components/OAuth2Button.tsx @@ -56,20 +56,8 @@ async function login(props: OAuth2ButtonProps, errorDialog: React.RefObject Date: Wed, 17 Feb 2021 09:30:47 +0300 Subject: Removes Path From Auth Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/api/auth.ts | 23 +++++++++++------------ src/components/OAuth2Button.tsx | 5 ++--- src/pages/FormPage.tsx | 6 ++---- 3 files changed, 15 insertions(+), 19 deletions(-) (limited to 'src/components') diff --git a/src/api/auth.ts b/src/api/auth.ts index ad97e67..cfaa563 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -91,11 +91,11 @@ function ensureMinimumScopes(scopes: unknown, expected: OAuthScopes | OAuthScope /** * Return true if the program has the requested scopes or higher. */ -export function checkScopes(scopes?: OAuthScopes[], path = ""): boolean { +export function checkScopes(scopes?: OAuthScopes[]): boolean { const cleanedScopes = ensureMinimumScopes(scopes, OAuthScopes.Identify); // Get Active Scopes And Ensure Type - const cookies = new Cookies().get(CookieNames.Scopes + path); + const cookies = new Cookies().get(CookieNames.Scopes); if (!cookies || !Array.isArray(cookies)) { return false; } @@ -169,7 +169,7 @@ export async function getDiscordCode(scopes: OAuthScopes[]): Promise<{code: stri } /** - * Sends a discord code from a given path to the backend, + * Sends a discord code to the backend, * and returns the resultant JWT and expiry date. * * @throws { APIErrors } On error, the APIErrors.Message is set, and an APIErrors object is thrown. @@ -218,27 +218,26 @@ export async function requestBackendJWT(code: string): Promise { } /** - * Handle a full authorization flow. Sets a token for the specified path with the JWT and scopes. + * Handle a full authorization flow. Sets a cookie with the JWT and scopes. * * @param scopes The scopes that should be authorized for the application. * @param disableFunction An optional function that can disable a component while processing. - * @param path The site path to save the token under. * * @throws { APIErrors } See documentation on { requestBackendJWT }. */ -export default async function authorize(scopes: OAuthScopes[] = [], disableFunction?: (newState: boolean) => void, path = "/"): Promise { - if (!checkScopes(scopes, path)) { +export default async function authorize(scopes: OAuthScopes[] = [], disableFunction?: (newState: boolean) => void): Promise { + if (!checkScopes(scopes)) { const cookies = new Cookies; - cookies.remove(CookieNames.Token + path); - cookies.remove(CookieNames.Scopes + path); + cookies.remove(CookieNames.Token); + cookies.remove(CookieNames.Scopes); if (disableFunction) { disableFunction(true); } await getDiscordCode(scopes).then(async discord_response =>{ await requestBackendJWT(discord_response.code).then(backend_response => { - const options: CookieSetOptions = {sameSite: "strict", expires: backend_response.Expiry, secure: PRODUCTION, path: path}; + const options: CookieSetOptions = {sameSite: "strict", expires: backend_response.Expiry, secure: PRODUCTION}; - cookies.set(CookieNames.Token + path, backend_response.JWT, options); - cookies.set(CookieNames.Scopes + path, discord_response.cleanedScopes, options); + cookies.set(CookieNames.Token, backend_response.JWT, options); + cookies.set(CookieNames.Scopes, discord_response.cleanedScopes, options); }); }).finally(() => { if (disableFunction) { disableFunction(false); } diff --git a/src/components/OAuth2Button.tsx b/src/components/OAuth2Button.tsx index 1ee456c..25c5871 100644 --- a/src/components/OAuth2Button.tsx +++ b/src/components/OAuth2Button.tsx @@ -11,7 +11,6 @@ import { selectable } from "../commonStyles"; interface OAuth2ButtonProps { scopes?: OAuthScopes[], - path?: string, rerender: () => void } @@ -47,7 +46,7 @@ const errorStyles = css` `; async function login(props: OAuth2ButtonProps, errorDialog: React.RefObject, setDisabled: (newState: boolean) => void) { - await authenticate(props.scopes, setDisabled, props.path).catch((reason: APIErrors) => { + await authenticate(props.scopes, setDisabled).catch((reason: APIErrors) => { // Display Error Message if (errorDialog.current) { errorDialog.current.style.visibility = "visible"; @@ -60,7 +59,7 @@ async function login(props: OAuth2ButtonProps, errorDialog: React.RefObject { - PAGE_PATH = "/form" - containerStyles = css` margin: auto; width: 50%; @@ -97,9 +95,9 @@ class Navigation extends React.Component { let submit = null; if (this.props.form_state) { - if (this.props.scopes.includes(OAuthScopes.Identify) && !checkScopes(this.props.scopes, this.PAGE_PATH)) { + if (this.props.scopes.includes(OAuthScopes.Identify) && !checkScopes(this.props.scopes)) { // Render OAuth button if login is required, and the scopes needed are not available - submit = this.setState({"logged_in": true})}/>; + submit = this.setState({"logged_in": true})}/>; } else { submit = ; } -- cgit v1.2.3 From 9f04d5f5effd07534c9eb5b9f8886c8f970d0dc6 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 00:46:47 +0300 Subject: Fixes Return Home Button Centering Fixes the centering of the return home button on closed forms, by removing the style wrapper. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/OAuth2Button.tsx | 6 +++--- src/pages/FormPage.tsx | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'src/components') diff --git a/src/components/OAuth2Button.tsx b/src/components/OAuth2Button.tsx index 25c5871..885c080 100644 --- a/src/components/OAuth2Button.tsx +++ b/src/components/OAuth2Button.tsx @@ -31,16 +31,16 @@ const errorStyles = css` visibility: hidden; width: 100%; left: 0; - + text-align: center; white-space: normal; box-sizing: border-box; - + padding: 0 15rem; @media (max-width: 750px) { padding: 0 5rem; } - + color: red; margin-top: 2.5rem; `; diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx index fa63282..8852ac5 100644 --- a/src/pages/FormPage.tsx +++ b/src/pages/FormPage.tsx @@ -96,12 +96,14 @@ class Navigation extends React.Component { let submit = null; if (this.props.form_state) { + let inner_submit; if (this.props.scopes.includes(OAuthScopes.Identify) && !checkScopes(this.props.scopes)) { // Render OAuth button if login is required, and the scopes needed are not available - submit = this.setState({"logged_in": true})}/>; + inner_submit = this.setState({"logged_in": true})}/>; } else { - submit = ; + inner_submit = ; } + submit =
{ inner_submit }
; } return ( @@ -110,7 +112,7 @@ class Navigation extends React.Component { Return Home

-
{ submit }
+ { submit }
); } -- cgit v1.2.3 From df350092c32af5500e3d949987bc16dd9aaa5225 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 03:17:33 +0300 Subject: Fixes Radio Button Rounding Makes radio buttons spheres instead of ovals. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/InputTypes/Radio.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/components') diff --git a/src/components/InputTypes/Radio.tsx b/src/components/InputTypes/Radio.tsx index a857964..d95dcdd 100644 --- a/src/components/InputTypes/Radio.tsx +++ b/src/components/InputTypes/Radio.tsx @@ -14,7 +14,7 @@ interface RadioProps { const styles = css` div { width: 0.7em; - height: 0.75em; + height: 0.7em; top: 0.18rem; border-radius: 50%; -- cgit v1.2.3 From b92baa668465c61170ca9cc8630c4f4b8bac7b71 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> Date: Sun, 7 Mar 2021 16:05:47 +0300 Subject: Makes Code Inputs TextAreas Changes the display of code to textareas until a proper solution is implemented. Signed-off-by: Hassan Abouelela <47495861+HassanAbouelela@users.noreply.github.com> --- src/components/InputTypes/index.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src/components') diff --git a/src/components/InputTypes/index.tsx b/src/components/InputTypes/index.tsx index bc65248..c6a83f1 100644 --- a/src/components/InputTypes/index.tsx +++ b/src/components/InputTypes/index.tsx @@ -1,5 +1,4 @@ import Checkbox from "./Checkbox"; -import Code from "./Code"; import Radio from "./Radio"; import Range from "./Range"; import Select from "./Select"; @@ -38,6 +37,7 @@ export default function create_input({ question, public_state }: QuestionProp, h /* eslint-disable react/react-in-jsx-scope */ switch (question.type) { + case QuestionType.Code: // TODO: Implement case QuestionType.TextArea: result =