feat: add spinner component, add layout context

This commit is contained in:
2023-12-31 11:34:24 +01:00
parent 1e09839696
commit dd9cc3e3aa
8 changed files with 126 additions and 21 deletions

View File

@@ -1,27 +1,36 @@
import { ReactNode } from 'react'
import { Outlet } from 'react-router-dom'
import { Outlet, useOutletContext } from 'react-router-dom'
import { Header, HeaderProps } from '@/components'
import { Header, HeaderProps, Spinner } from '@/components'
import { useMeQuery } from '@/services/auth/auth.service'
import s from './layout.module.scss'
type AuthContext = {
isAuthenticated: boolean
}
export function useAuthContext() {
return useOutletContext<AuthContext>()
}
export const Layout = () => {
const { data, isError, isLoading } = useMeQuery()
const isAuthenticated = !isError && !isLoading
if (isLoading) {
return <div>loading...</div>
return <Spinner fullScreen />
}
return (
<LayoutPrimitive
avatar={data?.avatar ?? null}
email={data?.email ?? ''}
isLoggedIn={!isError && !!data}
isLoggedIn={isAuthenticated}
onLogout={() => {}}
userName={data?.name ?? ''}
>
<Outlet />
<Outlet context={{ isAuthenticated } satisfies AuthContext} />
</LayoutPrimitive>
)
}

View File

@@ -1,3 +1,4 @@
export * from './spinner'
export * from './avatar'
export * from './dropdown'
export * from '../layout/header'

View File

@@ -0,0 +1 @@
export * from './spinner'

View File

@@ -0,0 +1,57 @@
.fullScreenContainer {
position: fixed;
z-index: 9999;
top: var(--header-height);
left: 0;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: calc(100vh - var(--header-height));
background-color: rgb(0 0 0 / 50%);
}
.loader {
position: relative;
display: inline-block;
box-sizing: border-box;
width: 48px;
height: 48px;
border: 3px solid #fff;
border-radius: 50%;
animation: rotation 1s linear infinite;
}
.loader::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-sizing: border-box;
width: 40px;
height: 40px;
border: 3px solid transparent;
border-bottom-color: var(--color-accent-500);
border-radius: 50%;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Spinner } from './'
const meta = {
component: Spinner,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
title: 'Components/Spinner',
} satisfies Meta<typeof Spinner>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}

View File

@@ -0,0 +1,30 @@
import { CSSProperties, ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'
import { clsx } from 'clsx'
import s from './spinner.module.scss'
export type SpinnerProps = {
fullScreen?: boolean
size?: CSSProperties['width']
} & ComponentPropsWithoutRef<'span'>
export const Spinner = forwardRef<ElementRef<'span'>, SpinnerProps>(
({ className, fullScreen, size = '48px', style, ...rest }, ref) => {
const styles = {
height: size,
width: size,
...style,
} satisfies CSSProperties
if (fullScreen) {
return (
<div className={s.fullScreenContainer}>
<span className={clsx(s.loader, className)} ref={ref} style={styles} {...rest} />
</div>
)
}
return <span className={clsx(s.loader, className)} ref={ref} style={styles} {...rest} />
}
)

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'
import { Button, DecksTable, Page, Slider, TextField, Typography } from '@/components'
import { Button, DecksTable, Page, Slider, Spinner, TextField, Typography } from '@/components'
import { DeckDialog } from '@/components/decks/deck-dialog'
import { DeleteDeckDialog } from '@/components/decks/delete-deck-dialog'
import { Pagination } from '@/components/ui/pagination'
@@ -81,7 +81,7 @@ export const DecksPage = () => {
const openCreateModal = () => setShowCreateModal(true)
if (!decks || !me) {
return <div>loading...</div>
return <Spinner fullScreen />
}
return (

View File

@@ -6,11 +6,10 @@ import {
createBrowserRouter,
} from 'react-router-dom'
import { Layout } from '@/components/layout'
import { Layout, useAuthContext } from '@/components/layout'
import { DeckPage } from '@/pages/deck-page/deck-page'
import { DecksPage, SignInPage } from './pages'
import { useMeQuery } from './services/auth/auth.service'
const publicRoutes: RouteObject[] = [
{
@@ -49,22 +48,11 @@ const router = createBrowserRouter([
])
export const Router = () => {
const { isLoading } = useMeQuery()
if (isLoading) {
return <div>loading...</div>
}
return <RouterProvider router={router} />
}
function PrivateRoutes() {
const { isError, isLoading } = useMeQuery()
if (isLoading) {
return <div>loading...</div>
}
const isAuthenticated = !isError
const { isAuthenticated } = useAuthContext()
return isAuthenticated ? <Outlet /> : <Navigate to={'/login'} />
}