diff --git a/next.config.mts b/next.config.mjs similarity index 88% rename from next.config.mts rename to next.config.mjs index 961889cdb4..3ea1f7dffe 100644 --- a/next.config.mts +++ b/next.config.mjs @@ -1,5 +1,4 @@ import { config } from 'dotenv' -import type { NextConfig } from 'next' import NextBundleAnalyzer from '@next/bundle-analyzer' import { withSentryConfig } from '@sentry/nextjs' @@ -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', diff --git a/src/app/page.tsx b/src/app/page.tsx index 76c7445ee8..4473d41487 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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
{data?.user.avatar}
} diff --git a/src/components/layout/header/Header.tsx b/src/components/layout/header/Header.tsx index 1151347097..9782f9f984 100644 --- a/src/components/layout/header/Header.tsx +++ b/src/components/layout/header/Header.tsx @@ -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 ( @@ -17,11 +18,15 @@ const MemoedHeader = memo(() => (
- +
+ + +
+ d
diff --git a/src/components/layout/header/HeaderContent.tsx b/src/components/layout/header/HeaderContent.tsx index 41c50ce425..2430c987e5 100644 --- a/src/components/layout/header/HeaderContent.tsx +++ b/src/components/layout/header/HeaderContent.tsx @@ -46,10 +46,7 @@ const AnimatedMenu: Component = ({ children }) => { ) } -function ForDesktop({ - className, - ...props -}: React.HTMLAttributes) { +const ForDesktop: Component = ({ className }) => { const mouseX = useMotionValue(0) const mouseY = useMotionValue(0) const radius = useMotionValue(0) @@ -63,12 +60,11 @@ function ForDesktop({ [mouseX, mouseY, radius], ) - const pathname = usePathname() - const { config: headerMenuConfig } = useHeaderConfig() return ( - + ) } +const HeaderMenuItem = memo<{ + section: IHeaderMenu +}>(({ section }) => { + const pathname = usePathname() + const href = section.path + const isActive = pathname === href || pathname.startsWith(`${href}/`) + return ( + + + + + {section.icon} + + {section.title} + + + + ) +}) const MenuPopover: Component<{ subMenu: IHeaderMenu['subMenu'] }> = memo(({ children, subMenu }) => { @@ -151,7 +152,7 @@ const MenuPopover: Component<{ ) }) -function NavItem({ +function AnimatedItem({ href, children, className, diff --git a/src/components/layout/header/HeaderDataConfigureProvider.tsx b/src/components/layout/header/HeaderDataConfigureProvider.tsx index 2dd17d82ea..8fefb33a4a 100644 --- a/src/components/layout/header/HeaderDataConfigureProvider.tsx +++ b/src/components/layout/header/HeaderDataConfigureProvider.tsx @@ -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' @@ -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(() => { diff --git a/src/components/layout/header/SiteOwnerAvatar.tsx b/src/components/layout/header/SiteOwnerAvatar.tsx new file mode 100644 index 0000000000..b57f505887 --- /dev/null +++ b/src/components/layout/header/SiteOwnerAvatar.tsx @@ -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 ( +
+ +
+ ) +} diff --git a/src/hooks/data/use-aggregation.ts b/src/hooks/data/use-aggregation.ts index 1b67ecf1c8..47304dec96 100644 --- a/src/hooks/data/use-aggregation.ts +++ b/src/hooks/data/use-aggregation.ts @@ -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, +) => { return useQuery({ ...aggregation.root(), + ...options, }) } diff --git a/src/providers/root/aggregation-data-provider.tsx b/src/providers/root/aggregation-data-provider.tsx new file mode 100644 index 0000000000..cdd17f0d3a --- /dev/null +++ b/src/providers/root/aggregation-data-provider.tsx @@ -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) + +export const AggregationProvider: FC = ({ 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 = ( + selector: (atomValue: AggregateRoot) => T, + deps: any[] = [], +): T | null => + useAtomValue( + // @ts-expect-error + selectAtom( + aggregationDataAtom, + useCallback( + (atomValue) => (!atomValue ? null : selector(atomValue)), + deps, + ), + ), + ) diff --git a/src/providers/root/index.tsx b/src/providers/root/index.tsx index e341442356..98341e94f5 100644 --- a/src/providers/root/index.tsx +++ b/src/providers/root/index.tsx @@ -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' @@ -23,6 +24,7 @@ const contexts: JSX.Element[] = [ , , , + , , , ,