From automation
Complete guide to implementing modals in the finstreet context. Covers the store, modal component, and optional open button. Use when building or modifying any modal.
npx claudepluginhub joshuarweaver/cascade-code-languages-misc-1 --plugin finstreet-fe-claude-pluginsThis skill uses the workspace's default tool permissions.
Every modal consists of up to three files:
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Every modal consists of up to three files:
Before creating any files, invoke the automation:path-resolver skill with your input parameters (featureName, subFeatureName, featureType, product, role) to resolve the correct paths. Use the returned Feature Path as {parentDirectory} in the directory structure below.
{parentDirectory}/
├── store.ts
├── {ModalName}Modal.tsx
└── Open{ModalName}ModalButton.tsx ← only if requested
File: store.ts
import { create } from "zustand";
type {ModalName}ModalData = {
financingCaseId: string;
} | null;
interface {ModalName}ModalStore {
isOpen: boolean;
data: {ModalName}ModalData;
setIsOpen: (isOpen: boolean) => void;
setData: (data: {ModalName}ModalData) => void;
}
export const use{ModalName}Modal = create<{ModalName}ModalStore>((set) => ({
isOpen: false,
data: null,
setIsOpen: (isOpen) => set({ isOpen }),
setData: (data) => set({ data, isOpen: true }),
}));
data holds the typed payload the modal needs (e.g., IDs passed from the trigger)setData always sets isOpen: true — opening and setting data happen together{ModalName}ModalData type to match exactly what the modal content needsFile: {ModalName}Modal.tsx
"use client";
import {
Modal,
ModalContent,
ModalTitle,
} from "@finstreet/ui/components/patterns/Modal";
import { use{ModalName}Modal } from "./store";
import { Suspense } from "react";
import { useExtracted } from "next-intl";
import { Headline } from "@finstreet/ui/components/base/Headline";
import { Typography } from "@finstreet/ui/components/base/Typography";
import { VStack } from "@styled-system/jsx";
export const {ModalName}Modal = () => {
const { isOpen, data, setIsOpen } = use{ModalName}Modal();
const t = useExtracted();
if (!data) {
return null;
}
const { financingCaseId } = data;
return (
<Modal open={isOpen} onClose={() => setIsOpen(false)}>
<ModalTitle>
{/* See title patterns below */}
</ModalTitle>
<ModalContent>
{/* Modal content here */}
</ModalContent>
</Modal>
);
};
Use exactly one of these based on whether a subheading is present in the context:
With title and subheading:
<ModalTitle>
<VStack gap={1} alignItems={"flex-start"}>
<Headline>{t("{German title}")}</Headline>
<Typography color={"text.dark"}>{t("{German subheading}")}</Typography>
</VStack>
</ModalTitle>
Title only (no subheading):
<ModalTitle>
{t("{German title}")}
</ModalTitle>
if (!data) return null before destructuring data<Suspense>File: Open{ModalName}ModalButton.tsx
Only create this file when explicitly asked to.
"use client";
import { use{ModalName}Modal } from "./store";
import { Button } from "@finstreet/ui/components/base/Button";
import { useExtracted } from "next-intl";
type Open{ModalName}ModalButtonProps = {
financingCaseId: string;
};
export const Open{ModalName}ModalButton = ({
financingCaseId,
}: Open{ModalName}ModalButtonProps) => {
const { setData } = use{ModalName}Modal();
const t = useExtracted();
return (
<Button onClick={() => setData({ financingCaseId })}>
{t("{German button label}")}
</Button>
);
};
setData (not setIsOpen) from the button — setData already opens the modal{ModalName}ModalData type from the store