umami/src/app/(main)/boards/BoardProvider.tsx
Mike Cao 50edb71687 Simplify i18n: remove old react-intl artifacts, rename formatMessage to t, replace FormattedMessage with t.rich().
- Rewrite messages.ts to plain string key maps (remove MessageDescriptor)
- Rewrite useMessages hook to expose t from useTranslations() directly
- Rename formatMessage → t across 193 consumer files
- Replace custom FormattedMessage component with next-intl t.rich()
- Update 52 language files to use rich text tags (<b>, <a>)
- Remove all direct imports from @/components/messages in favor of useMessages()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 11:19:04 -08:00

108 lines
3.1 KiB
TypeScript

'use client';
import { Loading, useToast } from '@umami/react-zen';
import { createContext, type ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { v4 as uuid } from 'uuid';
import { useApi, useMessages, useModified, useNavigation } from '@/components/hooks';
import { useBoardQuery } from '@/components/hooks/queries/useBoardQuery';
import type { Board, BoardParameters } from '@/lib/types';
export type LayoutGetter = () => Partial<BoardParameters> | null;
export interface BoardContextValue {
board: Partial<Board>;
editing: boolean;
updateBoard: (data: Partial<Board>) => void;
saveBoard: () => Promise<Board>;
isPending: boolean;
registerLayoutGetter: (getter: LayoutGetter) => void;
}
export const BoardContext = createContext<BoardContextValue>(null);
const createDefaultBoard = (): Partial<Board> => ({
name: '',
description: '',
parameters: {
rows: [{ id: uuid(), columns: [{ id: uuid(), component: null }] }],
},
});
export function BoardProvider({
boardId,
editing = false,
children,
}: {
boardId?: string;
editing?: boolean;
children: ReactNode;
}) {
const { data, isFetching, isLoading } = useBoardQuery(boardId);
const { post, useMutation } = useApi();
const { touch } = useModified();
const { toast } = useToast();
const { t, labels, messages } = useMessages();
const { router, renderUrl } = useNavigation();
const [board, setBoard] = useState<Partial<Board>>(data ?? createDefaultBoard());
const layoutGetterRef = useRef<LayoutGetter | null>(null);
const registerLayoutGetter = useCallback((getter: LayoutGetter) => {
layoutGetterRef.current = getter;
}, []);
useEffect(() => {
if (data) {
setBoard(data);
}
}, [data]);
const { mutateAsync, isPending } = useMutation({
mutationFn: (boardData: Partial<Board>) => {
if (boardData.id) {
return post(`/boards/${boardData.id}`, boardData);
}
return post('/boards', { ...boardData, type: 'dashboard', slug: '' });
},
});
const updateBoard = useCallback((data: Partial<Board>) => {
setBoard(current => ({ ...current, ...data }));
}, []);
const saveBoard = useCallback(async () => {
const defaultName = t(labels.untitled);
// Get current layout sizes from BoardBody if registered
const layoutData = layoutGetterRef.current?.();
const parameters = layoutData ? { ...board.parameters, ...layoutData } : board.parameters;
const result = await mutateAsync({
...board,
name: board.name || defaultName,
parameters,
});
toast(t(messages.saved));
touch('boards');
if (board.id) {
touch(`board:${board.id}`);
} else if (result?.id) {
router.push(renderUrl(`/boards/${result.id}`));
}
return result;
}, [board, mutateAsync, toast, t, labels.untitled, messages.saved, touch, router, renderUrl]);
if (boardId && isFetching && isLoading) {
return <Loading placement="absolute" />;
}
return (
<BoardContext.Provider
value={{ board, editing, updateBoard, saveBoard, isPending, registerLayoutGetter }}
>
{children}
</BoardContext.Provider>
);
}