Compare commits

...

20 Commits
v2.8.3 ... main

Author SHA1 Message Date
hamster1963
e03e6232fb chore: update dependencies in package.json to latest versions 2025-04-23 17:04:27 +08:00
hamster1963
484266666d chore: bump version to 2.9.3 in package.json 2025-04-10 10:39:03 +08:00
hamster1963
fb1a74ec09 style: adjust padding and spacing in ThemeSwitcher component 2025-04-10 10:37:02 +08:00
hamster1963
812397235c chore: update dependencies in package.json to latest versions 2025-04-10 10:35:10 +08:00
hamster1963
3411783401 chore: bump version to 2.9.2 in package.json 2025-04-02 13:30:01 +08:00
hamster1963
1ab1559c20 feat: implement theme switcher with radio buttons and add localization for Traditional Chinese 2025-04-02 13:18:42 +08:00
hamster1963
0837393903 chore: update dependencies in package.json to latest versions 2025-04-02 11:36:46 +08:00
hamster1963
55cec2e055 chore: bump version to 2.9.1 in package.json 2025-03-26 09:04:29 +08:00
hamster1963
14d7f1e416 refactor: replace bcrypt with CryptoJS for password hashing and update dependencies 2025-03-26 09:04:06 +08:00
hamster1963
c267b489e4 chore: bump version to 2.9.0 in package.json 2025-03-25 17:38:44 +08:00
仓鼠
8a1ce73564
refactor: improve Switch component state management and initial rende… (#265)
* refactor: improve Switch component state management and initial render handling

* chore: auto-fix linting and formatting issues
2025-03-25 17:31:05 +08:00
hamster1963
6b273622df chore: update permissions to allow write access for Deploy and auto-fix workflows 2025-03-25 16:57:41 +08:00
hamster1963
80c4500822 chore: update dependencies in package.json 2025-03-25 16:23:45 +08:00
仓鼠
aa14f6045f
chore: update permissions for auto-fix lint and format workflow (#264) 2025-03-25 16:20:33 +08:00
仓鼠
4719c2210e
Potential fix for code scanning alert no. 4: Cache Poisoning via execution of untrusted code (#262)
* Potential fix for code scanning alert no. 4: Cache Poisoning via execution of untrusted code

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* chore: auto-fix linting and formatting issues

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-03-25 16:15:09 +08:00
仓鼠
079ff0be32
chore: add permissions for contents read in Deploy.yml (#263)
* chore: add permissions for contents read in Deploy.yml

* chore: auto-fix linting and formatting issues
2025-03-25 16:14:04 +08:00
仓鼠
38ebfcee44
Merge pull request #261 from hamster1963/alert-autofix-7
Potential fix for code scanning alert no. 7: Use of password hash with insufficient computational effort
2025-03-25 16:07:01 +08:00
仓鼠
365ba91bff chore: add @types/bcrypt 2025-03-25 07:45:25 +00:00
hamster1963
865a5ba8ee chore: auto-fix linting and formatting issues 2025-03-25 07:31:50 +00:00
仓鼠
37adab9208
Potential fix for code scanning alert no. 7: Use of password hash with insufficient computational effort
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-03-25 15:31:27 +08:00
13 changed files with 149 additions and 74 deletions

View File

@ -1,5 +1,7 @@
name: Build and push Docker image name: Build and push Docker image
permissions:
contents: write
on: on:
push: push:
tags: tags:

View File

@ -1,7 +1,10 @@
name: Auto Fix Lint and Format name: Auto Fix Lint and Format
permissions:
contents: write
pull-requests: write
on: on:
pull_request_target: pull_request:
types: [opened, synchronize] types: [opened, synchronize]
jobs: jobs:

View File

@ -21,7 +21,8 @@
"useSortedClasses": "error" "useSortedClasses": "error"
}, },
"a11y": { "a11y": {
"useKeyWithClickEvents": "off" "useKeyWithClickEvents": "off",
"noLabelWithoutControl": "off"
}, },
"security": { "security": {
"noDangerouslySetInnerHtml": "off" "noDangerouslySetInnerHtml": "off"

BIN
bun.lockb

Binary file not shown.

View File

@ -20,10 +20,8 @@ export default function Switch({
const tagRefs = useRef(allTag.map(() => createRef<HTMLDivElement>())) const tagRefs = useRef(allTag.map(() => createRef<HTMLDivElement>()))
const t = useTranslations("ServerListClient") const t = useTranslations("ServerListClient")
const locale = useLocale() const locale = useLocale()
const [indicator, setIndicator] = useState<{ x: number; w: number }>({ const [indicator, setIndicator] = useState<{ x: number; w: number } | null>(null)
x: 0, const [isFirstRender, setIsFirstRender] = useState(true)
w: 0,
})
useEffect(() => { useEffect(() => {
const savedTag = sessionStorage.getItem("selectedTag") const savedTag = sessionStorage.getItem("selectedTag")
@ -59,7 +57,13 @@ export default function Switch({
w: currentTagElement.offsetWidth, w: currentTagElement.offsetWidth,
}) })
} }
}, [nowTag, locale])
if (isFirstRender) {
setTimeout(() => {
setIsFirstRender(false)
}, 50)
}
}, [nowTag, locale, allTag, isFirstRender])
useEffect(() => { useEffect(() => {
const currentTagElement = tagRefs.current[allTag.indexOf(nowTag)]?.current const currentTagElement = tagRefs.current[allTag.indexOf(nowTag)]?.current
@ -84,14 +88,14 @@ export default function Switch({
className="scrollbar-hidden z-50 flex flex-col items-start overflow-x-scroll rounded-[50px]" className="scrollbar-hidden z-50 flex flex-col items-start overflow-x-scroll rounded-[50px]"
> >
<div className="relative flex items-center gap-1 rounded-[50px] bg-stone-100 p-[3px] dark:bg-stone-800"> <div className="relative flex items-center gap-1 rounded-[50px] bg-stone-100 p-[3px] dark:bg-stone-800">
{indicator.w > 0 && ( {indicator && (
<div <div
className="absolute top-[3px] left-0 z-10 h-[35px] bg-white shadow-black/5 shadow-lg dark:bg-stone-700 dark:shadow-white/5" className="absolute top-[3px] left-0 z-10 h-[35px] bg-white shadow-black/5 shadow-lg dark:bg-stone-700 dark:shadow-white/5"
style={{ style={{
borderRadius: 24, borderRadius: 24,
width: `${indicator.w}px`, width: `${indicator.w}px`,
transform: `translateX(${indicator.x}px)`, transform: `translateX(${indicator.x}px)`,
transition: "all 0.5s cubic-bezier(0.4, 0, 0.2, 1)", transition: isFirstRender ? "none" : "all 0.5s cubic-bezier(0.4, 0, 0.2, 1)",
}} }}
/> />
)} )}

View File

@ -4,23 +4,28 @@ import { Button } from "@/components/ui/button"
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import { cn } from "@/lib/utils" import { CheckIcon, MinusIcon, Moon, Sun } from "lucide-react"
import { CheckCircleIcon } from "@heroicons/react/20/solid"
import { Moon, Sun } from "lucide-react"
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import { useTheme } from "next-themes" import { useTheme } from "next-themes"
import { useId } from "react"
import { RadioGroup, RadioGroupItem } from "./ui/radio-group"
const items = [
{ value: "light", label: "Light", image: "/ui-light.png" },
{ value: "dark", label: "Dark", image: "/ui-dark.png" },
{ value: "system", label: "System", image: "/ui-system.png" },
]
export function ModeToggle() { export function ModeToggle() {
const { setTheme, theme } = useTheme() const { setTheme, theme } = useTheme()
const t = useTranslations("ThemeSwitcher") const t = useTranslations("ThemeSwitcher")
const handleSelect = (e: Event, newTheme: string) => { const handleSelect = (newTheme: string) => {
e.preventDefault()
setTheme(newTheme) setTheme(newTheme)
} }
const id = useId()
return ( return (
<DropdownMenu> <DropdownMenu>
@ -35,31 +40,40 @@ export function ModeToggle() {
<span className="sr-only">Toggle theme</span> <span className="sr-only">Toggle theme</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="flex flex-col gap-0.5" align="end"> <DropdownMenuContent className="px-2 pt-2 pb-1.5" align="end">
<DropdownMenuItem <fieldset className="space-y-4">
className={cn("rounded-b-[5px]", { <RadioGroup className="flex gap-2" defaultValue={theme} onValueChange={handleSelect}>
"gap-3 bg-muted font-semibold": theme === "light", {items.map((item) => (
})} <label key={`${id}-${item.value}`}>
onSelect={(e) => handleSelect(e, "light")} <RadioGroupItem
> id={`${id}-${item.value}`}
{t("Light")} {theme === "light" && <CheckCircleIcon className="size-4" />} value={item.value}
</DropdownMenuItem> className="peer sr-only after:absolute after:inset-0"
<DropdownMenuItem />
className={cn("rounded-[5px]", { <img
"gap-3 bg-muted font-semibold": theme === "dark", src={item.image}
})} alt={item.label}
onSelect={(e) => handleSelect(e, "dark")} width={88}
> height={70}
{t("Dark")} {theme === "dark" && <CheckCircleIcon className="size-4" />} className="relative cursor-pointer overflow-hidden rounded-[8px] border border-neutral-300 shadow-xs outline-none transition-[color,box-shadow] peer-focus-visible:ring-[3px] peer-focus-visible:ring-ring/50 peer-data-disabled:cursor-not-allowed peer-data-[state=checked]:bg-accent peer-data-disabled:opacity-50 dark:border-neutral-700"
</DropdownMenuItem> />
<DropdownMenuItem <span className="group mt-2 flex items-center gap-1 peer-data-[state=unchecked]:text-muted-foreground/70">
className={cn("rounded-t-[5px]", { <CheckIcon
"gap-3 bg-muted font-semibold": theme === "system", size={16}
})} className="group-peer-data-[state=unchecked]:hidden"
onSelect={(e) => handleSelect(e, "system")} aria-hidden="true"
> />
{t("System")} {theme === "system" && <CheckCircleIcon className="size-4" />} <MinusIcon
</DropdownMenuItem> size={16}
className="group-peer-data-[state=checked]:hidden"
aria-hidden="true"
/>
<span className="font-medium text-xs">{t(item.label)}</span>
</span>
</label>
))}
</RadioGroup>
</fieldset>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
) )

View File

@ -0,0 +1,50 @@
"use client"
import { cn } from "@/lib/utils"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import type * as React from "react"
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props}
/>
)
}
function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
"aspect-square size-4 shrink-0 rounded-full border border-input shadow-xs outline-none transition-shadow focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:aria-invalid:ring-destructive/40",
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center text-current">
<svg
role="img"
aria-label="Radio indicator"
width="6"
height="6"
viewBox="0 0 6 6"
fill="currentcolor"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="3" cy="3" r="3" />
</svg>
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
}
export { RadioGroup, RadioGroupItem }

View File

@ -4,7 +4,7 @@ import getEnv from "./lib/env-entry"
export const localeItems = [ export const localeItems = [
{ code: "en", name: "English" }, { code: "en", name: "English" },
{ code: "ja", name: "日本語" }, { code: "ja", name: "日本語" },
{ code: "zh-t", name: "中文繁體" }, { code: "zh-TW", name: "中文繁體" },
{ code: "zh", name: "中文简体" }, { code: "zh", name: "中文简体" },
] ]

View File

@ -1,9 +1,9 @@
{ {
"name": "nezha-dash", "name": "nezha-dash",
"version": "2.8.3", "version": "2.9.3",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack -p 3040", "dev": "next dev -p 3040",
"start": "node .next/standalone/server.js", "start": "node .next/standalone/server.js",
"lint": "biome lint", "lint": "biome lint",
"lint:fix": "biome lint --fix", "lint:fix": "biome lint --fix",
@ -15,22 +15,23 @@
"dependencies": { "dependencies": {
"@ducanh2912/next-pwa": "^10.2.9", "@ducanh2912/next-pwa": "^10.2.9",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-label": "^2.1.2", "@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-navigation-menu": "^1.2.5", "@radix-ui/react-navigation-menu": "^1.2.10",
"@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-popover": "^1.1.11",
"@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-progress": "^1.1.4",
"@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-radio-group": "^1.3.4",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-separator": "^1.1.4",
"@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-switch": "^1.2.2",
"@radix-ui/react-tooltip": "^1.2.4",
"@trivago/prettier-plugin-sort-imports": "^5.2.2", "@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/d3-geo": "^3.1.0", "@types/d3-geo": "^3.1.0",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.6.2",
"babel-plugin-react-compiler": "^19.0.0-beta-e552027-20250112", "babel-plugin-react-compiler": "^19.0.0-beta-ebf51a3-20250411",
"caniuse-lite": "^1.0.30001707", "caniuse-lite": "^1.0.30001715",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
@ -41,35 +42,35 @@
"flag-icons": "^7.3.2", "flag-icons": "^7.3.2",
"i18n-iso-countries": "^7.14.0", "i18n-iso-countries": "^7.14.0",
"lucide-react": "^0.474.0", "lucide-react": "^0.474.0",
"luxon": "^3.5.0", "luxon": "^3.6.1",
"maxmind": "^4.3.24", "maxmind": "^4.3.24",
"next": "^15.2.3", "next": "^15.3.1",
"next-auth": "^5.0.0-beta.25", "next-auth": "^5.0.0-beta.26",
"next-intl": "^3.26.5", "next-intl": "^4.0.3",
"next-runtime-env": "^3.2.2", "next-runtime-env": "^3.3.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.0.0", "react": "^19.1.0",
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "^19.0.0", "react-dom": "^19.1.0",
"react-intersection-observer": "^9.16.0", "react-intersection-observer": "^9.16.0",
"react-wrap-balancer": "^1.1.1", "react-wrap-balancer": "^1.1.1",
"recharts": "^2.15.1", "recharts": "^2.15.3",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"swr": "^2.3.3", "swr": "^2.3.3",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^3.2.0",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
"@next/bundle-analyzer": "^15.2.3", "@next/bundle-analyzer": "^15.3.1",
"@tailwindcss/postcss": "^4.0.15", "@tailwindcss/postcss": "^4.1.4",
"@types/node": "^22.13.11", "@types/node": "^22.14.1",
"@types/react": "^19.0.12", "@types/react": "^19.1.2",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.1.2",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"tailwindcss": "^4.0.15", "tailwindcss": "^4.1.4",
"typescript": "^5.8.2", "typescript": "^5.8.3",
"vercel": "^39.4.2" "vercel": "^41.6.2"
}, },
"overrides": { "overrides": {
"react-is": "^19.0.0-rc-69d4b800-20241021" "react-is": "^19.0.0-rc-69d4b800-20241021"

BIN
public/ui-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

BIN
public/ui-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

BIN
public/ui-system.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B