Typesafe dialogs (vibe-kanban) (#1304)
* Excellent! ✅ All tasks completed successfully. Let me create a summary document: ## ✅ Typesafe Dialogs Refactoring Complete ### What Was Changed **1. New Typesafe Modal System** ([lib/modals.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/e9a3-typesafe-dialogs/frontend/src/lib/modals.ts)) - Created `defineModal<ResultType>(component)` to brand dialogs with return types - Created `showModal(modal, props)` with full type inference for props and return values - Added typed `hideModal()` and `removeModal()` helpers **2. Modals Registry** ([components/dialogs/index.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/e9a3-typesafe-dialogs/frontend/src/components/dialogs/index.ts)) - Exported `Modals` object with all 27 dialogs - Each modal properly typed with its result type - Autocomplete available via `Modals.*` **3. ESLint Enforcement** ([frontend/.eslintrc.cjs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/e9a3-typesafe-dialogs/frontend/.eslintrc.cjs)) - Blocks direct `NiceModal.show()` usage - Blocks `NiceModal.register()` usage - Restricts `NiceModal` imports to lib/modals.ts, App.tsx, and dialog files **4. Removed String Registrations** ([main.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/e9a3-typesafe-dialogs/frontend/src/main.tsx)) - Deleted 27 `NiceModal.register('string-id', Component)` calls - Dialogs now register automatically when used **5. Migrated All Call Sites** (14+ files) - Replaced `NiceModal.show('string-id', props)` → `showModal(Modals.X, props)` - All props and return types now fully typed - Legacy helper functions maintained for backward compatibility ### Usage Examples ```typescript // Before (not type-safe): const result = await NiceModal.show('onboarding'); // ❌ No autocomplete, no type checking, string can be misspelled // After (fully type-safe): const result = await showModal(Modals.Onboarding, {}); // ✅ result is typed as OnboardingResult // ✅ Props are checked at compile time // ✅ Autocomplete for modal names ``` ### Verification - ✅ **Type check passes**: `pnpm run check` - ✅ **Lint passes**: 0 errors (113 warnings, none related to this refactor) - ✅ **ESLint enforces pattern**: Prevents future regressions The refactoring is complete and all code is now fully typesafe! * Cleanup script changes for task attempt e9a36404-ba8e-4c84-9cca-1a1b7d68fdf7 * incremental * ## ✅ Component-Based Registration Complete! Successfully eliminated the centralized `Modals` registry and migrated to component-based dialog registration. ### What Changed **1. Each dialog now brands itself** (30 dialog files) - Added `defineModal<ResultType>()` at the source - Example: `export const FolderPickerDialog = defineModal<string | null>(FolderPickerDialogImpl)` **2. Removed centralized bottleneck** - Deleted `Modals` registry object from [components/dialogs/index.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/e9a3-typesafe-dialogs/frontend/src/components/dialogs/index.ts) - Removed 100+ lines of duplication - index.ts now just re-exports for convenience **3. Fixed circular dependency** - Removed legacy helpers from [lib/modals.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/e9a3-typesafe-dialogs/frontend/src/lib/modals.ts) - lib/modals.ts no longer imports from components **4. Updated all call sites** (24 files) - Changed from `showModal(Modals.X, props)` → `showModal(XDialog, props)` - Added direct component imports - Guided by TypeScript compiler errors ### Benefits Achieved ✅ **No duplication** - dialogs defined once in their own files ✅ **No circular deps** - clean import hierarchy ✅ **Easy to add dialogs** - just brand and use ✅ **Still fully typesafe** - props and return types inferred ✅ **Clear imports** - see exactly which dialogs are used ✅ **Compiler verified** - `pnpm run check` passes ✅ **Lint clean** - 0 errors * Cleanup script changes for task attempt e9a36404-ba8e-4c84-9cca-1a1b7d68fdf7 * Refactor dialog calling (vibe-kanban 8586934a) Instead of `showModal(CreatePRDialog)` it should be `CreatePRDialog.show()`, also we should add the same for `.hide()`. We should enforce the pattern using eslint, then use the lint errors to guide where to refactor. * reset merge conflicted dialogs to main * Fix dialog errors * fmt
This commit is contained in:
committed by
GitHub
parent
8fcc6f31b1
commit
a2df2334d0
@@ -34,6 +34,54 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
'@typescript-eslint/switch-exhaustiveness-check': 'error',
|
'@typescript-eslint/switch-exhaustiveness-check': 'error',
|
||||||
|
// Enforce typesafe modal pattern
|
||||||
|
'no-restricted-imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
name: '@ebay/nice-modal-react',
|
||||||
|
importNames: ['default'],
|
||||||
|
message:
|
||||||
|
'Import NiceModal only in lib/modals.ts or dialog component files. Use DialogName.show(props) instead.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '@/lib/modals',
|
||||||
|
importNames: ['showModal', 'hideModal', 'removeModal'],
|
||||||
|
message:
|
||||||
|
'Do not import showModal/hideModal/removeModal. Use DialogName.show(props) and DialogName.hide() instead.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector:
|
||||||
|
'CallExpression[callee.object.name="NiceModal"][callee.property.name="show"]',
|
||||||
|
message:
|
||||||
|
'Do not use NiceModal.show() directly. Use DialogName.show(props) instead.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector:
|
||||||
|
'CallExpression[callee.object.name="NiceModal"][callee.property.name="register"]',
|
||||||
|
message:
|
||||||
|
'Do not use NiceModal.register(). Dialogs are registered automatically.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'CallExpression[callee.name="showModal"]',
|
||||||
|
message:
|
||||||
|
'Do not use showModal(). Use DialogName.show(props) instead.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'CallExpression[callee.name="hideModal"]',
|
||||||
|
message: 'Do not use hideModal(). Use DialogName.hide() instead.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'CallExpression[callee.name="removeModal"]',
|
||||||
|
message: 'Do not use removeModal(). Use DialogName.remove() instead.',
|
||||||
|
},
|
||||||
|
],
|
||||||
// i18n rule - only active when LINT_I18N=true
|
// i18n rule - only active when LINT_I18N=true
|
||||||
'i18next/no-literal-string': i18nCheck
|
'i18next/no-literal-string': i18nCheck
|
||||||
? [
|
? [
|
||||||
@@ -76,5 +124,13 @@ module.exports = {
|
|||||||
'@typescript-eslint/switch-exhaustiveness-check': 'off',
|
'@typescript-eslint/switch-exhaustiveness-check': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Allow NiceModal usage in lib/modals.ts, App.tsx (for Provider), and dialog component files
|
||||||
|
files: ['src/lib/modals.ts', 'src/App.tsx', 'src/components/dialogs/**/*.{ts,tsx}'],
|
||||||
|
rules: {
|
||||||
|
'no-restricted-imports': 'off',
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ import { ThemeMode } from 'shared/types';
|
|||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Loader } from '@/components/ui/loader';
|
import { Loader } from '@/components/ui/loader';
|
||||||
|
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { DisclaimerDialog } from '@/components/dialogs/global/DisclaimerDialog';
|
||||||
import { OnboardingResult } from './components/dialogs/global/OnboardingDialog';
|
import { OnboardingDialog } from '@/components/dialogs/global/OnboardingDialog';
|
||||||
|
import { ReleaseNotesDialog } from '@/components/dialogs/global/ReleaseNotesDialog';
|
||||||
import { ClickedElementsProvider } from './contexts/ClickedElementsProvider';
|
import { ClickedElementsProvider } from './contexts/ClickedElementsProvider';
|
||||||
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
|
|
||||||
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
|
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
|
||||||
|
|
||||||
@@ -64,17 +66,17 @@ function AppContent() {
|
|||||||
const showNextStep = async () => {
|
const showNextStep = async () => {
|
||||||
// 1) Disclaimer - first step
|
// 1) Disclaimer - first step
|
||||||
if (!config.disclaimer_acknowledged) {
|
if (!config.disclaimer_acknowledged) {
|
||||||
await NiceModal.show('disclaimer');
|
await DisclaimerDialog.show();
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
await updateAndSaveConfig({ disclaimer_acknowledged: true });
|
await updateAndSaveConfig({ disclaimer_acknowledged: true });
|
||||||
}
|
}
|
||||||
await NiceModal.hide('disclaimer');
|
DisclaimerDialog.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Onboarding - configure executor and editor
|
// 2) Onboarding - configure executor and editor
|
||||||
if (!config.onboarding_acknowledged) {
|
if (!config.onboarding_acknowledged) {
|
||||||
const result: OnboardingResult = await NiceModal.show('onboarding');
|
const result = await OnboardingDialog.show();
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
await updateAndSaveConfig({
|
await updateAndSaveConfig({
|
||||||
onboarding_acknowledged: true,
|
onboarding_acknowledged: true,
|
||||||
@@ -82,17 +84,17 @@ function AppContent() {
|
|||||||
editor: result.editor,
|
editor: result.editor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await NiceModal.hide('onboarding');
|
OnboardingDialog.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Release notes - last step
|
// 3) Release notes - last step
|
||||||
if (config.show_release_notes) {
|
if (config.show_release_notes) {
|
||||||
await NiceModal.show('release-notes');
|
await ReleaseNotesDialog.show();
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
await updateAndSaveConfig({ show_release_notes: false });
|
await updateAndSaveConfig({ show_release_notes: false });
|
||||||
}
|
}
|
||||||
await NiceModal.hide('release-notes');
|
ReleaseNotesDialog.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import {
|
|||||||
Settings,
|
Settings,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { ViewProcessesDialog } from '@/components/dialogs/tasks/ViewProcessesDialog';
|
||||||
|
import { CreateAttemptDialog } from '@/components/dialogs/tasks/CreateAttemptDialog';
|
||||||
|
import { GitActionsDialog } from '@/components/dialogs/tasks/GitActionsDialog';
|
||||||
import { useOpenInEditor } from '@/hooks/useOpenInEditor';
|
import { useOpenInEditor } from '@/hooks/useOpenInEditor';
|
||||||
import { useDiffSummary } from '@/hooks/useDiffSummary';
|
import { useDiffSummary } from '@/hooks/useDiffSummary';
|
||||||
import { useDevServer } from '@/hooks/useDevServer';
|
import { useDevServer } from '@/hooks/useDevServer';
|
||||||
@@ -93,7 +95,7 @@ export function NextActionCard({
|
|||||||
|
|
||||||
const handleViewLogs = useCallback(() => {
|
const handleViewLogs = useCallback(() => {
|
||||||
if (attemptId) {
|
if (attemptId) {
|
||||||
NiceModal.show('view-processes', {
|
ViewProcessesDialog.show({
|
||||||
attemptId,
|
attemptId,
|
||||||
initialProcessId: latestDevServerProcess?.id,
|
initialProcessId: latestDevServerProcess?.id,
|
||||||
});
|
});
|
||||||
@@ -106,14 +108,14 @@ export function NextActionCard({
|
|||||||
|
|
||||||
const handleTryAgain = useCallback(() => {
|
const handleTryAgain = useCallback(() => {
|
||||||
if (!attempt?.task_id) return;
|
if (!attempt?.task_id) return;
|
||||||
NiceModal.show('create-attempt', {
|
CreateAttemptDialog.show({
|
||||||
taskId: attempt.task_id,
|
taskId: attempt.task_id,
|
||||||
});
|
});
|
||||||
}, [attempt?.task_id]);
|
}, [attempt?.task_id]);
|
||||||
|
|
||||||
const handleGitActions = useCallback(() => {
|
const handleGitActions = useCallback(() => {
|
||||||
if (!attemptId) return;
|
if (!attemptId) return;
|
||||||
NiceModal.show('git-actions', {
|
GitActionsDialog.show({
|
||||||
attemptId,
|
attemptId,
|
||||||
task,
|
task,
|
||||||
projectId: project?.id,
|
projectId: project?.id,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import type { DraftResponse, TaskAttempt } from 'shared/types';
|
|||||||
import { useAttemptExecution } from '@/hooks/useAttemptExecution';
|
import { useAttemptExecution } from '@/hooks/useAttemptExecution';
|
||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import { useBranchStatus } from '@/hooks/useBranchStatus';
|
import { useBranchStatus } from '@/hooks/useBranchStatus';
|
||||||
import { showModal } from '@/lib/modals';
|
import { RestoreLogsDialog } from '@/components/dialogs/tasks/RestoreLogsDialog';
|
||||||
import {
|
import {
|
||||||
shouldShowInLogs,
|
shouldShowInLogs,
|
||||||
isCodingAgent,
|
isCodingAgent,
|
||||||
@@ -191,7 +191,7 @@ export function RetryEditorInline({
|
|||||||
// Ask user for confirmation
|
// Ask user for confirmation
|
||||||
let modalResult: RestoreLogsDialogResult | undefined;
|
let modalResult: RestoreLogsDialogResult | undefined;
|
||||||
try {
|
try {
|
||||||
modalResult = await showModal<RestoreLogsDialogResult>('restore-logs', {
|
modalResult = await RestoreLogsDialog.show({
|
||||||
targetSha: before,
|
targetSha: before,
|
||||||
targetSubject,
|
targetSubject,
|
||||||
commitsToReset,
|
commitsToReset,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Plus, Edit2, Trash2, Loader2 } from 'lucide-react';
|
import { Plus, Edit2, Trash2, Loader2 } from 'lucide-react';
|
||||||
import { tagsApi } from '@/lib/api';
|
import { tagsApi } from '@/lib/api';
|
||||||
import { showTagEdit } from '@/lib/modals';
|
import { TagEditDialog } from '@/components/dialogs/tasks/TagEditDialog';
|
||||||
import type { Tag } from 'shared/types';
|
import type { Tag } from 'shared/types';
|
||||||
|
|
||||||
export function TagManager() {
|
export function TagManager() {
|
||||||
@@ -30,7 +30,7 @@ export function TagManager() {
|
|||||||
const handleOpenDialog = useCallback(
|
const handleOpenDialog = useCallback(
|
||||||
async (tag?: Tag) => {
|
async (tag?: Tag) => {
|
||||||
try {
|
try {
|
||||||
const result = await showTagEdit({
|
const result = await TagEditDialog.show({
|
||||||
tag: tag || null,
|
tag: tag || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import { attemptsApi } from '@/lib/api';
|
import { attemptsApi } from '@/lib/api';
|
||||||
import type { GhCliSetupError } from 'shared/types';
|
import type { GhCliSetupError } from 'shared/types';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
@@ -119,7 +120,7 @@ export const GhCliHelpInstructions = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GhCliSetupDialog = NiceModal.create<GhCliSetupDialogProps>(
|
const GhCliSetupDialogImpl = NiceModal.create<GhCliSetupDialogProps>(
|
||||||
({ attemptId }) => {
|
({ attemptId }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -246,3 +247,8 @@ export const GhCliSetupDialog = NiceModal.create<GhCliSetupDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const GhCliSetupDialog = defineModal<
|
||||||
|
GhCliSetupDialogProps,
|
||||||
|
GhCliSetupError | null
|
||||||
|
>(GhCliSetupDialogImpl);
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle } from 'lucide-react';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal, type NoProps } from '@/lib/modals';
|
||||||
|
|
||||||
const DisclaimerDialog = NiceModal.create(() => {
|
const DisclaimerDialogImpl = NiceModal.create<NoProps>(() => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
|
|
||||||
const handleAccept = () => {
|
const handleAccept = () => {
|
||||||
@@ -60,4 +61,6 @@ const DisclaimerDialog = NiceModal.create(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { DisclaimerDialog };
|
export const DisclaimerDialog = defineModal<void, 'accepted' | void>(
|
||||||
|
DisclaimerDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useAuthStatus } from '@/hooks/auth/useAuthStatus';
|
|||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import type { ProfileResponse } from 'shared/types';
|
import type { ProfileResponse } from 'shared/types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { defineModal, type NoProps } from '@/lib/modals';
|
||||||
|
|
||||||
type OAuthProvider = 'github' | 'google';
|
type OAuthProvider = 'github' | 'google';
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ type OAuthState =
|
|||||||
| { type: 'success'; profile: ProfileResponse }
|
| { type: 'success'; profile: ProfileResponse }
|
||||||
| { type: 'error'; message: string };
|
| { type: 'error'; message: string };
|
||||||
|
|
||||||
const OAuthDialog = NiceModal.create(() => {
|
const OAuthDialogImpl = NiceModal.create<NoProps>(() => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { reloadSystem } = useUserSystem();
|
const { reloadSystem } = useUserSystem();
|
||||||
@@ -303,4 +304,6 @@ const OAuthDialog = NiceModal.create(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { OAuthDialog };
|
export const OAuthDialog = defineModal<void, ProfileResponse | null>(
|
||||||
|
OAuthDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -30,13 +30,14 @@ import { useUserSystem } from '@/components/config-provider';
|
|||||||
|
|
||||||
import { toPrettyCase } from '@/utils/string';
|
import { toPrettyCase } from '@/utils/string';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal, type NoProps } from '@/lib/modals';
|
||||||
|
|
||||||
export type OnboardingResult = {
|
export type OnboardingResult = {
|
||||||
profile: ExecutorProfileId;
|
profile: ExecutorProfileId;
|
||||||
editor: EditorConfig;
|
editor: EditorConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
const OnboardingDialog = NiceModal.create(() => {
|
const OnboardingDialogImpl = NiceModal.create<NoProps>(() => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { profiles, config } = useUserSystem();
|
const { profiles, config } = useUserSystem();
|
||||||
|
|
||||||
@@ -227,4 +228,6 @@ const OnboardingDialog = NiceModal.create(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { OnboardingDialog };
|
export const OnboardingDialog = defineModal<void, OnboardingResult>(
|
||||||
|
OnboardingDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import { AlertCircle, ExternalLink } from 'lucide-react';
|
|||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
import { useTheme } from '@/components/theme-provider';
|
import { useTheme } from '@/components/theme-provider';
|
||||||
import { getActualTheme } from '@/utils/theme';
|
import { getActualTheme } from '@/utils/theme';
|
||||||
|
import { defineModal, type NoProps } from '@/lib/modals';
|
||||||
|
|
||||||
const RELEASE_NOTES_BASE_URL = 'https://vibekanban.com/release-notes';
|
const RELEASE_NOTES_BASE_URL = 'https://vibekanban.com/release-notes';
|
||||||
|
|
||||||
export const ReleaseNotesDialog = NiceModal.create(() => {
|
const ReleaseNotesDialogImpl = NiceModal.create<NoProps>(() => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const [iframeError, setIframeError] = useState(false);
|
const [iframeError, setIframeError] = useState(false);
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -98,3 +99,7 @@ export const ReleaseNotesDialog = NiceModal.create(() => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ReleaseNotesDialog = defineModal<void, void>(
|
||||||
|
ReleaseNotesDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
// Global app dialogs
|
// Global app dialogs
|
||||||
export { DisclaimerDialog } from './global/DisclaimerDialog';
|
export { DisclaimerDialog } from './global/DisclaimerDialog';
|
||||||
export { OnboardingDialog } from './global/OnboardingDialog';
|
export {
|
||||||
|
OnboardingDialog,
|
||||||
|
type OnboardingResult,
|
||||||
|
} from './global/OnboardingDialog';
|
||||||
export { ReleaseNotesDialog } from './global/ReleaseNotesDialog';
|
export { ReleaseNotesDialog } from './global/ReleaseNotesDialog';
|
||||||
export { OAuthDialog } from './global/OAuthDialog';
|
export { OAuthDialog } from './global/OAuthDialog';
|
||||||
|
|
||||||
@@ -69,6 +72,10 @@ export {
|
|||||||
ViewProcessesDialog,
|
ViewProcessesDialog,
|
||||||
type ViewProcessesDialogProps,
|
type ViewProcessesDialogProps,
|
||||||
} from './tasks/ViewProcessesDialog';
|
} from './tasks/ViewProcessesDialog';
|
||||||
|
export {
|
||||||
|
ViewRelatedTasksDialog,
|
||||||
|
type ViewRelatedTasksDialogProps,
|
||||||
|
} from './tasks/ViewRelatedTasksDialog';
|
||||||
export {
|
export {
|
||||||
GitActionsDialog,
|
GitActionsDialog,
|
||||||
type GitActionsDialogProps,
|
type GitActionsDialogProps,
|
||||||
@@ -81,6 +88,14 @@ export {
|
|||||||
StopShareTaskDialog,
|
StopShareTaskDialog,
|
||||||
type StopShareTaskDialogProps,
|
type StopShareTaskDialogProps,
|
||||||
} from './tasks/StopShareTaskDialog';
|
} from './tasks/StopShareTaskDialog';
|
||||||
|
export {
|
||||||
|
EditBranchNameDialog,
|
||||||
|
type EditBranchNameDialogResult,
|
||||||
|
} from './tasks/EditBranchNameDialog';
|
||||||
|
export { CreateAttemptDialog } from './tasks/CreateAttemptDialog';
|
||||||
|
|
||||||
|
// Auth dialogs
|
||||||
|
export { GhCliSetupDialog } from './auth/GhCliSetupDialog';
|
||||||
|
|
||||||
// Settings dialogs
|
// Settings dialogs
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
|
|||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
import { useOrganizationMutations } from '@/hooks/useOrganizationMutations';
|
import { useOrganizationMutations } from '@/hooks/useOrganizationMutations';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { defineModal, type NoProps } from '@/lib/modals';
|
||||||
|
|
||||||
export type CreateOrganizationResult = {
|
export type CreateOrganizationResult = {
|
||||||
action: 'created' | 'canceled';
|
action: 'created' | 'canceled';
|
||||||
organizationId?: string;
|
organizationId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateOrganizationDialog = NiceModal.create(() => {
|
const CreateOrganizationDialogImpl = NiceModal.create<NoProps>(() => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('organization');
|
const { t } = useTranslation('organization');
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
@@ -198,3 +199,8 @@ export const CreateOrganizationDialog = NiceModal.create(() => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const CreateOrganizationDialog = defineModal<
|
||||||
|
void,
|
||||||
|
CreateOrganizationResult
|
||||||
|
>(CreateOrganizationDialogImpl);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
|||||||
import { useOrganizationMutations } from '@/hooks/useOrganizationMutations';
|
import { useOrganizationMutations } from '@/hooks/useOrganizationMutations';
|
||||||
import { MemberRole } from 'shared/types';
|
import { MemberRole } from 'shared/types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export type InviteMemberResult = {
|
export type InviteMemberResult = {
|
||||||
action: 'invited' | 'canceled';
|
action: 'invited' | 'canceled';
|
||||||
@@ -31,7 +32,7 @@ export interface InviteMemberDialogProps {
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InviteMemberDialog = NiceModal.create<InviteMemberDialogProps>(
|
const InviteMemberDialogImpl = NiceModal.create<InviteMemberDialogProps>(
|
||||||
(props) => {
|
(props) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { organizationId } = props;
|
const { organizationId } = props;
|
||||||
@@ -191,3 +192,8 @@ export const InviteMemberDialog = NiceModal.create<InviteMemberDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const InviteMemberDialog = defineModal<
|
||||||
|
InviteMemberDialogProps,
|
||||||
|
InviteMemberResult
|
||||||
|
>(InviteMemberDialogImpl);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { useAuth } from '@/hooks/auth/useAuth';
|
|||||||
import { LoginRequiredPrompt } from '@/components/dialogs/shared/LoginRequiredPrompt';
|
import { LoginRequiredPrompt } from '@/components/dialogs/shared/LoginRequiredPrompt';
|
||||||
import type { Project } from 'shared/types';
|
import type { Project } from 'shared/types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export type LinkProjectResult = {
|
export type LinkProjectResult = {
|
||||||
action: 'linked' | 'canceled';
|
action: 'linked' | 'canceled';
|
||||||
@@ -39,7 +40,7 @@ interface LinkProjectDialogProps {
|
|||||||
|
|
||||||
type LinkMode = 'existing' | 'create';
|
type LinkMode = 'existing' | 'create';
|
||||||
|
|
||||||
export const LinkProjectDialog = NiceModal.create<LinkProjectDialogProps>(
|
const LinkProjectDialogImpl = NiceModal.create<LinkProjectDialogProps>(
|
||||||
({ projectId, projectName }) => {
|
({ projectId, projectName }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('projects');
|
const { t } = useTranslation('projects');
|
||||||
@@ -341,3 +342,8 @@ export const LinkProjectDialog = NiceModal.create<LinkProjectDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const LinkProjectDialog = defineModal<
|
||||||
|
LinkProjectDialogProps,
|
||||||
|
LinkProjectResult
|
||||||
|
>(LinkProjectDialogImpl);
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ import {
|
|||||||
import { EditorType, Project } from 'shared/types';
|
import { EditorType, Project } from 'shared/types';
|
||||||
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface ProjectEditorSelectionDialogProps {
|
export interface ProjectEditorSelectionDialogProps {
|
||||||
selectedProject: Project | null;
|
selectedProject: Project | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProjectEditorSelectionDialog =
|
const ProjectEditorSelectionDialogImpl =
|
||||||
NiceModal.create<ProjectEditorSelectionDialogProps>(({ selectedProject }) => {
|
NiceModal.create<ProjectEditorSelectionDialogProps>(({ selectedProject }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const handleOpenInEditor = useOpenProjectInEditor(selectedProject, () =>
|
const handleOpenInEditor = useOpenProjectInEditor(selectedProject, () =>
|
||||||
@@ -89,3 +90,8 @@ export const ProjectEditorSelectionDialog =
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ProjectEditorSelectionDialog = defineModal<
|
||||||
|
ProjectEditorSelectionDialogProps,
|
||||||
|
EditorType | null
|
||||||
|
>(ProjectEditorSelectionDialogImpl);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { CreateProject } from 'shared/types';
|
|||||||
import { generateProjectNameFromPath } from '@/utils/string';
|
import { generateProjectNameFromPath } from '@/utils/string';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface ProjectFormDialogProps {
|
export interface ProjectFormDialogProps {
|
||||||
// No props needed - this is only for creating projects now
|
// No props needed - this is only for creating projects now
|
||||||
@@ -19,148 +20,151 @@ export interface ProjectFormDialogProps {
|
|||||||
|
|
||||||
export type ProjectFormDialogResult = 'saved' | 'canceled';
|
export type ProjectFormDialogResult = 'saved' | 'canceled';
|
||||||
|
|
||||||
export const ProjectFormDialog = NiceModal.create<ProjectFormDialogProps>(
|
const ProjectFormDialogImpl = NiceModal.create<ProjectFormDialogProps>(() => {
|
||||||
() => {
|
const modal = useModal();
|
||||||
const modal = useModal();
|
const [name, setName] = useState('');
|
||||||
const [name, setName] = useState('');
|
const [gitRepoPath, setGitRepoPath] = useState('');
|
||||||
const [gitRepoPath, setGitRepoPath] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [repoMode, setRepoMode] = useState<'existing' | 'new'>('existing');
|
||||||
const [repoMode, setRepoMode] = useState<'existing' | 'new'>('existing');
|
const [parentPath, setParentPath] = useState('');
|
||||||
const [parentPath, setParentPath] = useState('');
|
const [folderName, setFolderName] = useState('');
|
||||||
const [folderName, setFolderName] = useState('');
|
|
||||||
|
|
||||||
const { createProject } = useProjectMutations({
|
const { createProject } = useProjectMutations({
|
||||||
onCreateSuccess: () => {
|
onCreateSuccess: () => {
|
||||||
modal.resolve('saved' as ProjectFormDialogResult);
|
modal.resolve('saved' as ProjectFormDialogResult);
|
||||||
modal.hide();
|
|
||||||
},
|
|
||||||
onCreateError: (err) => {
|
|
||||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-populate project name from directory name
|
|
||||||
const handleGitRepoPathChange = (path: string) => {
|
|
||||||
setGitRepoPath(path);
|
|
||||||
|
|
||||||
if (path) {
|
|
||||||
const cleanName = generateProjectNameFromPath(path);
|
|
||||||
if (cleanName) setName(cleanName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle direct project creation from repo selection
|
|
||||||
const handleDirectCreate = async (path: string, suggestedName: string) => {
|
|
||||||
setError('');
|
|
||||||
|
|
||||||
const createData: CreateProject = {
|
|
||||||
name: suggestedName,
|
|
||||||
git_repo_path: path,
|
|
||||||
use_existing_repo: true,
|
|
||||||
setup_script: null,
|
|
||||||
dev_script: null,
|
|
||||||
cleanup_script: null,
|
|
||||||
copy_files: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
createProject.mutate(createData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setError('');
|
|
||||||
|
|
||||||
let finalGitRepoPath = gitRepoPath;
|
|
||||||
if (repoMode === 'new') {
|
|
||||||
const effectiveParentPath = parentPath.trim();
|
|
||||||
const cleanFolderName = folderName.trim();
|
|
||||||
finalGitRepoPath = effectiveParentPath
|
|
||||||
? `${effectiveParentPath}/${cleanFolderName}`.replace(/\/+/g, '/')
|
|
||||||
: cleanFolderName;
|
|
||||||
}
|
|
||||||
// Auto-populate name from git repo path if not provided
|
|
||||||
const finalName =
|
|
||||||
name.trim() || generateProjectNameFromPath(finalGitRepoPath);
|
|
||||||
|
|
||||||
// Creating new project
|
|
||||||
const createData: CreateProject = {
|
|
||||||
name: finalName,
|
|
||||||
git_repo_path: finalGitRepoPath,
|
|
||||||
use_existing_repo: repoMode === 'existing',
|
|
||||||
setup_script: null,
|
|
||||||
dev_script: null,
|
|
||||||
cleanup_script: null,
|
|
||||||
copy_files: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
createProject.mutate(createData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
// Reset form
|
|
||||||
setName('');
|
|
||||||
setGitRepoPath('');
|
|
||||||
setParentPath('');
|
|
||||||
setFolderName('');
|
|
||||||
setError('');
|
|
||||||
|
|
||||||
modal.resolve('canceled' as ProjectFormDialogResult);
|
|
||||||
modal.hide();
|
modal.hide();
|
||||||
|
},
|
||||||
|
onCreateError: (err) => {
|
||||||
|
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-populate project name from directory name
|
||||||
|
const handleGitRepoPathChange = (path: string) => {
|
||||||
|
setGitRepoPath(path);
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
const cleanName = generateProjectNameFromPath(path);
|
||||||
|
if (cleanName) setName(cleanName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle direct project creation from repo selection
|
||||||
|
const handleDirectCreate = async (path: string, suggestedName: string) => {
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
const createData: CreateProject = {
|
||||||
|
name: suggestedName,
|
||||||
|
git_repo_path: path,
|
||||||
|
use_existing_repo: true,
|
||||||
|
setup_script: null,
|
||||||
|
dev_script: null,
|
||||||
|
cleanup_script: null,
|
||||||
|
copy_files: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenChange = (open: boolean) => {
|
createProject.mutate(createData);
|
||||||
if (!open) {
|
};
|
||||||
handleCancel();
|
|
||||||
}
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
let finalGitRepoPath = gitRepoPath;
|
||||||
|
if (repoMode === 'new') {
|
||||||
|
const effectiveParentPath = parentPath.trim();
|
||||||
|
const cleanFolderName = folderName.trim();
|
||||||
|
finalGitRepoPath = effectiveParentPath
|
||||||
|
? `${effectiveParentPath}/${cleanFolderName}`.replace(/\/+/g, '/')
|
||||||
|
: cleanFolderName;
|
||||||
|
}
|
||||||
|
// Auto-populate name from git repo path if not provided
|
||||||
|
const finalName =
|
||||||
|
name.trim() || generateProjectNameFromPath(finalGitRepoPath);
|
||||||
|
|
||||||
|
// Creating new project
|
||||||
|
const createData: CreateProject = {
|
||||||
|
name: finalName,
|
||||||
|
git_repo_path: finalGitRepoPath,
|
||||||
|
use_existing_repo: repoMode === 'existing',
|
||||||
|
setup_script: null,
|
||||||
|
dev_script: null,
|
||||||
|
cleanup_script: null,
|
||||||
|
copy_files: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
createProject.mutate(createData);
|
||||||
<Dialog open={modal.visible} onOpenChange={handleOpenChange}>
|
};
|
||||||
<DialogContent className="overflow-x-hidden">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Create Project</DialogTitle>
|
|
||||||
<DialogDescription>Choose your repository source</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="mx-auto w-full max-w-2xl overflow-x-hidden px-1">
|
const handleCancel = () => {
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
// Reset form
|
||||||
<ProjectFormFields
|
setName('');
|
||||||
isEditing={false}
|
setGitRepoPath('');
|
||||||
repoMode={repoMode}
|
setParentPath('');
|
||||||
setRepoMode={setRepoMode}
|
setFolderName('');
|
||||||
gitRepoPath={gitRepoPath}
|
setError('');
|
||||||
handleGitRepoPathChange={handleGitRepoPathChange}
|
|
||||||
parentPath={parentPath}
|
modal.resolve('canceled' as ProjectFormDialogResult);
|
||||||
setParentPath={setParentPath}
|
modal.hide();
|
||||||
setFolderName={setFolderName}
|
};
|
||||||
setName={setName}
|
|
||||||
name={name}
|
const handleOpenChange = (open: boolean) => {
|
||||||
setupScript=""
|
if (!open) {
|
||||||
setSetupScript={() => {}}
|
handleCancel();
|
||||||
devScript=""
|
}
|
||||||
setDevScript={() => {}}
|
};
|
||||||
cleanupScript=""
|
|
||||||
setCleanupScript={() => {}}
|
return (
|
||||||
copyFiles=""
|
<Dialog open={modal.visible} onOpenChange={handleOpenChange}>
|
||||||
setCopyFiles={() => {}}
|
<DialogContent className="overflow-x-hidden">
|
||||||
error={error}
|
<DialogHeader>
|
||||||
setError={setError}
|
<DialogTitle>Create Project</DialogTitle>
|
||||||
projectId={undefined}
|
<DialogDescription>Choose your repository source</DialogDescription>
|
||||||
onCreateProject={handleDirectCreate}
|
</DialogHeader>
|
||||||
/>
|
|
||||||
{repoMode === 'new' && (
|
<div className="mx-auto w-full max-w-2xl overflow-x-hidden px-1">
|
||||||
<Button
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
type="submit"
|
<ProjectFormFields
|
||||||
disabled={createProject.isPending || !folderName.trim()}
|
isEditing={false}
|
||||||
className="w-full"
|
repoMode={repoMode}
|
||||||
>
|
setRepoMode={setRepoMode}
|
||||||
{createProject.isPending ? 'Creating...' : 'Create Project'}
|
gitRepoPath={gitRepoPath}
|
||||||
</Button>
|
handleGitRepoPathChange={handleGitRepoPathChange}
|
||||||
)}
|
parentPath={parentPath}
|
||||||
</form>
|
setParentPath={setParentPath}
|
||||||
</div>
|
setFolderName={setFolderName}
|
||||||
</DialogContent>
|
setName={setName}
|
||||||
</Dialog>
|
name={name}
|
||||||
);
|
setupScript=""
|
||||||
}
|
setSetupScript={() => {}}
|
||||||
);
|
devScript=""
|
||||||
|
setDevScript={() => {}}
|
||||||
|
cleanupScript=""
|
||||||
|
setCleanupScript={() => {}}
|
||||||
|
copyFiles=""
|
||||||
|
setCopyFiles={() => {}}
|
||||||
|
error={error}
|
||||||
|
setError={setError}
|
||||||
|
projectId={undefined}
|
||||||
|
onCreateProject={handleDirectCreate}
|
||||||
|
/>
|
||||||
|
{repoMode === 'new' && (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={createProject.isPending || !folderName.trim()}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{createProject.isPending ? 'Creating...' : 'Create Project'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ProjectFormDialog = defineModal<
|
||||||
|
ProjectFormDialogProps,
|
||||||
|
ProjectFormDialogResult
|
||||||
|
>(ProjectFormDialogImpl);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface CreateConfigurationDialogProps {
|
export interface CreateConfigurationDialogProps {
|
||||||
executorType: string;
|
executorType: string;
|
||||||
@@ -31,7 +32,7 @@ export type CreateConfigurationResult = {
|
|||||||
cloneFrom?: string | null;
|
cloneFrom?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateConfigurationDialog =
|
const CreateConfigurationDialogImpl =
|
||||||
NiceModal.create<CreateConfigurationDialogProps>(
|
NiceModal.create<CreateConfigurationDialogProps>(
|
||||||
({ executorType, existingConfigs }) => {
|
({ executorType, existingConfigs }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
@@ -156,3 +157,8 @@ export const CreateConfigurationDialog =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const CreateConfigurationDialog = defineModal<
|
||||||
|
CreateConfigurationDialogProps,
|
||||||
|
CreateConfigurationResult
|
||||||
|
>(CreateConfigurationDialogImpl);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface DeleteConfigurationDialogProps {
|
export interface DeleteConfigurationDialogProps {
|
||||||
configName: string;
|
configName: string;
|
||||||
@@ -19,7 +20,7 @@ export interface DeleteConfigurationDialogProps {
|
|||||||
|
|
||||||
export type DeleteConfigurationResult = 'deleted' | 'canceled';
|
export type DeleteConfigurationResult = 'deleted' | 'canceled';
|
||||||
|
|
||||||
export const DeleteConfigurationDialog =
|
const DeleteConfigurationDialogImpl =
|
||||||
NiceModal.create<DeleteConfigurationDialogProps>(
|
NiceModal.create<DeleteConfigurationDialogProps>(
|
||||||
({ configName, executorType }) => {
|
({ configName, executorType }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
@@ -92,3 +93,8 @@ export const DeleteConfigurationDialog =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const DeleteConfigurationDialog = defineModal<
|
||||||
|
DeleteConfigurationDialogProps,
|
||||||
|
DeleteConfigurationResult
|
||||||
|
>(DeleteConfigurationDialogImpl);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
import { AlertTriangle, Info, CheckCircle, XCircle } from 'lucide-react';
|
import { AlertTriangle, Info, CheckCircle, XCircle } from 'lucide-react';
|
||||||
import type { ConfirmResult } from '@/lib/modals';
|
import { defineModal, type ConfirmResult } from '@/lib/modals';
|
||||||
|
|
||||||
export interface ConfirmDialogProps {
|
export interface ConfirmDialogProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -20,7 +20,7 @@ export interface ConfirmDialogProps {
|
|||||||
icon?: boolean;
|
icon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfirmDialog = NiceModal.create<ConfirmDialogProps>((props) => {
|
const ConfirmDialogImpl = NiceModal.create<ConfirmDialogProps>((props) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
@@ -83,4 +83,6 @@ const ConfirmDialog = NiceModal.create<ConfirmDialogProps>((props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { ConfirmDialog };
|
export const ConfirmDialog = defineModal<ConfirmDialogProps, ConfirmResult>(
|
||||||
|
ConfirmDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import { fileSystemApi } from '@/lib/api';
|
import { fileSystemApi } from '@/lib/api';
|
||||||
import { DirectoryEntry, DirectoryListResponse } from 'shared/types';
|
import { DirectoryEntry, DirectoryListResponse } from 'shared/types';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface FolderPickerDialogProps {
|
export interface FolderPickerDialogProps {
|
||||||
value?: string;
|
value?: string;
|
||||||
@@ -29,7 +30,7 @@ export interface FolderPickerDialogProps {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FolderPickerDialog = NiceModal.create<FolderPickerDialogProps>(
|
const FolderPickerDialogImpl = NiceModal.create<FolderPickerDialogProps>(
|
||||||
({
|
({
|
||||||
value = '',
|
value = '',
|
||||||
title = 'Select Folder',
|
title = 'Select Folder',
|
||||||
@@ -288,3 +289,8 @@ export const FolderPickerDialog = NiceModal.create<FolderPickerDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const FolderPickerDialog = defineModal<
|
||||||
|
FolderPickerDialogProps,
|
||||||
|
string | null
|
||||||
|
>(FolderPickerDialogImpl);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useCallback, type ComponentProps } from 'react';
|
import { useCallback, type ComponentProps } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { LogIn, type LucideIcon } from 'lucide-react';
|
import { LogIn, type LucideIcon } from 'lucide-react';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { OAuthDialog } from '@/components/dialogs/global/OAuthDialog';
|
||||||
import { OAuthDialog } from '@/components/dialogs';
|
|
||||||
|
|
||||||
import { Alert } from '@/components/ui/alert';
|
import { Alert } from '@/components/ui/alert';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -38,7 +37,7 @@ export function LoginRequiredPrompt({
|
|||||||
onAction();
|
onAction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void NiceModal.show(OAuthDialog);
|
void OAuthDialog.show();
|
||||||
}, [onAction]);
|
}, [onAction]);
|
||||||
|
|
||||||
const Icon = icon ?? LogIn;
|
const Icon = icon ?? LogIn;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import BranchSelector from '@/components/tasks/BranchSelector';
|
import BranchSelector from '@/components/tasks/BranchSelector';
|
||||||
import type { GitBranch } from 'shared/types';
|
import type { GitBranch } from 'shared/types';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface ChangeTargetBranchDialogProps {
|
export interface ChangeTargetBranchDialogProps {
|
||||||
branches: GitBranch[];
|
branches: GitBranch[];
|
||||||
@@ -23,7 +24,7 @@ export type ChangeTargetBranchDialogResult = {
|
|||||||
branchName?: string;
|
branchName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChangeTargetBranchDialog =
|
const ChangeTargetBranchDialogImpl =
|
||||||
NiceModal.create<ChangeTargetBranchDialogProps>(
|
NiceModal.create<ChangeTargetBranchDialogProps>(
|
||||||
({ branches, isChangingTargetBranch: isChangingTargetBranch = false }) => {
|
({ branches, isChangingTargetBranch: isChangingTargetBranch = false }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
@@ -100,3 +101,8 @@ export const ChangeTargetBranchDialog =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ChangeTargetBranchDialog = defineModal<
|
||||||
|
ChangeTargetBranchDialogProps,
|
||||||
|
ChangeTargetBranchDialogResult
|
||||||
|
>(ChangeTargetBranchDialogImpl);
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ import { useProject } from '@/contexts/project-context';
|
|||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import { paths } from '@/lib/paths';
|
import { paths } from '@/lib/paths';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import type { ExecutorProfileId, BaseCodingAgent } from 'shared/types';
|
import type { ExecutorProfileId, BaseCodingAgent } from 'shared/types';
|
||||||
|
|
||||||
export interface CreateAttemptDialogProps {
|
export interface CreateAttemptDialogProps {
|
||||||
taskId: string;
|
taskId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateAttemptDialog = NiceModal.create<CreateAttemptDialogProps>(
|
const CreateAttemptDialogImpl = NiceModal.create<CreateAttemptDialogProps>(
|
||||||
({ taskId }) => {
|
({ taskId }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const navigate = useNavigateWithSearch();
|
const navigate = useNavigateWithSearch();
|
||||||
@@ -135,6 +136,7 @@ export const CreateAttemptDialog = NiceModal.create<CreateAttemptDialogProps>(
|
|||||||
profile: effectiveProfile,
|
profile: effectiveProfile,
|
||||||
baseBranch: effectiveBranch,
|
baseBranch: effectiveBranch,
|
||||||
});
|
});
|
||||||
|
|
||||||
modal.hide();
|
modal.hide();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to create attempt:', err);
|
console.error('Failed to create attempt:', err);
|
||||||
@@ -210,3 +212,7 @@ export const CreateAttemptDialog = NiceModal.create<CreateAttemptDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const CreateAttemptDialog = defineModal<CreateAttemptDialogProps, void>(
|
||||||
|
CreateAttemptDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -37,285 +37,310 @@ import type {
|
|||||||
} from '@/components/dialogs/auth/GhCliSetupDialog';
|
} from '@/components/dialogs/auth/GhCliSetupDialog';
|
||||||
import type { GhCliSetupError } from 'shared/types';
|
import type { GhCliSetupError } from 'shared/types';
|
||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
const CreatePrDialog = NiceModal.create(() => {
|
import { defineModal } from '@/lib/modals';
|
||||||
const modal = useModal();
|
|
||||||
const { t } = useTranslation('tasks');
|
|
||||||
const { isLoaded } = useAuth();
|
|
||||||
const { environment } = useUserSystem();
|
|
||||||
const data = modal.args as
|
|
||||||
| { attempt: TaskAttempt; task: TaskWithAttemptStatus; projectId: string }
|
|
||||||
| undefined;
|
|
||||||
const [prTitle, setPrTitle] = useState('');
|
|
||||||
const [prBody, setPrBody] = useState('');
|
|
||||||
const [prBaseBranch, setPrBaseBranch] = useState('');
|
|
||||||
const [creatingPR, setCreatingPR] = useState(false);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [ghCliHelp, setGhCliHelp] = useState<GhCliSupportContent | null>(null);
|
|
||||||
const [branches, setBranches] = useState<GitBranch[]>([]);
|
|
||||||
const [branchesLoading, setBranchesLoading] = useState(false);
|
|
||||||
|
|
||||||
const getGhCliHelpTitle = (variant: GhCliSupportVariant) =>
|
interface CreatePRDialogProps {
|
||||||
variant === 'homebrew'
|
attempt: TaskAttempt;
|
||||||
? 'Homebrew is required for automatic setup'
|
task: TaskWithAttemptStatus;
|
||||||
: 'GitHub CLI needs manual setup';
|
projectId: string;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
|
||||||
if (!modal.visible || !data || !isLoaded) {
|
({ attempt, task, projectId }) => {
|
||||||
return;
|
const modal = useModal();
|
||||||
}
|
const { t } = useTranslation('tasks');
|
||||||
|
const { isLoaded } = useAuth();
|
||||||
|
const { environment } = useUserSystem();
|
||||||
|
const [prTitle, setPrTitle] = useState('');
|
||||||
|
const [prBody, setPrBody] = useState('');
|
||||||
|
const [prBaseBranch, setPrBaseBranch] = useState('');
|
||||||
|
const [creatingPR, setCreatingPR] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [ghCliHelp, setGhCliHelp] = useState<GhCliSupportContent | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [branches, setBranches] = useState<GitBranch[]>([]);
|
||||||
|
const [branchesLoading, setBranchesLoading] = useState(false);
|
||||||
|
|
||||||
setPrTitle(`${data.task.title} (vibe-kanban)`);
|
const getGhCliHelpTitle = (variant: GhCliSupportVariant) =>
|
||||||
setPrBody(data.task.description || '');
|
variant === 'homebrew'
|
||||||
|
? 'Homebrew is required for automatic setup'
|
||||||
|
: 'GitHub CLI needs manual setup';
|
||||||
|
|
||||||
// Always fetch branches for dropdown population
|
useEffect(() => {
|
||||||
if (data.projectId) {
|
if (!modal.visible || !isLoaded) {
|
||||||
setBranchesLoading(true);
|
return;
|
||||||
projectsApi
|
}
|
||||||
.getBranches(data.projectId)
|
|
||||||
.then((projectBranches) => {
|
|
||||||
setBranches(projectBranches);
|
|
||||||
|
|
||||||
// Set smart default: task target branch OR current branch
|
setPrTitle(`${task.title} (vibe-kanban)`);
|
||||||
if (data.attempt.target_branch) {
|
setPrBody(task.description || '');
|
||||||
setPrBaseBranch(data.attempt.target_branch);
|
|
||||||
} else {
|
// Always fetch branches for dropdown population
|
||||||
const currentBranch = projectBranches.find((b) => b.is_current);
|
if (projectId) {
|
||||||
if (currentBranch) {
|
setBranchesLoading(true);
|
||||||
setPrBaseBranch(currentBranch.name);
|
projectsApi
|
||||||
|
.getBranches(projectId)
|
||||||
|
.then((projectBranches) => {
|
||||||
|
setBranches(projectBranches);
|
||||||
|
|
||||||
|
// Set smart default: task target branch OR current branch
|
||||||
|
if (attempt.target_branch) {
|
||||||
|
setPrBaseBranch(attempt.target_branch);
|
||||||
|
} else {
|
||||||
|
const currentBranch = projectBranches.find((b) => b.is_current);
|
||||||
|
if (currentBranch) {
|
||||||
|
setPrBaseBranch(currentBranch.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.catch(console.error)
|
||||||
.catch(console.error)
|
.finally(() => setBranchesLoading(false));
|
||||||
.finally(() => setBranchesLoading(false));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setError(null); // Reset error when opening
|
setError(null); // Reset error when opening
|
||||||
setGhCliHelp(null);
|
setGhCliHelp(null);
|
||||||
}, [modal.visible, data, isLoaded]);
|
}, [modal.visible, isLoaded, task, attempt, projectId]);
|
||||||
|
|
||||||
const isMacEnvironment = useMemo(
|
const isMacEnvironment = useMemo(
|
||||||
() => environment?.os_type?.toLowerCase().includes('mac'),
|
() => environment?.os_type?.toLowerCase().includes('mac'),
|
||||||
[environment?.os_type]
|
[environment?.os_type]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleConfirmCreatePR = useCallback(async () => {
|
const handleConfirmCreatePR = useCallback(async () => {
|
||||||
if (!data?.projectId || !data?.attempt.id) return;
|
if (!projectId || !attempt.id) return;
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
setGhCliHelp(null);
|
setGhCliHelp(null);
|
||||||
setCreatingPR(true);
|
setCreatingPR(true);
|
||||||
|
|
||||||
|
const handleGhCliSetupOutcome = (
|
||||||
|
setupResult: GhCliSetupError | null,
|
||||||
|
fallbackMessage: string
|
||||||
|
) => {
|
||||||
|
if (setupResult === null) {
|
||||||
|
setError(null);
|
||||||
|
setGhCliHelp(null);
|
||||||
|
setCreatingPR(false);
|
||||||
|
modal.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ui = mapGhCliErrorToUi(setupResult, fallbackMessage, t);
|
||||||
|
|
||||||
|
if (ui.variant) {
|
||||||
|
setGhCliHelp(ui);
|
||||||
|
setError(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleGhCliSetupOutcome = (
|
|
||||||
setupResult: GhCliSetupError | null,
|
|
||||||
fallbackMessage: string
|
|
||||||
) => {
|
|
||||||
if (setupResult === null) {
|
|
||||||
setError(null);
|
|
||||||
setGhCliHelp(null);
|
setGhCliHelp(null);
|
||||||
|
setError(ui.message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await attemptsApi.createPR(attempt.id, {
|
||||||
|
title: prTitle,
|
||||||
|
body: prBody || null,
|
||||||
|
target_branch: prBaseBranch || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
setPrTitle('');
|
||||||
|
setPrBody('');
|
||||||
|
setPrBaseBranch('');
|
||||||
setCreatingPR(false);
|
setCreatingPR(false);
|
||||||
modal.hide();
|
modal.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ui = mapGhCliErrorToUi(setupResult, fallbackMessage, t);
|
setCreatingPR(false);
|
||||||
|
|
||||||
if (ui.variant) {
|
const defaultGhCliErrorMessage =
|
||||||
setGhCliHelp(ui);
|
result.message || 'Failed to run GitHub CLI setup.';
|
||||||
setError(null);
|
|
||||||
return;
|
const showGhCliSetupDialog = async () => {
|
||||||
|
const setupResult = await GhCliSetupDialog.show({
|
||||||
|
attemptId: attempt.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
handleGhCliSetupOutcome(setupResult, defaultGhCliErrorMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
switch (result.error) {
|
||||||
|
case GitHubServiceError.GH_CLI_NOT_INSTALLED: {
|
||||||
|
if (isMacEnvironment) {
|
||||||
|
await showGhCliSetupDialog();
|
||||||
|
} else {
|
||||||
|
const ui = mapGhCliErrorToUi(
|
||||||
|
'SETUP_HELPER_NOT_SUPPORTED',
|
||||||
|
defaultGhCliErrorMessage,
|
||||||
|
t
|
||||||
|
);
|
||||||
|
setGhCliHelp(ui.variant ? ui : null);
|
||||||
|
setError(ui.variant ? null : ui.message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case GitHubServiceError.TOKEN_INVALID: {
|
||||||
|
if (isMacEnvironment) {
|
||||||
|
await showGhCliSetupDialog();
|
||||||
|
} else {
|
||||||
|
const ui = mapGhCliErrorToUi(
|
||||||
|
'SETUP_HELPER_NOT_SUPPORTED',
|
||||||
|
defaultGhCliErrorMessage,
|
||||||
|
t
|
||||||
|
);
|
||||||
|
setGhCliHelp(ui.variant ? ui : null);
|
||||||
|
setError(ui.variant ? null : ui.message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case GitHubServiceError.INSUFFICIENT_PERMISSIONS:
|
||||||
|
setError(t('createPrDialog.errors.insufficientPermissions'));
|
||||||
|
setGhCliHelp(null);
|
||||||
|
return;
|
||||||
|
case GitHubServiceError.REPO_NOT_FOUND_OR_NO_ACCESS:
|
||||||
|
setError(t('createPrDialog.errors.repoNotFoundOrNoAccess'));
|
||||||
|
setGhCliHelp(null);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
setError(
|
||||||
|
result.message || t('createPrDialog.errors.failedToCreate')
|
||||||
|
);
|
||||||
|
setGhCliHelp(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setGhCliHelp(null);
|
if (result.message) {
|
||||||
setError(ui.message);
|
setError(result.message);
|
||||||
};
|
setGhCliHelp(null);
|
||||||
|
} else {
|
||||||
|
setError(t('createPrDialog.errors.failedToCreate'));
|
||||||
|
setGhCliHelp(null);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
attempt,
|
||||||
|
projectId,
|
||||||
|
prBaseBranch,
|
||||||
|
prBody,
|
||||||
|
prTitle,
|
||||||
|
modal,
|
||||||
|
isMacEnvironment,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
const result = await attemptsApi.createPR(data.attempt.id, {
|
const handleCancelCreatePR = useCallback(() => {
|
||||||
title: prTitle,
|
modal.hide();
|
||||||
body: prBody || null,
|
// Reset form to empty state
|
||||||
target_branch: prBaseBranch || null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
setPrTitle('');
|
setPrTitle('');
|
||||||
setPrBody('');
|
setPrBody('');
|
||||||
setPrBaseBranch('');
|
setPrBaseBranch('');
|
||||||
setCreatingPR(false);
|
}, [modal]);
|
||||||
modal.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCreatingPR(false);
|
return (
|
||||||
|
<>
|
||||||
const defaultGhCliErrorMessage =
|
<Dialog
|
||||||
result.message || 'Failed to run GitHub CLI setup.';
|
open={modal.visible}
|
||||||
|
onOpenChange={() => handleCancelCreatePR()}
|
||||||
const showGhCliSetupDialog = async () => {
|
>
|
||||||
const setupResult = (await NiceModal.show(GhCliSetupDialog, {
|
<DialogContent className="sm:max-w-[525px]">
|
||||||
attemptId: data.attempt.id,
|
<DialogHeader>
|
||||||
})) as GhCliSetupError | null;
|
<DialogTitle>{t('createPrDialog.title')}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
handleGhCliSetupOutcome(setupResult, defaultGhCliErrorMessage);
|
{t('createPrDialog.description')}
|
||||||
};
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
if (result.error) {
|
{!isLoaded ? (
|
||||||
switch (result.error) {
|
<div className="flex justify-center py-8">
|
||||||
case GitHubServiceError.GH_CLI_NOT_INSTALLED: {
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
if (isMacEnvironment) {
|
|
||||||
await showGhCliSetupDialog();
|
|
||||||
} else {
|
|
||||||
const ui = mapGhCliErrorToUi(
|
|
||||||
'SETUP_HELPER_NOT_SUPPORTED',
|
|
||||||
defaultGhCliErrorMessage,
|
|
||||||
t
|
|
||||||
);
|
|
||||||
setGhCliHelp(ui.variant ? ui : null);
|
|
||||||
setError(ui.variant ? null : ui.message);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case GitHubServiceError.TOKEN_INVALID: {
|
|
||||||
if (isMacEnvironment) {
|
|
||||||
await showGhCliSetupDialog();
|
|
||||||
} else {
|
|
||||||
const ui = mapGhCliErrorToUi(
|
|
||||||
'SETUP_HELPER_NOT_SUPPORTED',
|
|
||||||
defaultGhCliErrorMessage,
|
|
||||||
t
|
|
||||||
);
|
|
||||||
setGhCliHelp(ui.variant ? ui : null);
|
|
||||||
setError(ui.variant ? null : ui.message);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case GitHubServiceError.INSUFFICIENT_PERMISSIONS:
|
|
||||||
setError(t('createPrDialog.errors.insufficientPermissions'));
|
|
||||||
setGhCliHelp(null);
|
|
||||||
return;
|
|
||||||
case GitHubServiceError.REPO_NOT_FOUND_OR_NO_ACCESS:
|
|
||||||
setError(t('createPrDialog.errors.repoNotFoundOrNoAccess'));
|
|
||||||
setGhCliHelp(null);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
setError(result.message || t('createPrDialog.errors.failedToCreate'));
|
|
||||||
setGhCliHelp(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.message) {
|
|
||||||
setError(result.message);
|
|
||||||
setGhCliHelp(null);
|
|
||||||
} else {
|
|
||||||
setError(t('createPrDialog.errors.failedToCreate'));
|
|
||||||
setGhCliHelp(null);
|
|
||||||
}
|
|
||||||
}, [data, prBaseBranch, prBody, prTitle, modal, isMacEnvironment]);
|
|
||||||
|
|
||||||
const handleCancelCreatePR = useCallback(() => {
|
|
||||||
modal.hide();
|
|
||||||
// Reset form to empty state
|
|
||||||
setPrTitle('');
|
|
||||||
setPrBody('');
|
|
||||||
setPrBaseBranch('');
|
|
||||||
}, [modal]);
|
|
||||||
|
|
||||||
// Don't render if no data
|
|
||||||
if (!data) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog open={modal.visible} onOpenChange={() => handleCancelCreatePR()}>
|
|
||||||
<DialogContent className="sm:max-w-[525px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{t('createPrDialog.title')}</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
{t('createPrDialog.description')}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
{!isLoaded ? (
|
|
||||||
<div className="flex justify-center py-8">
|
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4 py-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="pr-title">
|
|
||||||
{t('createPrDialog.titleLabel')}
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="pr-title"
|
|
||||||
value={prTitle}
|
|
||||||
onChange={(e) => setPrTitle(e.target.value)}
|
|
||||||
placeholder={t('createPrDialog.titlePlaceholder')}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
) : (
|
||||||
<Label htmlFor="pr-body">
|
<div className="space-y-4 py-4">
|
||||||
{t('createPrDialog.descriptionLabel')}
|
<div className="space-y-2">
|
||||||
</Label>
|
<Label htmlFor="pr-title">
|
||||||
<Textarea
|
{t('createPrDialog.titleLabel')}
|
||||||
id="pr-body"
|
</Label>
|
||||||
value={prBody}
|
<Input
|
||||||
onChange={(e) => setPrBody(e.target.value)}
|
id="pr-title"
|
||||||
placeholder={t('createPrDialog.descriptionPlaceholder')}
|
value={prTitle}
|
||||||
rows={4}
|
onChange={(e) => setPrTitle(e.target.value)}
|
||||||
/>
|
placeholder={t('createPrDialog.titlePlaceholder')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="pr-body">
|
||||||
|
{t('createPrDialog.descriptionLabel')}
|
||||||
|
</Label>
|
||||||
|
<Textarea
|
||||||
|
id="pr-body"
|
||||||
|
value={prBody}
|
||||||
|
onChange={(e) => setPrBody(e.target.value)}
|
||||||
|
placeholder={t('createPrDialog.descriptionPlaceholder')}
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="pr-base">
|
||||||
|
{t('createPrDialog.baseBranchLabel')}
|
||||||
|
</Label>
|
||||||
|
<BranchSelector
|
||||||
|
branches={branches}
|
||||||
|
selectedBranch={prBaseBranch}
|
||||||
|
onBranchSelect={setPrBaseBranch}
|
||||||
|
placeholder={
|
||||||
|
branchesLoading
|
||||||
|
? t('createPrDialog.loadingBranches')
|
||||||
|
: t('createPrDialog.selectBaseBranch')
|
||||||
|
}
|
||||||
|
className={
|
||||||
|
branchesLoading ? 'opacity-50 cursor-not-allowed' : ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{ghCliHelp?.variant && (
|
||||||
|
<Alert variant="default">
|
||||||
|
<AlertTitle>
|
||||||
|
{getGhCliHelpTitle(ghCliHelp.variant)}
|
||||||
|
</AlertTitle>
|
||||||
|
<AlertDescription className="space-y-3">
|
||||||
|
<p>{ghCliHelp.message}</p>
|
||||||
|
<GhCliHelpInstructions
|
||||||
|
variant={ghCliHelp.variant}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{error && <Alert variant="destructive">{error}</Alert>}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
)}
|
||||||
<Label htmlFor="pr-base">
|
<DialogFooter>
|
||||||
{t('createPrDialog.baseBranchLabel')}
|
<Button variant="outline" onClick={handleCancelCreatePR}>
|
||||||
</Label>
|
{t('common:buttons.cancel')}
|
||||||
<BranchSelector
|
</Button>
|
||||||
branches={branches}
|
<Button
|
||||||
selectedBranch={prBaseBranch}
|
onClick={handleConfirmCreatePR}
|
||||||
onBranchSelect={setPrBaseBranch}
|
disabled={creatingPR || !prTitle.trim()}
|
||||||
placeholder={
|
className="bg-blue-600 hover:bg-blue-700"
|
||||||
branchesLoading
|
>
|
||||||
? t('createPrDialog.loadingBranches')
|
{creatingPR ? (
|
||||||
: t('createPrDialog.selectBaseBranch')
|
<>
|
||||||
}
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
className={
|
{t('createPrDialog.creating')}
|
||||||
branchesLoading ? 'opacity-50 cursor-not-allowed' : ''
|
</>
|
||||||
}
|
) : (
|
||||||
/>
|
t('createPrDialog.createButton')
|
||||||
</div>
|
)}
|
||||||
{ghCliHelp?.variant && (
|
</Button>
|
||||||
<Alert variant="default">
|
</DialogFooter>
|
||||||
<AlertTitle>
|
</DialogContent>
|
||||||
{getGhCliHelpTitle(ghCliHelp.variant)}
|
</Dialog>
|
||||||
</AlertTitle>
|
</>
|
||||||
<AlertDescription className="space-y-3">
|
);
|
||||||
<p>{ghCliHelp.message}</p>
|
}
|
||||||
<GhCliHelpInstructions variant={ghCliHelp.variant} t={t} />
|
);
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{error && <Alert variant="destructive">{error}</Alert>}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={handleCancelCreatePR}>
|
|
||||||
{t('common:buttons.cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleConfirmCreatePR}
|
|
||||||
disabled={creatingPR || !prTitle.trim()}
|
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
{creatingPR ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
{t('createPrDialog.creating')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
t('createPrDialog.createButton')
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export { CreatePrDialog as CreatePRDialog };
|
export const CreatePRDialog = defineModal<CreatePRDialogProps, void>(
|
||||||
|
CreatePRDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ import { Alert } from '@/components/ui/alert';
|
|||||||
import { tasksApi } from '@/lib/api';
|
import { tasksApi } from '@/lib/api';
|
||||||
import type { TaskWithAttemptStatus } from 'shared/types';
|
import type { TaskWithAttemptStatus } from 'shared/types';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface DeleteTaskConfirmationDialogProps {
|
export interface DeleteTaskConfirmationDialogProps {
|
||||||
task: TaskWithAttemptStatus;
|
task: TaskWithAttemptStatus;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteTaskConfirmationDialog =
|
const DeleteTaskConfirmationDialogImpl =
|
||||||
NiceModal.create<DeleteTaskConfirmationDialogProps>(({ task }) => {
|
NiceModal.create<DeleteTaskConfirmationDialogProps>(({ task }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
@@ -93,4 +94,7 @@ const DeleteTaskConfirmationDialog =
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { DeleteTaskConfirmationDialog };
|
export const DeleteTaskConfirmationDialog = defineModal<
|
||||||
|
DeleteTaskConfirmationDialogProps,
|
||||||
|
void
|
||||||
|
>(DeleteTaskConfirmationDialogImpl);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import { useRenameBranch } from '@/hooks/useRenameBranch';
|
import { useRenameBranch } from '@/hooks/useRenameBranch';
|
||||||
|
|
||||||
export interface EditBranchNameDialogProps {
|
export interface EditBranchNameDialogProps {
|
||||||
@@ -23,7 +24,7 @@ export type EditBranchNameDialogResult = {
|
|||||||
branchName?: string;
|
branchName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EditBranchNameDialog = NiceModal.create<EditBranchNameDialogProps>(
|
const EditBranchNameDialogImpl = NiceModal.create<EditBranchNameDialogProps>(
|
||||||
({ attemptId, currentBranchName }) => {
|
({ attemptId, currentBranchName }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation(['tasks', 'common']);
|
const { t } = useTranslation(['tasks', 'common']);
|
||||||
@@ -136,3 +137,8 @@ export const EditBranchNameDialog = NiceModal.create<EditBranchNameDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const EditBranchNameDialog = defineModal<
|
||||||
|
EditBranchNameDialogProps,
|
||||||
|
EditBranchNameDialogResult
|
||||||
|
>(EditBranchNameDialogImpl);
|
||||||
|
|||||||
@@ -18,77 +18,82 @@ import {
|
|||||||
import { EditorType } from 'shared/types';
|
import { EditorType } from 'shared/types';
|
||||||
import { useOpenInEditor } from '@/hooks/useOpenInEditor';
|
import { useOpenInEditor } from '@/hooks/useOpenInEditor';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface EditorSelectionDialogProps {
|
export interface EditorSelectionDialogProps {
|
||||||
selectedAttemptId?: string;
|
selectedAttemptId?: string;
|
||||||
filePath?: string;
|
filePath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorSelectionDialog =
|
const EditorSelectionDialogImpl = NiceModal.create<EditorSelectionDialogProps>(
|
||||||
NiceModal.create<EditorSelectionDialogProps>(
|
({ selectedAttemptId, filePath }) => {
|
||||||
({ selectedAttemptId, filePath }) => {
|
const modal = useModal();
|
||||||
const modal = useModal();
|
const handleOpenInEditor = useOpenInEditor(selectedAttemptId, () =>
|
||||||
const handleOpenInEditor = useOpenInEditor(selectedAttemptId, () =>
|
modal.hide()
|
||||||
modal.hide()
|
);
|
||||||
);
|
const [selectedEditor, setSelectedEditor] = useState<EditorType>(
|
||||||
const [selectedEditor, setSelectedEditor] = useState<EditorType>(
|
EditorType.VS_CODE
|
||||||
EditorType.VS_CODE
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
handleOpenInEditor({ editorType: selectedEditor, filePath });
|
handleOpenInEditor({ editorType: selectedEditor, filePath });
|
||||||
modal.resolve(selectedEditor);
|
modal.resolve(selectedEditor);
|
||||||
modal.hide();
|
modal.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
modal.resolve(null);
|
modal.resolve(null);
|
||||||
modal.hide();
|
modal.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={modal.visible}
|
open={modal.visible}
|
||||||
onOpenChange={(open) => !open && handleCancel()}
|
onOpenChange={(open) => !open && handleCancel()}
|
||||||
>
|
>
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Choose Editor</DialogTitle>
|
<DialogTitle>Choose Editor</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
The default editor failed to open. Please select an alternative
|
The default editor failed to open. Please select an alternative
|
||||||
editor to open the task worktree.
|
editor to open the task worktree.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-4">
|
<div className="grid gap-4 py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">Editor</label>
|
<label className="text-sm font-medium">Editor</label>
|
||||||
<Select
|
<Select
|
||||||
value={selectedEditor}
|
value={selectedEditor}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
setSelectedEditor(value as EditorType)
|
setSelectedEditor(value as EditorType)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{Object.values(EditorType).map((editor) => (
|
{Object.values(EditorType).map((editor) => (
|
||||||
<SelectItem key={editor} value={editor}>
|
<SelectItem key={editor} value={editor}>
|
||||||
{editor}
|
{editor}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
</div>
|
||||||
<Button variant="outline" onClick={handleCancel}>
|
<DialogFooter>
|
||||||
Cancel
|
<Button variant="outline" onClick={handleCancel}>
|
||||||
</Button>
|
Cancel
|
||||||
<Button onClick={handleConfirm}>Open Editor</Button>
|
</Button>
|
||||||
</DialogFooter>
|
<Button onClick={handleConfirm}>Open Editor</Button>
|
||||||
</DialogContent>
|
</DialogFooter>
|
||||||
</Dialog>
|
</DialogContent>
|
||||||
);
|
</Dialog>
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const EditorSelectionDialog = defineModal<
|
||||||
|
EditorSelectionDialogProps,
|
||||||
|
EditorType | null
|
||||||
|
>(EditorSelectionDialogImpl);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import type {
|
|||||||
TaskWithAttemptStatus,
|
TaskWithAttemptStatus,
|
||||||
} from 'shared/types';
|
} from 'shared/types';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface GitActionsDialogProps {
|
export interface GitActionsDialogProps {
|
||||||
attemptId: string;
|
attemptId: string;
|
||||||
@@ -97,7 +98,7 @@ function GitActionsDialogContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GitActionsDialog = NiceModal.create<GitActionsDialogProps>(
|
const GitActionsDialogImpl = NiceModal.create<GitActionsDialogProps>(
|
||||||
({ attemptId, task, projectId: providedProjectId }) => {
|
({ attemptId, task, projectId: providedProjectId }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
@@ -159,3 +160,7 @@ export const GitActionsDialog = NiceModal.create<GitActionsDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const GitActionsDialog = defineModal<GitActionsDialogProps, void>(
|
||||||
|
GitActionsDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -43,7 +44,7 @@ const buildMemberLabel = (member: OrganizationMemberWithProfile): string => {
|
|||||||
return member.user_id;
|
return member.user_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReassignDialog = NiceModal.create<ReassignDialogProps>(
|
const ReassignDialogImpl = NiceModal.create<ReassignDialogProps>(
|
||||||
({ sharedTask }) => {
|
({ sharedTask }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { userId } = useAuth();
|
const { userId } = useAuth();
|
||||||
@@ -238,3 +239,8 @@ export const ReassignDialog = NiceModal.create<ReassignDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ReassignDialog = defineModal<
|
||||||
|
ReassignDialogProps,
|
||||||
|
SharedTaskRecord | null
|
||||||
|
>(ReassignDialogImpl);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import BranchSelector from '@/components/tasks/BranchSelector';
|
import BranchSelector from '@/components/tasks/BranchSelector';
|
||||||
import type { GitBranch } from 'shared/types';
|
import type { GitBranch } from 'shared/types';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface RebaseDialogProps {
|
export interface RebaseDialogProps {
|
||||||
branches: GitBranch[];
|
branches: GitBranch[];
|
||||||
@@ -27,7 +28,7 @@ export type RebaseDialogResult = {
|
|||||||
upstreamBranch?: string;
|
upstreamBranch?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RebaseDialog = NiceModal.create<RebaseDialogProps>(
|
const RebaseDialogImpl = NiceModal.create<RebaseDialogProps>(
|
||||||
({
|
({
|
||||||
branches,
|
branches,
|
||||||
isRebasing = false,
|
isRebasing = false,
|
||||||
@@ -155,3 +156,7 @@ export const RebaseDialog = NiceModal.create<RebaseDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const RebaseDialog = defineModal<RebaseDialogProps, RebaseDialogResult>(
|
||||||
|
RebaseDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { AlertTriangle, GitCommit } from 'lucide-react';
|
import { AlertTriangle, GitCommit } from 'lucide-react';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface RestoreLogsDialogProps {
|
export interface RestoreLogsDialogProps {
|
||||||
targetSha: string | null;
|
targetSha: string | null;
|
||||||
@@ -35,7 +36,7 @@ export type RestoreLogsDialogResult = {
|
|||||||
forceWhenDirty?: boolean;
|
forceWhenDirty?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RestoreLogsDialog = NiceModal.create<RestoreLogsDialogProps>(
|
const RestoreLogsDialogImpl = NiceModal.create<RestoreLogsDialogProps>(
|
||||||
({
|
({
|
||||||
targetSha,
|
targetSha,
|
||||||
targetSubject,
|
targetSubject,
|
||||||
@@ -383,3 +384,8 @@ export const RestoreLogsDialog = NiceModal.create<RestoreLogsDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const RestoreLogsDialog = defineModal<
|
||||||
|
RestoreLogsDialogProps,
|
||||||
|
RestoreLogsDialogResult
|
||||||
|
>(RestoreLogsDialogImpl);
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
import { OAuthDialog } from '@/components/dialogs/global/OAuthDialog';
|
||||||
|
import { LinkProjectDialog } from '@/components/dialogs/projects/LinkProjectDialog';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import { Link as LinkIcon, Loader2 } from 'lucide-react';
|
import { Link as LinkIcon, Loader2 } from 'lucide-react';
|
||||||
import type { TaskWithAttemptStatus } from 'shared/types';
|
import type { TaskWithAttemptStatus } from 'shared/types';
|
||||||
import { LoginRequiredPrompt } from '@/components/dialogs/shared/LoginRequiredPrompt';
|
import { LoginRequiredPrompt } from '@/components/dialogs/shared/LoginRequiredPrompt';
|
||||||
import { LinkProjectDialog } from '@/components/dialogs/projects/LinkProjectDialog';
|
|
||||||
import { useAuth } from '@/hooks';
|
import { useAuth } from '@/hooks';
|
||||||
import { useProject } from '@/contexts/project-context';
|
import { useProject } from '@/contexts/project-context';
|
||||||
import { useTaskMutations } from '@/hooks/useTaskMutations';
|
import { useTaskMutations } from '@/hooks/useTaskMutations';
|
||||||
@@ -24,7 +26,7 @@ export interface ShareDialogProps {
|
|||||||
task: TaskWithAttemptStatus;
|
task: TaskWithAttemptStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShareDialog = NiceModal.create<ShareDialogProps>(({ task }) => {
|
const ShareDialogImpl = NiceModal.create<ShareDialogProps>(({ task }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
const { loading: systemLoading } = useUserSystem();
|
const { loading: systemLoading } = useUserSystem();
|
||||||
@@ -67,7 +69,7 @@ const ShareDialog = NiceModal.create<ShareDialogProps>(({ task }) => {
|
|||||||
modal.hide();
|
modal.hide();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (getStatus(err) === 401) {
|
if (getStatus(err) === 401) {
|
||||||
void NiceModal.show('oauth');
|
void OAuthDialog.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setShareError(getReadableError(err));
|
setShareError(getReadableError(err));
|
||||||
@@ -77,7 +79,7 @@ const ShareDialog = NiceModal.create<ShareDialogProps>(({ task }) => {
|
|||||||
const handleLinkProject = () => {
|
const handleLinkProject = () => {
|
||||||
if (!project) return;
|
if (!project) return;
|
||||||
|
|
||||||
void NiceModal.show(LinkProjectDialog, {
|
void LinkProjectDialog.show({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
});
|
});
|
||||||
@@ -169,4 +171,6 @@ const ShareDialog = NiceModal.create<ShareDialogProps>(({ task }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { ShareDialog };
|
export const ShareDialog = defineModal<ShareDialogProps, boolean>(
|
||||||
|
ShareDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Alert } from '@/components/ui/alert';
|
import { Alert } from '@/components/ui/alert';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { SharedTaskRecord } from '@/hooks/useProjectTasks';
|
import type { SharedTaskRecord } from '@/hooks/useProjectTasks';
|
||||||
import { useTaskMutations } from '@/hooks/useTaskMutations';
|
import { useTaskMutations } from '@/hooks/useTaskMutations';
|
||||||
@@ -19,7 +20,7 @@ export interface StopShareTaskDialogProps {
|
|||||||
sharedTask: SharedTaskRecord;
|
sharedTask: SharedTaskRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StopShareTaskDialog = NiceModal.create<StopShareTaskDialogProps>(
|
const StopShareTaskDialogImpl = NiceModal.create<StopShareTaskDialogProps>(
|
||||||
({ sharedTask }) => {
|
({ sharedTask }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
@@ -130,4 +131,6 @@ const StopShareTaskDialog = NiceModal.create<StopShareTaskDialogProps>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export { StopShareTaskDialog };
|
export const StopShareTaskDialog = defineModal<StopShareTaskDialogProps, void>(
|
||||||
|
StopShareTaskDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Loader2 } from 'lucide-react';
|
|||||||
import { tagsApi } from '@/lib/api';
|
import { tagsApi } from '@/lib/api';
|
||||||
import type { Tag, CreateTag, UpdateTag } from 'shared/types';
|
import type { Tag, CreateTag, UpdateTag } from 'shared/types';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
|
|
||||||
export interface TagEditDialogProps {
|
export interface TagEditDialogProps {
|
||||||
tag?: Tag | null; // null for create mode
|
tag?: Tag | null; // null for create mode
|
||||||
@@ -23,7 +24,7 @@ export interface TagEditDialogProps {
|
|||||||
|
|
||||||
export type TagEditResult = 'saved' | 'canceled';
|
export type TagEditResult = 'saved' | 'canceled';
|
||||||
|
|
||||||
export const TagEditDialog = NiceModal.create<TagEditDialogProps>(({ tag }) => {
|
const TagEditDialogImpl = NiceModal.create<TagEditDialogProps>(({ tag }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const { t } = useTranslation('settings');
|
const { t } = useTranslation('settings');
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
@@ -201,3 +202,7 @@ export const TagEditDialog = NiceModal.create<TagEditDialogProps>(({ tag }) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const TagEditDialog = defineModal<TagEditDialogProps, TagEditResult>(
|
||||||
|
TagEditDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useCallback, useRef, useState, useMemo } from 'react';
|
import { useEffect, useCallback, useRef, useState, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import { useForm, useStore } from '@tanstack/react-form';
|
import { useForm, useStore } from '@tanstack/react-form';
|
||||||
import { Image as ImageIcon } from 'lucide-react';
|
import { Image as ImageIcon } from 'lucide-react';
|
||||||
@@ -76,7 +77,7 @@ type TaskFormValues = {
|
|||||||
autoStart: boolean;
|
autoStart: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TaskFormDialog = NiceModal.create<TaskFormDialogProps>((props) => {
|
const TaskFormDialogImpl = NiceModal.create<TaskFormDialogProps>((props) => {
|
||||||
const { mode, projectId } = props;
|
const { mode, projectId } = props;
|
||||||
const editMode = mode === 'edit';
|
const editMode = mode === 'edit';
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
@@ -622,3 +623,7 @@ export const TaskFormDialog = NiceModal.create<TaskFormDialogProps>((props) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const TaskFormDialog = defineModal<TaskFormDialogProps, void>(
|
||||||
|
TaskFormDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -14,7 +15,7 @@ export interface ViewProcessesDialogProps {
|
|||||||
initialProcessId?: string | null;
|
initialProcessId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ViewProcessesDialog = NiceModal.create<ViewProcessesDialogProps>(
|
const ViewProcessesDialogImpl = NiceModal.create<ViewProcessesDialogProps>(
|
||||||
({ attemptId, initialProcessId }) => {
|
({ attemptId, initialProcessId }) => {
|
||||||
const { t } = useTranslation('tasks');
|
const { t } = useTranslation('tasks');
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
@@ -53,3 +54,7 @@ export const ViewProcessesDialog = NiceModal.create<ViewProcessesDialogProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ViewProcessesDialog = defineModal<ViewProcessesDialogProps, void>(
|
||||||
|
ViewProcessesDialogImpl
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||||
|
import { defineModal } from '@/lib/modals';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -20,7 +21,7 @@ export interface ViewRelatedTasksDialogProps {
|
|||||||
onNavigateToTask?: (taskId: string) => void;
|
onNavigateToTask?: (taskId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ViewRelatedTasksDialog =
|
const ViewRelatedTasksDialogImpl =
|
||||||
NiceModal.create<ViewRelatedTasksDialogProps>(
|
NiceModal.create<ViewRelatedTasksDialogProps>(
|
||||||
({ attemptId, projectId, attempt, onNavigateToTask }) => {
|
({ attemptId, projectId, attempt, onNavigateToTask }) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
@@ -166,3 +167,8 @@ export const ViewRelatedTasksDialog =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ViewRelatedTasksDialog = defineModal<
|
||||||
|
ViewRelatedTasksDialogProps,
|
||||||
|
void
|
||||||
|
>(ViewRelatedTasksDialogImpl);
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { OAuthDialog } from '@/components/dialogs/global/OAuthDialog';
|
||||||
import { OAuthDialog } from '@/components/dialogs';
|
|
||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import { oauthApi } from '@/lib/api';
|
import { oauthApi } from '@/lib/api';
|
||||||
|
|
||||||
@@ -117,7 +116,7 @@ export function Navbar() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenOAuth = async () => {
|
const handleOpenOAuth = async () => {
|
||||||
const profile = await NiceModal.show(OAuthDialog);
|
const profile = await OAuthDialog.show();
|
||||||
if (profile) {
|
if (profile) {
|
||||||
await reloadSystem();
|
await reloadSystem();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type { TaskWithAttemptStatus, TaskAttempt } from 'shared/types';
|
|||||||
import { NewCardContent } from '../ui/new-card';
|
import { NewCardContent } from '../ui/new-card';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon } from 'lucide-react';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { CreateAttemptDialog } from '@/components/dialogs/tasks/CreateAttemptDialog';
|
||||||
import MarkdownRenderer from '@/components/ui/markdown-renderer';
|
import MarkdownRenderer from '@/components/ui/markdown-renderer';
|
||||||
import { DataTable, type ColumnDef } from '@/components/ui/table';
|
import { DataTable, type ColumnDef } from '@/components/ui/table';
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ const TaskPanel = ({ task }: TaskPanelProps) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
NiceModal.show('create-attempt', {
|
CreateAttemptDialog.show({
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { useEffect, useRef } from 'react';
|
|||||||
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
import { useOpenProjectInEditor } from '@/hooks/useOpenProjectInEditor';
|
||||||
import { useNavigateWithSearch } from '@/hooks';
|
import { useNavigateWithSearch } from '@/hooks';
|
||||||
import { projectsApi } from '@/lib/api';
|
import { projectsApi } from '@/lib/api';
|
||||||
import { showLinkProject } from '@/lib/modals';
|
import { LinkProjectDialog } from '@/components/dialogs/projects/LinkProjectDialog';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
import { useProjectMutations } from '@/hooks/useProjectMutations';
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ function ProjectCard({
|
|||||||
|
|
||||||
const handleLinkProject = async () => {
|
const handleLinkProject = async () => {
|
||||||
try {
|
try {
|
||||||
await showLinkProject({
|
await LinkProjectDialog.show({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { useScriptPlaceholders } from '@/hooks/useScriptPlaceholders';
|
|||||||
import { CopyFilesField } from './copy-files-field';
|
import { CopyFilesField } from './copy-files-field';
|
||||||
// Removed collapsible sections for simplicity; show fields always in edit mode
|
// Removed collapsible sections for simplicity; show fields always in edit mode
|
||||||
import { fileSystemApi } from '@/lib/api';
|
import { fileSystemApi } from '@/lib/api';
|
||||||
import { showFolderPicker } from '@/lib/modals';
|
import { FolderPickerDialog } from '@/components/dialogs/shared/FolderPickerDialog';
|
||||||
import { DirectoryEntry } from 'shared/types';
|
import { DirectoryEntry } from 'shared/types';
|
||||||
import { generateProjectNameFromPath } from '@/utils/string';
|
import { generateProjectNameFromPath } from '@/utils/string';
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ export function ProjectFormFields({
|
|||||||
className="p-4 border border-dashed cursor-pointer hover:shadow-md transition-shadow rounded-lg bg-card"
|
className="p-4 border border-dashed cursor-pointer hover:shadow-md transition-shadow rounded-lg bg-card"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setError('');
|
setError('');
|
||||||
const selectedPath = await showFolderPicker({
|
const selectedPath = await FolderPickerDialog.show({
|
||||||
title: 'Select Git Repository',
|
title: 'Select Git Repository',
|
||||||
description: 'Choose an existing git repository',
|
description: 'Choose an existing git repository',
|
||||||
});
|
});
|
||||||
@@ -341,7 +341,7 @@ export function ProjectFormFields({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const selectedPath = await showFolderPicker({
|
const selectedPath = await FolderPickerDialog.show({
|
||||||
title: 'Select Parent Directory',
|
title: 'Select Parent Directory',
|
||||||
description: 'Choose where to create the new repository',
|
description: 'Choose where to create the new repository',
|
||||||
value: parentPath,
|
value: parentPath,
|
||||||
@@ -381,7 +381,7 @@ export function ProjectFormFields({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const selectedPath = await showFolderPicker({
|
const selectedPath = await FolderPickerDialog.show({
|
||||||
title: 'Select Git Repository',
|
title: 'Select Git Repository',
|
||||||
description: 'Choose an existing git repository',
|
description: 'Choose an existing git repository',
|
||||||
value: gitRepoPath,
|
value: gitRepoPath,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Project } from 'shared/types';
|
import { Project } from 'shared/types';
|
||||||
import { showProjectForm } from '@/lib/modals';
|
import { ProjectFormDialog } from '@/components/dialogs/projects/ProjectFormDialog';
|
||||||
import { projectsApi } from '@/lib/api';
|
import { projectsApi } from '@/lib/api';
|
||||||
import { AlertCircle, Loader2, Plus } from 'lucide-react';
|
import { AlertCircle, Loader2, Plus } from 'lucide-react';
|
||||||
import ProjectCard from '@/components/projects/ProjectCard.tsx';
|
import ProjectCard from '@/components/projects/ProjectCard.tsx';
|
||||||
@@ -37,7 +37,7 @@ export function ProjectList() {
|
|||||||
|
|
||||||
const handleCreateProject = async () => {
|
const handleCreateProject = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await showProjectForm();
|
const result = await ProjectFormDialog.show({});
|
||||||
if (result === 'saved') {
|
if (result === 'saved') {
|
||||||
fetchProjects();
|
fetchProjects();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ import type {
|
|||||||
TaskAttempt,
|
TaskAttempt,
|
||||||
TaskWithAttemptStatus,
|
TaskWithAttemptStatus,
|
||||||
} from 'shared/types';
|
} from 'shared/types';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { ChangeTargetBranchDialog } from '@/components/dialogs/tasks/ChangeTargetBranchDialog';
|
||||||
import { showModal } from '@/lib/modals';
|
import { RebaseDialog } from '@/components/dialogs/tasks/RebaseDialog';
|
||||||
|
import { CreatePRDialog } from '@/components/dialogs/tasks/CreatePRDialog';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useGitOperations } from '@/hooks/useGitOperations';
|
import { useGitOperations } from '@/hooks/useGitOperations';
|
||||||
|
|
||||||
@@ -75,10 +76,7 @@ function GitOperations({
|
|||||||
|
|
||||||
const handleChangeTargetBranchDialogOpen = async () => {
|
const handleChangeTargetBranchDialogOpen = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await showModal<{
|
const result = await ChangeTargetBranchDialog.show({
|
||||||
action: 'confirmed' | 'canceled';
|
|
||||||
branchName: string;
|
|
||||||
}>('change-target-branch-dialog', {
|
|
||||||
branches,
|
branches,
|
||||||
isChangingTargetBranch: isChangingTargetBranch,
|
isChangingTargetBranch: isChangingTargetBranch,
|
||||||
});
|
});
|
||||||
@@ -194,11 +192,7 @@ function GitOperations({
|
|||||||
const handleRebaseDialogOpen = async () => {
|
const handleRebaseDialogOpen = async () => {
|
||||||
try {
|
try {
|
||||||
const defaultTargetBranch = selectedAttempt.target_branch;
|
const defaultTargetBranch = selectedAttempt.target_branch;
|
||||||
const result = await showModal<{
|
const result = await RebaseDialog.show({
|
||||||
action: 'confirmed' | 'canceled';
|
|
||||||
branchName?: string;
|
|
||||||
upstreamBranch?: string;
|
|
||||||
}>('rebase-dialog', {
|
|
||||||
branches,
|
branches,
|
||||||
isRebasing: rebasing,
|
isRebasing: rebasing,
|
||||||
initialTargetBranch: defaultTargetBranch,
|
initialTargetBranch: defaultTargetBranch,
|
||||||
@@ -226,7 +220,7 @@ function GitOperations({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NiceModal.show('create-pr', {
|
CreatePRDialog.show({
|
||||||
attempt: selectedAttempt,
|
attempt: selectedAttempt,
|
||||||
task,
|
task,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -11,10 +11,18 @@ import {
|
|||||||
import { MoreHorizontal } from 'lucide-react';
|
import { MoreHorizontal } from 'lucide-react';
|
||||||
import type { TaskWithAttemptStatus, TaskAttempt } from 'shared/types';
|
import type { TaskWithAttemptStatus, TaskAttempt } from 'shared/types';
|
||||||
import { useOpenInEditor } from '@/hooks/useOpenInEditor';
|
import { useOpenInEditor } from '@/hooks/useOpenInEditor';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { DeleteTaskConfirmationDialog } from '@/components/dialogs/tasks/DeleteTaskConfirmationDialog';
|
||||||
|
import { ViewProcessesDialog } from '@/components/dialogs/tasks/ViewProcessesDialog';
|
||||||
|
import { ViewRelatedTasksDialog } from '@/components/dialogs/tasks/ViewRelatedTasksDialog';
|
||||||
|
import { CreateAttemptDialog } from '@/components/dialogs/tasks/CreateAttemptDialog';
|
||||||
|
import { GitActionsDialog } from '@/components/dialogs/tasks/GitActionsDialog';
|
||||||
|
import { EditBranchNameDialog } from '@/components/dialogs/tasks/EditBranchNameDialog';
|
||||||
|
import { ShareDialog } from '@/components/dialogs/tasks/ShareDialog';
|
||||||
|
import { ReassignDialog } from '@/components/dialogs/tasks/ReassignDialog';
|
||||||
|
import { StopShareTaskDialog } from '@/components/dialogs/tasks/StopShareTaskDialog';
|
||||||
import { useProject } from '@/contexts/project-context';
|
import { useProject } from '@/contexts/project-context';
|
||||||
import { openTaskForm } from '@/lib/openTaskForm';
|
import { openTaskForm } from '@/lib/openTaskForm';
|
||||||
import { ViewRelatedTasksDialog } from '@/components/dialogs/tasks/ViewRelatedTasksDialog';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import type { SharedTaskRecord } from '@/hooks/useProjectTasks';
|
import type { SharedTaskRecord } from '@/hooks/useProjectTasks';
|
||||||
import { useAuth } from '@/hooks';
|
import { useAuth } from '@/hooks';
|
||||||
@@ -56,7 +64,7 @@ export function ActionsDropdown({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!projectId || !task) return;
|
if (!projectId || !task) return;
|
||||||
try {
|
try {
|
||||||
await NiceModal.show('delete-task-confirmation', {
|
await DeleteTaskConfirmationDialog.show({
|
||||||
task,
|
task,
|
||||||
projectId,
|
projectId,
|
||||||
});
|
});
|
||||||
@@ -74,13 +82,13 @@ export function ActionsDropdown({
|
|||||||
const handleViewProcesses = (e: React.MouseEvent) => {
|
const handleViewProcesses = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!attempt?.id) return;
|
if (!attempt?.id) return;
|
||||||
NiceModal.show('view-processes', { attemptId: attempt.id });
|
ViewProcessesDialog.show({ attemptId: attempt.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewRelatedTasks = (e: React.MouseEvent) => {
|
const handleViewRelatedTasks = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!attempt?.id || !projectId) return;
|
if (!attempt?.id || !projectId) return;
|
||||||
NiceModal.show(ViewRelatedTasksDialog, {
|
ViewRelatedTasksDialog.show({
|
||||||
attemptId: attempt.id,
|
attemptId: attempt.id,
|
||||||
projectId,
|
projectId,
|
||||||
attempt,
|
attempt,
|
||||||
@@ -95,7 +103,7 @@ export function ActionsDropdown({
|
|||||||
const handleCreateNewAttempt = (e: React.MouseEvent) => {
|
const handleCreateNewAttempt = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!task?.id) return;
|
if (!task?.id) return;
|
||||||
NiceModal.show('create-attempt', {
|
CreateAttemptDialog.show({
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -116,7 +124,7 @@ export function ActionsDropdown({
|
|||||||
const handleGitActions = (e: React.MouseEvent) => {
|
const handleGitActions = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!attempt?.id || !task) return;
|
if (!attempt?.id || !task) return;
|
||||||
NiceModal.show('git-actions', {
|
GitActionsDialog.show({
|
||||||
attemptId: attempt.id,
|
attemptId: attempt.id,
|
||||||
task,
|
task,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -126,7 +134,7 @@ export function ActionsDropdown({
|
|||||||
const handleEditBranchName = (e: React.MouseEvent) => {
|
const handleEditBranchName = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!attempt?.id) return;
|
if (!attempt?.id) return;
|
||||||
NiceModal.show('edit-branch-name-dialog', {
|
EditBranchNameDialog.show({
|
||||||
attemptId: attempt.id,
|
attemptId: attempt.id,
|
||||||
currentBranchName: attempt.branch,
|
currentBranchName: attempt.branch,
|
||||||
});
|
});
|
||||||
@@ -134,19 +142,19 @@ export function ActionsDropdown({
|
|||||||
const handleShare = (e: React.MouseEvent) => {
|
const handleShare = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!task || isShared) return;
|
if (!task || isShared) return;
|
||||||
NiceModal.show('share-task', { task });
|
ShareDialog.show({ task });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReassign = (e: React.MouseEvent) => {
|
const handleReassign = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!sharedTask) return;
|
if (!sharedTask) return;
|
||||||
NiceModal.show('reassign-shared-task', { sharedTask });
|
ReassignDialog.show({ sharedTask });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStopShare = (e: React.MouseEvent) => {
|
const handleStopShare = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!sharedTask) return;
|
if (!sharedTask) return;
|
||||||
NiceModal.show('stop-share-shared-task', { sharedTask });
|
StopShareTaskDialog.show({ sharedTask });
|
||||||
};
|
};
|
||||||
|
|
||||||
const canReassign =
|
const canReassign =
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { attemptsApi } from '@/lib/api';
|
import { attemptsApi } from '@/lib/api';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { EditorSelectionDialog } from '@/components/dialogs/tasks/EditorSelectionDialog';
|
||||||
import type { EditorType } from 'shared/types';
|
import type { EditorType } from 'shared/types';
|
||||||
|
|
||||||
type OpenEditorOptions = {
|
type OpenEditorOptions = {
|
||||||
@@ -35,7 +35,7 @@ export function useOpenInEditor(
|
|||||||
if (onShowEditorDialog) {
|
if (onShowEditorDialog) {
|
||||||
onShowEditorDialog();
|
onShowEditorDialog();
|
||||||
} else {
|
} else {
|
||||||
NiceModal.show('editor-selection', {
|
EditorSelectionDialog.show({
|
||||||
selectedAttemptId: attemptId,
|
selectedAttemptId: attemptId,
|
||||||
filePath,
|
filePath,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { projectsApi } from '@/lib/api';
|
import { projectsApi } from '@/lib/api';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { ProjectEditorSelectionDialog } from '@/components/dialogs/projects/ProjectEditorSelectionDialog';
|
||||||
import type { EditorType, Project } from 'shared/types';
|
import type { EditorType, Project } from 'shared/types';
|
||||||
|
|
||||||
export function useOpenProjectInEditor(
|
export function useOpenProjectInEditor(
|
||||||
@@ -24,7 +24,7 @@ export function useOpenProjectInEditor(
|
|||||||
if (onShowEditorDialog) {
|
if (onShowEditorDialog) {
|
||||||
onShowEditorDialog();
|
onShowEditorDialog();
|
||||||
} else {
|
} else {
|
||||||
NiceModal.show('project-editor-selection', {
|
ProjectEditorSelectionDialog.show({
|
||||||
selectedProject: project,
|
selectedProject: project,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,41 @@
|
|||||||
import NiceModal from '@ebay/nice-modal-react';
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
import type {
|
import type React from 'react';
|
||||||
FolderPickerDialogProps,
|
import type { NiceModalHocProps } from '@ebay/nice-modal-react';
|
||||||
TagEditDialogProps,
|
|
||||||
TagEditResult,
|
|
||||||
ProjectFormDialogProps,
|
|
||||||
ProjectFormDialogResult,
|
|
||||||
LinkProjectResult,
|
|
||||||
} from '@/components/dialogs';
|
|
||||||
|
|
||||||
/**
|
// Use this instead of {} to avoid ban-types
|
||||||
* Typed wrapper around NiceModal.show with better TypeScript support
|
export type NoProps = Record<string, never>;
|
||||||
* @param modal - Modal ID (string) or component reference
|
|
||||||
* @param props - Props to pass to the modal
|
// Map P for component props: void -> NoProps; otherwise P
|
||||||
* @returns Promise that resolves with the modal's result
|
type ComponentProps<P> = [P] extends [void] ? NoProps : P;
|
||||||
*/
|
|
||||||
export function showModal<T = void>(
|
// Map P for .show() args: void -> []; otherwise [props: P]
|
||||||
modal: string,
|
type ShowArgs<P> = [P] extends [void] ? [] : [props: P];
|
||||||
props: Record<string, unknown> = {}
|
|
||||||
): Promise<T> {
|
// Modalized component with static show/hide/remove methods
|
||||||
return NiceModal.show<T>(modal, props) as Promise<T>;
|
export type Modalized<P, R> = React.ComponentType<ComponentProps<P>> & {
|
||||||
|
__modalResult?: R;
|
||||||
|
show: (...args: ShowArgs<P>) => Promise<R>;
|
||||||
|
hide: () => void;
|
||||||
|
remove: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function defineModal<P, R>(
|
||||||
|
component: React.ComponentType<ComponentProps<P> & NiceModalHocProps>
|
||||||
|
): Modalized<P, R> {
|
||||||
|
const c = component as unknown as Modalized<P, R>;
|
||||||
|
c.show = ((...args: any[]) =>
|
||||||
|
NiceModal.show(component as any, args[0])) as Modalized<P, R>['show'];
|
||||||
|
c.hide = () => NiceModal.hide(component as any);
|
||||||
|
c.remove = () => NiceModal.remove(component as any);
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Common modal result types for standardization
|
||||||
* Show folder picker dialog
|
|
||||||
* @param props - Props for folder picker
|
|
||||||
* @returns Promise that resolves with selected path or null if cancelled
|
|
||||||
*/
|
|
||||||
export function showFolderPicker(
|
|
||||||
props: FolderPickerDialogProps = {}
|
|
||||||
): Promise<string | null> {
|
|
||||||
return showModal<string | null>(
|
|
||||||
'folder-picker',
|
|
||||||
props as Record<string, unknown>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show task tag edit dialog
|
|
||||||
* @param props - Props for tag edit dialog
|
|
||||||
* @returns Promise that resolves with 'saved' or 'canceled'
|
|
||||||
*/
|
|
||||||
export function showTagEdit(props: TagEditDialogProps): Promise<TagEditResult> {
|
|
||||||
return showModal<TagEditResult>('tag-edit', props as Record<string, unknown>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show project form dialog
|
|
||||||
* @param props - Props for project form dialog
|
|
||||||
* @returns Promise that resolves with 'saved' or 'canceled'
|
|
||||||
*/
|
|
||||||
export function showProjectForm(
|
|
||||||
props: ProjectFormDialogProps = {}
|
|
||||||
): Promise<ProjectFormDialogResult> {
|
|
||||||
return showModal<ProjectFormDialogResult>(
|
|
||||||
'project-form',
|
|
||||||
props as Record<string, unknown>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show link project dialog
|
|
||||||
* @param props - Props for link project dialog (projectId and projectName)
|
|
||||||
* @returns Promise that resolves with link result
|
|
||||||
*/
|
|
||||||
export function showLinkProject(props: {
|
|
||||||
projectId: string;
|
|
||||||
projectName: string;
|
|
||||||
}): Promise<LinkProjectResult> {
|
|
||||||
return showModal<LinkProjectResult>(
|
|
||||||
'link-project',
|
|
||||||
props as Record<string, unknown>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide a modal by ID
|
|
||||||
*/
|
|
||||||
export function hideModal(modal: string): void {
|
|
||||||
NiceModal.hide(modal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a modal by ID
|
|
||||||
*/
|
|
||||||
export function removeModal(modal: string): void {
|
|
||||||
NiceModal.remove(modal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide all currently visible modals
|
|
||||||
*/
|
|
||||||
export function hideAllModals(): void {
|
|
||||||
// NiceModal doesn't have a direct hideAll, so we'll implement as needed
|
|
||||||
console.log('Hide all modals - implement as needed');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common modal result types for standardization
|
|
||||||
*/
|
|
||||||
export type ConfirmResult = 'confirmed' | 'canceled';
|
export type ConfirmResult = 'confirmed' | 'canceled';
|
||||||
export type DeleteResult = 'deleted' | 'canceled';
|
export type DeleteResult = 'deleted' | 'canceled';
|
||||||
export type SaveResult = 'saved' | 'canceled';
|
export type SaveResult = 'saved' | 'canceled';
|
||||||
|
|
||||||
/**
|
// Error handling utility for modal operations
|
||||||
* Error handling utility for modal operations
|
|
||||||
*/
|
|
||||||
export function getErrorMessage(error: unknown): string {
|
export function getErrorMessage(error: unknown): string {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return error.message;
|
return error.message;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { TaskFormDialog } from '@/components/dialogs/tasks/TaskFormDialog';
|
||||||
import type { TaskFormDialogProps } from '@/components/dialogs/tasks/TaskFormDialog';
|
import type { TaskFormDialogProps } from '@/components/dialogs/tasks/TaskFormDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6,5 +6,5 @@ import type { TaskFormDialogProps } from '@/components/dialogs/tasks/TaskFormDia
|
|||||||
* This replaces the previous TaskFormDialogContainer pattern
|
* This replaces the previous TaskFormDialogContainer pattern
|
||||||
*/
|
*/
|
||||||
export function openTaskForm(props: TaskFormDialogProps) {
|
export function openTaskForm(props: TaskFormDialogProps) {
|
||||||
return NiceModal.show('task-form', props);
|
return TaskFormDialog.show(props);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,71 +6,11 @@ import { ClickToComponent } from 'click-to-react-component';
|
|||||||
import { VibeKanbanWebCompanion } from 'vibe-kanban-web-companion';
|
import { VibeKanbanWebCompanion } from 'vibe-kanban-web-companion';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
|
||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
import posthog from 'posthog-js';
|
import posthog from 'posthog-js';
|
||||||
import { PostHogProvider } from 'posthog-js/react';
|
import { PostHogProvider } from 'posthog-js/react';
|
||||||
// Import modal type definitions
|
// Import modal type definitions
|
||||||
import './types/modals';
|
import './types/modals';
|
||||||
// Import and register modals
|
|
||||||
import {
|
|
||||||
CreatePRDialog,
|
|
||||||
ConfirmDialog,
|
|
||||||
DisclaimerDialog,
|
|
||||||
OnboardingDialog,
|
|
||||||
ReleaseNotesDialog,
|
|
||||||
OAuthDialog,
|
|
||||||
TaskFormDialog,
|
|
||||||
EditorSelectionDialog,
|
|
||||||
DeleteTaskConfirmationDialog,
|
|
||||||
FolderPickerDialog,
|
|
||||||
TagEditDialog,
|
|
||||||
ChangeTargetBranchDialog,
|
|
||||||
RebaseDialog,
|
|
||||||
CreateConfigurationDialog,
|
|
||||||
DeleteConfigurationDialog,
|
|
||||||
ProjectFormDialog,
|
|
||||||
ProjectEditorSelectionDialog,
|
|
||||||
RestoreLogsDialog,
|
|
||||||
ViewProcessesDialog,
|
|
||||||
GitActionsDialog,
|
|
||||||
ShareDialog,
|
|
||||||
ReassignDialog,
|
|
||||||
StopShareTaskDialog,
|
|
||||||
CreateOrganizationDialog,
|
|
||||||
LinkProjectDialog,
|
|
||||||
} from './components/dialogs';
|
|
||||||
import { CreateAttemptDialog } from './components/dialogs/tasks/CreateAttemptDialog';
|
|
||||||
import { EditBranchNameDialog } from './components/dialogs/tasks/EditBranchNameDialog';
|
|
||||||
|
|
||||||
// Register modals
|
|
||||||
NiceModal.register('create-pr', CreatePRDialog);
|
|
||||||
NiceModal.register('confirm', ConfirmDialog);
|
|
||||||
NiceModal.register('disclaimer', DisclaimerDialog);
|
|
||||||
NiceModal.register('onboarding', OnboardingDialog);
|
|
||||||
NiceModal.register('release-notes', ReleaseNotesDialog);
|
|
||||||
NiceModal.register('oauth', OAuthDialog);
|
|
||||||
NiceModal.register('delete-task-confirmation', DeleteTaskConfirmationDialog);
|
|
||||||
NiceModal.register('task-form', TaskFormDialog);
|
|
||||||
NiceModal.register('editor-selection', EditorSelectionDialog);
|
|
||||||
NiceModal.register('folder-picker', FolderPickerDialog);
|
|
||||||
NiceModal.register('tag-edit', TagEditDialog);
|
|
||||||
NiceModal.register('change-target-branch-dialog', ChangeTargetBranchDialog);
|
|
||||||
NiceModal.register('rebase-dialog', RebaseDialog);
|
|
||||||
NiceModal.register('create-configuration', CreateConfigurationDialog);
|
|
||||||
NiceModal.register('delete-configuration', DeleteConfigurationDialog);
|
|
||||||
NiceModal.register('project-form', ProjectFormDialog);
|
|
||||||
NiceModal.register('project-editor-selection', ProjectEditorSelectionDialog);
|
|
||||||
NiceModal.register('restore-logs', RestoreLogsDialog);
|
|
||||||
NiceModal.register('view-processes', ViewProcessesDialog);
|
|
||||||
NiceModal.register('create-attempt', CreateAttemptDialog);
|
|
||||||
NiceModal.register('git-actions', GitActionsDialog);
|
|
||||||
NiceModal.register('edit-branch-name-dialog', EditBranchNameDialog);
|
|
||||||
NiceModal.register('share-task', ShareDialog);
|
|
||||||
NiceModal.register('reassign-shared-task', ReassignDialog);
|
|
||||||
NiceModal.register('stop-share-shared-task', StopShareTaskDialog);
|
|
||||||
NiceModal.register('create-organization', CreateOrganizationDialog);
|
|
||||||
NiceModal.register('link-project', LinkProjectDialog);
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useLocation,
|
useLocation,
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import { Loader2 } from 'lucide-react';
|
|||||||
import { ExecutorConfigForm } from '@/components/ExecutorConfigForm';
|
import { ExecutorConfigForm } from '@/components/ExecutorConfigForm';
|
||||||
import { useProfiles } from '@/hooks/useProfiles';
|
import { useProfiles } from '@/hooks/useProfiles';
|
||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import { showModal } from '@/lib/modals';
|
import { CreateConfigurationDialog } from '@/components/dialogs/settings/CreateConfigurationDialog';
|
||||||
|
import { DeleteConfigurationDialog } from '@/components/dialogs/settings/DeleteConfigurationDialog';
|
||||||
|
|
||||||
export function AgentSettings() {
|
export function AgentSettings() {
|
||||||
const { t } = useTranslation('settings');
|
const { t } = useTranslation('settings');
|
||||||
@@ -84,11 +85,7 @@ export function AgentSettings() {
|
|||||||
// Open create dialog
|
// Open create dialog
|
||||||
const openCreateDialog = async () => {
|
const openCreateDialog = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await showModal<{
|
const result = await CreateConfigurationDialog.show({
|
||||||
action: 'created' | 'canceled';
|
|
||||||
configName?: string;
|
|
||||||
cloneFrom?: string | null;
|
|
||||||
}>('create-configuration', {
|
|
||||||
executorType: selectedExecutorType,
|
executorType: selectedExecutorType,
|
||||||
existingConfigs: Object.keys(
|
existingConfigs: Object.keys(
|
||||||
localParsedProfiles?.executors?.[selectedExecutorType] || {}
|
localParsedProfiles?.executors?.[selectedExecutorType] || {}
|
||||||
@@ -141,13 +138,10 @@ export function AgentSettings() {
|
|||||||
// Open delete dialog
|
// Open delete dialog
|
||||||
const openDeleteDialog = async (configName: string) => {
|
const openDeleteDialog = async (configName: string) => {
|
||||||
try {
|
try {
|
||||||
const result = await showModal<'deleted' | 'canceled'>(
|
const result = await DeleteConfigurationDialog.show({
|
||||||
'delete-configuration',
|
configName,
|
||||||
{
|
executorType: selectedExecutorType,
|
||||||
configName,
|
});
|
||||||
executorType: selectedExecutorType,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result === 'deleted') {
|
if (result === 'deleted') {
|
||||||
await handleDeleteConfiguration(configName);
|
await handleDeleteConfiguration(configName);
|
||||||
|
|||||||
@@ -25,15 +25,12 @@ import { useOrganizationMutations } from '@/hooks/useOrganizationMutations';
|
|||||||
import { useUserSystem } from '@/components/config-provider';
|
import { useUserSystem } from '@/components/config-provider';
|
||||||
import { useAuth } from '@/hooks/auth/useAuth';
|
import { useAuth } from '@/hooks/auth/useAuth';
|
||||||
import { LoginRequiredPrompt } from '@/components/dialogs/shared/LoginRequiredPrompt';
|
import { LoginRequiredPrompt } from '@/components/dialogs/shared/LoginRequiredPrompt';
|
||||||
import NiceModal from '@ebay/nice-modal-react';
|
import { CreateOrganizationDialog } from '@/components/dialogs/org/CreateOrganizationDialog';
|
||||||
import {
|
import { InviteMemberDialog } from '@/components/dialogs/org/InviteMemberDialog';
|
||||||
InviteMemberDialog,
|
import type {
|
||||||
type InviteMemberResult,
|
InviteMemberResult,
|
||||||
} from '@/components/dialogs/org/InviteMemberDialog';
|
CreateOrganizationResult,
|
||||||
import {
|
} from '@/components/dialogs';
|
||||||
CreateOrganizationDialog,
|
|
||||||
type CreateOrganizationResult,
|
|
||||||
} from '@/components/dialogs/org/CreateOrganizationDialog';
|
|
||||||
import { MemberListItem } from '@/components/org/MemberListItem';
|
import { MemberListItem } from '@/components/org/MemberListItem';
|
||||||
import { PendingInvitationItem } from '@/components/org/PendingInvitationItem';
|
import { PendingInvitationItem } from '@/components/org/PendingInvitationItem';
|
||||||
import { RemoteProjectItem } from '@/components/org/RemoteProjectItem';
|
import { RemoteProjectItem } from '@/components/org/RemoteProjectItem';
|
||||||
@@ -176,9 +173,8 @@ export function OrganizationSettings() {
|
|||||||
|
|
||||||
const handleCreateOrganization = async () => {
|
const handleCreateOrganization = async () => {
|
||||||
try {
|
try {
|
||||||
const result: CreateOrganizationResult = await NiceModal.show(
|
const result: CreateOrganizationResult =
|
||||||
CreateOrganizationDialog
|
await CreateOrganizationDialog.show();
|
||||||
);
|
|
||||||
|
|
||||||
if (result.action === 'created' && result.organizationId) {
|
if (result.action === 'created' && result.organizationId) {
|
||||||
// No need to refetch - the mutation hook handles cache invalidation
|
// No need to refetch - the mutation hook handles cache invalidation
|
||||||
@@ -195,10 +191,9 @@ export function OrganizationSettings() {
|
|||||||
if (!selectedOrgId) return;
|
if (!selectedOrgId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: InviteMemberResult = await NiceModal.show(
|
const result: InviteMemberResult = await InviteMemberDialog.show({
|
||||||
InviteMemberDialog,
|
organizationId: selectedOrgId,
|
||||||
{ organizationId: selectedOrgId }
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (result.action === 'invited') {
|
if (result.action === 'invited') {
|
||||||
// No need to refetch - the mutation hook handles cache invalidation
|
// No need to refetch - the mutation hook handles cache invalidation
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { useProjectMutations } from '@/hooks/useProjectMutations';
|
|||||||
import { useScriptPlaceholders } from '@/hooks/useScriptPlaceholders';
|
import { useScriptPlaceholders } from '@/hooks/useScriptPlaceholders';
|
||||||
import { CopyFilesField } from '@/components/projects/copy-files-field';
|
import { CopyFilesField } from '@/components/projects/copy-files-field';
|
||||||
import { AutoExpandingTextarea } from '@/components/ui/auto-expanding-textarea';
|
import { AutoExpandingTextarea } from '@/components/ui/auto-expanding-textarea';
|
||||||
import { showFolderPicker } from '@/lib/modals';
|
import { FolderPickerDialog } from '@/components/dialogs/shared/FolderPickerDialog';
|
||||||
import type { Project, UpdateProject } from 'shared/types';
|
import type { Project, UpdateProject } from 'shared/types';
|
||||||
|
|
||||||
interface ProjectFormState {
|
interface ProjectFormState {
|
||||||
@@ -369,7 +369,7 @@ export function ProjectSettings() {
|
|||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const selectedPath = await showFolderPicker({
|
const selectedPath = await FolderPickerDialog.show({
|
||||||
title: 'Select Git Repository',
|
title: 'Select Git Repository',
|
||||||
description: 'Choose an existing git repository',
|
description: 'Choose an existing git repository',
|
||||||
value: draft.git_repo_path,
|
value: draft.git_repo_path,
|
||||||
|
|||||||
Reference in New Issue
Block a user