feat: migrate to Next.js for Mini App and enhance project structure
- Replaced the previous webapp with a new Mini App built using Next.js, improving performance and maintainability. - Updated the `.gitignore` to exclude Next.js build artifacts and node modules. - Revised documentation in `AGENTS.md`, `README.md`, and `architecture.md` to reflect the new Mini App structure and technology stack. - Enhanced Dockerfile to support the new build process for the Next.js application. - Updated CI workflow to build and test the Next.js application. - Added new configuration options for the Mini App, including `MINI_APP_SHORT_NAME` for improved deep linking. - Refactored frontend testing setup to accommodate the new structure and testing framework. - Removed legacy webapp files and dependencies to streamline the project.
This commit is contained in:
85
webapp-next/src/store/app-store.ts
Normal file
85
webapp-next/src/store/app-store.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Global application state (Zustand).
|
||||
* Replaces the mutable state from webapp/js/dom.js.
|
||||
*/
|
||||
|
||||
import { create } from "zustand";
|
||||
import type { DutyWithUser, CalendarEvent } from "@/types";
|
||||
import { getStartParamFromUrl } from "@/lib/launch-params";
|
||||
|
||||
export type CurrentView = "calendar" | "currentDuty";
|
||||
|
||||
export interface AppState {
|
||||
currentMonth: Date;
|
||||
lang: "ru" | "en";
|
||||
duties: DutyWithUser[];
|
||||
calendarEvents: CalendarEvent[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
accessDenied: boolean;
|
||||
/** Server detail from API 403 response; shown in AccessDenied component. */
|
||||
accessDeniedDetail: string | null;
|
||||
currentView: CurrentView;
|
||||
selectedDay: string | null;
|
||||
|
||||
setCurrentMonth: (d: Date) => void;
|
||||
nextMonth: () => void;
|
||||
prevMonth: () => void;
|
||||
setDuties: (d: DutyWithUser[]) => void;
|
||||
setCalendarEvents: (e: CalendarEvent[]) => void;
|
||||
setLoading: (v: boolean) => void;
|
||||
setError: (msg: string | null) => void;
|
||||
setAccessDenied: (v: boolean) => void;
|
||||
setAccessDeniedDetail: (v: string | null) => void;
|
||||
setLang: (v: "ru" | "en") => void;
|
||||
setCurrentView: (v: CurrentView) => void;
|
||||
setSelectedDay: (key: string | null) => void;
|
||||
/** Batch multiple state updates into a single re-render. */
|
||||
batchUpdate: (partial: Partial<Pick<AppState, "currentMonth" | "lang" | "duties" | "calendarEvents" | "loading" | "error" | "accessDenied" | "accessDeniedDetail" | "currentView" | "selectedDay">>) => void;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const initialMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
|
||||
/** Initial view: currentDuty when opened via deep link (startParam=duty), else calendar. */
|
||||
function getInitialView(): CurrentView {
|
||||
if (typeof window === "undefined") return "calendar";
|
||||
return getStartParamFromUrl() === "duty" ? "currentDuty" : "calendar";
|
||||
}
|
||||
|
||||
export const useAppStore = create<AppState>((set) => ({
|
||||
currentMonth: initialMonth,
|
||||
lang: "en",
|
||||
duties: [],
|
||||
calendarEvents: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
accessDenied: false,
|
||||
accessDeniedDetail: null,
|
||||
currentView: getInitialView(),
|
||||
selectedDay: null,
|
||||
|
||||
setCurrentMonth: (d) => set({ currentMonth: d }),
|
||||
nextMonth: () =>
|
||||
set((s) => {
|
||||
const next = new Date(s.currentMonth);
|
||||
next.setMonth(next.getMonth() + 1);
|
||||
return { currentMonth: next };
|
||||
}),
|
||||
prevMonth: () =>
|
||||
set((s) => {
|
||||
const prev = new Date(s.currentMonth);
|
||||
prev.setMonth(prev.getMonth() - 1);
|
||||
return { currentMonth: prev };
|
||||
}),
|
||||
setDuties: (d) => set({ duties: d }),
|
||||
setCalendarEvents: (e) => set({ calendarEvents: e }),
|
||||
setLoading: (v) => set({ loading: v }),
|
||||
setError: (msg) => set({ error: msg }),
|
||||
setAccessDenied: (v) => set({ accessDenied: v }),
|
||||
setAccessDeniedDetail: (v) => set({ accessDeniedDetail: v }),
|
||||
setLang: (v) => set({ lang: v }),
|
||||
setCurrentView: (v) => set({ currentView: v }),
|
||||
setSelectedDay: (key) => set({ selectedDay: key }),
|
||||
batchUpdate: (partial) => set(partial),
|
||||
}));
|
||||
Reference in New Issue
Block a user