mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-21 04:01:41 +10:00
refactor(v4.0.0-alpha): beginning of a new era
This commit is contained in:
21
apps/client/src/stores/auth.ts
Normal file
21
apps/client/src/stores/auth.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { UserDto } from "@reactive-resume/dto";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
interface AuthState {
|
||||
user: UserDto | null;
|
||||
}
|
||||
|
||||
interface AuthActions {
|
||||
setUser: (user: UserDto | null) => void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState & AuthActions>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
user: null,
|
||||
setUser: (user) => set({ user }),
|
||||
}),
|
||||
{ name: "auth" },
|
||||
),
|
||||
);
|
||||
108
apps/client/src/stores/builder.ts
Normal file
108
apps/client/src/stores/builder.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { ImperativePanelHandle } from "react-resizable-panels";
|
||||
import { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
|
||||
import { create } from "zustand";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
|
||||
type Sheet = {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
type Panel = {
|
||||
isDragging: boolean;
|
||||
ref: ImperativePanelHandle | null;
|
||||
setRef: (ref: ImperativePanelHandle | null) => void;
|
||||
setDragging: (dragging: boolean) => void;
|
||||
};
|
||||
|
||||
interface BuilderState {
|
||||
transform: {
|
||||
ref: Omit<ReactZoomPanPinchRef, "instance"> | null;
|
||||
setRef: (ref: Omit<ReactZoomPanPinchRef, "instance"> | null) => void;
|
||||
};
|
||||
sheet: {
|
||||
left: Sheet;
|
||||
right: Sheet;
|
||||
};
|
||||
panel: {
|
||||
left: Panel;
|
||||
right: Panel;
|
||||
};
|
||||
}
|
||||
|
||||
interface BuilderActions {
|
||||
toggle: (side: "left" | "right") => void;
|
||||
}
|
||||
|
||||
export const useBuilderStore = create<BuilderState & BuilderActions>()(
|
||||
immer((set) => ({
|
||||
transform: {
|
||||
ref: null,
|
||||
setRef: (ref) => {
|
||||
set((state) => {
|
||||
state.transform.ref = ref;
|
||||
});
|
||||
},
|
||||
},
|
||||
sheet: {
|
||||
left: {
|
||||
open: false,
|
||||
setOpen: (open) => {
|
||||
set((state) => {
|
||||
state.sheet.left.open = open;
|
||||
});
|
||||
},
|
||||
},
|
||||
right: {
|
||||
open: false,
|
||||
setOpen: (open) => {
|
||||
set((state) => {
|
||||
state.sheet.right.open = open;
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
panel: {
|
||||
left: {
|
||||
ref: null,
|
||||
isDragging: false,
|
||||
setRef: (ref) => {
|
||||
set((state) => {
|
||||
state.panel.left.ref = ref;
|
||||
});
|
||||
},
|
||||
setDragging: (dragging) => {
|
||||
set((state) => {
|
||||
state.panel.left.isDragging = dragging;
|
||||
});
|
||||
},
|
||||
},
|
||||
right: {
|
||||
ref: null,
|
||||
isDragging: false,
|
||||
setRef: (ref) => {
|
||||
set((state) => {
|
||||
state.panel.right.ref = ref;
|
||||
});
|
||||
},
|
||||
setDragging: (dragging) => {
|
||||
set((state) => {
|
||||
state.panel.right.isDragging = dragging;
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
toggle: (side) => {
|
||||
set((state) => {
|
||||
const panelRef = state.panel[side].ref;
|
||||
|
||||
if (panelRef) {
|
||||
const collapsed = panelRef.getCollapsed();
|
||||
collapsed ? panelRef.expand() : panelRef.collapse();
|
||||
} else {
|
||||
state.sheet[side].open = !state.sheet[side].open;
|
||||
}
|
||||
});
|
||||
},
|
||||
})),
|
||||
);
|
||||
46
apps/client/src/stores/dialog.ts
Normal file
46
apps/client/src/stores/dialog.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { SectionKey } from "@reactive-resume/schema";
|
||||
import { create } from "zustand";
|
||||
|
||||
export type DialogName = "resume" | "import" | "two-factor" | SectionKey;
|
||||
|
||||
export type DialogMode = "create" | "update" | "duplicate" | "delete";
|
||||
|
||||
export type DialogPayload<T = unknown> = {
|
||||
id: DialogName;
|
||||
item?: T;
|
||||
};
|
||||
|
||||
type Dialog<T = unknown> = {
|
||||
name: DialogName;
|
||||
mode: DialogMode;
|
||||
payload?: DialogPayload<T>;
|
||||
};
|
||||
|
||||
interface DialogState {
|
||||
dialog: Dialog | null;
|
||||
}
|
||||
|
||||
interface DialogActions {
|
||||
setDialog: <T>(dialog: Dialog<T> | null) => void;
|
||||
}
|
||||
|
||||
export const useDialogStore = create<DialogState & DialogActions>()((set) => ({
|
||||
dialog: null,
|
||||
setDialog: (dialog) => set({ dialog }),
|
||||
}));
|
||||
|
||||
export const useDialog = <T = unknown>(name: DialogName) => {
|
||||
const dialog = useDialogStore((state) => {
|
||||
if (name.startsWith("custom.")) name = "custom";
|
||||
return state.dialog?.name === name ? state.dialog : null;
|
||||
});
|
||||
|
||||
return {
|
||||
isOpen: !!dialog,
|
||||
mode: dialog?.mode,
|
||||
payload: dialog?.payload as DialogPayload<T>,
|
||||
open: (mode: DialogMode, payload?: DialogPayload<T>) =>
|
||||
useDialogStore.setState({ dialog: { name, mode, payload } }),
|
||||
close: () => useDialogStore.setState({ dialog: null }),
|
||||
};
|
||||
};
|
||||
17
apps/client/src/stores/openai.ts
Normal file
17
apps/client/src/stores/openai.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
interface OpenAIStore {
|
||||
apiKey: string | null;
|
||||
setApiKey: (apiKey: string | null) => void;
|
||||
}
|
||||
|
||||
export const useOpenAiStore = create<OpenAIStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
apiKey: null,
|
||||
setApiKey: (apiKey) => set({ apiKey }),
|
||||
}),
|
||||
{ name: "openai" },
|
||||
),
|
||||
);
|
||||
79
apps/client/src/stores/resume.ts
Normal file
79
apps/client/src/stores/resume.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { ResumeDto } from "@reactive-resume/dto";
|
||||
import { CustomSection, defaultSection, SectionKey } from "@reactive-resume/schema";
|
||||
import { removeItemInLayout } from "@reactive-resume/utils";
|
||||
import _set from "lodash.set";
|
||||
import { temporal, TemporalState } from "zundo";
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||
|
||||
import { debouncedUpdateResume } from "../services/resume";
|
||||
|
||||
type ResumeStore = {
|
||||
resume: ResumeDto;
|
||||
|
||||
// Actions
|
||||
setValue: (path: string, value: unknown) => void;
|
||||
|
||||
// Custom Section Actions
|
||||
addSection: () => void;
|
||||
removeSection: (sectionId: SectionKey) => void;
|
||||
};
|
||||
|
||||
export const useResumeStore = create<ResumeStore>()(
|
||||
temporal(
|
||||
immer((set) => ({
|
||||
resume: {} as ResumeDto,
|
||||
setValue: (path, value) => {
|
||||
set((state) => {
|
||||
if (path === "visibility") {
|
||||
state.resume.visibility = value as "public" | "private";
|
||||
} else {
|
||||
state.resume.data = _set(state.resume.data, path, value);
|
||||
}
|
||||
|
||||
debouncedUpdateResume(JSON.parse(JSON.stringify(state.resume)));
|
||||
});
|
||||
},
|
||||
addSection: () => {
|
||||
const section: CustomSection = {
|
||||
...defaultSection,
|
||||
id: createId(),
|
||||
name: "Custom Section",
|
||||
items: [],
|
||||
};
|
||||
|
||||
set((state) => {
|
||||
state.resume.data.metadata.layout[0][0].push(`custom.${section.id}`);
|
||||
state.resume.data = _set(state.resume.data, `sections.custom.${section.id}`, section);
|
||||
|
||||
debouncedUpdateResume(JSON.parse(JSON.stringify(state.resume)));
|
||||
});
|
||||
},
|
||||
removeSection: (sectionId: SectionKey) => {
|
||||
if (sectionId.startsWith("custom.")) {
|
||||
const id = sectionId.split("custom.")[1];
|
||||
|
||||
set((state) => {
|
||||
removeItemInLayout(sectionId, state.resume.data.metadata.layout);
|
||||
delete state.resume.data.sections.custom[id];
|
||||
|
||||
debouncedUpdateResume(JSON.parse(JSON.stringify(state.resume)));
|
||||
});
|
||||
}
|
||||
},
|
||||
})),
|
||||
{
|
||||
limit: 100,
|
||||
wrapTemporal: (fn) => devtools(fn),
|
||||
partialize: ({ resume }) => ({ resume }),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export const useTemporalResumeStore = <T>(
|
||||
selector: (state: TemporalState<Pick<ResumeStore, "resume">>) => T,
|
||||
equality?: (a: T, b: T) => boolean,
|
||||
) => useStoreWithEqualityFn(useResumeStore.temporal, selector, equality);
|
||||
Reference in New Issue
Block a user