Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect Authentication Logic #34

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 0 additions & 82 deletions components/Parallax/index.tsx

This file was deleted.

30 changes: 0 additions & 30 deletions components/Parallax/styles.module.css

This file was deleted.

33 changes: 28 additions & 5 deletions components/Playlist/SelectPlatform.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { Fragment, useState } from 'react';
import React, { FormEvent, useState } from 'react';
import { RadioGroup } from '@headlessui/react';
import cn from 'classnames';
import { useRouter } from 'next/router';

import styles from './styles.module.css';
import { SupportedPlatformsForLogin } from '../../utils/platforms';
import { SupportedPlatformsForLogin, getPlatformLabelForLogin } from '../../utils/platforms';
import type {
LoginPlatform,
RadioGroupRenderProps
Expand All @@ -12,12 +13,28 @@ import CheckIcon from '../icons/CheckIcon';


const SelectPlatform: React.FC = () => {
const router = useRouter();
const [userPlatform, setUserPlatform] = useState<LoginPlatform | null>(null);

const isUserPlatformSelected = userPlatform !== null;

const handlePlatformSelect = (e: FormEvent<HTMLFormElement>): void => {
e.preventDefault();

if (userPlatform) {
const platformLabel = getPlatformLabelForLogin(userPlatform.name);
const currentUrl = window.location.href;

router.replace(`/auth/example/${platformLabel}?type=web&next=${currentUrl}`);
return;
}

// TODO: log this to sentry
console.error('Invalid platform selection. You have to select a platfrom before proceeding.');
};

return (
<section className={styles.selectPlatformContainer}>
<form className={styles.selectPlatformContainer} onSubmit={handlePlatformSelect}>
<section className={styles.platformsSelectContainer}>
<RadioGroup.Description>Select a platform to clone the playlist.</RadioGroup.Description>
<RadioGroup value={userPlatform} onChange={setUserPlatform}>
Expand Down Expand Up @@ -48,8 +65,14 @@ const SelectPlatform: React.FC = () => {
</RadioGroup>
</section>

<button className={styles.loginBtn} disabled={!isUserPlatformSelected}>LOGIN</button>
</section>
<button
className={styles.loginBtn}
disabled={!isUserPlatformSelected}
type="submit"
>
LOGIN
</button>
</form>
);
}

Expand Down
37 changes: 31 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ import { MenuId } from 'react-contexify';

import {
ContextMenuChildName,
APPLE_MUSIC_TYPE,
SPOTIFY_TYPE,
DEEZER_TYPE
} from './utils/constants';

import {
ContactFormReducerEnum,
ANNIE_TYPE,
SPOTIFY_TYPE,
Expand Down Expand Up @@ -261,3 +255,34 @@ export interface ModalProps {
title: string;
description?: string;
}

export interface AuthExampleProps {
error?: string;
url: string;
type: string;
provider: string;
nextUrl: string;
}

export interface AuthExampleServerSideProps {
props: AuthExampleProps;
}

export interface AuthRedirectProps {
error: string | null;
url: string | null;
}

export interface AuthRedirectServerSideProps {
props: AuthRedirectProps;
}

export interface AuthVerifyProps {
error: string | null;
code: string | null;
provider: string | null;
}

export interface AuthVerifyServerSideProps {
props: AuthVerifyProps;
}
4 changes: 2 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ module.exports = {
publicRuntimeConfig: {
isDev,
apiBaseUrl: process.env.ANNIE_API_BASE_URL,
NONCE: COMMIT_REF,
ENV: process.env.NODE_ENV || 'development',
},
env: {
COMMIT_REF,
NONCE: COMMIT_REF,
ENV: process.env.NODE_ENV || 'development',
BASE_URL: process.env.ANNIE_CLIENT_BASE_URL,
API_BASE_URL: process.env.ANNIE_API_BASE_URL
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"react-hot-toast": "1.0.2",
"react-parallax": "3.0.3",
"react-select": "4.1.0",
"react-use": "^17.3.1",
"typescript": "4.3.5"
},
"scripts": {
Expand Down
6 changes: 5 additions & 1 deletion pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import Document, {
Html,
} from 'next/document';
import { ServerPortal } from '@jesstelford/react-portal-universal/server';
import getConfig from 'next/config';

import { GA_TRACKING_ID } from '../utils/googleAnalytics';
import { Fragment } from 'react';


const { publicRuntimeConfig } = getConfig();

class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const portals = new ServerPortal();
Expand All @@ -33,7 +37,7 @@ class MyDocument extends Document {

render() {
const analyticsUrl: string = `https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`;
const { NONCE, ENV } = process.env;
const { NONCE, ENV } = publicRuntimeConfig;
const isDev: boolean = ENV === 'development';
// const referrer = 'strict-origin';
// const cspContent = `default-src 'self' *.dzcdn.net api.anniemusic.app googletagmanager.com; style-src 'strict-dynamic' 'nonce-w329sdada'; script-src 'strict-dynamic'; object-src 'none'; font-src 'self'; img-src 'self' res.cloudinary.com *.scdn.co *.dzcdn.net; frame-src airtable.com; base-uri 'self'; frame-ancestors airtable.com; report-uri http://localhost:5000/api/v1/complaint/csp`;
Expand Down
124 changes: 124 additions & 0 deletions pages/auth/[provider].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { Fragment, useState } from 'react';
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
import { useRouter } from 'next/router';
import getConfig from 'next/config';
import { useLocalStorage, useMount } from 'react-use';

import Header from '../../components/Header';
import Footer from '../../components/Footer';
import CircleSpinner from '../../components/Spinner/CircleSpinner';
import styles from './styles.module.css';
import type {
AuthRedirectProps,
AuthRedirectServerSideProps
} from '../..';
import { isValidProvider } from '../../utils/provider';
import { AUTH_NEXT_URL_KEY, FetchStatus } from '../../utils/constants';


const { publicRuntimeConfig } = getConfig();

const AuthRedirectPage: NextPage<AuthRedirectProps> = (props: AuthRedirectProps) => {
const { url, error } = props;

const [status, setStatus] = useState<FetchStatus>(FetchStatus.IDLE);
const [isMobile, setIsMobile] = useState<boolean>(false);
const router = useRouter();
const [nextUrl] = useLocalStorage(AUTH_NEXT_URL_KEY, undefined, { raw: true });

const handleAuthVerification = () => {
setIsMobile(!nextUrl);
setStatus(FetchStatus.SUCCESS);
console.log('mounted', nextUrl);
};

useMount(handleAuthVerification);

const renderContent = (): JSX.Element => {
if (error || status === FetchStatus.ERROR) {
const message = error || 'An error occurred while verifying your authentication credentials. Please try again.';

return (
<Fragment>
<p>{message}</p>
</Fragment>
);
}

if (url && status === FetchStatus.SUCCESS) {
switch (isMobile) {
case true:
window.location.href = `annie://login${url}`;
break;
case false:
router.replace(`/auth/verify${url}`);
break;
default:
break;
}

return <div />;
}

return <CircleSpinner />;
}


return (
<Fragment>
<Header />
<section className={styles.authContainer}>
{renderContent()}
</section>
<Footer />
</Fragment>
);
};

export const getServerSideProps: GetServerSideProps = async (
context: GetServerSidePropsContext
): Promise<AuthRedirectServerSideProps> => {
const { provider, code = '', error: _error = '', user } = context.query;

const transformedProvider = (provider as string).toLowerCase();
const authError = (_error as string).toLowerCase();
const transformedCode = code as string;

try {
if (isValidProvider(transformedProvider) && !authError) {
let authRedirectEndpoint = `${publicRuntimeConfig.apiBaseUrl}/auth/${transformedProvider}?code=${transformedCode}`;

if (user) {
authRedirectEndpoint += `&user=${user}`;
}

const response = await fetch(authRedirectEndpoint);

if (response.ok) {
const { data } = await response.json();

console.log(JSON.stringify(data, null, 2), '<====');

return {
props: {
error: null,
url: data,
}
}
}
}
} catch (error) {
// need to figure out something to do with the error
// TODO: log this to sentry
console.warn(error);
}

return {
props: {
error: 'An error occurred while verifying your authentication credentials. Please try again.',
url: null
}
};
}

export default AuthRedirectPage;
Loading