feat(frontend): Login - split components into multiple subcomponents (#42)
- closes #40 I have divided Login into many subcomponents. I think that each component has a name that clearly states its purpose.
This commit is contained in:
@@ -28,7 +28,7 @@ import type { StageIndex } from "../../models/Stage";
|
||||
|
||||
import { CustomizeStage } from "./stages/Customize";
|
||||
import { DisplayStage } from "./stages/Display";
|
||||
import { LoginStage } from "./stages/Login";
|
||||
import { LoginStage } from "./stages/Login/Login";
|
||||
import { SelectCardStage } from "./stages/SelectCard";
|
||||
import { ThemeStage } from "./stages/Theme";
|
||||
|
||||
@@ -358,7 +358,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
|
||||
</div>
|
||||
{stage === 0 && (
|
||||
<LoginStage
|
||||
onContinueAsGuestClick={() => {
|
||||
onContinueAsGuest={() => {
|
||||
setStage(1);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { JSX, RefObject } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { Button } from "../../../components/Generic/Button";
|
||||
import { CardImage } from "../../../components/Card/CardImage";
|
||||
import {
|
||||
CLIENT_ID,
|
||||
DEMO_GIST,
|
||||
DEMO_REPO,
|
||||
DEMO_USER,
|
||||
DEMO_WAKATIME_USER,
|
||||
GITHUB_PRIVATE_AUTH_URL,
|
||||
GITHUB_PUBLIC_AUTH_URL,
|
||||
HOST,
|
||||
} from "../../../constants";
|
||||
import { FaGithub as GithubIcon } from "react-icons/fa";
|
||||
import { logout } from "../../../redux/slices/user";
|
||||
import {
|
||||
useIsAuthenticated,
|
||||
usePrivateAccess,
|
||||
useUserId,
|
||||
useUserKey,
|
||||
} from "../../../redux/selectors/userSelectors";
|
||||
import { deleteAccount } from "../../../api/user";
|
||||
|
||||
function useOutsideAlerter(
|
||||
ref: RefObject<HTMLElement | null>,
|
||||
action: () => void,
|
||||
) {
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Alert if clicked on outside of element
|
||||
*/
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the event listener
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
// Unbind the event listener on clean up
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [action, ref]);
|
||||
}
|
||||
|
||||
interface LoginStageProps {
|
||||
onContinueAsGuestClick: () => void;
|
||||
}
|
||||
|
||||
export function LoginStage({
|
||||
onContinueAsGuestClick,
|
||||
}: LoginStageProps): JSX.Element {
|
||||
const userId = useUserId();
|
||||
const userKey = useUserKey();
|
||||
const privateAccess = usePrivateAccess();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
dispatch(logout({ userKey: null }));
|
||||
}, [dispatch]);
|
||||
|
||||
const openDeleteModal = () => {
|
||||
setShowDeleteModal(true);
|
||||
};
|
||||
|
||||
const closeDeleteModal = () => {
|
||||
setShowDeleteModal(false);
|
||||
};
|
||||
|
||||
const wrapperRef = useRef(null);
|
||||
useOutsideAlerter(wrapperRef, closeDeleteModal);
|
||||
|
||||
const handleAccountDelete = async () => {
|
||||
const success = await deleteAccount(userId as string, userKey as string);
|
||||
if (success) {
|
||||
handleLogout();
|
||||
window.location.href = `https://github.com/settings/connections/applications/${CLIENT_ID}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Card data
|
||||
const cards = [
|
||||
{
|
||||
demoImageSrc: `/pin?repo=${DEMO_REPO}&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `/top-langs?username=${DEMO_USER}&langs_count=4&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `?username=${DEMO_USER}&include_all_commits=true&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `/wakatime?username=${DEMO_WAKATIME_USER}&langs_count=6&card_width=450&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `/gist?id=${DEMO_GIST}&disable_animations=true`,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-wrap">
|
||||
<div className={clsx("md:flex", { "opacity-25": showDeleteModal })}>
|
||||
<div className="lg:block lg:w-3/5 lg:p-8">
|
||||
<div className="bg-gray-200 rounded-sm w-full h-full m-auto p-8 shadow lg:h-auto">
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
{/* Access Level Management Buttons */}
|
||||
<div className="mb-4">
|
||||
{privateAccess ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href={`https://${HOST}/api/downgrade?user_key=${userKey as string}`}
|
||||
>
|
||||
<Button className="h-12 flex justify-center items-center w-[320px] text-black border border-black bg-white hover:bg-gray-100">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">
|
||||
Downgrade to Public Access
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Switch to public access if you prefer not to share
|
||||
private contributions.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-4">
|
||||
<a href={GITHUB_PRIVATE_AUTH_URL}>
|
||||
<Button className="h-12 flex justify-center items-center w-[320px] text-white bg-blue-500 hover:bg-blue-600">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">
|
||||
Upgrade to Private Access
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Upgrade to include contributions in private repositories
|
||||
for more complete and accurate stats.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Delete Account Button */}
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<Button
|
||||
className="h-12 flex justify-center items-center w-[320px] text-black border border-black bg-white hover:bg-gray-100"
|
||||
onClick={openDeleteModal}
|
||||
>
|
||||
<span className="xl:text-lg text-red-600">
|
||||
Delete Account
|
||||
</span>
|
||||
</Button>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
This will delete your GitHub-Stats-Extended account and then
|
||||
redirect you to a GitHub screen where you can revoke your
|
||||
access token.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Logout Button */}
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<Button
|
||||
className="h-12 flex justify-center items-center w-[320px] text-black border border-black bg-white hover:bg-gray-100"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<span className="xl:text-lg">Log Out</span>
|
||||
</Button>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Log out from GitHub-Stats-Extended.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* User is not logged in - show login options */}
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<a href={GITHUB_PUBLIC_AUTH_URL}>
|
||||
<Button className="h-12 flex justify-center items-center w-[260px] text-white bg-blue-500 hover:bg-blue-600">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">
|
||||
GitHub Public Access
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Generate stats based on your contributions in public
|
||||
repositories.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<a href={GITHUB_PRIVATE_AUTH_URL}>
|
||||
<Button className="h-12 flex justify-center items-center w-[260px] text-black border border-black bg-white hover:bg-gray-100">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">
|
||||
GitHub Private Access
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Include contributions from private repositories for more
|
||||
complete and accurate stats.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
className="h-12 flex justify-center items-center w-[260px] text-black border border-black bg-white hover:bg-gray-100"
|
||||
onClick={onContinueAsGuestClick}
|
||||
>
|
||||
<span className="ml-2 xl:text-lg">Continue as Guest</span>
|
||||
</Button>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Explore options using sample data. Insert your own username
|
||||
in the last step.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-full lg:w-2/5 flex lg:flex-col lg:p-8 relative overflow-hidden">
|
||||
<div className="relative w-full h-full">
|
||||
{cards.map((card, index) => {
|
||||
const radius = 60;
|
||||
const centerX = 70;
|
||||
const startAngle = Math.PI * 0.75; // 135 degrees
|
||||
const endAngle = Math.PI * 1.25; // 225 degrees
|
||||
|
||||
const angle =
|
||||
startAngle +
|
||||
(endAngle - startAngle) * (index / (cards.length - 1));
|
||||
const x = centerX + radius * Math.cos(angle);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={card.demoImageSrc}
|
||||
style={{
|
||||
left: `${x}%`,
|
||||
position: "relative",
|
||||
zoom: "0.5",
|
||||
marginBottom: "1%",
|
||||
}}
|
||||
>
|
||||
<CardImage
|
||||
imageSrc={card.demoImageSrc}
|
||||
compact={false}
|
||||
stage={0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showDeleteModal && (
|
||||
<div>
|
||||
<div className="fixed left-0 top-0 w-full h-full">
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<div
|
||||
className="w-96 p-4 bg-white rounded-sm border-2 border-gray-200"
|
||||
ref={wrapperRef}
|
||||
>
|
||||
<p className="mb-1 text-2xl text-gray-700">Delete Account</p>
|
||||
<hr />
|
||||
<br />
|
||||
<p>
|
||||
Are you sure you want to delete your account from GitHub
|
||||
Trends?
|
||||
</p>
|
||||
<br />
|
||||
<div className="flex flex-wrap">
|
||||
<Button
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white rounded-[0.25rem]"
|
||||
onClick={() => {
|
||||
setShowDeleteModal(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-gray-200 hover:bg-gray-300 ml-auto rounded-[0.25rem] text-red-600 border-2"
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={handleAccountDelete}
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { JSX } from "react";
|
||||
|
||||
import { useIsAuthenticated } from "../../../../redux/selectors/userSelectors";
|
||||
|
||||
import { LoginOptions } from "./LoginOptions";
|
||||
import { LoginAccountManagement } from "./LoginAccountManagement";
|
||||
|
||||
interface LoginStageProps {
|
||||
onContinueAsGuest: () => void;
|
||||
}
|
||||
|
||||
export function LoginStage({
|
||||
onContinueAsGuest,
|
||||
}: LoginStageProps): JSX.Element {
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <LoginAccountManagement />;
|
||||
}
|
||||
|
||||
return <LoginOptions onContinueAsGuest={onContinueAsGuest} />;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useEffect, useRef, type JSX, type RefObject } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import { Button } from "../../../../components/Generic/Button";
|
||||
|
||||
function useOutsideAlerter(
|
||||
ref: RefObject<HTMLElement | null>,
|
||||
action: () => void,
|
||||
) {
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Alert if clicked on outside of element
|
||||
*/
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the event listener
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
// Unbind the event listener on clean up
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [action, ref]);
|
||||
}
|
||||
|
||||
interface LoginAccountDeleteModalProps {
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export function LoginAccountDeleteModal(
|
||||
props: LoginAccountDeleteModalProps,
|
||||
): JSX.Element {
|
||||
const { onConfirm, onClose } = props;
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
useOutsideAlerter(wrapperRef, onClose);
|
||||
|
||||
return createPortal(
|
||||
<div className="fixed left-0 top-0 w-full h-full">
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<div
|
||||
className="w-96 p-4 bg-white rounded-sm border-2 border-gray-200"
|
||||
ref={wrapperRef}
|
||||
>
|
||||
<p className="mb-1 text-2xl text-gray-700">Delete Account</p>
|
||||
<hr />
|
||||
<br />
|
||||
<p>
|
||||
Are you sure you want to delete your account from GitHub Trends?
|
||||
</p>
|
||||
<br />
|
||||
<div className="flex flex-wrap">
|
||||
<Button
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white rounded-[0.25rem]"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-gray-200 hover:bg-gray-300 ml-auto rounded-[0.25rem] text-red-600 border-2"
|
||||
onClick={onConfirm}
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { useCallback, useState, type JSX } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { FaGithub as GithubIcon } from "react-icons/fa";
|
||||
|
||||
import {
|
||||
usePrivateAccess,
|
||||
useUserId,
|
||||
useUserKey,
|
||||
} from "../../../../redux/selectors/userSelectors";
|
||||
import { deleteAccount } from "../../../../api/user";
|
||||
import { Button } from "../../../../components/Generic/Button";
|
||||
import {
|
||||
CLIENT_ID,
|
||||
HOST,
|
||||
GITHUB_PRIVATE_AUTH_URL,
|
||||
} from "../../../../constants";
|
||||
import { LoginAccountDeleteModal } from "./LoginAccountDeleteModal";
|
||||
import { logout } from "../../../../redux/slices/user";
|
||||
|
||||
import { LoginBox } from "./LoginBox";
|
||||
|
||||
export function LoginAccountManagement(): JSX.Element {
|
||||
const userId = useUserId();
|
||||
const userKey = useUserKey();
|
||||
const privateAccess = usePrivateAccess();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const openDeleteModal = () => {
|
||||
setShowDeleteModal(true);
|
||||
};
|
||||
|
||||
const closeDeleteModal = () => {
|
||||
setShowDeleteModal(false);
|
||||
};
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
dispatch(logout({ userKey: null }));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleAccountDelete = async () => {
|
||||
const success = await deleteAccount(userId as string, userKey as string);
|
||||
if (success) {
|
||||
handleLogout();
|
||||
window.location.href = `https://github.com/settings/connections/applications/${CLIENT_ID}`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<LoginBox isOpaque={showDeleteModal}>
|
||||
<div className="mb-4">
|
||||
{privateAccess ? (
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href={`https://${HOST}/api/downgrade?user_key=${userKey as string}`}
|
||||
>
|
||||
<Button className="h-12 flex justify-center items-center w-[320px] text-black border border-black bg-white hover:bg-gray-100">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">
|
||||
Downgrade to Public Access
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Switch to public access if you prefer not to share private
|
||||
contributions.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-4">
|
||||
<a href={GITHUB_PRIVATE_AUTH_URL}>
|
||||
<Button className="h-12 flex justify-center items-center w-[320px] text-white bg-blue-500 hover:bg-blue-600">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">
|
||||
Upgrade to Private Access
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Upgrade to include contributions in private repositories for more
|
||||
complete and accurate stats.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Delete Account Button */}
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<Button
|
||||
className="h-12 flex justify-center items-center w-[320px] text-black border border-black bg-white hover:bg-gray-100"
|
||||
onClick={openDeleteModal}
|
||||
>
|
||||
<span className="xl:text-lg text-red-600">Delete Account</span>
|
||||
</Button>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
This will delete your GitHub-Stats-Extended account and then redirect
|
||||
you to a GitHub screen where you can revoke your access token.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Logout Button */}
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<Button
|
||||
className="h-12 flex justify-center items-center w-[320px] text-black border border-black bg-white hover:bg-gray-100"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<span className="xl:text-lg">Log Out</span>
|
||||
</Button>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Log out from GitHub-Stats-Extended.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{showDeleteModal && (
|
||||
<LoginAccountDeleteModal
|
||||
onClose={closeDeleteModal}
|
||||
onConfirm={() => {
|
||||
void handleAccountDelete();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</LoginBox>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { JSX, ReactNode } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { LoginBoxDemoCards } from "./LoginBoxDemoCards";
|
||||
|
||||
interface LoginBoxProps {
|
||||
children: ReactNode;
|
||||
|
||||
isOpaque?: boolean;
|
||||
}
|
||||
|
||||
export function LoginBox(props: LoginBoxProps): JSX.Element {
|
||||
const { children, isOpaque = false } = props;
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-wrap">
|
||||
<div className={clsx("md:flex", { "opacity-25": isOpaque })}>
|
||||
<div className="lg:block lg:w-3/5 lg:p-8">
|
||||
<div className="bg-gray-200 rounded-sm w-full h-full m-auto p-8 shadow lg:h-auto">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LoginBoxDemoCards />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { JSX } from "react";
|
||||
|
||||
import { CardImage } from "../../../../components/Card/CardImage";
|
||||
import {
|
||||
DEMO_REPO,
|
||||
DEMO_USER,
|
||||
DEMO_WAKATIME_USER,
|
||||
DEMO_GIST,
|
||||
} from "../../../../constants";
|
||||
|
||||
const cards: Array<{ demoImageSrc: string }> = [
|
||||
{
|
||||
demoImageSrc: `/pin?repo=${DEMO_REPO}&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `/top-langs?username=${DEMO_USER}&langs_count=4&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `?username=${DEMO_USER}&include_all_commits=true&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `/wakatime?username=${DEMO_WAKATIME_USER}&langs_count=6&card_width=450&disable_animations=true`,
|
||||
},
|
||||
{
|
||||
demoImageSrc: `/gist?id=${DEMO_GIST}&disable_animations=true`,
|
||||
},
|
||||
];
|
||||
|
||||
function getCardXPosition(cardIndex: number): number {
|
||||
const radius = 60;
|
||||
const centerX = 70;
|
||||
const startAngle = Math.PI * 0.75; // 135 degrees
|
||||
const endAngle = Math.PI * 1.25; // 225 degrees
|
||||
|
||||
const angle =
|
||||
startAngle + (endAngle - startAngle) * (cardIndex / (cards.length - 1));
|
||||
const x = centerX + radius * Math.cos(angle);
|
||||
return x;
|
||||
}
|
||||
|
||||
export function LoginBoxDemoCards(): JSX.Element {
|
||||
return (
|
||||
<div className="w-full h-full lg:w-2/5 flex lg:flex-col lg:p-8 relative overflow-hidden">
|
||||
<div className="relative w-full h-full">
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={card.demoImageSrc}
|
||||
style={{
|
||||
left: `${getCardXPosition(index)}%`,
|
||||
position: "relative",
|
||||
zoom: "0.5",
|
||||
marginBottom: "1%",
|
||||
}}
|
||||
>
|
||||
<CardImage imageSrc={card.demoImageSrc} compact={false} stage={0} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { JSX } from "react";
|
||||
import { FaGithub as GithubIcon } from "react-icons/fa";
|
||||
|
||||
import { Button } from "../../../../components/Generic/Button";
|
||||
import {
|
||||
GITHUB_PUBLIC_AUTH_URL,
|
||||
GITHUB_PRIVATE_AUTH_URL,
|
||||
} from "../../../../constants";
|
||||
|
||||
import { LoginBox } from "./LoginBox";
|
||||
|
||||
interface LoginOptionsProps {
|
||||
onContinueAsGuest: () => void;
|
||||
}
|
||||
|
||||
export function LoginOptions(props: LoginOptionsProps): JSX.Element {
|
||||
const { onContinueAsGuest } = props;
|
||||
|
||||
return (
|
||||
<LoginBox>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<a href={GITHUB_PUBLIC_AUTH_URL}>
|
||||
<Button className="h-12 flex justify-center items-center w-[260px] text-white bg-blue-500 hover:bg-blue-600">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">GitHub Public Access</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Generate stats based on your contributions in public repositories.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<a href={GITHUB_PRIVATE_AUTH_URL}>
|
||||
<Button className="h-12 flex justify-center items-center w-[260px] text-black border border-black bg-white hover:bg-gray-100">
|
||||
<GithubIcon className="w-6 h-6" />
|
||||
<span className="ml-2 xl:text-lg">GitHub Private Access</span>
|
||||
</Button>
|
||||
</a>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Include contributions from private repositories for more complete and
|
||||
accurate stats.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
className="h-12 flex justify-center items-center w-[260px] text-black border border-black bg-white hover:bg-gray-100"
|
||||
onClick={onContinueAsGuest}
|
||||
>
|
||||
<span className="ml-2 xl:text-lg">Continue as Guest</span>
|
||||
</Button>
|
||||
<p className="text-sm text-gray-600 flex-1">
|
||||
Explore options using sample data. Insert your own username in the
|
||||
last step.
|
||||
</p>
|
||||
</div>
|
||||
</LoginBox>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user