Skip to content

Commit

Permalink
feat: add user login feature
Browse files Browse the repository at this point in the history
  • Loading branch information
JimmyLv committed Mar 5, 2023
1 parent bd83307 commit 8f6afc6
Show file tree
Hide file tree
Showing 18 changed files with 1,946 additions and 37 deletions.
6 changes: 5 additions & 1 deletion .example.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
OPENAI_API_KEY=
OPENAI_API_KEY=sk-xxx
BILIBILI_SESSION_TOKEN=
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
UPSTASH_RATE_REDIS_REST_URL=
UPSTASH_RATE_REDIS_REST_TOKEN=
LEMON_API_KEY=
SUPABASE_HOSTNAME="xxxx.supabase.co"
NEXT_PUBLIC_SUPABASE_URL="https://${SUPABASE_HOSTNAME}"
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
10 changes: 7 additions & 3 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Poppins } from "@next/font/google";
import clsx from "clsx";
import Image from "next/image";
import React from "react";
import SignIn from "~/components/SignIn";
import { BASE_DOMAIN } from "~/utils/constants";
import Github from "../components/GitHub";

const poppins = Poppins({ weight: "800", subsets: ["latin"] });
Expand All @@ -22,7 +25,7 @@ export default function Header() {
height={50}
/>
</a>
<a href="https://b.jimmylv.cn">
<a href={BASE_DOMAIN}>
<h2 className={clsx("text-lg sm:text-3xl", poppins.className)}>
<span className="text-pink-400">哔哩哔哩</span> BiliGPT
</h2>
Expand All @@ -48,13 +51,13 @@ export default function Header() {
🔥 <span className="hidden sm:block">给我提</span>反馈?
</a>
<a
href="https://b.jimmylv.cn/ios"
href={BASE_DOMAIN + "/ios"}
rel="noreferrer noopener"
target="_blank"
className="flex items-center space-x-2"
>
<Image src="/shortcuts.png" alt="logo" width={33} height={33} />
<span className="relin-paragraph-target text-slate-500">(iOS版)</span>
<span className="relin-paragraph-target text-slate-500 hidden sm:block">(iOS版)</span>
</a>
<a
href="https://github.com/JimmyLv/BiliGPT"
Expand All @@ -64,6 +67,7 @@ export default function Header() {
>
<Github width="33" height="33" />
</a>
<SignIn />
</div>
</div>
);
Expand Down
38 changes: 38 additions & 0 deletions components/SignIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useUser } from "@supabase/auth-helpers-react";
import { AnimatePresence, motion } from "framer-motion";
import { useSignInModal } from "~/components/sign-in-modal";
import UserDropdown from "~/components/user-dropdown";
import { FADE_IN_ANIMATION_SETTINGS } from "~/utils/constants";

export default function SignIn() {
const user = useUser();
console.log("========user========", user);
const { SignInModal, setShowSignInModal } = useSignInModal();
/*useEffect(() => {
async function loadData() {
const { data } = await supabaseClient.from('test').select('*')
setData(data)
}
// Only run query once user is logged in.
if (user) loadData()
}, [user])*/
return (
<div>
<SignInModal />

<AnimatePresence>
{user ? (
<UserDropdown />
) : (
<motion.button
className="rounded-full border border-black bg-black p-1.5 px-4 text-sm text-white transition-all hover:bg-white hover:text-black"
onClick={() => setShowSignInModal(true)}
{...FADE_IN_ANIMATION_SETTINGS}
>
Sign In
</motion.button>
)}
</AnimatePresence>
</div>
);
}
68 changes: 68 additions & 0 deletions components/shared/leaflet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useEffect, useRef, ReactNode, Dispatch, SetStateAction } from "react";
import { AnimatePresence, motion, useAnimation } from "framer-motion";

export default function Leaflet({
setShow,
children,
}: {
setShow: Dispatch<SetStateAction<boolean>>;
children: ReactNode;
}) {
const leafletRef = useRef<HTMLDivElement>(null);
const controls = useAnimation();
const transitionProps = { type: "spring", stiffness: 500, damping: 30 };
useEffect(() => {
controls.start({
y: 20,
transition: transitionProps,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

async function handleDragEnd(_: any, info: any) {
const offset = info.offset.y;
const velocity = info.velocity.y;
const height = leafletRef.current?.getBoundingClientRect().height || 0;
if (offset > height / 2 || velocity > 800) {
await controls.start({ y: "100%", transition: transitionProps });
setShow(false);
} else {
controls.start({ y: 0, transition: transitionProps });
}
}

return (
<AnimatePresence>
<motion.div
ref={leafletRef}
key="leaflet"
className="group fixed inset-x-0 bottom-0 z-40 w-screen cursor-grab bg-white pb-5 active:cursor-grabbing sm:hidden"
initial={{ y: "100%" }}
animate={controls}
exit={{ y: "100%" }}
transition={transitionProps}
drag="y"
dragDirectionLock
onDragEnd={handleDragEnd}
dragElastic={{ top: 0, bottom: 1 }}
dragConstraints={{ top: 0, bottom: 0 }}
>
<div
className={`rounded-t-4xl -mb-1 flex h-7 w-full items-center justify-center border-t border-gray-200`}
>
<div className="-mr-1 h-1 w-6 rounded-full bg-gray-300 transition-all group-active:rotate-12" />
<div className="h-1 w-6 rounded-full bg-gray-300 transition-all group-active:-rotate-12" />
</div>
{children}
</motion.div>
<motion.div
key="leaflet-backdrop"
className="fixed inset-0 z-30 bg-gray-100 bg-opacity-10 backdrop-blur"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setShow(false)}
/>
</AnimatePresence>
);
}
78 changes: 78 additions & 0 deletions components/shared/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useRef,
} from "react";
import FocusTrap from "focus-trap-react";
import { AnimatePresence, motion } from "framer-motion";
import Leaflet from "./leaflet";
import useWindowSize from "~/lib/hooks/use-window-size";

export default function Modal({
children,
showModal,
setShowModal,
}: {
children: React.ReactNode;
showModal: boolean;
setShowModal: Dispatch<SetStateAction<boolean>>;
}) {
const desktopModalRef = useRef(null);

const onKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") {
setShowModal(false);
}
},
[setShowModal],
);

