diff --git a/src/components/AuthenticationFlow/LinkedInButton.tsx b/src/components/AuthenticationFlow/LinkedInButton.tsx new file mode 100644 index 000000000..625d09d6d --- /dev/null +++ b/src/components/AuthenticationFlow/LinkedInButton.tsx @@ -0,0 +1,119 @@ +import { useEffect, useState } from 'preact/hooks'; +import Cookies from 'js-cookie'; +import LinkedIn from '../../icons/linkedin.svg'; +import SpinnerIcon from '../../icons/spinner.svg'; +import { TOKEN_COOKIE_NAME } from '../../lib/jwt'; +import { httpGet } from '../../lib/http'; + +type LinkedInButtonProps = {}; + +const LINKEDIN_REDIRECT_AT = 'linkedInRedirectAt'; +const LINKEDIN_LAST_PAGE = 'linkedInLastPage'; + +export function LinkedInButton(props: LinkedInButtonProps) { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + const icon = isLoading ? SpinnerIcon : LinkedIn; + + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get('code'); + const state = urlParams.get('state'); + const provider = urlParams.get('provider'); + + if (!code || !state || provider !== 'linkedin') { + return; + } + + setIsLoading(true); + httpGet<{ token: string }>( + `${import.meta.env.PUBLIC_API_URL}/v1-linkedin-callback${ + window.location.search + }` + ) + .then(({ response, error }) => { + if (!response?.token) { + setError(error?.message || 'Something went wrong.'); + setIsLoading(false); + + return; + } + + let redirectUrl = '/'; + const linkedInRedirectAt = localStorage.getItem(LINKEDIN_REDIRECT_AT); + const lastPageBeforeLinkedIn = localStorage.getItem(LINKEDIN_LAST_PAGE); + + // If the social redirect is there and less than 30 seconds old + // redirect to the page that user was on before they clicked the github login button + if (linkedInRedirectAt && lastPageBeforeLinkedIn) { + const socialRedirectAtTime = parseInt(linkedInRedirectAt, 10); + const now = Date.now(); + const timeSinceRedirect = now - socialRedirectAtTime; + + if (timeSinceRedirect < 30 * 1000) { + redirectUrl = lastPageBeforeLinkedIn; + } + } + + localStorage.removeItem(LINKEDIN_REDIRECT_AT); + localStorage.removeItem(LINKEDIN_LAST_PAGE); + Cookies.set(TOKEN_COOKIE_NAME, response.token, { + path: '/', + expires: 30, + }); + window.location.href = redirectUrl; + }) + .catch((err) => { + setError('Something went wrong. Please try again later.'); + setIsLoading(false); + }); + }, []); + + const handleClick = () => { + setIsLoading(true); + httpGet<{ loginUrl: string }>( + `${import.meta.env.PUBLIC_API_URL}/v1-linkedin-login` + ) + .then(({ response, error }) => { + if (!response?.loginUrl) { + setError(error?.message || 'Something went wrong.'); + setIsLoading(false); + + return; + } + + // For non authentication pages, we want to redirect back to the page + // the user was on before they clicked the social login button + if (!['/login', '/signup'].includes(window.location.pathname)) { + localStorage.setItem(LINKEDIN_REDIRECT_AT, Date.now().toString()); + localStorage.setItem(LINKEDIN_LAST_PAGE, window.location.pathname); + } + + window.location.href = response.loginUrl; + }) + .catch((err) => { + setError('Something went wrong. Please try again later.'); + setIsLoading(false); + }); + }; + + return ( + <> + + {error && ( +

{error}

+ )} + + ); +} diff --git a/src/components/AuthenticationFlow/LoginPopup.astro b/src/components/AuthenticationFlow/LoginPopup.astro index 241f38564..4f09aa67f 100644 --- a/src/components/AuthenticationFlow/LoginPopup.astro +++ b/src/components/AuthenticationFlow/LoginPopup.astro @@ -4,6 +4,7 @@ import EmailLoginForm from './EmailLoginForm'; import Divider from './Divider.astro'; import { GitHubButton } from './GitHubButton'; import { GoogleButton } from './GoogleButton'; +import { LinkedInButton } from './LinkedInButton'; --- @@ -19,6 +20,7 @@ import { GoogleButton } from './GoogleButton';
+
diff --git a/src/icons/linkedin.svg b/src/icons/linkedin.svg new file mode 100644 index 000000000..3f9e223bb --- /dev/null +++ b/src/icons/linkedin.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/pages/login.astro b/src/pages/login.astro index acae2566e..8aca01dd0 100644 --- a/src/pages/login.astro +++ b/src/pages/login.astro @@ -3,6 +3,7 @@ import Divider from '../components/AuthenticationFlow/Divider.astro'; import EmailLoginForm from '../components/AuthenticationFlow/EmailLoginForm'; import { GitHubButton } from '../components/AuthenticationFlow/GitHubButton'; import { GoogleButton } from '../components/AuthenticationFlow/GoogleButton'; +import { LinkedInButton } from '../components/AuthenticationFlow/LinkedInButton'; import AccountLayout from '../layouts/AccountLayout.astro'; --- @@ -24,6 +25,7 @@ import AccountLayout from '../layouts/AccountLayout.astro';
+
diff --git a/src/pages/signup.astro b/src/pages/signup.astro index 56c9f0f25..95f1dbeae 100644 --- a/src/pages/signup.astro +++ b/src/pages/signup.astro @@ -3,6 +3,7 @@ import Divider from '../components/AuthenticationFlow/Divider.astro'; import EmailSignupForm from '../components/AuthenticationFlow/EmailSignupForm'; import { GitHubButton } from '../components/AuthenticationFlow/GitHubButton'; import { GoogleButton } from '../components/AuthenticationFlow/GoogleButton'; +import { LinkedInButton } from '../components/AuthenticationFlow/LinkedInButton'; import AccountLayout from '../layouts/AccountLayout.astro'; --- @@ -31,6 +32,7 @@ import AccountLayout from '../layouts/AccountLayout.astro';
+