Skip to content

Commit

Permalink
feat: header with avatar
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei committed Jun 15, 2023
1 parent 0151192 commit aeaeacd
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 45 deletions.
18 changes: 16 additions & 2 deletions next.config.mts → next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { config } from 'dotenv'
import type { NextConfig } from 'next'

import NextBundleAnalyzer from '@next/bundle-analyzer'
import { withSentryConfig } from '@sentry/nextjs'
Expand All @@ -13,12 +12,27 @@ process.title = 'Springtide (NextJS)'
const env = config().parsed || {}
const isProd = process.env.NODE_ENV === 'production'

/**
* @type {import('next').nextConfig}
*/
// eslint-disable-next-line import/no-mutable-exports
let nextConfig: NextConfig = {
let nextConfig = {
experimental: {
appDir: true,
},

images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
dangerouslyAllowSVG: true,
contentSecurityPolicy:
"default-src 'self'; script-src 'none'; sandbox; style-src 'unsafe-inline';",
},

webpack: (config, options) => {
config.externals.push({
'utf-8-validate': 'commonjs utf-8-validate',
Expand Down
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client'

import { useAggregation } from '~/hooks/data/use-aggregation'
import { useAggregationQuery } from '~/hooks/data/use-aggregation'

export default function Home() {
const { data } = useAggregation()
const { data } = useAggregationQuery()
// throw new Error()
return <main>{data?.user.avatar}</main>
}
7 changes: 6 additions & 1 deletion src/components/layout/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BluredBackground } from './BluredBackground'
import { HeaderContent } from './HeaderContent'
import { HeaderDataConfigureProvider } from './HeaderDataConfigureProvider'
import { Logo } from './Logo'
import { SiteOwnerAvatar } from './SiteOwnerAvatar'

export const Header = () => {
return (
Expand All @@ -17,11 +18,15 @@ const MemoedHeader = memo(() => (
<header className="fixed left-0 right-0 top-0 z-[9] h-[4.5rem]">
<BluredBackground />
<div className="relative mx-auto grid h-full min-h-0 max-w-7xl grid-cols-[4.5rem_auto_8rem] lg:px-8">
<Logo />
<div className="relative">
<Logo />
<SiteOwnerAvatar />
</div>
<div className="flex min-w-0 flex-grow">
<div className="flex flex-grow items-center justify-center">
<HeaderContent />
</div>
d
</div>
<div className="flex items-center">
<Logo />
Expand Down
75 changes: 38 additions & 37 deletions src/components/layout/header/HeaderContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ const AnimatedMenu: Component = ({ children }) => {
)
}

function ForDesktop({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
const ForDesktop: Component = ({ className }) => {
const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
const radius = useMotionValue(0)
Expand All @@ -63,12 +60,11 @@ function ForDesktop({
[mouseX, mouseY, radius],
)

const pathname = usePathname()

const { config: headerMenuConfig } = useHeaderConfig()

return (
<nav
<motion.nav
layout="size"
onMouseMove={handleMouseMove}
className={clsxm(
'relative',
Expand All @@ -78,43 +74,48 @@ function ForDesktop({

className,
)}
{...props}
>
<ul className="flex bg-transparent px-4 font-medium text-zinc-800 dark:text-zinc-200 ">
{headerMenuConfig.map((section) => {
const href = section.path
const isActive = pathname === href || pathname.startsWith(`${href}/`)
return (
<MenuPopover subMenu={section.subMenu} key={href}>
<NavItem
href={href}
isActive={isActive}
className={clsxm(
'[&:hover_.icon]:-translate-x-[calc(100%+6px)] [&:hover_.icon]:opacity-100',
'[&.active_.icon]:-translate-x-[calc(100%+6px)] [&.active_.icon]:opacity-80',
'[&.active]:pl-6',
)}
>
<span className="relative">
<span
className={clsxm(
'pointer-events-none absolute bottom-0 left-0 top-0 flex items-center opacity-0 duration-200',
'icon',
)}
>
{section.icon}
</span>
{section.title}
</span>
</NavItem>
</MenuPopover>
)
return <HeaderMenuItem section={section} key={section.path} />
})}
</ul>
</nav>
</motion.nav>
)
}

const HeaderMenuItem = memo<{
section: IHeaderMenu
}>(({ section }) => {
const pathname = usePathname()
const href = section.path
const isActive = pathname === href || pathname.startsWith(`${href}/`)
return (
<MenuPopover subMenu={section.subMenu} key={href}>
<AnimatedItem
href={href}
isActive={isActive}
className={clsxm(
'transition-[padding] [&:hover_.icon]:-translate-x-[calc(100%+6px)] [&:hover_.icon]:opacity-100',
'[&.active_.icon]:-translate-x-[calc(100%+6px)] [&.active_.icon]:opacity-80',
'[&.active]:pl-6',
)}
>
<span className="relative">
<span
className={clsxm(
'pointer-events-none absolute bottom-0 left-0 top-0 flex items-center opacity-0 duration-200',
'icon',
)}
>
{section.icon}
</span>
{section.title}
</span>
</AnimatedItem>
</MenuPopover>
)
})
const MenuPopover: Component<{
subMenu: IHeaderMenu['subMenu']
}> = memo(({ children, subMenu }) => {
Expand Down Expand Up @@ -151,7 +152,7 @@ const MenuPopover: Component<{
)
})

function NavItem({
function AnimatedItem({
href,
children,
className,
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/header/HeaderDataConfigureProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { createContext, useContext, useEffect, useMemo, useState } from 'react'

import { useAggregation } from '~/hooks/data/use-aggregation'
import { useAggregationQuery } from '~/hooks/data/use-aggregation'
import { cloneDeep } from '~/lib/_'

import { headerMenuConfig as baseHeaderMenuConfig } from './config'
Expand All @@ -14,7 +14,7 @@ const HeaderMenuConfigContext = createContext({

export const useHeaderConfig = () => useContext(HeaderMenuConfigContext)
export const HeaderDataConfigureProvider: Component = ({ children }) => {
const { data } = useAggregation()
const { data } = useAggregationQuery()
const [headerMenuConfig, setHeaderMenuConfig] = useState(baseHeaderMenuConfig)

useEffect(() => {
Expand Down
22 changes: 22 additions & 0 deletions src/components/layout/header/SiteOwnerAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'

import Image from 'next/image'

import { useAggregationSelector } from '~/providers/root/aggregation-data-provider'

export const SiteOwnerAvatar = () => {
const avatar = useAggregationSelector((data) => data.user.avatar)

if (!avatar) return
return (
<div className="absolute bottom-[8px] right-[-5px] overflow-hidden rounded-full border-[1.5px] border-accent/50">
<Image
src={avatar}
alt=""
width={25}
height={25}
className="rounded-full border-[1.5px] border-transparent"
/>
</div>
)
}
7 changes: 6 additions & 1 deletion src/hooks/data/use-aggregation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useQuery } from '@tanstack/react-query'
import type { AggregateRoot } from '@mx-space/api-client'
import type { UseQueryOptions } from '@tanstack/react-query'

import { aggregation } from '../../queries/definition/aggregation'

export const useAggregation = () => {
export const useAggregationQuery = (
options?: UseQueryOptions<AggregateRoot, unknown, AggregateRoot, string[]>,
) => {
return useQuery({
...aggregation.root(),
...options,
})
}
41 changes: 41 additions & 0 deletions src/providers/root/aggregation-data-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useCallback, useEffect } from 'react'
import { atom, useAtomValue } from 'jotai'
import { selectAtom } from 'jotai/utils'
import type { AggregateRoot } from '@mx-space/api-client'
import type { FC, PropsWithChildren } from 'react'

import { useAggregationQuery } from '~/hooks/data/use-aggregation'
import { jotaiStore } from '~/lib/store'

const aggregationDataAtom = atom<null | AggregateRoot>(null)

export const AggregationProvider: FC<PropsWithChildren> = ({ children }) => {
const { data } = useAggregationQuery()

useEffect(() => {
if (!data) return
jotaiStore.set(aggregationDataAtom, data)
}, [data])

return children
}

/**
* Not recommended to use
*/
export const useAggregationData = () => useAtomValue(aggregationDataAtom)

export const useAggregationSelector = <T,>(
selector: (atomValue: AggregateRoot) => T,
deps: any[] = [],
): T | null =>
useAtomValue(
// @ts-expect-error
selectAtom(
aggregationDataAtom,
useCallback(
(atomValue) => (!atomValue ? null : selector(atomValue)),
deps,
),
),
)
2 changes: 2 additions & 0 deletions src/providers/root/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react'
import { ThemeProvider } from 'next-themes'
import type { PropsWithChildren } from 'react'

import { AggregationProvider } from './aggregation-data-provider'
import { DebugProvider } from './debug-provider'
import { JotaiStoreProvider } from './jotai-provider'
import { PageScrollInfoProvider } from './page-scroll-info-provider'
Expand All @@ -23,6 +24,7 @@ const contexts: JSX.Element[] = [
<ThemeProvider key="themeProvider" />,
<ReactQueryProvider key="reactQueryProvider" />,
<JotaiStoreProvider key="jotaiStoreProvider" />,
<AggregationProvider key="aggregationProvider" />,
<ViewportProvider key="viewportProvider" />,
<SocketProvider key="socketProvider" />,
<PageScrollInfoProvider key="PageScrollInfoProvider" />,
Expand Down

0 comments on commit aeaeacd

Please sign in to comment.