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

Feat/dashboard #44

Merged
merged 10 commits into from
Dec 12, 2023
4 changes: 2 additions & 2 deletions app/(stateless-toolbar)/browse/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const fetchTags = async () => {
const Browse = async () => {
const tags = await fetchTags()
return (
<div>
<div className="h-full flex flex-col">
<div className="sticky top-0 z-10 bg-shark-950 p-4 md:p-10">
<div className="w-fit bg-c4-gradient-green bg-clip-text text-[56px] font-extrabold leading-none text-transparent">
Topics
Expand All @@ -24,7 +24,7 @@ const Browse = async () => {
let’s start watching
</div>
</div>
<div className="h-full overflow-auto">
<div className="overflow-y-auto grow">
{tags
.filter(({ urls }) => !!urls.length)
.map(({ name, urls }) => (
Expand Down
75 changes: 75 additions & 0 deletions app/(stateless-toolbar)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client"

import { useEffect, useState } from "react"
import { useAuth } from "@/contexts/AuthContext"
import { useJwtStore } from "@/store/jwt"
import moment from "moment"

import RequireAuth from "@/components/helper/RequireAuth"

type UserStats = {
likeCount: number
memberSince: string
siteCount: number
}

const Dashboard = async () => {
const { socialLogin } = useAuth()
const { token, userId } = useJwtStore()
const [stats, setStats] = useState<UserStats | null>(null)
useEffect(() => {
const fetchUserData = async () => {
if (!token || !userId) return
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/${userId}`,
{ headers: { Authorization: `Bearer ${token}` } }
)
const { user } = await res.json()
setStats({
likeCount: user.likedUrls.length,
memberSince: user.createdAt,
siteCount: user.submittedUrls.length,
})
}
fetchUserData()
}, [token, userId])

return (
<RequireAuth>
<div className="flex h-full max-w-full flex-col items-center justify-center">
<div className="max-w-full break-words px-4">
<div className="flex items-center gap-4">
<div className="text-2xl/none font-bold text-shark-300">
Login with
</div>
{socialLogin && (
<div className="rounded-2xl bg-shark-600 px-2 py-1 text-sm/none">
{socialLogin.provider}
</div>
)}
</div>
<div className="mt-6 text-2xl/none text-shark-100">
{socialLogin ? `Email: ${socialLogin.email}` : "Web3 Login"}
</div>
<div className="mt-4 text-shark-200">
Member since {moment(stats?.memberSince).format("MMMM Do, YYYY")}
</div>
<div className="mt-12 text-2xl/none font-bold text-shark-300">
Websites submitted
</div>
<div className="mt-6 text-[56px]/none font-extrabold text-shark-50">
{stats?.siteCount ?? 0}
</div>
<div className="mt-10 text-2xl/none font-bold text-shark-300">
Likes submitted
</div>
<div className="mt-6 text-[56px]/none font-extrabold text-shark-50">
{stats?.likeCount ?? 0}
</div>
</div>
</div>
</RequireAuth>
)
}

export default Dashboard
2 changes: 1 addition & 1 deletion components/helper/RequireAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const RequireAuth = ({ children }: Props) => {
const { initializingW3A, signIn, signedIn } = useAuth()
if (initializingW3A) {
return (
<div className="flex h-full flex-col items-center justify-center px-4">
<div className="flex h-full flex-col items-center justify-center px-4 text-center">
<h2>Checking Web3Auth Connection...</h2>
<Loader2 className="mt-4 animate-spin stroke-c4-green" size={36} />
</div>
Expand Down
62 changes: 22 additions & 40 deletions components/main-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from "next/link"
import browserIcon from "@/assets/browser-icon.svg"
import xIcon from "@/assets/x-icon.svg"
import { useAuth } from "@/contexts/AuthContext"
import { Loader2 } from "lucide-react"

import { siteConfig } from "@/config/site"

Expand All @@ -24,12 +25,14 @@ export default function MainMenu({ open, onClose }: MainMenuProps) {
}}
>
{initializingW3A ? (
// TODO: Add spinner component
<div className="flex justify-center">Initializing Web3Auth...</div>
<div className="flex flex-col items-center justify-center">
Initializing Web3Auth
<Loader2 className="mt-2 animate-spin" size={22} />
</div>
) : (
<>
{signedIn && (
<div className="mb-4 md:mb-6">
{signedIn ? (
<div>
<Link href={siteConfig.mainNav.addSite.href}>
<div className="flex items-center justify-between border-b border-shark-800 p-3 transition-all hover:border-c4-green md:p-4">
<div className="w-full bg-c4-gradient-separator bg-clip-text text-sm text-transparent md:text-base">
Expand All @@ -44,43 +47,22 @@ export default function MainMenu({ open, onClose }: MainMenuProps) {
</div>
</Link>
</div>
) : (
<div
className="cursor-pointer border-b border-shark-800 p-3 text-sm text-shark-300 transition-all hover:border-c4-green hover:text-shark-200 md:p-4 md:text-base"
onClick={() => {
signIn()
onClose()
}}
>
Sign in / Sign up
</div>
)}
<div className="mb-4 md:mb-6">
{signedIn ? (
<Link prefetch href={siteConfig.mainNav.browseTopics.href}>
<div className="border-b border-shark-800 p-3 text-sm text-shark-300 transition-all hover:border-c4-green hover:text-shark-200 md:p-4 md:text-base">
{siteConfig.mainNav.browseTopics.title}
</div>
</Link>
) : (
<div
className="cursor-pointer border-b border-shark-800 p-3 text-sm text-shark-300 transition-all hover:border-c4-green hover:text-shark-200 md:p-4 md:text-base"
onClick={() => {
signIn()
onClose()
}}
>
Sign in / Sign up
</div>
)}
<Link href={siteConfig.mainNav.stats.href}>
<div className="border-b border-shark-800 p-3 text-sm text-shark-300 transition-all hover:border-c4-green hover:text-shark-200 md:p-4 md:text-base">
{siteConfig.mainNav.stats.title}
</div>
</Link>
<Link href={siteConfig.mainNav.about.href}>
<div className="border-b border-shark-800 p-3 text-sm text-shark-300 transition-all hover:border-c4-green hover:text-shark-200 md:p-4 md:text-base">
{siteConfig.mainNav.about.title}
</div>
</Link>
{signedIn && (
<Link href={siteConfig.mainNav.feedback.href}>
<div className="border-b border-shark-800 p-3 text-sm text-shark-300 transition-all hover:border-c4-green hover:text-shark-200 md:p-4 md:text-base">
{siteConfig.mainNav.feedback.title}
</div>
</Link>
)}
</div>
<Link href={siteConfig.mainNav.browseTopics.href}>
<div className="border-b border-shark-800 p-3 text-sm text-shark-300 transition-all hover:border-c4-green hover:text-shark-200 md:p-4 md:text-base">
{siteConfig.mainNav.browseTopics.title}
</div>
</Link>
{signedIn && (
<div
className="cursor-pointer p-3 text-sm text-shark-300 transition-all hover:text-shark-200 md:p-4 md:text-base"
Expand Down
1 change: 1 addition & 0 deletions components/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const Toolbar = ({
// Map route to toolbar label
const labelMap: { [path: string]: string } = {
"/browse": "Browse topics",
"/dashboard": "Dashboard",
"/landing": "Welcome to Channel 4",
"/submit-url": "Add a website",
}
Expand Down
22 changes: 22 additions & 0 deletions contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ import {
Provider,
} from "ethers"

type SocialLogin = {
email: string
provider: string
}

type AuthContextType = {
initializingW3A: boolean
socialLogin: SocialLogin | null
signer: JsonRpcSigner | null
signIn: () => void
signedIn: boolean
Expand All @@ -21,6 +27,7 @@ type AuthContextType = {

const AuthContext = createContext<AuthContextType>({
initializingW3A: false,
socialLogin: null,
signer: null,
signIn: () => undefined,
signedIn: false,
Expand All @@ -30,6 +37,7 @@ const AuthContext = createContext<AuthContextType>({
export const AuthProvider: React.FC<{ children: JSX.Element }> = ({
children,
}) => {
const [socialLogin, setSocialLogin] = useState<SocialLogin | null>(null)
const [signer, setSigner] = useState<JsonRpcSigner | null>(null)
const [web3Auth, setWeb3Auth] = useState<Web3Auth | null>(null)
const { token, userId, updateToken, updateUserId } = useJwtStore()
Expand All @@ -39,6 +47,17 @@ export const AuthProvider: React.FC<{ children: JSX.Element }> = ({
return await provider.getSigner()
}

const getProviderInfo = async (w3a: Web3Auth | null) => {
if (!w3a) return
const info = await w3a.getUserInfo()
if ("typeOfLogin" in info) {
setSocialLogin({
email: info.email ?? "",
provider: info.typeOfLogin ?? "",
})
}
}

const signIn = async () => {
try {
const web3AuthProvider = await web3Auth?.connect()
Expand All @@ -60,6 +79,7 @@ export const AuthProvider: React.FC<{ children: JSX.Element }> = ({
).then((response) => response.json())
// handle any server error messages
if (message) throw new Error(message)
await getProviderInfo(web3Auth)
updateToken(token)
updateUserId(user._id)
setSigner(rpcSigner)
Expand Down Expand Up @@ -107,6 +127,7 @@ export const AuthProvider: React.FC<{ children: JSX.Element }> = ({
if (client.status === "connected" && client.provider) {
const rpcSigner = await getSigner(client.provider)
setSigner(rpcSigner)
await getProviderInfo(client)
}
}
init()
Expand All @@ -116,6 +137,7 @@ export const AuthProvider: React.FC<{ children: JSX.Element }> = ({
<AuthContext.Provider
value={{
initializingW3A: !web3Auth,
socialLogin,
signer,
signIn,
signedIn,
Expand Down