feat 使用 shadcn-admin
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Cookie utility functions using manual document.cookie approach
|
||||
* Replaces js-cookie dependency for better consistency
|
||||
*/
|
||||
|
||||
const DEFAULT_MAX_AGE = 60 * 60 * 24 * 7 // 7 days
|
||||
|
||||
/**
|
||||
* Get a cookie value by name
|
||||
*/
|
||||
export function getCookie(name: string): string | undefined {
|
||||
if (typeof document === 'undefined') return undefined
|
||||
|
||||
const value = `; ${document.cookie}`
|
||||
const parts = value.split(`; ${name}=`)
|
||||
if (parts.length === 2) {
|
||||
const cookieValue = parts.pop()?.split(';').shift()
|
||||
return cookieValue
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie with name, value, and optional max age
|
||||
*/
|
||||
export function setCookie(
|
||||
name: string,
|
||||
value: string,
|
||||
maxAge: number = DEFAULT_MAX_AGE
|
||||
): void {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
document.cookie = `${name}=${value}; path=/; max-age=${maxAge}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cookie by setting its max age to 0
|
||||
*/
|
||||
export function removeCookie(name: string): void {
|
||||
if (typeof document === 'undefined') return
|
||||
|
||||
document.cookie = `${name}=; path=/; max-age=0`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { AxiosError } from 'axios'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export function handleServerError(error: unknown) {
|
||||
if (import.meta.env.DEV) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
let errMsg = 'Something went wrong!'
|
||||
|
||||
if (
|
||||
error &&
|
||||
typeof error === 'object' &&
|
||||
'status' in error &&
|
||||
Number(error.status) === 204
|
||||
) {
|
||||
errMsg = 'No content.'
|
||||
}
|
||||
|
||||
if (error instanceof AxiosError) {
|
||||
const title = error.response?.data?.title
|
||||
if (typeof title === 'string' && title.length > 0) {
|
||||
errMsg = title
|
||||
}
|
||||
}
|
||||
|
||||
toast.error(errMsg)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export function showSubmittedData(
|
||||
data: unknown,
|
||||
title: string = 'You submitted the following values:'
|
||||
) {
|
||||
toast.message(title, {
|
||||
description: (
|
||||
<pre className='mt-2 w-full overflow-x-auto rounded-md bg-slate-950 p-4'>
|
||||
<code className='text-white'>{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
+71
-2
@@ -1,6 +1,75 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function sleep(ms: number = 1000) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates page numbers for pagination with ellipsis
|
||||
* @param currentPage - Current page number (1-based)
|
||||
* @param totalPages - Total number of pages
|
||||
* @returns Array of page numbers and ellipsis strings
|
||||
*
|
||||
* Examples:
|
||||
* - Small dataset (≤5 pages): [1, 2, 3, 4, 5]
|
||||
* - Near beginning: [1, 2, 3, 4, '...', 10]
|
||||
* - In middle: [1, '...', 4, 5, 6, '...', 10]
|
||||
* - Near end: [1, '...', 7, 8, 9, 10]
|
||||
*/
|
||||
export function getPageNumbers(currentPage: number, totalPages: number) {
|
||||
const maxVisiblePages = 5 // Maximum number of page buttons to show
|
||||
const rangeWithDots = []
|
||||
|
||||
if (totalPages <= maxVisiblePages) {
|
||||
// If total pages is 5 or less, show all pages
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
rangeWithDots.push(i)
|
||||
}
|
||||
} else {
|
||||
// Always show first page
|
||||
rangeWithDots.push(1)
|
||||
|
||||
if (currentPage <= 3) {
|
||||
// Near the beginning: [1] [2] [3] [4] ... [10]
|
||||
for (let i = 2; i <= 4; i++) {
|
||||
rangeWithDots.push(i)
|
||||
}
|
||||
rangeWithDots.push('...', totalPages)
|
||||
} else if (currentPage >= totalPages - 2) {
|
||||
// Near the end: [1] ... [7] [8] [9] [10]
|
||||
rangeWithDots.push('...')
|
||||
for (let i = totalPages - 3; i <= totalPages; i++) {
|
||||
rangeWithDots.push(i)
|
||||
}
|
||||
} else {
|
||||
// In the middle: [1] ... [4] [5] [6] ... [10]
|
||||
rangeWithDots.push('...')
|
||||
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
|
||||
rangeWithDots.push(i)
|
||||
}
|
||||
rangeWithDots.push('...', totalPages)
|
||||
}
|
||||
}
|
||||
|
||||
return rangeWithDots
|
||||
}
|
||||
|
||||
/**
|
||||
* Initials from a display name: first character of the first word + first
|
||||
* character of the last word. One word only: first two characters. Empty: `?`.
|
||||
*/
|
||||
export function getDisplayNameInitials(displayName: string): string {
|
||||
const parts = displayName.trim().split(/\s+/).filter(Boolean)
|
||||
if (parts.length === 0) return '?'
|
||||
if (parts.length === 1) {
|
||||
return parts[0].slice(0, 2).toUpperCase()
|
||||
}
|
||||
const first = parts[0][0] ?? ''
|
||||
const last = parts[parts.length - 1]?.[0] ?? ''
|
||||
return (first + last).toUpperCase()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user