mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 23:26:38 +08:00
refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps
- Merge all files inside openflare-server to the repository root directory. - Relocate agent, relay, and flared subprojects from internal/ to internal/apps/. - Combine docker-compose files and update build context paths to root. - Update GitHub workflows and Dockerfiles to refer to new directories and package names. - Rewrite Go package imports across all files. - Resolve database renew test race condition and clean up docs.
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"use client"
|
||||
|
||||
import {useEffect} from "react"
|
||||
import {usePathname, useRouter} from "next/navigation"
|
||||
|
||||
import {useUser} from "@/contexts/user-context"
|
||||
|
||||
/** Redirects unauthenticated users to login while allowing the shell to render first. */
|
||||
export function useAuthRedirect() {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const {user, loading} = useUser()
|
||||
|
||||
useEffect(() => {
|
||||
if (loading || user) {
|
||||
return
|
||||
}
|
||||
|
||||
const queryString = window.location.search
|
||||
const callbackUrl = queryString ? `${pathname}${queryString}` : pathname
|
||||
const loginUrl = new URL("/login", window.location.origin)
|
||||
|
||||
loginUrl.searchParams.set("callbackUrl", callbackUrl)
|
||||
sessionStorage.setItem("redirect_after_login", callbackUrl)
|
||||
router.replace(loginUrl.toString())
|
||||
}, [loading, pathname, router, user])
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import * as React from 'react';
|
||||
|
||||
interface CommonControlledStateProps<T> {
|
||||
value?: T;
|
||||
defaultValue?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 受控状态 Hook
|
||||
* 用于处理受控和非受控组件的状态管理
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const [value, setValue] = useControlledState({ value: propValue, onChange: console.log })
|
||||
* ```
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function useControlledState<T, Rest extends any[] = []>(
|
||||
props: CommonControlledStateProps<T> & {
|
||||
onChange?: (value: T, ...args: Rest) => void;
|
||||
},
|
||||
): readonly [T, (next: T, ...args: Rest) => void] {
|
||||
const { value, defaultValue, onChange } = props;
|
||||
|
||||
const [state, setInternalState] = React.useState<T>(
|
||||
value !== undefined ? value : (defaultValue as T),
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (value !== undefined) setInternalState(value);
|
||||
}, [value]);
|
||||
|
||||
const setState = React.useCallback(
|
||||
(next: T, ...args: Rest) => {
|
||||
setInternalState(next);
|
||||
onChange?.(next, ...args);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return [state, setState] as const;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import {useInView, type UseInViewOptions} from 'motion/react';
|
||||
|
||||
interface UseIsInViewOptions {
|
||||
inView?: boolean;
|
||||
inViewOnce?: boolean;
|
||||
inViewMargin?: UseInViewOptions['margin'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测元素是否在视图中
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { ref, isInView } = useIsInView({ inViewOnce: true })
|
||||
* ```
|
||||
*/
|
||||
function useIsInView<T extends HTMLElement = HTMLElement>(
|
||||
ref: React.Ref<T>,
|
||||
options: UseIsInViewOptions = {},
|
||||
) {
|
||||
const { inView, inViewOnce = false, inViewMargin = '0px' } = options;
|
||||
const localRef = React.useRef<T>(null);
|
||||
React.useImperativeHandle(ref, () => localRef.current as T);
|
||||
const inViewResult = useInView(localRef, {
|
||||
once: inViewOnce,
|
||||
margin: inViewMargin,
|
||||
});
|
||||
const isInView = !inView || inViewResult;
|
||||
return { ref: localRef, isInView };
|
||||
}
|
||||
|
||||
export { useIsInView, type UseIsInViewOptions };
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
import services from '@/lib/services'
|
||||
|
||||
/**
|
||||
* 获取公共配置(使用 React Query 进行统一缓存与自动同步)
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { config, loading, error } = usePublicConfig()
|
||||
* ```
|
||||
*/
|
||||
export function usePublicConfig() {
|
||||
const { data: config, isLoading: loading, error } = useQuery({
|
||||
queryKey: ['public-config'],
|
||||
queryFn: () => services.config.getPublicConfig(),
|
||||
staleTime: 5 * 60 * 1000, // 5 分钟缓存
|
||||
})
|
||||
|
||||
return { config: config || null, loading, error }
|
||||
}
|
||||
Reference in New Issue
Block a user