mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
[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>
This commit is contained in:
@@ -18,8 +18,8 @@ const PlexLoginButton: React.FC<PlexLoginButtonProps> = ({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const authToken = await plexOAuth.login();
|
const authToken = await plexOAuth.login();
|
||||||
onAuthToken(authToken);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
onAuthToken(authToken);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (onError) {
|
if (onError) {
|
||||||
onError(e.message);
|
onError(e.message);
|
||||||
|
@@ -48,7 +48,11 @@ interface SyncStatus {
|
|||||||
libraries: Library[];
|
libraries: Library[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsPlex: React.FC = () => {
|
interface SettingsPlexProps {
|
||||||
|
onComplete?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { data, error, revalidate } = useSWR<PlexSettings>(
|
const { data, error, revalidate } = useSWR<PlexSettings>(
|
||||||
'/api/v1/settings/plex'
|
'/api/v1/settings/plex'
|
||||||
@@ -78,6 +82,9 @@ const SettingsPlex: React.FC = () => {
|
|||||||
} as PlexSettings);
|
} as PlexSettings);
|
||||||
|
|
||||||
revalidate();
|
revalidate();
|
||||||
|
if (onComplete) {
|
||||||
|
onComplete();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSubmitError(e.response.data.message);
|
setSubmitError(e.response.data.message);
|
||||||
} finally {
|
} finally {
|
||||||
|
54
src/components/Setup/LoginWithPlex.tsx
Normal file
54
src/components/Setup/LoginWithPlex.tsx
Normal file
@@ -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;
|
76
src/components/Setup/SetupSteps.tsx
Normal file
76
src/components/Setup/SetupSteps.tsx
Normal file
@@ -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;
|
101
src/components/Setup/index.tsx
Normal file
101
src/components/Setup/index.tsx
Normal file
@@ -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;
|
9
src/pages/setup.tsx
Normal file
9
src/pages/setup.tsx
Normal file
@@ -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;
|
Reference in New Issue
Block a user