Squashed commit of the following:

commit b2b0470a1d9267d042ec0ac66523c6635bf5b199
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 13:13:38 2026 +0200

    chore: update .gitignore to include .vite-hooks and modify pnpm-lock.yaml for dependencies

commit d28fadb5cd8706c874e616102878b4a394ec84c1
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 13:08:04 2026 +0200

    fix: remove timestamp conflict guard

commit c6998d9dbab19d09d3c8054feef1d2e4117555eb
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 12:11:51 2026 +0200

    chore(release): v5.1.5

commit f33d168711804880e1f12e88d24290aae16cc258
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 11:58:35 2026 +0200

    revert: compose.yml

commit d961e6535811a10c335525fb33a08d03e737278d
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 11:58:08 2026 +0200

    refactor(agent): replace 'revert' terminology with 'restore' for clarity, resolves #3086

commit 17f351171be218e33f01c469d95e4164d4c8dc57
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 11:10:41 2026 +0200

    refactor(pdf): simplify sidebar section filtering and update summary feature logic

commit d55179b9d76879e3204de185e8b53fadd0a107ed
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:53:37 2026 +0200

    chore: update pnpm-lock.yaml and turbo.json

commit 7cade6980e1a04352536bd44ef773f338c4ef599
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:38:30 2026 +0200

    fix(polyfill): add tested polyfill for Map Upsert methods

commit 26d175bb9c53d93225d1e907678445252c13d660
Merge: 1cf33dc6c 5b1297fa2
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:23:29 2026 +0200

    Merge remote-tracking branch 'origin/main' into feat/explore-hono-orpc-migration

    # Conflicts:
    #	packages/api/src/services/agent-url.ts
    #	packages/runtime-externals/package.json

commit 1cf33dc6c9d81735730ad656e16dab6501c6d6a1
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Tue May 19 09:22:12 2026 +0200

    chore: preserve branch changes before main sync

commit b380a4b00fdbcdd81ff4f8ef72b330fd027ccda5
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Mon May 18 07:50:28 2026 +0200

    chore: lot of fixes for monorepo migration

commit 8fcf0ec64e1c29572ebaff494338368bfcf75760
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 13:57:17 2026 +0200

    chore: update knip version and refine web app routing with new SEO endpoints

commit 234e68086ff15610a93877354c98e2c020364533
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 12:10:06 2026 +0200

    refactor(auth): update OAuth routes to include API prefix and remove unused schema endpoint

commit 91c84b9a8496b0ce21d71cae9f8b2a027638c9ac
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:54:29 2026 +0200

    chore: update dependencies and enhance PWA metadata in web app

commit 150117d4a5a9dd6cd92c64891aad8cae90f6a7af
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:12:35 2026 +0200

    docs: revise manifest-only pwa testing scope

commit 6b939a55661aec9dd8122b184e4b60a5c7325fb5
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:11:33 2026 +0200

    docs: add manifest-only pwa design

commit 1422e1fc96c400948b273210a1067251087d15d4
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:05:04 2026 +0200

    chore(dev): simplify server proxy config

commit bc2ff5a9f6fda41e6c40333c8f163aa23a6c5e48
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:04:50 2026 +0200

    docs: add unsafe oauth redirect plan

commit 445359ebe9b96c1515bf1c4c3f73ba8a8448ec12
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 11:04:34 2026 +0200

    feat(auth): add unsafe oauth redirect flag

commit 73fffdd24598e56b2793f7657919bc794835892e
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 10:55:02 2026 +0200

    docs: design unsafe oauth redirect flag

commit c0066aa19c15fc8a4c8e5179ed49889c117519f4
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 10:22:04 2026 +0200

    chore: update translation source paths

commit 9033da082418d252aafd6c2eed72f71f014be3d9
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 10:09:25 2026 +0200

    refactor(arch): react spa + hono migration

commit 6f27936c11bda895977dc63ee550c3346d4ce24b
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Fri May 15 01:10:47 2026 +0200

    docs: add docker nightly tagging design

commit ecc1fd9a88a0ee1dca2f1977dfc17f74527fe1da
Author: Amruth Pillai <im.amruth@gmail.com>
Date:   Thu May 14 20:05:44 2026 +0200

    feat: migrate to hono spa server
