[Design/Routes] Welcome Screen / Initial Setup (#42)
* feat(new component): welcome screen and initial setup component * feat(frontend): setup with login, settings, radarr/sonarr * feat(frontend): add login functionality to login step for setup Co-authored-by: Alexander Zoitos <azoitos1@gmail.com>pull/175/head
parent
06dc606bcf
commit
366074c12a
@ -0,0 +1,54 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useUser } from '../../hooks/useUser';
|
||||
import PlexLoginButton from '../PlexLoginButton';
|
||||
import axios from 'axios';
|
||||
|
||||
interface LoginWithPlexProps {
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
const LoginWithPlex: React.FC<LoginWithPlexProps> = ({ onComplete }) => {
|
||||
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
|
||||
const { user, revalidate } = useUser();
|
||||
|
||||
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
|
||||
// We take the token and attempt to login. If we get a success message, we will
|
||||
// ask swr to revalidate the user which _shouid_ come back with a valid user.
|
||||
|
||||
useEffect(() => {
|
||||
const login = async () => {
|
||||
const response = await axios.post('/api/v1/auth/login', { authToken });
|
||||
|
||||
if (response.data?.email) {
|
||||
revalidate();
|
||||
}
|
||||
};
|
||||
if (authToken) {
|
||||
login();
|
||||
}
|
||||
}, [authToken, revalidate]);
|
||||
|
||||
// Effect that is triggered whenever `useUser`'s user changes. If we get a new
|
||||
// valid user, we call onComplete which will take us to the next step in Setup.
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
onComplete();
|
||||
}
|
||||
}, [user, onComplete]);
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="flex justify-center font-bold text-xl mb-2">
|
||||
Welcome to Overseerr
|
||||
</div>
|
||||
<div className="flex justify-center text-sm pb-6 mb-2">
|
||||
Get started by logging in with your Plex account
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<PlexLoginButton onAuthToken={(authToken) => setAuthToken(authToken)} />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginWithPlex;
|
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
|
||||
interface CurrentStep {
|
||||
stepNumber: number;
|
||||
description: string;
|
||||
active?: boolean;
|
||||
completed?: boolean;
|
||||
isLastStep?: boolean;
|
||||
}
|
||||
|
||||
const SetupSteps: React.FC<CurrentStep> = ({
|
||||
stepNumber,
|
||||
description,
|
||||
active = false,
|
||||
completed = false,
|
||||
isLastStep = false,
|
||||
}) => {
|
||||
return (
|
||||
<li className="relative md:flex-1 md:flex">
|
||||
<div className="px-6 py-4 flex items-center text-sm leading-5 font-medium space-x-4">
|
||||
<div
|
||||
className={`flex-shrink-0 w-10 h-10 flex items-center justify-center border-2
|
||||
${active ? 'border-indigo-600 ' : 'border-white '}
|
||||
${completed ? 'bg-indigo-600 border-indigo-600 ' : ''} rounded-full`}
|
||||
>
|
||||
{completed && (
|
||||
<svg
|
||||
className="w-6 h-6 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{!completed && (
|
||||
<p className={active ? 'text-white' : 'text-indigo-200'}>
|
||||
{stepNumber}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
className={`text-sm leading-5 font-medium ${
|
||||
active ? 'text-white' : 'text-indigo-200'
|
||||
}`}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!isLastStep && (
|
||||
<div className="hidden md:block absolute top-0 right-0 h-full w-5">
|
||||
<svg
|
||||
className="h-full w-full text-cool-gray-600"
|
||||
viewBox="0 0 22 80"
|
||||
fill="none"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<path
|
||||
d="M0 -2L20 40L0 82"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
stroke="currentcolor"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetupSteps;
|
@ -0,0 +1,101 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useState } from 'react';
|
||||
import Button from '../Common/Button';
|
||||
import ImageFader from '../Common/ImageFader';
|
||||
import SettingsPlex from '../Settings/SettingsPlex';
|
||||
import SettingsServices from '../Settings/SettingsServices';
|
||||
import LoginWithPlex from './LoginWithPlex';
|
||||
import SetupSteps from './SetupSteps';
|
||||
|
||||
const Setup: React.FC = () => {
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [plexSettingsComplete, setPlexSettingsComplete] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-cool-gray-900 flex flex-col justify-center py-12 sm:px-6 lg:px-8 relative">
|
||||
<ImageFader
|
||||
backgroundImages={[
|
||||
'/images/rotate1.jpg',
|
||||
'/images/rotate2.jpg',
|
||||
'/images/rotate3.jpg',
|
||||
'/images/rotate4.jpg',
|
||||
]}
|
||||
/>
|
||||
<div className="px-4 sm:px-2 md:px-0 sm:mx-auto sm:w-full sm:max-w-2xl relative z-50">
|
||||
<img
|
||||
src="/logo.png"
|
||||
className="mx-auto max-h-32 w-auto mb-10"
|
||||
alt="Overseerr Logo"
|
||||
/>
|
||||
<nav className="relative z-50">
|
||||
<ul
|
||||
className=" bg-cool-gray-800 bg-opacity-50 border border-cool-gray-600 rounded-md divide-y divide-cool-gray-600 md:flex md:divide-y-0"
|
||||
style={{ backdropFilter: 'blur(5px)' }}
|
||||
>
|
||||
<SetupSteps
|
||||
stepNumber={1}
|
||||
description={'Login with Plex'}
|
||||
active={currentStep === 1}
|
||||
completed={currentStep > 1}
|
||||
/>
|
||||
<SetupSteps
|
||||
stepNumber={2}
|
||||
description={'Configure Plex'}
|
||||
active={currentStep === 2}
|
||||
completed={currentStep > 2}
|
||||
/>
|
||||
<SetupSteps
|
||||
stepNumber={3}
|
||||
description={'Configure Services'}
|
||||
active={currentStep === 3}
|
||||
isLastStep
|
||||
/>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="w-full mt-10 p-4 text-white bg-cool-gray-800 bg-opacity-50 border border-cool-gray-600 rounded-md">
|
||||
{currentStep === 1 && (
|
||||
<LoginWithPlex onComplete={() => setCurrentStep(2)} />
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<div>
|
||||
<SettingsPlex onComplete={() => setPlexSettingsComplete(true)} />
|
||||
<div className="mt-8 border-t border-cool-gray-700 pt-5">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
disabled={!plexSettingsComplete}
|
||||
onClick={() => setCurrentStep(3)}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<div>
|
||||
<SettingsServices />
|
||||
<div className="mt-8 border-t border-cool-gray-700 pt-5">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
onClick={() => router.push('/')}
|
||||
>
|
||||
Finish Setup
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Setup;
|
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { NextPage } from 'next';
|
||||
import Setup from '../components/Setup';
|
||||
|
||||
const SetupPage: NextPage = () => {
|
||||
return <Setup />;
|
||||
};
|
||||
|
||||
export default SetupPage;
|
Loading…
Reference in new issue