mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-05-06 13:48:55 +00:00
feat: enhance public notes functionality with flexible options (#141)
* feat: enhance public notes functionality with flexible options - Make public notes optional to prevent default values causing frontend issues - Add dual editing modes: raw text editing and custom fields (avoid hardcoded schema) - Set raw text mode as default, populate input on edit, submit raw text content always - Add toggle switch: submit raw text when enabled, submit empty & hide controls when disabled - New records default to disabled public notes; auto-expand on edit based on content * chore: auto-fix linting and formatting issues * feat: Add public annotation data structure and utility functions Implemented Zod validation patterns, default values, parsing functions, and utility functions for public notes, and updated related internationalization text. * chore: auto-fix linting and formatting issues * refactor(server): Replace i18n implementation Replace direct use of i18n.t with react-i18next's useTranslation hook to improve internationalization support. * refactor(public-note): Optimize data model and validation logic Removed the pruneEmpty function and simplified the date processing logic, making billingDataMod and planDataMod optional fields. Also optimized the validation logic to handle optional fields. * chore: auto-fix linting and formatting issues * fix zod validation & don't write empty values when parsing * use raw mode if object contains unknown fields * rename some features * chore: Update dependency package versions Upgrade multiple npm dependencies to their latest versions, including react, tailwindcss, and eslint. Ignore lock files. * fix(server): Fix default value when bill amount is undefined Changed undefined values for bill amount to the default value "0" to avoid potential null value errors. --------- Co-authored-by: Guccen <171530509+Chillln@users.noreply.github.com> Co-authored-by: uubulb <uub@suwako.de>
This commit is contained in:
@@ -22,3 +22,5 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
bun.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|||||||
+21
-21
@@ -28,7 +28,7 @@
|
|||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-switch": "^1.2.6",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@tailwindcss/postcss": "^4.1.13",
|
"@tailwindcss/postcss": "^4.1.14",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
"@types/luxon": "^3.7.1",
|
"@types/luxon": "^3.7.1",
|
||||||
@@ -41,44 +41,44 @@
|
|||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"framer-motion": "^12.23.22",
|
"framer-motion": "^12.23.22",
|
||||||
"i18next": "^25.5.2",
|
"i18next": "^25.5.3",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"jotai-zustand": "^0.6.0",
|
"jotai-zustand": "^0.6.0",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.545.0",
|
||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
"react": "^19.1.1",
|
"react": "^19.2.0",
|
||||||
"react-day-picker": "^9.11.0",
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.2.0",
|
||||||
"react-hook-form": "^7.63.0",
|
"react-hook-form": "^7.64.0",
|
||||||
"react-i18next": "^16.0.0",
|
"react-i18next": "^16.0.0",
|
||||||
"react-router-dom": "^7.9.3",
|
"react-router-dom": "^7.9.4",
|
||||||
"react-virtuoso": "^4.14.1",
|
"react-virtuoso": "^4.14.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^4.1.11",
|
"zod": "^4.1.12",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.37.0",
|
||||||
"@types/node": "^24.5.2",
|
"@types/node": "^24.7.0",
|
||||||
"@types/react": "^19.1.15",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.2.1",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"eslint": "^9.36.0",
|
"eslint": "^9.37.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^7.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.22",
|
"eslint-plugin-react-refresh": "^0.4.23",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"swagger-typescript-api": "^13.2.13",
|
"swagger-typescript-api": "^13.2.15",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.14",
|
||||||
"typescript": "~5.9.2",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.44.1",
|
"typescript-eslint": "^8.46.0",
|
||||||
"vite": "^7.1.7"
|
"vite": "^7.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+647
-627
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
import { buttonVariants } from "@/components/ui/button"
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef, HTMLAttributes } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, HTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
const AlertDialog = AlertDialogPrimitive.Root
|
const AlertDialog = AlertDialogPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const Avatar = forwardRef<
|
const Avatar = forwardRef<
|
||||||
ComponentRef<typeof AvatarPrimitive.Root>,
|
ComponentRef<typeof AvatarPrimitive.Root>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
import { forwardRef, ComponentPropsWithoutRef, ReactNode, ComponentProps } from "react"
|
import { ComponentProps, ComponentPropsWithoutRef, ReactNode, forwardRef } from "react"
|
||||||
|
|
||||||
const Breadcrumb = forwardRef<
|
const Breadcrumb = forwardRef<
|
||||||
HTMLElement,
|
HTMLElement,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { type VariantProps, cva } from "class-variance-authority"
|
import { type VariantProps, cva } from "class-variance-authority"
|
||||||
import { forwardRef, ButtonHTMLAttributes } from "react"
|
import { ButtonHTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
|
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
|
||||||
import { ComponentProps, useRef, useEffect } from "react"
|
import { ComponentProps, useEffect, useRef } from "react"
|
||||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { forwardRef, HTMLAttributes } from "react"
|
import { HTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
@@ -30,12 +30,11 @@ const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingEle
|
|||||||
)
|
)
|
||||||
CardTitle.displayName = "CardTitle"
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
const CardDescription = forwardRef<
|
const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => (
|
||||||
HTMLAttributes<HTMLParagraphElement>
|
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
)
|
||||||
))
|
|
||||||
CardDescription.displayName = "CardDescription"
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
import { Check } from "lucide-react"
|
import { Check } from "lucide-react"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const Checkbox = forwardRef<
|
const Checkbox = forwardRef<
|
||||||
ComponentRef<typeof CheckboxPrimitive.Root>,
|
ComponentRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Check, ChevronDown } from "lucide-react"
|
import { Check, ChevronDown } from "lucide-react"
|
||||||
import { forwardRef, ButtonHTMLAttributes, useState } from "react"
|
import { ButtonHTMLAttributes, forwardRef, useState } from "react"
|
||||||
|
|
||||||
interface ComboboxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
interface ComboboxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { cn } from "@/lib/utils"
|
|||||||
import { type DialogProps } from "@radix-ui/react-dialog"
|
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
import { Search } from "lucide-react"
|
import { Search } from "lucide-react"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef, HTMLAttributes } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, HTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
const Command = forwardRef<
|
const Command = forwardRef<
|
||||||
ComponentRef<typeof CommandPrimitive>,
|
ComponentRef<typeof CommandPrimitive>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
import { X } from "lucide-react"
|
import { X } from "lucide-react"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef, HTMLAttributes } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, HTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { ComponentProps, forwardRef, ComponentRef, ComponentPropsWithoutRef, HTMLAttributes } from "react"
|
import {
|
||||||
|
ComponentProps,
|
||||||
|
ComponentPropsWithoutRef,
|
||||||
|
ComponentRef,
|
||||||
|
HTMLAttributes,
|
||||||
|
forwardRef,
|
||||||
|
} from "react"
|
||||||
import { Drawer as DrawerPrimitive } from "vaul"
|
import { Drawer as DrawerPrimitive } from "vaul"
|
||||||
|
|
||||||
const Drawer = ({
|
const Drawer = ({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef, HTMLAttributes } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, HTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
+57
-52
@@ -2,7 +2,15 @@ import { Label } from "@/components/ui/label"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { createContext, useContext, forwardRef, HTMLAttributes, useId, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import {
|
||||||
|
ComponentPropsWithoutRef,
|
||||||
|
ComponentRef,
|
||||||
|
HTMLAttributes,
|
||||||
|
createContext,
|
||||||
|
forwardRef,
|
||||||
|
useContext,
|
||||||
|
useId,
|
||||||
|
} from "react"
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
ControllerProps,
|
ControllerProps,
|
||||||
@@ -95,65 +103,62 @@ const FormLabel = forwardRef<
|
|||||||
})
|
})
|
||||||
FormLabel.displayName = "FormLabel"
|
FormLabel.displayName = "FormLabel"
|
||||||
|
|
||||||
const FormControl = forwardRef<
|
const FormControl = forwardRef<ComponentRef<typeof Slot>, ComponentPropsWithoutRef<typeof Slot>>(
|
||||||
ComponentRef<typeof Slot>,
|
({ ...props }, ref) => {
|
||||||
ComponentPropsWithoutRef<typeof Slot>
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||||
>(({ ...props }, ref) => {
|
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slot
|
<Slot
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={
|
||||||
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
||||||
}
|
}
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
FormControl.displayName = "FormControl"
|
FormControl.displayName = "FormControl"
|
||||||
|
|
||||||
const FormDescription = forwardRef<
|
const FormDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => {
|
||||||
HTMLAttributes<HTMLParagraphElement>
|
const { formDescriptionId } = useFormField()
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const { formDescriptionId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formDescriptionId}
|
id={formDescriptionId}
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
FormDescription.displayName = "FormDescription"
|
FormDescription.displayName = "FormDescription"
|
||||||
|
|
||||||
const FormMessage = forwardRef<
|
const FormMessage = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, children, ...props }, ref) => {
|
||||||
HTMLAttributes<HTMLParagraphElement>
|
const { error, formMessageId } = useFormField()
|
||||||
>(({ className, children, ...props }, ref) => {
|
const body = error ? String(error?.message) : children
|
||||||
const { error, formMessageId } = useFormField()
|
|
||||||
const body = error ? String(error?.message) : children
|
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formMessageId}
|
id={formMessageId}
|
||||||
className={cn("text-sm font-medium text-destructive", className)}
|
className={cn("text-sm font-medium text-destructive", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
FormMessage.displayName = "FormMessage"
|
FormMessage.displayName = "FormMessage"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
+14
-16
@@ -1,23 +1,21 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { forwardRef, InputHTMLAttributes } from "react"
|
import { InputHTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}
|
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
const Input = forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
|
||||||
({ className, type, ...props }, ref) => {
|
return (
|
||||||
return (
|
<input
|
||||||
<input
|
type={type}
|
||||||
type={type}
|
className={cn(
|
||||||
className={cn(
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
className,
|
||||||
className,
|
)}
|
||||||
)}
|
ref={ref}
|
||||||
ref={ref}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
Input.displayName = "Input"
|
Input.displayName = "Input"
|
||||||
|
|
||||||
export { Input }
|
export { Input }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { type VariantProps, cva } from "class-variance-authority"
|
import { type VariantProps, cva } from "class-variance-authority"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva(
|
||||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { cn } from "@/lib/utils"
|
|||||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||||
import { cva } from "class-variance-authority"
|
import { cva } from "class-variance-authority"
|
||||||
import { ChevronDown } from "lucide-react"
|
import { ChevronDown } from "lucide-react"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const NavigationMenu = forwardRef<
|
const NavigationMenu = forwardRef<
|
||||||
ComponentRef<typeof NavigationMenuPrimitive.Root>,
|
ComponentRef<typeof NavigationMenuPrimitive.Root>,
|
||||||
|
|||||||
@@ -45,10 +45,7 @@ const PaginationLink = ({ className, isActive, size = "icon", ...props }: Pagina
|
|||||||
)
|
)
|
||||||
PaginationLink.displayName = "PaginationLink"
|
PaginationLink.displayName = "PaginationLink"
|
||||||
|
|
||||||
const PaginationPrevious = ({
|
const PaginationPrevious = ({ className, ...props }: ComponentProps<typeof PaginationLink>) => (
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: ComponentProps<typeof PaginationLink>) => (
|
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
aria-label="Go to previous page"
|
aria-label="Go to previous page"
|
||||||
size="default"
|
size="default"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const ScrollArea = forwardRef<
|
const ScrollArea = forwardRef<
|
||||||
ComponentRef<typeof ScrollAreaPrimitive.Root>,
|
ComponentRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const Select = SelectPrimitive.Root
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const Separator = forwardRef<
|
const Separator = forwardRef<
|
||||||
ComponentRef<typeof SeparatorPrimitive.Root>,
|
ComponentRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const Switch = forwardRef<
|
const Switch = forwardRef<
|
||||||
ComponentRef<typeof SwitchPrimitives.Root>,
|
ComponentRef<typeof SwitchPrimitives.Root>,
|
||||||
|
|||||||
+50
-52
@@ -1,5 +1,5 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { forwardRef, HTMLAttributes, ThHTMLAttributes, TdHTMLAttributes } from "react"
|
import { HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes, forwardRef } from "react"
|
||||||
|
|
||||||
const Table = forwardRef<HTMLTableElement, HTMLAttributes<HTMLTableElement>>(
|
const Table = forwardRef<HTMLTableElement, HTMLAttributes<HTMLTableElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
@@ -14,32 +14,29 @@ const Table = forwardRef<HTMLTableElement, HTMLAttributes<HTMLTableElement>>(
|
|||||||
)
|
)
|
||||||
Table.displayName = "Table"
|
Table.displayName = "Table"
|
||||||
|
|
||||||
const TableHeader = forwardRef<
|
const TableHeader = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
HTMLTableSectionElement,
|
({ className, ...props }, ref) => (
|
||||||
HTMLAttributes<HTMLTableSectionElement>
|
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
)
|
||||||
))
|
|
||||||
TableHeader.displayName = "TableHeader"
|
TableHeader.displayName = "TableHeader"
|
||||||
|
|
||||||
const TableBody = forwardRef<
|
const TableBody = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
HTMLTableSectionElement,
|
({ className, ...props }, ref) => (
|
||||||
HTMLAttributes<HTMLTableSectionElement>
|
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
)
|
||||||
))
|
|
||||||
TableBody.displayName = "TableBody"
|
TableBody.displayName = "TableBody"
|
||||||
|
|
||||||
const TableFooter = forwardRef<
|
const TableFooter = forwardRef<HTMLTableSectionElement, HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
HTMLTableSectionElement,
|
({ className, ...props }, ref) => (
|
||||||
HTMLAttributes<HTMLTableSectionElement>
|
<tfoot
|
||||||
>(({ className, ...props }, ref) => (
|
ref={ref}
|
||||||
<tfoot
|
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
||||||
ref={ref}
|
{...props}
|
||||||
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
/>
|
||||||
{...props}
|
),
|
||||||
/>
|
)
|
||||||
))
|
|
||||||
TableFooter.displayName = "TableFooter"
|
TableFooter.displayName = "TableFooter"
|
||||||
|
|
||||||
const TableRow = forwardRef<HTMLTableRowElement, HTMLAttributes<HTMLTableRowElement>>(
|
const TableRow = forwardRef<HTMLTableRowElement, HTMLAttributes<HTMLTableRowElement>>(
|
||||||
@@ -56,39 +53,40 @@ const TableRow = forwardRef<HTMLTableRowElement, HTMLAttributes<HTMLTableRowElem
|
|||||||
)
|
)
|
||||||
TableRow.displayName = "TableRow"
|
TableRow.displayName = "TableRow"
|
||||||
|
|
||||||
const TableHead = forwardRef<
|
const TableHead = forwardRef<HTMLTableCellElement, ThHTMLAttributes<HTMLTableCellElement>>(
|
||||||
HTMLTableCellElement,
|
({ className, ...props }, ref) => (
|
||||||
ThHTMLAttributes<HTMLTableCellElement>
|
<th
|
||||||
>(({ className, ...props }, ref) => (
|
ref={ref}
|
||||||
<th
|
className={cn(
|
||||||
ref={ref}
|
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||||
className={cn(
|
className,
|
||||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
)}
|
||||||
className,
|
{...props}
|
||||||
)}
|
/>
|
||||||
{...props}
|
),
|
||||||
/>
|
)
|
||||||
))
|
|
||||||
TableHead.displayName = "TableHead"
|
TableHead.displayName = "TableHead"
|
||||||
|
|
||||||
const TableCell = forwardRef<
|
const TableCell = forwardRef<HTMLTableCellElement, TdHTMLAttributes<HTMLTableCellElement>>(
|
||||||
HTMLTableCellElement,
|
({ className, ...props }, ref) => (
|
||||||
TdHTMLAttributes<HTMLTableCellElement>
|
<td
|
||||||
>(({ className, ...props }, ref) => (
|
ref={ref}
|
||||||
<td
|
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||||
ref={ref}
|
{...props}
|
||||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
/>
|
||||||
{...props}
|
),
|
||||||
/>
|
)
|
||||||
))
|
|
||||||
TableCell.displayName = "TableCell"
|
TableCell.displayName = "TableCell"
|
||||||
|
|
||||||
const TableCaption = forwardRef<
|
const TableCaption = forwardRef<HTMLTableCaptionElement, HTMLAttributes<HTMLTableCaptionElement>>(
|
||||||
HTMLTableCaptionElement,
|
({ className, ...props }, ref) => (
|
||||||
HTMLAttributes<HTMLTableCaptionElement>
|
<caption
|
||||||
>(({ className, ...props }, ref) => (
|
ref={ref}
|
||||||
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||||
))
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)
|
||||||
TableCaption.displayName = "TableCaption"
|
TableCaption.displayName = "TableCaption"
|
||||||
|
|
||||||
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
import { forwardRef, ComponentRef, ComponentPropsWithoutRef } from "react"
|
import { ComponentPropsWithoutRef, ComponentRef, forwardRef } from "react"
|
||||||
|
|
||||||
const Tabs = TabsPrimitive.Root
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { forwardRef, ComponentProps } from "react"
|
import { ComponentProps, forwardRef } from "react"
|
||||||
|
|
||||||
const Textarea = forwardRef<HTMLTextAreaElement, ComponentProps<"textarea">>(
|
const Textarea = forwardRef<HTMLTextAreaElement, ComponentProps<"textarea">>(
|
||||||
({ className, ...props }, ref) => {
|
({ className, ...props }, ref) => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
import { formatPath } from "@/lib/utils"
|
import { formatPath } from "@/lib/utils"
|
||||||
import { Dispatch, SetStateAction, FC, useState, Fragment } from "react"
|
import { Dispatch, FC, Fragment, SetStateAction, useState } from "react"
|
||||||
|
|
||||||
const ITEMS_TO_DISPLAY = 3
|
const ITEMS_TO_DISPLAY = 3
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { Separator } from "@/components/ui/separator"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { type VariantProps, cva } from "class-variance-authority"
|
import { type VariantProps, cva } from "class-variance-authority"
|
||||||
import { CheckIcon, ChevronDown, WandSparkles, XIcon } from "lucide-react"
|
import { CheckIcon, ChevronDown, WandSparkles, XIcon } from "lucide-react"
|
||||||
import { useState, forwardRef, KeyboardEvent } from "react"
|
import { KeyboardEvent, forwardRef, useState } from "react"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variants for the multi-select component to handle different styles.
|
* Variants for the multi-select component to handle different styles.
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ import { cn } from "@/lib/utils"
|
|||||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
import { type VariantProps, cva } from "class-variance-authority"
|
import { type VariantProps, cva } from "class-variance-authority"
|
||||||
import { X } from "lucide-react"
|
import { X } from "lucide-react"
|
||||||
import { ComponentPropsWithoutRef, Dispatch, SetStateAction, forwardRef, ComponentRef, HTMLAttributes } from "react"
|
import {
|
||||||
|
ComponentPropsWithoutRef,
|
||||||
|
ComponentRef,
|
||||||
|
Dispatch,
|
||||||
|
HTMLAttributes,
|
||||||
|
SetStateAction,
|
||||||
|
forwardRef,
|
||||||
|
} from "react"
|
||||||
|
|
||||||
const Sheet = SheetPrimitive.Root
|
const Sheet = SheetPrimitive.Root
|
||||||
|
|
||||||
@@ -35,29 +42,28 @@ interface SheetContentProps
|
|||||||
setOpen: Dispatch<SetStateAction<boolean>>
|
setOpen: Dispatch<SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const SheetContent = forwardRef<
|
const SheetContent = forwardRef<ComponentRef<typeof SheetPrimitive.Content>, SheetContentProps>(
|
||||||
ComponentRef<typeof SheetPrimitive.Content>,
|
({ side = "right", className, children, setOpen, ...props }, ref) => (
|
||||||
SheetContentProps
|
<SheetPortal>
|
||||||
>(({ side = "right", className, children, setOpen, ...props }, ref) => (
|
<SheetPrimitive.Content
|
||||||
<SheetPortal>
|
ref={ref}
|
||||||
<SheetPrimitive.Content
|
className={cn(sheetVariants({ side }), className)}
|
||||||
ref={ref}
|
{...props}
|
||||||
className={cn(sheetVariants({ side }), className)}
|
>
|
||||||
{...props}
|
{children}
|
||||||
>
|
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||||
{children}
|
<X
|
||||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
className="h-4 w-4"
|
||||||
<X
|
onClick={() => {
|
||||||
className="h-4 w-4"
|
setOpen(false)
|
||||||
onClick={() => {
|
}}
|
||||||
setOpen(false)
|
/>
|
||||||
}}
|
<span className="sr-only">Close</span>
|
||||||
/>
|
</SheetPrimitive.Close>
|
||||||
<span className="sr-only">Close</span>
|
</SheetPrimitive.Content>
|
||||||
</SheetPrimitive.Close>
|
</SheetPortal>
|
||||||
</SheetPrimitive.Content>
|
),
|
||||||
</SheetPortal>
|
)
|
||||||
))
|
|
||||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||||
|
|
||||||
const SheetHeader = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => (
|
const SheetHeader = ({ className, ...props }: HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
export function useMediaQuery(query: string) {
|
export function useMediaQuery(query: string) {
|
||||||
const [value, setValue] = useState(false)
|
const [value, setValue] = useState(false)
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import i18n from "./i18n"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zod schema for PublicNote
|
||||||
|
* Conventions:
|
||||||
|
* - All fields are strings and may be empty
|
||||||
|
* - IPv4/IPv6/autoRenewal must be "0" or "1"
|
||||||
|
* - cycle is one of Day/Week/Month/Year
|
||||||
|
* - Date fields can be empty, ISO-like, or the special value "0000-00-00T23:59:59+08:00"
|
||||||
|
*/
|
||||||
|
export const PublicNoteSchema = z.object({
|
||||||
|
billingDataMod: z
|
||||||
|
.object({
|
||||||
|
startDate: z.string().optional(),
|
||||||
|
endDate: z.string().optional(),
|
||||||
|
autoRenewal: z.string().optional(),
|
||||||
|
cycle: z.string().optional(),
|
||||||
|
amount: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
planDataMod: z
|
||||||
|
.object({
|
||||||
|
bandwidth: z.string().optional(),
|
||||||
|
trafficVol: z.string().optional(),
|
||||||
|
trafficType: z.string().optional(),
|
||||||
|
IPv4: z.string().optional(),
|
||||||
|
IPv6: z.string().optional(),
|
||||||
|
networkRoute: z.string().optional(),
|
||||||
|
extra: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type PublicNote = z.infer<typeof PublicNoteSchema>
|
||||||
|
|
||||||
|
export const defaultPublicNote: PublicNote = {}
|
||||||
|
|
||||||
|
export const isValidISOLike = (v: string) => {
|
||||||
|
if (!v) return true
|
||||||
|
if (v === "0000-00-00T23:59:59+08:00") return true
|
||||||
|
const d = new Date(v)
|
||||||
|
return !isNaN(d.getTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
export const normalizeISO = (v?: string) => {
|
||||||
|
if (!v) return undefined
|
||||||
|
if (v === "0000-00-00T23:59:59+08:00") return v
|
||||||
|
const date = new Date(v)
|
||||||
|
return isNaN(date.getTime()) ? v : date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a string into PublicNote; return the default object if not valid JSON or validation fails.
|
||||||
|
*/
|
||||||
|
export const parsePublicNote = (s?: string): PublicNote => {
|
||||||
|
if (!s) return defaultPublicNote
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(s)
|
||||||
|
const parsed = PublicNoteSchema.safeParse(obj)
|
||||||
|
if (parsed.success) {
|
||||||
|
return parsed.data
|
||||||
|
}
|
||||||
|
return defaultPublicNote
|
||||||
|
} catch {
|
||||||
|
return defaultPublicNote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validatePublicNote = (pn: PublicNote) => {
|
||||||
|
const errors: Partial<Record<string, string>> = {}
|
||||||
|
|
||||||
|
// Structural and enum validations
|
||||||
|
if (pn.billingDataMod?.autoRenewal && !/^(0|1)$/.test(pn.billingDataMod.autoRenewal)) {
|
||||||
|
errors["billing.autoRenewal"] = i18n.t("Validation.MustBe0Or1")
|
||||||
|
}
|
||||||
|
if (pn.billingDataMod?.cycle && !/^(Day|Week|Month|Year)$/i.test(pn.billingDataMod.cycle)) {
|
||||||
|
errors["billing.cycle"] = i18n.t("Validation.MustBeDayWeekMonthYear")
|
||||||
|
}
|
||||||
|
if (pn.planDataMod?.trafficType && !/^(1|2)$/.test(pn.planDataMod.trafficType)) {
|
||||||
|
errors["plan.trafficType"] = i18n.t("Validation.MustBe1Or2")
|
||||||
|
}
|
||||||
|
if (pn.planDataMod?.IPv4 !== undefined && !/^(0|1)$/.test(pn.planDataMod.IPv4)) {
|
||||||
|
errors["plan.IPv4"] = i18n.t("Validation.MustBe0Or1")
|
||||||
|
}
|
||||||
|
if (pn.planDataMod?.IPv6 !== undefined && !/^(0|1)$/.test(pn.planDataMod.IPv6)) {
|
||||||
|
errors["plan.IPv6"] = i18n.t("Validation.MustBe0Or1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date validity checks
|
||||||
|
if (pn.billingDataMod?.startDate && !isValidISOLike(pn.billingDataMod.startDate)) {
|
||||||
|
errors["billing.startDate"] = i18n.t("Validation.InvalidDate")
|
||||||
|
}
|
||||||
|
if (pn.billingDataMod?.endDate && !isValidISOLike(pn.billingDataMod.endDate)) {
|
||||||
|
errors["billing.endDate"] = i18n.t("Validation.InvalidDate")
|
||||||
|
}
|
||||||
|
|
||||||
|
return { errors, valid: Object.keys(errors).length === 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect default mode from string: JSON matching schema -> "structured"; otherwise "raw".
|
||||||
|
*/
|
||||||
|
export const detectPublicNoteMode = (s?: string): "structured" | "raw" => {
|
||||||
|
if (!s) return "raw"
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(s)
|
||||||
|
const parsed = PublicNoteSchema.strict().safeParse(obj)
|
||||||
|
return parsed.success ? "structured" : "raw"
|
||||||
|
} catch {
|
||||||
|
return "raw"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable patch by path, for use in component wrappers around setPublicNoteObj.
|
||||||
|
* Example path: "billingDataMod.startDate"
|
||||||
|
*/
|
||||||
|
export const applyPublicNotePatch = (
|
||||||
|
obj: PublicNote,
|
||||||
|
path: string,
|
||||||
|
value: string | undefined,
|
||||||
|
): PublicNote => {
|
||||||
|
const keys = path.split(".")
|
||||||
|
const draft: any = structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj))
|
||||||
|
let cur: any = draft
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
const k = keys[i]
|
||||||
|
cur[k] = { ...(cur[k] ?? {}) }
|
||||||
|
cur = cur[k]
|
||||||
|
}
|
||||||
|
cur[keys[keys.length - 1]] = value
|
||||||
|
return draft
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a date field while preserving time parts: if the previous value is a valid date,
|
||||||
|
* keep hours/minutes/seconds. Path example: "billingDataMod.startDate" | "billingDataMod.endDate"
|
||||||
|
*/
|
||||||
|
export const applyPublicNoteDate = (obj: PublicNote, path: string, date: Date): PublicNote => {
|
||||||
|
const keys = path.split(".")
|
||||||
|
const draft: any = structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj))
|
||||||
|
|
||||||
|
// Read previous value to preserve time components
|
||||||
|
let curRead: any = draft
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
const k = keys[i]
|
||||||
|
curRead = (curRead as any)[k]
|
||||||
|
if (!curRead) break
|
||||||
|
}
|
||||||
|
const leafKey = keys[keys.length - 1]
|
||||||
|
const prevVal: string | undefined = curRead ? curRead[leafKey] : undefined
|
||||||
|
|
||||||
|
const d = new Date(date)
|
||||||
|
if (prevVal) {
|
||||||
|
const pd = new Date(prevVal)
|
||||||
|
if (!isNaN(pd.getTime())) {
|
||||||
|
d.setHours(pd.getHours(), pd.getMinutes(), pd.getSeconds(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back
|
||||||
|
let curWrite: any = draft
|
||||||
|
for (let i = 0; i < keys.length - 1; i++) {
|
||||||
|
const k = keys[i]
|
||||||
|
curWrite[k] = { ...(curWrite[k] ?? {}) }
|
||||||
|
curWrite = curWrite[k]
|
||||||
|
}
|
||||||
|
curWrite[leafKey] = d.toISOString()
|
||||||
|
return draft
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the special "no expiry" value for endDate.
|
||||||
|
*/
|
||||||
|
export const toggleEndNoExpiry = (obj: PublicNote): PublicNote => {
|
||||||
|
const NO_EXPIRY = "0000-00-00T23:59:59+08:00"
|
||||||
|
const current = obj.billingDataMod?.endDate
|
||||||
|
const next = current === NO_EXPIRY ? "" : NO_EXPIRY
|
||||||
|
return applyPublicNotePatch(obj, "billingDataMod.endDate", next)
|
||||||
|
}
|
||||||
@@ -195,6 +195,7 @@
|
|||||||
"Value": "Value",
|
"Value": "Value",
|
||||||
"Preview": "Preview",
|
"Preview": "Preview",
|
||||||
"PublicNote": {
|
"PublicNote": {
|
||||||
|
"Label": "Public Note",
|
||||||
"Billing": "Billing",
|
"Billing": "Billing",
|
||||||
"Plan": "Plan",
|
"Plan": "Plan",
|
||||||
"StartDate": "Start Date",
|
"StartDate": "Start Date",
|
||||||
@@ -222,10 +223,13 @@
|
|||||||
"CancelNoExpiry": "Cancel No Expiry",
|
"CancelNoExpiry": "Cancel No Expiry",
|
||||||
"Free": "Free",
|
"Free": "Free",
|
||||||
"PayAsYouGo": "Pay as you go",
|
"PayAsYouGo": "Pay as you go",
|
||||||
"DropdownHint": "You may need to reselect for dropdown changes to take effect",
|
|
||||||
"CommaSeparated": "Separate multiple items with commas",
|
"CommaSeparated": "Separate multiple items with commas",
|
||||||
"Has": "Has",
|
"Has": "Has",
|
||||||
"None": "None"
|
"None": "None",
|
||||||
|
"CustomFields": "Custom Fields",
|
||||||
|
"ClearDate": "Clear Date",
|
||||||
|
"Clear": "Clear",
|
||||||
|
"RawText": "Raw Text"
|
||||||
},
|
},
|
||||||
"Validation": {
|
"Validation": {
|
||||||
"InvalidDate": "Invalid date",
|
"InvalidDate": "Invalid date",
|
||||||
@@ -233,7 +237,8 @@
|
|||||||
"MustBeDayWeekMonthYear": "Must be Day/Week/Month/Year",
|
"MustBeDayWeekMonthYear": "Must be Day/Week/Month/Year",
|
||||||
"MustBe1Or2": "Must be 1 or 2",
|
"MustBe1Or2": "Must be 1 or 2",
|
||||||
"DigitsOnly": "Digits only",
|
"DigitsOnly": "Digits only",
|
||||||
"InvalidForm": "Invalid form"
|
"InvalidForm": "Invalid form",
|
||||||
|
"InvalidJSON": "Invalid JSON"
|
||||||
},
|
},
|
||||||
"AlertRules": {
|
"AlertRules": {
|
||||||
"CoverAllServers": "Monitor all servers",
|
"CoverAllServers": "Monitor all servers",
|
||||||
@@ -241,5 +246,13 @@
|
|||||||
"IgnoreHint": "{{server}} ID: true/false",
|
"IgnoreHint": "{{server}} ID: true/false",
|
||||||
"IgnoreExample": "e.g., {\"1\": true, \"2\": false}"
|
"IgnoreExample": "e.g., {\"1\": true, \"2\": false}"
|
||||||
},
|
},
|
||||||
"Search": "Search..."
|
"Search": "Search...",
|
||||||
|
"Format": "Format",
|
||||||
|
"Formatted": "Formatted",
|
||||||
|
"Copy": "Copy",
|
||||||
|
"Paste": "Paste",
|
||||||
|
"CopiedToClipboard": "Copied to clipboard",
|
||||||
|
"ClipboardWriteFailed": "Clipboard write failed",
|
||||||
|
"PastedFromClipboard": "Pasted from clipboard",
|
||||||
|
"ClipboardReadFailed": "Clipboard read failed"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,7 @@
|
|||||||
"Preview": "预览",
|
"Preview": "预览",
|
||||||
"Option": "选项",
|
"Option": "选项",
|
||||||
"PublicNote": {
|
"PublicNote": {
|
||||||
|
"Label": "公开备注",
|
||||||
"Billing": "账单信息",
|
"Billing": "账单信息",
|
||||||
"Plan": "套餐配置",
|
"Plan": "套餐配置",
|
||||||
"StartDate": "开始时间",
|
"StartDate": "开始时间",
|
||||||
@@ -222,10 +223,13 @@
|
|||||||
"CancelNoExpiry": "取消不过期",
|
"CancelNoExpiry": "取消不过期",
|
||||||
"Free": "免费",
|
"Free": "免费",
|
||||||
"PayAsYouGo": "按量付费",
|
"PayAsYouGo": "按量付费",
|
||||||
"DropdownHint": "下拉项需要重新选择才能生效",
|
|
||||||
"CommaSeparated": "以英文逗号分隔多个",
|
"CommaSeparated": "以英文逗号分隔多个",
|
||||||
"Has": "有",
|
"Has": "有",
|
||||||
"None": "无"
|
"None": "无",
|
||||||
|
"CustomFields": "自定义字段",
|
||||||
|
"ClearDate": "清除日期",
|
||||||
|
"Clear": "清除",
|
||||||
|
"RawText": "原始文本"
|
||||||
},
|
},
|
||||||
"Validation": {
|
"Validation": {
|
||||||
"InvalidDate": "无效的日期格式",
|
"InvalidDate": "无效的日期格式",
|
||||||
@@ -233,7 +237,8 @@
|
|||||||
"MustBeDayWeekMonthYear": "必须为 Day/Week/Month/Year",
|
"MustBeDayWeekMonthYear": "必须为 Day/Week/Month/Year",
|
||||||
"MustBe1Or2": "只能为 1 或 2",
|
"MustBe1Or2": "只能为 1 或 2",
|
||||||
"DigitsOnly": "仅允许数字",
|
"DigitsOnly": "仅允许数字",
|
||||||
"InvalidForm": "表单校验失败"
|
"InvalidForm": "表单校验失败",
|
||||||
|
"InvalidJSON": "无效的 JSON"
|
||||||
},
|
},
|
||||||
"AlertRules": {
|
"AlertRules": {
|
||||||
"CoverAllServers": "监控所有服务器",
|
"CoverAllServers": "监控所有服务器",
|
||||||
@@ -241,5 +246,13 @@
|
|||||||
"IgnoreHint": "{{server}}ID: true/false",
|
"IgnoreHint": "{{server}}ID: true/false",
|
||||||
"IgnoreExample": "例如:{\"1\": true, \"2\": false}"
|
"IgnoreExample": "例如:{\"1\": true, \"2\": false}"
|
||||||
},
|
},
|
||||||
"Search": "搜索..."
|
"Search": "搜索...",
|
||||||
|
"Format": "格式化",
|
||||||
|
"Formatted": "已格式化",
|
||||||
|
"Copy": "复制",
|
||||||
|
"Paste": "粘贴",
|
||||||
|
"CopiedToClipboard": "已复制到剪贴板",
|
||||||
|
"ClipboardWriteFailed": "无法写入剪贴板",
|
||||||
|
"PastedFromClipboard": "已从剪贴板粘贴",
|
||||||
|
"ClipboardReadFailed": "无法读取剪贴板"
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-17
@@ -1,30 +1,27 @@
|
|||||||
import { createRoot } from "react-dom/client"
|
import { createRoot } from "react-dom/client"
|
||||||
import { RouterProvider, createBrowserRouter } from "react-router-dom"
|
import { RouterProvider, createBrowserRouter } from "react-router-dom"
|
||||||
|
|
||||||
import "./index.css"
|
import { TerminalPage } from "./components/terminal"
|
||||||
import "./lib/i18n"
|
import ErrorPage from "./error-page"
|
||||||
|
|
||||||
import { AuthProvider } from "./hooks/useAuth"
|
import { AuthProvider } from "./hooks/useAuth"
|
||||||
import { NotificationProvider } from "./hooks/useNotfication"
|
import { NotificationProvider } from "./hooks/useNotfication"
|
||||||
import { ServerProvider } from "./hooks/useServer"
|
import { ServerProvider } from "./hooks/useServer"
|
||||||
|
import "./index.css"
|
||||||
import Root from "./routes/root"
|
import "./lib/i18n"
|
||||||
import ErrorPage from "./error-page"
|
|
||||||
|
|
||||||
import ProtectedRoute from "./routes/protect"
|
|
||||||
import CronPage from "./routes/cron"
|
|
||||||
import LoginPage from "./routes/login"
|
|
||||||
import ServerPage from "./routes/server"
|
|
||||||
import ServicePage from "./routes/service"
|
|
||||||
import { TerminalPage } from "./components/terminal"
|
|
||||||
import DDNSPage from "./routes/ddns"
|
|
||||||
import NATPage from "./routes/nat"
|
|
||||||
import NotificationGroupPage from "./routes/notification-group"
|
|
||||||
import ServerGroupPage from "./routes/server-group"
|
|
||||||
import AlertRulePage from "./routes/alert-rule"
|
import AlertRulePage from "./routes/alert-rule"
|
||||||
|
import CronPage from "./routes/cron"
|
||||||
|
import DDNSPage from "./routes/ddns"
|
||||||
|
import LoginPage from "./routes/login"
|
||||||
|
import NATPage from "./routes/nat"
|
||||||
import NotificationPage from "./routes/notification"
|
import NotificationPage from "./routes/notification"
|
||||||
|
import NotificationGroupPage from "./routes/notification-group"
|
||||||
import OnlineUserPage from "./routes/online-user"
|
import OnlineUserPage from "./routes/online-user"
|
||||||
import ProfilePage from "./routes/profile"
|
import ProfilePage from "./routes/profile"
|
||||||
|
import ProtectedRoute from "./routes/protect"
|
||||||
|
import Root from "./routes/root"
|
||||||
|
import ServerPage from "./routes/server"
|
||||||
|
import ServerGroupPage from "./routes/server-group"
|
||||||
|
import ServicePage from "./routes/service"
|
||||||
import SettingsPage from "./routes/settings"
|
import SettingsPage from "./routes/settings"
|
||||||
import UserPage from "./routes/user"
|
import UserPage from "./routes/user"
|
||||||
import WAFPage from "./routes/waf"
|
import WAFPage from "./routes/waf"
|
||||||
|
|||||||
Reference in New Issue
Block a user