Add verify account functionality

pull/3813/head
Kamran Ahmed 2 years ago
parent d29aeaf000
commit 81be1d8802
  1. 2
      package.json
  2. 1244
      pnpm-lock.yaml
  3. 79
      src/components/RegistrationFlow/TriggerVerifyAccount.tsx
  4. 18
      src/icons/error.svg
  5. 64
      src/layouts/BaseLayout.astro
  6. 134
      src/layouts/SettingLayout.astro
  7. 73
      src/pages/verify-account.astro

@ -23,7 +23,7 @@
"@astrojs/preact": "^2.1.0", "@astrojs/preact": "^2.1.0",
"@astrojs/sitemap": "^1.2.2", "@astrojs/sitemap": "^1.2.2",
"@astrojs/tailwind": "^3.1.1", "@astrojs/tailwind": "^3.1.1",
"astro": "^2.2.1", "astro": "2.2.0",
"astro-compress": "^1.1.35", "astro-compress": "^1.1.35",
"jose": "^4.13.1", "jose": "^4.13.1",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",

File diff suppressed because it is too large Load Diff

@ -0,0 +1,79 @@
import SpinnerIcon from '../../icons/spinner.svg';
import ErrorIcon from '../../icons/error.svg';
import { useEffect, useState } from 'preact/hooks';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/constants';
export function TriggerVerifyAccount() {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState('');
const triggerVerify = (code: string) => {
setIsLoading(true);
fetch(`${import.meta.env.PUBLIC_API_URL}/v1-verify-account`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code,
}),
})
.then((res) => res.json())
.then((data: any) => {
if (!data.token) {
throw new Error('Something went wrong. Please try again..');
}
Cookies.set(TOKEN_COOKIE_NAME, data.token);
window.location.href = '/';
})
.catch((err) => {
setIsLoading(false);
setError('Something went wrong. Please try again.');
});
};
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code')!;
if (!code) {
setIsLoading(false);
setError('Something went wrong. Please try again later.');
return;
}
triggerVerify(code);
}, []);
return (
<div className="mx-auto flex max-w-md flex-col items-center pt-0 sm:pt-12">
<div className="mx-auto max-w-md text-center">
{isLoading && (
<img
alt={'Please wait.'}
src={SpinnerIcon}
class={'mx-auto h-16 w-16 animate-spin'}
/>
)}
{error && (
<img
alt={'Please wait.'}
src={ErrorIcon}
className={'mx-auto h-16 w-16'}
/>
)}
<h2 className="mb-1 mt-4 text-center text-xl font-semibold sm:mb-3 sm:mt-4 sm:text-2xl">
Verifying your account
</h2>
<div className="text-sm sm:text-base">
{isLoading && <p>Please wait while we verify your account..</p>}
{error && <p class="text-red-700">{error}</p>}
</div>
</div>
</div>
);
}

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="512px" height="512px">
<linearGradient id="wRKXFJsqHCxLE9yyOYHkza" x1="9.858" x2="38.142" y1="9.858" y2="38.142"
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#f44f5a"/>
<stop offset=".443" stop-color="#ee3d4a"/>
<stop offset="1" stop-color="#e52030"/>
</linearGradient>
<path fill="url(#wRKXFJsqHCxLE9yyOYHkza)"
d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"/>
<path d="M33.192,28.95L28.243,24l4.95-4.95c0.781-0.781,0.781-2.047,0-2.828l-1.414-1.414 c-0.781-0.781-2.047-0.781-2.828,0L24,19.757l-4.95-4.95c-0.781-0.781-2.047-0.781-2.828,0l-1.414,1.414 c-0.781,0.781-0.781,2.047,0,2.828l4.95,4.95l-4.95,4.95c-0.781,0.781-0.781,2.047,0,2.828l1.414,1.414 c0.781,0.781,2.047,0.781,2.828,0l4.95-4.95l4.95,4.95c0.781,0.781,2.047,0.781,2.828,0l1.414-1.414 C33.973,30.997,33.973,29.731,33.192,28.95z"
opacity=".05"/>
<path d="M32.839,29.303L27.536,24l5.303-5.303c0.586-0.586,0.586-1.536,0-2.121l-1.414-1.414 c-0.586-0.586-1.536-0.586-2.121,0L24,20.464l-5.303-5.303c-0.586-0.586-1.536-0.586-2.121,0l-1.414,1.414 c-0.586,0.586-0.586,1.536,0,2.121L20.464,24l-5.303,5.303c-0.586,0.586-0.586,1.536,0,2.121l1.414,1.414 c0.586,0.586,1.536,0.586,2.121,0L24,27.536l5.303,5.303c0.586,0.586,1.536,0.586,2.121,0l1.414-1.414 C33.425,30.839,33.425,29.889,32.839,29.303z"
opacity=".07"/>
<path fill="#fff"
d="M31.071,15.515l1.414,1.414c0.391,0.391,0.391,1.024,0,1.414L18.343,32.485 c-0.391,0.391-1.024,0.391-1.414,0l-1.414-1.414c-0.391-0.391-0.391-1.024,0-1.414l14.142-14.142 C30.047,15.124,30.681,15.124,31.071,15.515z"/>
<path fill="#fff"
d="M32.485,31.071l-1.414,1.414c-0.391,0.391-1.024,0.391-1.414,0L15.515,18.343 c-0.391-0.391-0.391-1.024,0-1.414l1.414-1.414c0.391-0.391,1.024-0.391,1.414,0l14.142,14.142 C32.876,30.047,32.876,30.681,32.485,31.071z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -1,14 +1,13 @@
--- ---
import '../styles/global.css'; import Analytics from '../components/Analytics/Analytics.astro';
import Authenticator from '../components/Authenticator/Authenticator.astro';
import Footer from '../components/Footer.astro';
import Navigation from '../components/Navigation/Navigation.astro'; import Navigation from '../components/Navigation/Navigation.astro';
import OpenSourceBanner from '../components/OpenSourceBanner.astro'; import OpenSourceBanner from '../components/OpenSourceBanner.astro';
import Footer from '../components/Footer.astro';
import type { SponsorType } from '../components/Sponsor/Sponsor.astro'; import type { SponsorType } from '../components/Sponsor/Sponsor.astro';
import Sponsor from '../components/Sponsor/Sponsor.astro'; import Sponsor from '../components/Sponsor/Sponsor.astro';
import YouTubeBanner from '../components/YouTubeBanner.astro';
import { siteConfig } from '../lib/config'; import { siteConfig } from '../lib/config';
import Analytics from '../components/Analytics/Analytics.astro'; import '../styles/global.css';
import Authenticator from '../components/Authenticator/Authenticator.astro';
export interface Props { export interface Props {
title: string; title: string;
@ -39,7 +38,9 @@ const currentPageAbsoluteUrl = `https://roadmap.sh${permalink}`;
const canonicalUrl = givenCanonical || currentPageAbsoluteUrl; const canonicalUrl = givenCanonical || currentPageAbsoluteUrl;
const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${import.meta.env.GITHUB_SHA}`; const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${
import.meta.env.GITHUB_SHA
}`;
--- ---
<!DOCTYPE html> <!DOCTYPE html>
@ -52,7 +53,11 @@ const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${i
<meta name='description' content={description} /> <meta name='description' content={description} />
<meta name='author' content='Kamran Ahmed' /> <meta name='author' content='Kamran Ahmed' />
<meta name='keywords' content={keywords.join(', ')} /> <meta name='keywords' content={keywords.join(', ')} />
{redirectUrl && <meta http-equiv='refresh' content={`1;url=${redirectUrl}`} />} {
redirectUrl && (
<meta http-equiv='refresh' content={`1;url=${redirectUrl}`} />
)
}
{noIndex && <meta name='robots' content='noindex' />} {noIndex && <meta name='robots' content='noindex' />}
<meta <meta
name='viewport' name='viewport'
@ -77,23 +82,48 @@ const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${i
<meta name='mobile-web-app-capable' content='yes' /> <meta name='mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-capable' content='yes' /> <meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' /> <meta
name='apple-mobile-web-app-status-bar-style'
content='black-translucent'
/>
<meta name='apple-mobile-web-app-title' content='roadmap.sh' /> <meta name='apple-mobile-web-app-title' content='roadmap.sh' />
<meta name='application-name' content='roadmap.sh' /> <meta name='application-name' content='roadmap.sh' />
<link rel='apple-touch-icon' sizes='180x180' href='/manifest/apple-touch-icon.png' /> <link
rel='apple-touch-icon'
sizes='180x180'
href='/manifest/apple-touch-icon.png'
/>
<meta name='msapplication-TileColor' content='#101010' /> <meta name='msapplication-TileColor' content='#101010' />
<meta name='theme-color' content='#848a9a' /> <meta name='theme-color' content='#848a9a' />
<link rel='manifest' href='/manifest/manifest.json' /> <link rel='manifest' href='/manifest/manifest.json' />
<link rel='icon' type='image/png' sizes='32x32' href='/manifest/icon32.png' /> <link
<link rel='icon' type='image/png' sizes='16x16' href='/manifest/icon16.png' /> rel='icon'
<link rel='shortcut icon' href='/manifest/favicon.ico' type='image/x-icon' /> type='image/png'
sizes='32x32'
href='/manifest/icon32.png'
/>
<link
rel='icon'
type='image/png'
sizes='16x16'
href='/manifest/icon16.png'
/>
<link
rel='shortcut icon'
href='/manifest/favicon.ico'
type='image/x-icon'
/>
<link rel='icon' href='/manifest/favicon.ico' type='image/x-icon' /> <link rel='icon' href='/manifest/favicon.ico' type='image/x-icon' />
<slot name='after-header' /> <slot name='after-header' />
{jsonLd.length > 0 && <script type='application/ld+json' set:html={JSON.stringify(jsonLd)} />} {
jsonLd.length > 0 && (
<script type='application/ld+json' set:html={JSON.stringify(jsonLd)} />
)
}
</head> </head>
<body> <body>
<slot name='page-header'> <slot name='page-header'>
@ -106,9 +136,11 @@ const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${i
<OpenSourceBanner /> <OpenSourceBanner />
<Footer /> <Footer />
{sponsor && <Sponsor sponsor={sponsor} />} {sponsor && <Sponsor sponsor={sponsor} />}
<slot name='after-footer' />
<Analytics />
<Authenticator />
</slot> </slot>
<Analytics />
<Authenticator />
<slot name='after-footer' />
</body> </body>
</html> </html>

@ -1,132 +1,12 @@
--- ---
import '../styles/global.css'; import BaseLayout,{ Props as BaseLayoutProps } from './BaseLayout.astro';
import Navigation from '../components/Navigation/Navigation.astro';
import type { SponsorType } from '../components/Sponsor/Sponsor.astro';
import YouTubeBanner from '../components/YouTubeBanner.astro';
import { siteConfig } from '../lib/config';
export interface Props { export interface Props extends BaseLayoutProps {}
title: string;
redirectUrl?: string;
description?: string;
keywords?: string[];
noIndex?: boolean;
canonicalUrl?: string;
permalink?: string;
sponsor?: SponsorType;
jsonLd?: Record<string, unknown>[];
}
const { const props = Astro.props;
title = siteConfig.title,
description = siteConfig.description,
keywords = siteConfig.keywords,
noIndex = false,
permalink = '',
canonicalUrl: givenCanonical = '',
sponsor,
jsonLd = [],
redirectUrl = '',
} = Astro.props;
// Remove trailing slashes to consider the page as canonical
const currentPageAbsoluteUrl = `https://roadmap.sh${permalink}`;
const canonicalUrl = givenCanonical || currentPageAbsoluteUrl;
const commitUrl = `https://github.com/kamranahmedse/developer-roadmap/commit/${
import.meta.env.GITHUB_SHA
}`;
--- ---
<!DOCTYPE html> <BaseLayout {...props}>
<html lang='en'> <slot />
<head> <div slot='page-footer'></div>
<meta charset='UTF-8' /> </BaseLayout>
<meta name='generator' content={Astro.generator} />
<meta name='commit' content={commitUrl} />
<title>{title}</title>
<meta name='description' content={description} />
<meta name='author' content='Kamran Ahmed' />
<meta name='keywords' content={keywords.join(', ')} />
{
redirectUrl && (
<meta http-equiv='refresh' content={`1;url=${redirectUrl}`} />
)
}
{noIndex && <meta name='robots' content='noindex' />}
<meta
name='viewport'
content='width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0'
/>
<meta http-equiv='Content-Language' content='en' />
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:creator' content='@kamranahmedse' />
<meta property='og:image:width' content='1200' />
<meta property='og:image:height' content='630' />
<meta property='og:image' content='https://roadmap.sh/images/og-img.png' />
<meta property='og:image:alt' content='roadmap.sh' />
<meta property='og:site_name' content='roadmap.sh' />
<meta property='og:title' content={title} />
<meta property='og:description' content={description} />
<meta property='og:type' content='website' />
<meta property='og:url' content={currentPageAbsoluteUrl} />
<link rel='canonical' href={canonicalUrl} />
<meta name='mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta
name='apple-mobile-web-app-status-bar-style'
content='black-translucent'
/>
<meta name='apple-mobile-web-app-title' content='roadmap.sh' />
<meta name='application-name' content='roadmap.sh' />
<link
rel='apple-touch-icon'
sizes='180x180'
href='/manifest/apple-touch-icon.png'
/>
<meta name='msapplication-TileColor' content='#101010' />
<meta name='theme-color' content='#848a9a' />
<link rel='manifest' href='/manifest/manifest.json' />
<link
rel='icon'
type='image/png'
sizes='32x32'
href='/manifest/icon32.png'
/>
<link
rel='icon'
type='image/png'
sizes='16x16'
href='/manifest/icon16.png'
/>
<link
rel='shortcut icon'
href='/manifest/favicon.ico'
type='image/x-icon'
/>
<link rel='icon' href='/manifest/favicon.ico' type='image/x-icon' />
<slot name='after-header' />
{
jsonLd.length > 0 && (
<script type='application/ld+json' set:html={JSON.stringify(jsonLd)} />
)
}
</head>
<body>
<slot name='page-header'>
<YouTubeBanner />
<Navigation />
</slot>
<slot />
</body>
</html>

@ -1,79 +1,10 @@
--- ---
import Spinner from '../components/Spinner.astro'; import { TriggerVerifyAccount } from '../components/RegistrationFlow/TriggerVerifyAccount';
import SettingLayout from '../layouts/SettingLayout.astro'; import SettingLayout from '../layouts/SettingLayout.astro';
--- ---
<SettingLayout title='Verify account'> <SettingLayout title='Verify account'>
<div class='container py-16'> <div class='container py-16'>
<div class='mx-auto flex max-w-md flex-col items-center'> <TriggerVerifyAccount client:load />
<h2 class='mb-6 text-3xl font-bold sm:text-center sm:text-4xl'>
Verifying account
</h2>
<Spinner data-verification-spinner class='!h-10 !w-10' />
<p
class='hidden text-center text-sm text-red-500'
data-verification-error
>
Invalid verification code, please try again.
</p>
<p
class='mx-auto hidden max-w-sm text-center text-sm text-slate-500'
data-verification-success
>
Redirecting you to the home page.
</p>
</div>
</div> </div>
</SettingLayout> </SettingLayout>
<script>
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../lib/constants';
const spinner = document.querySelector('[data-verification-spinner]');
const error = document.querySelector('[data-verification-error]');
const success = document.querySelector('[data-verification-success]');
// Check if the user is logged in already or not
if (Cookies.get(TOKEN_COOKIE_NAME)) {
window.location.href = '/';
}
window.addEventListener('load', () => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) {
fetch('http://localhost:8080/v1-verify-account', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code,
}),
}).then(async (res) => {
// If the response is ok, then we can redirect the user to the home page and set the token
if (res.ok) {
spinner?.remove();
success?.classList.remove('hidden');
const json = await res.json();
if (json.token) {
Cookies.set(TOKEN_COOKIE_NAME, json.token);
} else {
Cookies.remove(TOKEN_COOKIE_NAME);
}
// Redirect the user to the home page
window.location.href = '/';
} else {
spinner?.remove();
error?.classList.remove('hidden');
}
});
}
});
</script>

Loading…
Cancel
Save