Commit fda20374 authored by Vũ Đình Nguyên's avatar Vũ Đình Nguyên

feat(page-representatives-employers): creat page-representatives employers

parent 96b115a1
......@@ -92,6 +92,9 @@ importers:
'@tanstack/react-query':
specifier: ^5.90.5
version: 5.90.5(react@19.2.0)
'@uidotdev/usehooks':
specifier: ^2.4.1
version: 2.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
......@@ -113,6 +116,9 @@ importers:
html-react-parser:
specifier: ^5.2.7
version: 5.2.7(@types/react@19.2.2)(react@19.2.0)
lodash-es:
specifier: ^4.17.21
version: 4.17.21
lucide-react:
specifier: ^0.548.0
version: 0.548.0(react@19.2.0)
......@@ -1740,6 +1746,13 @@ packages:
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@uidotdev/usehooks@2.4.1':
resolution: {integrity: sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg==}
engines: {node: '>=16'}
peerDependencies:
react: '>=18.0.0'
react-dom: '>=18.0.0'
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
cpu: [arm]
......@@ -2923,6 +2936,9 @@ packages:
resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==}
engines: {node: '>=20'}
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
lodash.isempty@4.4.0:
resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==}
......@@ -5553,6 +5569,11 @@ snapshots:
'@typescript-eslint/types': 8.46.2
eslint-visitor-keys: 4.2.1
'@uidotdev/usehooks@2.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
optional: true
......@@ -6869,6 +6890,8 @@ snapshots:
dependencies:
p-locate: 6.0.0
lodash-es@4.17.21: {}
lodash.isempty@4.4.0: {}
lodash.merge@4.6.2: {}
......
"use client";
import React, { useState } from "react";
import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
export default function Page() {
const [submitSearch] = useState("");
const [page, setPage] = useState(1);
const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} />
))}
<div className="w-full flex justify-center mt-4">
<Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/>
</div>
</div>
</main>
{/* Sidebar */}
<aside className="space-y-6">
<ListFilter />
<div className="bg-white border rounded-md overflow-hidden">
<div className="w-full h-56 relative bg-gray-100">
<Image
src="/banner.webp"
alt="Quảng cáo"
fill
className="object-cover"
/>
</div>
</div>
</aside>
</div>
</div>
</div>
);
}
......@@ -24,7 +24,7 @@ const CATEGORIES: Category[] = [
]
const ListCategory: React.FC = () => {
const ListCategory: React.FC<{ categories?: Category[] }> = ({ categories = CATEGORIES }) => {
const pathname = usePathname() || ""
const isActive = (href: string) => {
......@@ -38,7 +38,7 @@ const ListCategory: React.FC = () => {
<div className="w-full px-4 sm:px-6 lg:px-8">
<div className="py-3">
<div className="flex flex-wrap gap-4 sm:gap-8 items-center max-w-full overflow-x-auto">
{CATEGORIES.map((c) => {
{categories.map((c) => {
const menu: Menu = { id: c.href, name: c.title, link: c.href }
const active = isActive(c.href)
return (
......
......@@ -11,7 +11,16 @@ const DEFAULT_CATEGORIES: Category[] = [
{ id: 'policy', title: 'Hỏi đáp về chính sách', count: 0 },
{ id: 'biz', title: 'Tin Doanh Nghiệp', count: 9 },
{ id: 'member', title: 'Tin Hội Viên', count: 17 },
{ id: 'law', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 }
{ id: 'law1', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law2', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law3', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law4', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law5', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law6', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law7', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law8', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law9', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
{ id: 'law51', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 },
]
export const ListFilter: React.FC<{
......@@ -20,6 +29,7 @@ export const ListFilter: React.FC<{
onReset?: () => void
}> = ({ categories = DEFAULT_CATEGORIES, onSearch, onReset }) => {
const [query, setQuery] = useState('')
const [visibleCount, setVisibleCount] = useState(5)
const [selected, setSelected] = useState<Record<string, boolean>>(() => {
const map: Record<string, boolean> = {}
categories.forEach((c) => (map[c.id] = false))
......@@ -46,8 +56,8 @@ export const ListFilter: React.FC<{
/>
</div>
{/* <div className="flex flex-col gap-3 mb-6">
{categories.map((c) => (
<div className="flex flex-col gap-3 mb-6">
{categories.slice(0, visibleCount).map((c) => (
<label key={c.id} className="flex items-center gap-3">
<Checkbox checked={!!selected[c.id]} onCheckedChange={() => toggle(c.id)} />
<div className="flex justify-between w-full items-center">
......@@ -56,7 +66,26 @@ export const ListFilter: React.FC<{
</div>
</label>
))}
</div> */}
<div className="mt-2 flex items-center gap-3">
{categories.length > visibleCount && (
<button
className="text-sm text-primary self-start"
onClick={() => setVisibleCount((v) => v + 5)}
>
Xem thêm
</button>
)}
{visibleCount > 5 && (
<button
className="text-sm text-gray-500 self-start"
onClick={() => setVisibleCount(5)}
>
Thu gọn
</button>
)}
</div>
</div>
<div className="flex gap-3">
<Button className="flex-1 rounded-none font-medium text-lg text-white hover:bg-muted-foreground hover:outline-1 outline-primary hover:text-primary" onClick={() => onSearch?.(query)}>
......@@ -70,6 +99,7 @@ export const ListFilter: React.FC<{
const map: Record<string, boolean> = {}
categories.forEach((c) => (map[c.id] = false))
setSelected(map)
setVisibleCount(5)
onReset?.()
}}
>
......
'use client'
// Local simplified menu type instead of importing HeaderMenu
type Menu = {
id: string | number
name: string
......
"use client";
import React, { useState } from "react";
import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { PATHS } from '@constants/paths'
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import {Pagination} from '@components/base/pagination'
import { Pagination} from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from '@api/endpoints/news'
import { GetNewsResponseType } from '@api/types/NewsPage.type'
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
export default function Page() {
const [submitSearch] = useState('')
const [page, setPage] = useState(1)
const [submitSearch] = useState("");
const [page, setPage] = useState(1);
const pageSize = 5
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
})
return (
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory />
<ListCategory
categories={[
{ title: 'Tin liên quan', href: `${PATHS.ownerRepresentatives}/tin-lien-quan` },
{ title: 'Sự kiện', href: `${PATHS.ownerRepresentatives}/su-kien` },
{ title: 'Chủ đề', href: `${PATHS.ownerRepresentatives}/chu-de` },
]}
/>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background p-6">
<div className='pb-5 overflow-hidden'>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} />
))}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} />
))}
<div className='w-full flex justify-center mt-4'>
<Pagination
current={allData?.responseData.currentPage ?? page}
total={allData?.responseData.totalPages ?? 1}
onChange={(p) => setPage(p)}
/>
</div>
</div>
<div className="w-full flex justify-center mt-4">
<Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/>
</div>
</div>
</main>
{/* Sidebar */}
......@@ -60,4 +68,4 @@ export default function Page() {
</div>
</div>
);
}
\ No newline at end of file
}
import React from 'react'
import { Button } from '@/components/ui/button'
// Core
import { useMemo } from 'react'
// import { useTranslation } from 'react-i18next'
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, MoreHorizontal } from 'lucide-react'
type PaginationProps = {
current: number
total: number
onChange?: (page: number) => void
}
// Core
import { Button } from '@components/ui/button'
import { useIsMobile } from '@hooks/use-mobile'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@components/ui/tooltip'
export const Pagination: React.FC<PaginationProps> = ({ current, total, onChange }) => {
if (total <= 1) return null
// Internal
import { type PaginationProps } from './lib'
// import en from './lib/locales/en.json'
// import vi from './lib/locales/vi.json'
const handle = (p: number) => () => onChange?.(p)
export * from './lib'
const pages: (number | '...')[] = []
const delta = 2
const left = Math.max(1, current - delta)
const right = Math.min(total, current + delta)
// Component
for (let i = 1; i <= total; i++) {
if (i === 1 || i === total || (i >= left && i <= right)) {
pages.push(i)
} else if (pages[pages.length - 1] !== '...') {
pages.push('...')
}
export function Pagination(props: PaginationProps) {
// Props
const {
pageCount,
page,
isHasPreviousPage = page > 1,
isHasNextPage = page < pageCount,
neighborPageCount = 1,
jumpedPageCount = 5,
onChangePage,
onGoToPreviousPage = () => props.onChangePage(page - 1),
onGoToNextPage = () => props.onChangePage(page + 1)
} = props
// Hooks
const isMobile = useIsMobile()
// const { t } = useTranslation('component/pagination')
// Methods
// Handle jump previous page
const handleJumpPreviousPage = () => {
const newPage = Math.max(1, page - jumpedPageCount)
onChangePage(newPage)
}
// Handle jump next page
const handleJumpNextPage = () => {
const newPage = Math.min(pageCount, page + jumpedPageCount)
onChangePage(newPage)
}
// Memos
// Displayed pages
const displayedPages = useMemo(() => {
const result: number[] = []
if (isMobile) {
result.push(page)
return result
}
if (pageCount <= 3 + neighborPageCount * 2) {
if (pageCount === 0) {
result.push(1)
}
for (let i = 1; i <= pageCount; i += 1) {
result.push(i)
}
} else {
let left = Math.max(1, page - neighborPageCount)
let right = Math.min(page + neighborPageCount, pageCount)
if (page - 1 <= neighborPageCount) {
right = 1 + neighborPageCount * 2
}
if (pageCount - page <= neighborPageCount) {
left = pageCount - neighborPageCount * 2
}
for (let i = left; i <= right; i += 1) {
result.push(i)
}
if (page - 1 >= neighborPageCount * 2 && page !== 1 + 2) {
result.unshift(-Infinity)
}
if (pageCount - page >= neighborPageCount * 2 && page !== pageCount - 2) {
result.push(Infinity)
}
if (left !== 1) {
result.unshift(1)
}
if (right !== pageCount) {
result.push(pageCount)
}
}
return result
}, [neighborPageCount, page, pageCount, isMobile])
// Template
return (
<nav className="flex items-center justify-center space-x-2 py-4" aria-label="Pagination">
<Button variant="ghost" onClick={handle(Math.max(1, current - 1))} disabled={current === 1}>
«
<div className='flex items-center gap-1 select-none'>
<Button
variant='ghost'
size={isMobile ? 'icon' : 'default'}
disabled={!isHasPreviousPage}
onClick={onGoToPreviousPage}
>
<ChevronLeft />
{/* <span className='hidden xl:inline-block'>{t('previousPageButton')}</span> */}
</Button>
{pages.map((p, idx) => (
<React.Fragment key={`${p}-${idx}`}>
{p === '...'
? <span className="px-2 text-muted-foreground">{p}</span>
: (
p === current
? (
<span
aria-current="page"
className="px-3 py-1 rounded text-yellow-500 font-semibold"
>
{p}
</span>
)
: (
<Button
variant="ghost"
onClick={handle(p as number)}
>
{p}
{displayedPages.map((displayedPage) => {
if (displayedPage === -Infinity) {
return (
<TooltipProvider key={displayedPage} delayDuration={400}>
<Tooltip>
<TooltipTrigger asChild>
<Button size='icon' className='group' variant='ghost' onClick={handleJumpPreviousPage}>
<MoreHorizontal className='block group-hover:hidden' />
<ChevronsLeft className='hidden group-hover:block' />
</Button>
))}
</React.Fragment>
))}
</TooltipTrigger>
<Button variant="ghost" onClick={handle(Math.min(total, current + 1))} disabled={current === total}>
»
<TooltipContent>
{/* {jumpedPageCount} {t('previousPageJumpingButton')} */}
{jumpedPageCount}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}
if (displayedPage === Infinity) {
return (
<TooltipProvider key={displayedPage} delayDuration={400}>
<Tooltip>
<TooltipTrigger asChild>
<Button key={page} size='icon' className='group' variant='ghost' onClick={handleJumpNextPage}>
<MoreHorizontal className='block group-hover:hidden' />
<ChevronsRight className='hidden group-hover:block' />
</Button>
</TooltipTrigger>
<TooltipContent>
{/* {jumpedPageCount} {t('nextPageJumpingButton')} */}
{jumpedPageCount}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}
return (
<Button
size='icon'
key={displayedPage}
variant={displayedPage === page ? 'default' : 'ghost'}
onClick={() => onChangePage(displayedPage)}
>
{displayedPage}
</Button>
)
})}
<Button variant='ghost' size={isMobile ? 'icon' : 'default'} disabled={!isHasNextPage} onClick={onGoToNextPage}>
{/* <span className='hidden xl:inline-block'>{t('nextPageButton')}</span> */}
<ChevronRight />
</Button>
</nav>
</div>
)
}
export default Pagination
{
"previousPageButton": "Previous",
"previousPageJumpingButton": "previous pages",
"nextPageJumpingButton": "next pages",
"nextPageButton": "Next"
}
{
"previousPageButton": "Trước",
"previousPageJumpingButton": "trang trước",
"nextPageJumpingButton": "trang tiếp theo",
"nextPageButton": "Sau"
}
// Types
export interface PaginationProps {
pageCount: number
page: number
isHasPreviousPage?: boolean
isHasNextPage?: boolean
neighborPageCount?: number
jumpedPageCount?: number
onGoToPreviousPage?: () => void
onGoToNextPage?: () => void
onChangePage: (page: number) => void
}
......@@ -17,7 +17,7 @@ const buttonVariants = cva(
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
ghost: "hover:bg-primary/90 hover:text-primary-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
......
import { useEffect, useState } from 'react'
const useDebounce = <T>(value: T, delay?: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay ?? 500)
return () => {
clearTimeout(timer)
}
}, [value, delay])
return debouncedValue
}
export default useDebounce
export declare const useIsMobile: () => boolean
// Core
import { useState, useEffect } from 'react'
// Constants
const MOBILE_BREAKPOINT = 1024
// Hook
export const useIsMobile = () => {
// States — initialize lazily using window when available to avoid
// synchronously calling setState inside an effect.
const [isMobile, setIsMobile] = useState(() =>
typeof window !== 'undefined' ? window.innerWidth < MOBILE_BREAKPOINT : false
)
// Effects — only register the listener; don't call setState synchronously here.
useEffect(() => {
if (typeof window === 'undefined') return
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
mql.addEventListener('change', onChange)
return () => mql.removeEventListener('change', onChange)
}, [])
// Result
return isMobile
}
import { useMediaQuery } from '@uidotdev/usehooks'
export default useScreenSize
/**
* This hook follows the default Tailwind media breakpoints\
* References here: https://tailwindcss.com/docs/responsive-design
*
*/
export function useScreenSize(): ScreenSize {
const is2xl = useMediaQuery('(width >= 96rem)') // 1536px
const isXl = useMediaQuery('(width >= 80rem)') // 1280px
const isLg = useMediaQuery('(width >= 64rem)') // 1024px
const isMd = useMediaQuery('(width >= 48rem)') // 768px
// sm = 640px
if (is2xl) return '2xl'
if (isXl) return 'xl'
if (isLg) return 'lg'
if (isMd) return 'md'
return 'sm'
}
export type ScreenSize = '2xl' | 'xl' | 'lg' | 'md' | 'sm'
// useStore.ts
import { useState, useEffect } from 'react'
const useServerSideStore = <T, F>(
store: (callback: (state: T) => unknown) => unknown,
callback: (state: T) => F,
initData?: F
) => {
const result = store(callback) as F
const [data, setData] = useState<F | undefined>(initData)
useEffect(() => setData(result), [result])
return data as F
}
export default useServerSideStore
// import type { SignInMutationData } from '@api/types/auth'
// import type { User } from '@api/models/user'
// import { set } from 'date-fns'
// import { usePostApiAuthenticate, useGetApiAuthenticateGetMyInfo } from '@api/endpoints/authenticate'
// import { useShallow } from 'zustand/react/shallow'
// import useAuthStore from '@stores/auth'
// import useProfileStore from '@stores/profile'
// import { useCallback, useMemo } from 'react'
const useSignInHandler = () => {
// Mutations
// const { mutateAsync: signIn } = usePostApiAuthenticate()
// const { refetch: getMyInfo } = useGetApiAuthenticateGetMyInfo<{ user: User }>({
// query: {
// enabled: false,
// queryKey: []
// }
// })
// // Stores
// const authStore = useAuthStore(
// useShallow(({ storedUsername, storedPassword, setStore }) => ({
// storedUsername,
// storedPassword,
// setStore
// }))
// )
// const setProfileStore = useProfileStore((state) => state.setStore)
// // Basic Sign in
// const defaultValue = useMemo(
// () => ({
// username: authStore.storedUsername ?? '',
// password: authStore.storedPassword ?? ''
// }),
// []
// )
// const setUserInfo = useCallback(async () => {
// const { data: myInfo } = await getMyInfo()
// if (myInfo?.user) setProfileStore(myInfo.user)
// return true
// }, [])
// const signInHandler = useCallback(
// async (fieldValues: { username: string; password: string; isStoreAccount: boolean }) => {
// try {
// const response = (await signIn({
// data: {
// username: fieldValues.username,
// password: fieldValues.password
// }
// })) as unknown as SignInMutationData
// authStore.setStore({ token: response.token })
// authStore.setStore({
// isSignedIn: true,
// expiredAt: set(new Date(), { hours: 23, minutes: 59, seconds: 59 }),
// storedUsername: fieldValues.isStoreAccount ? fieldValues.username : null,
// storedPassword: fieldValues.isStoreAccount ? fieldValues.password : null
// })
// await setUserInfo()
// } catch (error) {
// authStore.setStore({ token: null })
// throw error
// }
// },
// []
// )
// // External Sign in
// const broadcastChannel = useMemo(() => new BroadcastChannel('ext-signin'), [])
// const onSelectGoogleSignIn = useCallback((redirect_url: string) => {
// const popup = window.open(`${window.origin}${redirect_url}`, '_blank', 'popup=true,width=800,height=600')
// if (!popup) return
// // Listen to login message
// return { broadcastChannel, popup }
// }, [])
// const onGoogleRedirectResponse = useCallback(async (message: { error?: string; message?: string }) => {
// broadcastChannel.postMessage(message)
// }, [])
// return {
// // Basic Sign in
// defaultValue,
// signInHandler,
// // External Sign In
// onSelectGoogleSignIn,
// onGoogleRedirectResponse,
// setUserInfo
// }
}
export default useSignInHandler
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment