mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-16 12:33:18 +00:00
feat: add spinner component, add layout context
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './spinner'
|
||||
export * from './avatar'
|
||||
export * from './dropdown'
|
||||
export * from '../layout/header'
|
||||
|
||||
1
src/components/ui/spinner/index.ts
Normal file
1
src/components/ui/spinner/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './spinner'
|
||||
57
src/components/ui/spinner/spinner.module.scss
Normal file
57
src/components/ui/spinner/spinner.module.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
19
src/components/ui/spinner/spinner.stories.tsx
Normal file
19
src/components/ui/spinner/spinner.stories.tsx
Normal 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: {},
|
||||
}
|
||||
30
src/components/ui/spinner/spinner.tsx
Normal file
30
src/components/ui/spinner/spinner.tsx
Normal 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} />
|
||||
}
|
||||
)
|
||||
@@ -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 (
|
||||
|
||||
@@ -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'} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user