This commit is contained in:
Amruth Pillai
2026-05-19 13:14:21 +02:00
parent 5b1297fa2b
commit 62f8270b3e
518 changed files with 29398 additions and 26871 deletions
@@ -0,0 +1,6 @@
import { defineDialogRenderer, defineDialogRendererRegistry } from "../renderer-registry";
import { CreateApiKeyDialog } from "./create";
export const apiKeyDialogRendererRegistry = defineDialogRendererRegistry("api-key", [
defineDialogRenderer("api-key.create", () => <CreateApiKeyDialog />),
]);
+3
View File
@@ -0,0 +1,3 @@
import z from "zod";
export const apiKeyDialogSchemas = [z.object({ type: z.literal("api-key.create"), data: z.undefined() })] as const;
+10
View File
@@ -0,0 +1,10 @@
import { defineDialogRenderer, defineDialogRendererRegistry } from "../renderer-registry";
import { ChangePasswordDialog } from "./change-password";
import { DisableTwoFactorDialog } from "./disable-two-factor";
import { EnableTwoFactorDialog } from "./enable-two-factor";
export const authDialogRendererRegistry = defineDialogRendererRegistry("auth", [
defineDialogRenderer("auth.change-password", () => <ChangePasswordDialog />),
defineDialogRenderer("auth.two-factor.enable", () => <EnableTwoFactorDialog />),
defineDialogRenderer("auth.two-factor.disable", () => <DisableTwoFactorDialog />),
]);
+7
View File
@@ -0,0 +1,7 @@
import z from "zod";
export const authDialogSchemas = [
z.object({ type: z.literal("auth.change-password"), data: z.undefined() }),
z.object({ type: z.literal("auth.two-factor.enable"), data: z.undefined() }),
z.object({ type: z.literal("auth.two-factor.disable"), data: z.undefined() }),
] as const;
+2 -64
View File
@@ -1,73 +1,11 @@
import { match } from "ts-pattern";
import { Dialog } from "@reactive-resume/ui/components/dialog";
import { CreateApiKeyDialog } from "./api-key/create";
import { ChangePasswordDialog } from "./auth/change-password";
import { DisableTwoFactorDialog } from "./auth/disable-two-factor";
import { EnableTwoFactorDialog } from "./auth/enable-two-factor";
import { CreateResumeDialog, DuplicateResumeDialog, UpdateResumeDialog } from "./resume";
import { ImportResumeDialog } from "./resume/import";
import { CreateAwardDialog, UpdateAwardDialog } from "./resume/sections/award";
import { CreateCertificationDialog, UpdateCertificationDialog } from "./resume/sections/certification";
import { CreateCoverLetterDialog, UpdateCoverLetterDialog } from "./resume/sections/cover-letter";
import { CreateCustomSectionDialog, UpdateCustomSectionDialog } from "./resume/sections/custom";
import { CreateEducationDialog, UpdateEducationDialog } from "./resume/sections/education";
import { CreateExperienceDialog, UpdateExperienceDialog } from "./resume/sections/experience";
import { CreateInterestDialog, UpdateInterestDialog } from "./resume/sections/interest";
import { CreateLanguageDialog, UpdateLanguageDialog } from "./resume/sections/language";
import { CreateProfileDialog, UpdateProfileDialog } from "./resume/sections/profile";
import { CreateProjectDialog, UpdateProjectDialog } from "./resume/sections/project";
import { CreatePublicationDialog, UpdatePublicationDialog } from "./resume/sections/publication";
import { CreateReferenceDialog, UpdateReferenceDialog } from "./resume/sections/reference";
import { CreateSkillDialog, UpdateSkillDialog } from "./resume/sections/skill";
import { CreateSummaryItemDialog, UpdateSummaryItemDialog } from "./resume/sections/summary-item";
import { CreateVolunteerDialog, UpdateVolunteerDialog } from "./resume/sections/volunteer";
import { TemplateGalleryDialog } from "./resume/template/gallery";
import { renderDialog } from "./renderers";
import { useDialogStore } from "./store";
export function DialogManager() {
const { open, activeDialog, onOpenChange } = useDialogStore();
const DialogContent = match(activeDialog)
.with({ type: "auth.change-password" }, () => <ChangePasswordDialog />)
.with({ type: "auth.two-factor.enable" }, () => <EnableTwoFactorDialog />)
.with({ type: "auth.two-factor.disable" }, () => <DisableTwoFactorDialog />)
.with({ type: "api-key.create" }, () => <CreateApiKeyDialog />)
.with({ type: "resume.create" }, () => <CreateResumeDialog />)
.with({ type: "resume.update" }, ({ data }) => <UpdateResumeDialog data={data} />)
.with({ type: "resume.duplicate" }, ({ data }) => <DuplicateResumeDialog data={data} />)
.with({ type: "resume.import" }, () => <ImportResumeDialog />)
.with({ type: "resume.template.gallery" }, () => <TemplateGalleryDialog />)
.with({ type: "resume.sections.profiles.create" }, ({ data }) => <CreateProfileDialog data={data} />)
.with({ type: "resume.sections.profiles.update" }, ({ data }) => <UpdateProfileDialog data={data} />)
.with({ type: "resume.sections.experience.create" }, ({ data }) => <CreateExperienceDialog data={data} />)
.with({ type: "resume.sections.experience.update" }, ({ data }) => <UpdateExperienceDialog data={data} />)
.with({ type: "resume.sections.education.create" }, ({ data }) => <CreateEducationDialog data={data} />)
.with({ type: "resume.sections.education.update" }, ({ data }) => <UpdateEducationDialog data={data} />)
.with({ type: "resume.sections.skills.create" }, ({ data }) => <CreateSkillDialog data={data} />)
.with({ type: "resume.sections.skills.update" }, ({ data }) => <UpdateSkillDialog data={data} />)
.with({ type: "resume.sections.projects.create" }, ({ data }) => <CreateProjectDialog data={data} />)
.with({ type: "resume.sections.projects.update" }, ({ data }) => <UpdateProjectDialog data={data} />)
.with({ type: "resume.sections.certifications.create" }, ({ data }) => <CreateCertificationDialog data={data} />)
.with({ type: "resume.sections.certifications.update" }, ({ data }) => <UpdateCertificationDialog data={data} />)
.with({ type: "resume.sections.languages.create" }, ({ data }) => <CreateLanguageDialog data={data} />)
.with({ type: "resume.sections.languages.update" }, ({ data }) => <UpdateLanguageDialog data={data} />)
.with({ type: "resume.sections.publications.create" }, ({ data }) => <CreatePublicationDialog data={data} />)
.with({ type: "resume.sections.publications.update" }, ({ data }) => <UpdatePublicationDialog data={data} />)
.with({ type: "resume.sections.awards.create" }, ({ data }) => <CreateAwardDialog data={data} />)
.with({ type: "resume.sections.awards.update" }, ({ data }) => <UpdateAwardDialog data={data} />)
.with({ type: "resume.sections.interests.create" }, ({ data }) => <CreateInterestDialog data={data} />)
.with({ type: "resume.sections.interests.update" }, ({ data }) => <UpdateInterestDialog data={data} />)
.with({ type: "resume.sections.volunteer.create" }, ({ data }) => <CreateVolunteerDialog data={data} />)
.with({ type: "resume.sections.volunteer.update" }, ({ data }) => <UpdateVolunteerDialog data={data} />)
.with({ type: "resume.sections.references.create" }, ({ data }) => <CreateReferenceDialog data={data} />)
.with({ type: "resume.sections.references.update" }, ({ data }) => <UpdateReferenceDialog data={data} />)
.with({ type: "resume.sections.summary.create" }, ({ data }) => <CreateSummaryItemDialog data={data} />)
.with({ type: "resume.sections.summary.update" }, ({ data }) => <UpdateSummaryItemDialog data={data} />)
.with({ type: "resume.sections.cover-letter.create" }, ({ data }) => <CreateCoverLetterDialog data={data} />)
.with({ type: "resume.sections.cover-letter.update" }, ({ data }) => <UpdateCoverLetterDialog data={data} />)
.with({ type: "resume.sections.custom.create" }, ({ data }) => <CreateCustomSectionDialog data={data} />)
.with({ type: "resume.sections.custom.update" }, ({ data }) => <UpdateCustomSectionDialog data={data} />)
.otherwise(() => null);
const DialogContent = renderDialog(activeDialog);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
+26
View File
@@ -0,0 +1,26 @@
import type { ReactNode } from "react";
import type { DialogSchema, DialogType } from "./schemas";
export interface DialogRendererEntry<T extends DialogType = DialogType> {
type: T;
render: (dialog: Extract<DialogSchema, { type: T }>) => ReactNode;
}
export type AnyDialogRendererEntry = {
[T in DialogType]: DialogRendererEntry<T>;
}[DialogType];
export interface DialogRendererRegistry {
domain: string;
renderers: readonly AnyDialogRendererEntry[];
}
export const defineDialogRenderer = <T extends DialogType>(
type: T,
render: (dialog: Extract<DialogSchema, { type: T }>) => ReactNode,
): DialogRendererEntry<T> => ({ type, render });
export const defineDialogRendererRegistry = (
domain: string,
renderers: readonly AnyDialogRendererEntry[],
): DialogRendererRegistry => ({ domain, renderers });
+21
View File
@@ -0,0 +1,21 @@
import type { DialogSchema } from "./schemas";
import { apiKeyDialogRendererRegistry } from "./api-key/registry";
import { authDialogRendererRegistry } from "./auth/registry";
import { resumeDialogRendererRegistry } from "./resume/registry";
const dialogRendererRegistries = [
authDialogRendererRegistry,
apiKeyDialogRendererRegistry,
resumeDialogRendererRegistry,
] as const;
export const renderDialog = (dialog: DialogSchema | null) => {
if (!dialog) return null;
for (const registry of dialogRendererRegistries) {
const renderer = registry.renderers.find((entry) => entry.type === dialog.type);
if (renderer) return renderer.render(dialog as never);
}
return null;
};
+1 -1
View File
@@ -34,7 +34,7 @@ import {
} from "@reactive-resume/ui/components/input-group";
import { generateId, generateRandomName, slugify } from "@reactive-resume/utils/string";
import { ChipInput } from "@/components/input/chip-input";
import { usePatchResume } from "@/components/resume/builder-resume-draft";
import { usePatchResume } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { authClient } from "@/libs/auth/client";
import { getResumeErrorMessage } from "@/libs/error-message";
+61
View File
@@ -0,0 +1,61 @@
import { defineDialogRenderer, defineDialogRendererRegistry } from "../renderer-registry";
import { CreateResumeDialog, DuplicateResumeDialog, UpdateResumeDialog } from ".";
import { ImportResumeDialog } from "./import";
import { CreateAwardDialog, UpdateAwardDialog } from "./sections/award";
import { CreateCertificationDialog, UpdateCertificationDialog } from "./sections/certification";
import { CreateCoverLetterDialog, UpdateCoverLetterDialog } from "./sections/cover-letter";
import { CreateCustomSectionDialog, UpdateCustomSectionDialog } from "./sections/custom";
import { CreateEducationDialog, UpdateEducationDialog } from "./sections/education";
import { CreateExperienceDialog, UpdateExperienceDialog } from "./sections/experience";
import { CreateInterestDialog, UpdateInterestDialog } from "./sections/interest";
import { CreateLanguageDialog, UpdateLanguageDialog } from "./sections/language";
import { CreateProfileDialog, UpdateProfileDialog } from "./sections/profile";
import { CreateProjectDialog, UpdateProjectDialog } from "./sections/project";
import { CreatePublicationDialog, UpdatePublicationDialog } from "./sections/publication";
import { CreateReferenceDialog, UpdateReferenceDialog } from "./sections/reference";
import { CreateSkillDialog, UpdateSkillDialog } from "./sections/skill";
import { CreateSummaryItemDialog, UpdateSummaryItemDialog } from "./sections/summary-item";
import { CreateVolunteerDialog, UpdateVolunteerDialog } from "./sections/volunteer";
import { TemplateGalleryDialog } from "./template/gallery";
export const resumeDialogRendererRegistry = defineDialogRendererRegistry("resume", [
defineDialogRenderer("resume.create", () => <CreateResumeDialog />),
defineDialogRenderer("resume.update", ({ data }) => <UpdateResumeDialog data={data} />),
defineDialogRenderer("resume.duplicate", ({ data }) => <DuplicateResumeDialog data={data} />),
defineDialogRenderer("resume.import", () => <ImportResumeDialog />),
defineDialogRenderer("resume.template.gallery", () => <TemplateGalleryDialog />),
defineDialogRenderer("resume.sections.profiles.create", ({ data }) => <CreateProfileDialog data={data} />),
defineDialogRenderer("resume.sections.profiles.update", ({ data }) => <UpdateProfileDialog data={data} />),
defineDialogRenderer("resume.sections.experience.create", ({ data }) => <CreateExperienceDialog data={data} />),
defineDialogRenderer("resume.sections.experience.update", ({ data }) => <UpdateExperienceDialog data={data} />),
defineDialogRenderer("resume.sections.education.create", ({ data }) => <CreateEducationDialog data={data} />),
defineDialogRenderer("resume.sections.education.update", ({ data }) => <UpdateEducationDialog data={data} />),
defineDialogRenderer("resume.sections.skills.create", ({ data }) => <CreateSkillDialog data={data} />),
defineDialogRenderer("resume.sections.skills.update", ({ data }) => <UpdateSkillDialog data={data} />),
defineDialogRenderer("resume.sections.projects.create", ({ data }) => <CreateProjectDialog data={data} />),
defineDialogRenderer("resume.sections.projects.update", ({ data }) => <UpdateProjectDialog data={data} />),
defineDialogRenderer("resume.sections.certifications.create", ({ data }) => (
<CreateCertificationDialog data={data} />
)),
defineDialogRenderer("resume.sections.certifications.update", ({ data }) => (
<UpdateCertificationDialog data={data} />
)),
defineDialogRenderer("resume.sections.languages.create", ({ data }) => <CreateLanguageDialog data={data} />),
defineDialogRenderer("resume.sections.languages.update", ({ data }) => <UpdateLanguageDialog data={data} />),
defineDialogRenderer("resume.sections.publications.create", ({ data }) => <CreatePublicationDialog data={data} />),
defineDialogRenderer("resume.sections.publications.update", ({ data }) => <UpdatePublicationDialog data={data} />),
defineDialogRenderer("resume.sections.awards.create", ({ data }) => <CreateAwardDialog data={data} />),
defineDialogRenderer("resume.sections.awards.update", ({ data }) => <UpdateAwardDialog data={data} />),
defineDialogRenderer("resume.sections.interests.create", ({ data }) => <CreateInterestDialog data={data} />),
defineDialogRenderer("resume.sections.interests.update", ({ data }) => <UpdateInterestDialog data={data} />),
defineDialogRenderer("resume.sections.volunteer.create", ({ data }) => <CreateVolunteerDialog data={data} />),
defineDialogRenderer("resume.sections.volunteer.update", ({ data }) => <UpdateVolunteerDialog data={data} />),
defineDialogRenderer("resume.sections.references.create", ({ data }) => <CreateReferenceDialog data={data} />),
defineDialogRenderer("resume.sections.references.update", ({ data }) => <UpdateReferenceDialog data={data} />),
defineDialogRenderer("resume.sections.summary.create", ({ data }) => <CreateSummaryItemDialog data={data} />),
defineDialogRenderer("resume.sections.summary.update", ({ data }) => <UpdateSummaryItemDialog data={data} />),
defineDialogRenderer("resume.sections.cover-letter.create", ({ data }) => <CreateCoverLetterDialog data={data} />),
defineDialogRenderer("resume.sections.cover-letter.update", ({ data }) => <UpdateCoverLetterDialog data={data} />),
defineDialogRenderer("resume.sections.custom.create", ({ data }) => <CreateCustomSectionDialog data={data} />),
defineDialogRenderer("resume.sections.custom.update", ({ data }) => <UpdateCustomSectionDialog data={data} />),
]);
+152
View File
@@ -0,0 +1,152 @@
import z from "zod";
import {
awardItemSchema,
certificationItemSchema,
coverLetterItemSchema,
customSectionSchema,
educationItemSchema,
experienceItemSchema,
interestItemSchema,
languageItemSchema,
profileItemSchema,
projectItemSchema,
publicationItemSchema,
referenceItemSchema,
skillItemSchema,
summaryItemSchema,
volunteerItemSchema,
} from "@reactive-resume/schema/resume/data";
export const resumeDialogSchemas = [
z.object({ type: z.literal("resume.create"), data: z.undefined() }),
z.object({
type: z.literal("resume.update"),
data: z.object({ id: z.string(), name: z.string(), slug: z.string(), tags: z.array(z.string()) }),
}),
z.object({ type: z.literal("resume.import"), data: z.undefined() }),
z.object({
type: z.literal("resume.duplicate"),
data: z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
tags: z.array(z.string()),
shouldRedirect: z.boolean().optional(),
}),
}),
z.object({ type: z.literal("resume.template.gallery"), data: z.undefined() }),
z.object({
type: z.literal("resume.sections.profiles.create"),
data: z.object({ item: profileItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.profiles.update"),
data: z.object({ item: profileItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.experience.create"),
data: z.object({ item: experienceItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.experience.update"),
data: z.object({ item: experienceItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.education.create"),
data: z.object({ item: educationItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.education.update"),
data: z.object({ item: educationItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.projects.create"),
data: z.object({ item: projectItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.projects.update"),
data: z.object({ item: projectItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.skills.create"),
data: z.object({ item: skillItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.skills.update"),
data: z.object({ item: skillItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.languages.create"),
data: z.object({ item: languageItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.languages.update"),
data: z.object({ item: languageItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.awards.create"),
data: z.object({ item: awardItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.awards.update"),
data: z.object({ item: awardItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.certifications.create"),
data: z.object({ item: certificationItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.certifications.update"),
data: z.object({ item: certificationItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.publications.create"),
data: z.object({ item: publicationItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.publications.update"),
data: z.object({ item: publicationItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.interests.create"),
data: z.object({ item: interestItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.interests.update"),
data: z.object({ item: interestItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.volunteer.create"),
data: z.object({ item: volunteerItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.volunteer.update"),
data: z.object({ item: volunteerItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.references.create"),
data: z.object({ item: referenceItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.references.update"),
data: z.object({ item: referenceItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.summary.create"),
data: z.object({ item: summaryItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.summary.update"),
data: z.object({ item: summaryItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.cover-letter.create"),
data: z.object({ item: coverLetterItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.cover-letter.update"),
data: z.object({ item: coverLetterItemSchema, customSectionId: z.string().optional() }),
}),
z.object({ type: z.literal("resume.sections.custom.create"), data: customSectionSchema.optional() }),
z.object({ type: z.literal("resume.sections.custom.update"), data: customSectionSchema }),
] as const;
@@ -17,8 +17,8 @@ import { Input } from "@reactive-resume/ui/components/input";
import { Switch } from "@reactive-resume/ui/components/switch";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -16,8 +16,8 @@ import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/
import { Switch } from "@reactive-resume/ui/components/switch";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -14,8 +14,8 @@ import {
} from "@reactive-resume/ui/components/dialog";
import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/ui/components/form";
import { RichInput } from "@/components/input/rich-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { useAppForm, withForm } from "@/libs/tanstack-form";
@@ -19,9 +19,9 @@ import {
import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/ui/components/form";
import { Input } from "@reactive-resume/ui/components/input";
import { generateId } from "@reactive-resume/utils/string";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { Combobox } from "@/components/ui/combobox";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { useAppForm, withForm } from "@/libs/tanstack-form";
@@ -16,8 +16,8 @@ import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/
import { Switch } from "@reactive-resume/ui/components/switch";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -20,8 +20,8 @@ import { Switch } from "@reactive-resume/ui/components/switch";
import { generateId } from "@reactive-resume/utils/string";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -19,8 +19,8 @@ import { cn } from "@reactive-resume/utils/style";
import { ChipInput } from "@/components/input/chip-input";
import { ColorPicker } from "@/components/input/color-picker";
import { IconPicker } from "@/components/input/icon-picker";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -15,8 +15,8 @@ import {
} from "@reactive-resume/ui/components/dialog";
import { FormControl, FormDescription, FormItem, FormLabel, FormMessage } from "@reactive-resume/ui/components/form";
import { Slider } from "@reactive-resume/ui/components/slider";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -26,8 +26,8 @@ import { cn } from "@reactive-resume/utils/style";
import { ColorPicker } from "@/components/input/color-picker";
import { IconPicker } from "@/components/input/icon-picker";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -16,8 +16,8 @@ import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/
import { Switch } from "@reactive-resume/ui/components/switch";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -16,8 +16,8 @@ import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/
import { Switch } from "@reactive-resume/ui/components/switch";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -16,8 +16,8 @@ import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/
import { Switch } from "@reactive-resume/ui/components/switch";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -21,8 +21,8 @@ import { cn } from "@reactive-resume/utils/style";
import { ChipInput } from "@/components/input/chip-input";
import { ColorPicker } from "@/components/input/color-picker";
import { IconPicker } from "@/components/input/icon-picker";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -14,8 +14,8 @@ import {
} from "@reactive-resume/ui/components/dialog";
import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/ui/components/form";
import { RichInput } from "@/components/input/rich-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { useAppForm, withForm } from "@/libs/tanstack-form";
@@ -16,8 +16,8 @@ import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/
import { Switch } from "@reactive-resume/ui/components/switch";
import { RichInput } from "@/components/input/rich-input";
import { URLInput } from "@/components/input/url-input";
import { useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useUpdateResumeData } from "@/features/resume/builder/draft";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { makeSectionItem } from "@/libs/resume/make-section-item";
import { createSectionItem, updateSectionItem } from "@/libs/resume/section-actions";
@@ -9,7 +9,7 @@ import { useDialogStore } from "@/dialogs/store";
const updateResumeData = vi.hoisted(() => vi.fn());
vi.mock("@/components/resume/builder-resume-draft", () => ({
vi.mock("@/features/resume/builder/draft", () => ({
useCurrentResume: () => ({
data: { metadata: { template: "ditto" } },
}),
@@ -10,8 +10,8 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from "@reactive-resume/
import { ScrollArea } from "@reactive-resume/ui/components/scroll-area";
import { cn } from "@reactive-resume/utils/style";
import { CometCard } from "@/components/animation/comet-card";
import { useCurrentResume, useUpdateResumeData } from "@/components/resume/builder-resume-draft";
import { useDialogStore } from "@/dialogs/store";
import { useCurrentResume, useUpdateResumeData } from "@/features/resume/builder/draft";
import { templates } from "./data";
export function TemplateGalleryDialog(_: DialogProps<"resume.template.gallery">) {
+24
View File
@@ -0,0 +1,24 @@
import z from "zod";
import { apiKeyDialogSchemas } from "./api-key/schema";
import { authDialogSchemas } from "./auth/schema";
import { resumeDialogSchemas } from "./resume/schema";
export const dialogSchemaRegistries = [
{ domain: "auth", schemas: authDialogSchemas },
{ domain: "api-key", schemas: apiKeyDialogSchemas },
{ domain: "resume", schemas: resumeDialogSchemas },
] as const;
const dialogSchemaEntries = [...authDialogSchemas, ...apiKeyDialogSchemas, ...resumeDialogSchemas] as const;
export const dialogTypeSchema = z.discriminatedUnion("type", dialogSchemaEntries);
export type DialogSchema = z.infer<typeof dialogTypeSchema>;
export type DialogType = DialogSchema["type"];
export type DialogData<T extends DialogType> = Extract<DialogSchema, { type: T }>["data"];
type DialogPropsData<T extends DialogType> =
DialogData<T> extends undefined ? Record<string, never> : { data: DialogData<T> };
export type DialogProps<T extends DialogType> = DialogPropsData<T>;
+8
View File
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { dialogSchemaRegistries, dialogTypeSchema } from "./schemas";
import { useDialogStore } from "./store";
describe("useDialogStore", () => {
@@ -13,6 +14,13 @@ describe("useDialogStore", () => {
});
describe("openDialog", () => {
it("uses schema entries from domain registries", () => {
expect(dialogSchemaRegistries.map((registry) => registry.domain)).toEqual(["auth", "api-key", "resume"]);
expect(dialogTypeSchema.options).toHaveLength(
dialogSchemaRegistries.reduce((total, registry) => total + registry.schemas.length, 0),
);
});
it("opens a dialog and sets activeDialog", () => {
useDialogStore.getState().openDialog("api-key.create", undefined);
+2 -165
View File
@@ -1,170 +1,7 @@
import z from "zod";
import type { DialogData, DialogProps, DialogSchema, DialogType } from "./schemas";
import { create } from "zustand/react";
import {
awardItemSchema,
certificationItemSchema,
coverLetterItemSchema,
customSectionSchema,
educationItemSchema,
experienceItemSchema,
interestItemSchema,
languageItemSchema,
profileItemSchema,
projectItemSchema,
publicationItemSchema,
referenceItemSchema,
skillItemSchema,
summaryItemSchema,
volunteerItemSchema,
} from "@reactive-resume/schema/resume/data";
const dialogTypeSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("auth.change-password"), data: z.undefined() }),
z.object({ type: z.literal("auth.two-factor.enable"), data: z.undefined() }),
z.object({ type: z.literal("auth.two-factor.disable"), data: z.undefined() }),
z.object({ type: z.literal("api-key.create"), data: z.undefined() }),
z.object({ type: z.literal("resume.create"), data: z.undefined() }),
z.object({
type: z.literal("resume.update"),
data: z.object({ id: z.string(), name: z.string(), slug: z.string(), tags: z.array(z.string()) }),
}),
z.object({ type: z.literal("resume.import"), data: z.undefined() }),
z.object({
type: z.literal("resume.duplicate"),
data: z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
tags: z.array(z.string()),
shouldRedirect: z.boolean().optional(),
}),
}),
z.object({ type: z.literal("resume.template.gallery"), data: z.undefined() }),
z.object({
type: z.literal("resume.sections.profiles.create"),
data: z.object({ item: profileItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.profiles.update"),
data: z.object({ item: profileItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.experience.create"),
data: z.object({ item: experienceItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.experience.update"),
data: z.object({ item: experienceItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.education.create"),
data: z.object({ item: educationItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.education.update"),
data: z.object({ item: educationItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.projects.create"),
data: z.object({ item: projectItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.projects.update"),
data: z.object({ item: projectItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.skills.create"),
data: z.object({ item: skillItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.skills.update"),
data: z.object({ item: skillItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.languages.create"),
data: z.object({ item: languageItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.languages.update"),
data: z.object({ item: languageItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.awards.create"),
data: z.object({ item: awardItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.awards.update"),
data: z.object({ item: awardItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.certifications.create"),
data: z.object({ item: certificationItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.certifications.update"),
data: z.object({ item: certificationItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.publications.create"),
data: z.object({ item: publicationItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.publications.update"),
data: z.object({ item: publicationItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.interests.create"),
data: z.object({ item: interestItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.interests.update"),
data: z.object({ item: interestItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.volunteer.create"),
data: z.object({ item: volunteerItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.volunteer.update"),
data: z.object({ item: volunteerItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.references.create"),
data: z.object({ item: referenceItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.references.update"),
data: z.object({ item: referenceItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.summary.create"),
data: z.object({ item: summaryItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.summary.update"),
data: z.object({ item: summaryItemSchema, customSectionId: z.string().optional() }),
}),
z.object({
type: z.literal("resume.sections.cover-letter.create"),
data: z.object({ item: coverLetterItemSchema.optional(), customSectionId: z.string().optional() }).optional(),
}),
z.object({
type: z.literal("resume.sections.cover-letter.update"),
data: z.object({ item: coverLetterItemSchema, customSectionId: z.string().optional() }),
}),
z.object({ type: z.literal("resume.sections.custom.create"), data: customSectionSchema.optional() }),
z.object({ type: z.literal("resume.sections.custom.update"), data: customSectionSchema }),
]);
type DialogSchema = z.infer<typeof dialogTypeSchema>;
type DialogType = DialogSchema["type"];
type DialogData<T extends DialogType> = Extract<DialogSchema, { type: T }>["data"];
type DialogPropsData<T extends DialogType> =
DialogData<T> extends undefined ? Record<string, never> : { data: DialogData<T> };
export type DialogProps<T extends DialogType> = DialogPropsData<T>;
export type { DialogProps };
interface DialogStoreState {
open: boolean;