Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/components/routes/create-plan/CreatePlan.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ vi.mock(
describe("CreatePlan Component", () => {
beforeEach(() => {
vi.clearAllMocks();
// `clearAllMocks` resets call history but keeps implementations set via
// `mockReturnValue`. The navigation tests force `useBlocker` into a
// "blocked" state, which opens the navigation dialog; reset it here so
// that leaked dialog doesn't `aria-hidden` the form in later tests.
vi.mocked(useBlocker).mockReturnValue({
state: "unblocked",
proceed: undefined,
reset: undefined,
location: undefined,
});
vi.mocked(useParams).mockReturnValue({ groupId: "test-group-id" });
vi.mocked(useLocation).mockReturnValue({
pathname: "/groups/test-group-id/plan/new",
Expand Down Expand Up @@ -428,13 +438,35 @@ describe("CreatePlan Component", () => {
screen.getByRole("button", { name: 'Create "Brand New Tag"' }),
);

// Clicking the create option opens a multi-language dialog with the
// typed name pre-filled for the default (EN) language.
const createTagButton = await screen.findByRole("button", {
name: "Create Tag",
});
fireEvent.click(createTagButton);

await waitFor(() => {
expect(axiosInstance.post).toHaveBeenCalledWith(
"/api/v1/cms/tags",
expect.objectContaining({ name: "Brand New Tag" }),
expect.objectContaining({
metadata: expect.arrayContaining([
expect.objectContaining({
language: "EN",
name: "Brand New Tag",
}),
]),
}),
expect.any(Object),
);
});

// Successful creation closes the dialog; wait for it so the Radix
// scroll-lock is torn down and does not leak into later tests.
await waitFor(() => {
expect(
screen.queryByRole("button", { name: "Create Tag" }),
).not.toBeInTheDocument();
});
});

it("shows no image preview initially", () => {
Expand Down
12 changes: 12 additions & 0 deletions src/components/routes/create-plan/CreatePlan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { planSchema } from "@/schema/PlanSchema";
import { z } from "zod";
import { useTranslate } from "@tolgee/react";
import PlanTagSearchInput from "./PlanTagSearchInput";
import PlanVideosSection from "./PlanVideosSection";
import type { PlanVideoSummary } from "@/components/routes/task/api/planApi";
import {
planTagsToIds,
type PlanTagSummary,
Expand Down Expand Up @@ -259,6 +261,11 @@ const Createplan = () => {
}
}, [planId, planData]);

const planVideos = useMemo<PlanVideoSummary[]>(
() => (Array.isArray(planData?.videos) ? planData.videos : []),
[planData],
);
Comment thread
greptile-apps[bot] marked this conversation as resolved.

const canUpdate = form.formState.isDirty;

const hasUnsavedChanges = canUpdate && !form.formState.isSubmitSuccessful;
Expand Down Expand Up @@ -933,6 +940,11 @@ const Createplan = () => {
</Pecha.FormItem>
)}
/>

{planId && (
<PlanVideosSection planId={planId} videos={planVideos} />
)}

<div className="pt-8 w-full flex justify-end">
{isCreateMode ? (
<Pecha.Button
Expand Down
5 changes: 4 additions & 1 deletion src/components/routes/create-plan/PlanNewLegacyRedirect.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { getSeries, resolveSeriesGroupId } from "@/components/routes/create-series/api/seriesApi";
import {
getSeries,
resolveSeriesGroupId,
} from "@/components/routes/create-series/api/seriesApi";
import { parsePlanNewFromSeriesState } from "./planNewFromSeriesState";
import { ROUTES } from "@/routes/paths";

Expand Down
52 changes: 40 additions & 12 deletions src/components/routes/create-plan/PlanTagSearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@ const PlanTagSearchInput = ({
Object.fromEntries(initialTags.map((tag) => [tag.id, tag.name])),
);
const [showCreateDialog, setShowCreateDialog] = useState(false);
const [activeLanguages, setActiveLanguages] = useState<LanguageCode[]>(["EN"]);
const [languageData, setLanguageData] = useState<Record<LanguageCode, LanguageData>>({
const [activeLanguages, setActiveLanguages] = useState<LanguageCode[]>([
"EN",
]);
const [languageData, setLanguageData] = useState<
Record<LanguageCode, LanguageData>
>({
EN: { name: "", description: "" },
BO: { name: "", description: "" },
ZH: { name: "", description: "" },
Expand Down Expand Up @@ -155,7 +159,9 @@ const PlanTagSearchInput = ({
for (const lang of activeLanguages) {
const data = languageData[lang];
if (!data.name.trim()) {
toast.error(`Name is required for ${PLAN_LANGUAGE.find(l => l.value === lang)?.label}`);
toast.error(
`Name is required for ${PLAN_LANGUAGE.find((l) => l.value === lang)?.label}`,
);
return;
}
metadata.push({
Expand All @@ -182,7 +188,7 @@ const PlanTagSearchInput = ({
const updateLanguageField = (
lang: LanguageCode,
field: keyof LanguageData,
value: string
value: string,
) => {
setLanguageData((prev) => ({
...prev,
Expand Down Expand Up @@ -331,7 +337,10 @@ const PlanTagSearchInput = ({
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm font-bold">Languages</label>
{PLAN_LANGUAGE.filter((lang) => !activeLanguages.includes(lang.value as LanguageCode)).length > 0 && (
{PLAN_LANGUAGE.filter(
(lang) =>
!activeLanguages.includes(lang.value as LanguageCode),
).length > 0 && (
<Pecha.DropdownMenu>
<Pecha.DropdownMenuTrigger asChild>
<Button
Expand All @@ -345,10 +354,15 @@ const PlanTagSearchInput = ({
</Button>
</Pecha.DropdownMenuTrigger>
<Pecha.DropdownMenuContent>
{PLAN_LANGUAGE.filter((lang) => !activeLanguages.includes(lang.value as LanguageCode)).map((lang) => (
{PLAN_LANGUAGE.filter(
(lang) =>
!activeLanguages.includes(lang.value as LanguageCode),
).map((lang) => (
<Pecha.DropdownMenuItem
key={lang.value}
onClick={() => addLanguage(lang.value as LanguageCode)}
onClick={() =>
addLanguage(lang.value as LanguageCode)
}
>
{lang.label}
</Pecha.DropdownMenuItem>
Expand All @@ -358,9 +372,13 @@ const PlanTagSearchInput = ({
)}
</div>
{activeLanguages.map((lang) => {
const langLabel = PLAN_LANGUAGE.find((l) => l.value === lang)?.label || lang;
const langLabel =
PLAN_LANGUAGE.find((l) => l.value === lang)?.label || lang;
return (
<div key={lang} className="relative rounded-lg border border-input bg-[#FAFAFA] dark:bg-[#262626] p-4 space-y-3">
<div
key={lang}
className="relative rounded-lg border border-input bg-[#FAFAFA] dark:bg-[#262626] p-4 space-y-3"
>
{activeLanguages.length > 1 && (
<button
type="button"
Expand All @@ -371,12 +389,16 @@ const PlanTagSearchInput = ({
<IoMdClose className="h-4 w-4" />
</button>
)}
<div className="text-sm font-semibold text-muted-foreground mb-2">{langLabel}</div>
<div className="text-sm font-semibold text-muted-foreground mb-2">
{langLabel}
</div>
<div className="space-y-2">
<label className="text-sm font-bold">Name</label>
<Input
value={languageData[lang].name}
onChange={(e) => updateLanguageField(lang, "name", e.target.value)}
onChange={(e) =>
updateLanguageField(lang, "name", e.target.value)
}
placeholder="Tag name"
required
className="bg-white dark:bg-[#181818]"
Expand All @@ -386,7 +408,13 @@ const PlanTagSearchInput = ({
<label className="text-sm font-bold">Description</label>
<Textarea
value={languageData[lang].description}
onChange={(e) => updateLanguageField(lang, "description", e.target.value)}
onChange={(e) =>
updateLanguageField(
lang,
"description",
e.target.value,
)
}
placeholder="Optional description"
className="field-sizing-fixed min-h-[80px] max-h-32 resize-none bg-white dark:bg-[#181818]"
/>
Expand Down
Loading
Loading