useEffect(() => {
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [onKeyDown]);

const { isMobile, isDesktop } = useWindowSize();

return (
<AnimatePresence>
{showModal && (
<>
{isMobile && <Leaflet setShow={setShowModal}>{children}</Leaflet>}
{isDesktop && (
<>
<FocusTrap focusTrapOptions={{ initialFocus: false }}>
<motion.div
ref={desktopModalRef}
key="desktop-modal"
className="fixed inset-0 z-40 hidden min-h-screen items-center justify-center md:flex"
initial={{ scale: 0.95 }}
animate={{ scale: 1 }}
exit={{ scale: 0.95 }}
onMouseDown={(e) => {
if (desktopModalRef.current === e.target) {
setShowModal(false);
}
}}
>
{children}
</motion.div>
</FocusTrap>
<motion.div
key="desktop-backdrop"
className="fixed inset-0 z-30 bg-gray-100 bg-opacity-10 backdrop-blur"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setShowModal(false)}
/>
</>
)}
</>
)}
</AnimatePresence>
);
}
42 changes: 42 additions & 0 deletions components/shared/popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Dispatch, SetStateAction, ReactNode, useRef } from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import useWindowSize from "~/lib/hooks/use-window-size";
import Leaflet from "./leaflet";

export default function Popover({
children,
content,
align = "center",
openPopover,
setOpenPopover,
}: {
children: ReactNode;
content: ReactNode | string;
align?: "center" | "start" | "end";
openPopover: boolean;
setOpenPopover: Dispatch<SetStateAction<boolean>>;
}) {
const { isMobile, isDesktop } = useWindowSize();
return (
<>
{isMobile && children}
{openPopover && isMobile && (
<Leaflet setShow={setOpenPopover}>{content}</Leaflet>
)}
{isDesktop && (
<PopoverPrimitive.Root>
<PopoverPrimitive.Trigger className="inline-flex" asChild>
{children}
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Content
sideOffset={4}
align={align}
className="z-20 animate-slide-up-fade items-center rounded-md border border-gray-200 bg-white drop-shadow-lg"
>
{content}
</PopoverPrimitive.Content>
</PopoverPrimitive.Root>
)}
</>
);
}
84 changes: 84 additions & 0 deletions components/sign-in-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useSupabaseClient } from "@supabase/auth-helpers-react";
import { Auth } from "@supabase/auth-ui-react";
import { ThemeSupa } from "@supabase/auth-ui-shared";
import React, {
Dispatch,
SetStateAction,
useCallback,
useMemo,
useState,
} from "react";
import Modal from "~/components/shared/modal";
import Image from "next/image";
import { BASE_DOMAIN } from "~/utils/constants";

const SignInModal = ({
showSignInModal,
setShowSignInModal,
}: {
showSignInModal: boolean;
setShowSignInModal: Dispatch<SetStateAction<boolean>>;
}) => {
const supabaseClient = useSupabaseClient();
return (
<Modal showModal={showSignInModal} setShowModal={setShowSignInModal}>
<div className="w-full overflow-hidden shadow-xl md:max-w-md md:rounded-2xl md:border md:border-gray-200">
<div className="flex flex-col items-center justify-center space-y-3 border-b border-gray-200 bg-white px-4 py-6 pt-8 text-center md:px-16">
<a href={BASE_DOMAIN}>
<Image
src="/tv-logo.png"
alt="Logo"
className="h-10 w-10 rounded-full"
width={20}
height={20}
/>
</a>
<h3 className="font-display text-2xl font-bold">登录</h3>
<p className="text-sm text-pink-400">Prompt, Publish, Profit</p>
</div>

<div className="flex flex-col space-y-4 bg-gray-50 px-4 py-8 md:px-16">
<Auth
supabaseClient={supabaseClient}
providers={[
"notion",
"github",
// "google", "facebook",
// "twitter",
]}
appearance={{
theme: ThemeSupa,
variables: {
default: {
colors: {
brand: "#F17EB8",
brandAccent: "#f88dbf",
// brandButtonText: "white",
},
},
},
}}
/>
</div>
</div>
</Modal>
);
};

export function useSignInModal() {
const [showSignInModal, setShowSignInModal] = useState(false);

const SignInModalCallback = useCallback(() => {
return (
<SignInModal
showSignInModal={showSignInModal}
setShowSignInModal={setShowSignInModal}
/>
);
}, [showSignInModal, setShowSignInModal]);

return useMemo(
() => ({ setShowSignInModal, SignInModal: SignInModalCallback }),
[setShowSignInModal, SignInModalCallback]
);
}
Loading

0 comments on commit 8f6afc6

Please sign in to comment.