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'; ---