The diff appears large due to Prettier formatting, but actual code changes are minimal. - Made header sticky with proper flex layout - Added responsive text sizes (`text-xs sm:text-sm`) - Improved text wrapping (`break-words overflow-wrap-anywhere`) - Made tab labels responsive (shorter for small screen) - Added `flex-shrink-0` to prevent icon/button squishing - Stack footer buttons vertically on small screen closes #1746 <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Made the Import App dialog responsive on small screens. Improves readability and prevents controls from squishing. - **Bug Fixes** - Added a sticky header so the title/description stay visible while scrolling. - Made text and tab labels responsive, with better word wrapping to avoid overflow. - Prevented icon/button compression and stacked footer buttons vertically on mobile. - Updated e2e test by removing the AI_RULES snapshot to match the new UI. <sup>Written for commit 1025631018964aea37689ab2196e0169755e3739. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
committed by
GitHub
parent
39876f114a
commit
76875fefec
@@ -102,9 +102,6 @@ test("should import from repository list", async ({ po }) => {
|
|||||||
await expect(
|
await expect(
|
||||||
po.page.getByRole("heading", { name: "Import App" }),
|
po.page.getByRole("heading", { name: "Import App" }),
|
||||||
).not.toBeVisible();
|
).not.toBeVisible();
|
||||||
|
|
||||||
// Verify AI_RULES generation prompt
|
|
||||||
await po.snapshotMessages();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should support advanced options with custom commands", async ({ po }) => {
|
test("should support advanced options with custom commands", async ({ po }) => {
|
||||||
|
|||||||
@@ -298,383 +298,429 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
|
|||||||
const hasInstallCommand = installCommand.trim().length > 0;
|
const hasInstallCommand = installCommand.trim().length > 0;
|
||||||
const hasStartCommand = startCommand.trim().length > 0;
|
const hasStartCommand = startCommand.trim().length > 0;
|
||||||
const commandsValid = hasInstallCommand === hasStartCommand;
|
const commandsValid = hasInstallCommand === hasStartCommand;
|
||||||
// Add this component inside the ImportAppDialog.tsx file, before the main component
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
<DialogContent className="max-w-2xl max-h-[98vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl w-[calc(100vw-2rem)] max-h-[98vh] overflow-y-auto flex flex-col p-0">
|
||||||
<DialogHeader>
|
<DialogHeader className="sticky top-0 bg-background border-b px-6 py-4">
|
||||||
<DialogTitle>Import App</DialogTitle>
|
<DialogTitle>Import App</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription className="text-sm">
|
||||||
Import existing app from local folder or clone from Github.
|
Import existing app from local folder or clone from Github.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
<div className="px-6 pb-6 overflow-y-auto flex-1">
|
||||||
<Alert className="border-blue-500/20 text-blue-500">
|
<Alert className="border-blue-500/20 text-blue-500 mb-2">
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4 flex-shrink-0" />
|
||||||
<AlertDescription>
|
<AlertDescription className="text-xs sm:text-sm">
|
||||||
App import is an experimental feature. If you encounter any issues,
|
App import is an experimental feature. If you encounter any
|
||||||
please report them using the Help button.
|
issues, please report them using the Help button.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Tabs defaultValue="local-folder" className="w-full">
|
<Tabs defaultValue="local-folder" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3 h-auto">
|
||||||
<TabsTrigger value="local-folder">Local Folder</TabsTrigger>
|
<TabsTrigger
|
||||||
<TabsTrigger value="github-repos">Your GitHub Repos</TabsTrigger>
|
value="local-folder"
|
||||||
<TabsTrigger value="github-url">GitHub URL</TabsTrigger>
|
className="text-xs sm:text-sm px-2 py-2"
|
||||||
</TabsList>
|
>
|
||||||
<TabsContent value="local-folder" className="space-y-4">
|
Local Folder
|
||||||
<div className="py-4">
|
</TabsTrigger>
|
||||||
{!selectedPath ? (
|
<TabsTrigger
|
||||||
<Button
|
value="github-repos"
|
||||||
onClick={handleSelectFolder}
|
className="text-xs sm:text-sm px-2 py-2"
|
||||||
disabled={selectFolderMutation.isPending}
|
>
|
||||||
className="w-full"
|
<span className="hidden sm:inline">Your GitHub Repos</span>
|
||||||
>
|
<span className="sm:hidden">GitHub Repos</span>
|
||||||
{selectFolderMutation.isPending ? (
|
</TabsTrigger>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<TabsTrigger
|
||||||
) : (
|
value="github-url"
|
||||||
<Folder className="mr-2 h-4 w-4" />
|
className="text-xs sm:text-sm px-2 py-2"
|
||||||
)}
|
>
|
||||||
{selectFolderMutation.isPending
|
GitHub URL
|
||||||
? "Selecting folder..."
|
</TabsTrigger>
|
||||||
: "Select Folder"}
|
</TabsList>
|
||||||
</Button>
|
<TabsContent value="local-folder" className="space-y-4">
|
||||||
) : (
|
<div className="py-4">
|
||||||
<div className="space-y-4">
|
{!selectedPath ? (
|
||||||
<div className="rounded-md border p-4">
|
<Button
|
||||||
<div className="flex items-start justify-between gap-2">
|
onClick={handleSelectFolder}
|
||||||
<div className="min-w-0 flex-1">
|
disabled={selectFolderMutation.isPending}
|
||||||
<p className="text-sm font-medium">Selected folder:</p>
|
className="w-full"
|
||||||
<p className="text-sm text-muted-foreground break-all">
|
>
|
||||||
{selectedPath}
|
{selectFolderMutation.isPending ? (
|
||||||
</p>
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
</div>
|
) : (
|
||||||
<Button
|
<Folder className="mr-2 h-4 w-4" />
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleClear}
|
|
||||||
className="h-8 w-8 p-0 flex-shrink-0"
|
|
||||||
disabled={importAppMutation.isPending}
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Clear selection</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{nameExists && (
|
|
||||||
<p className="text-sm text-yellow-500">
|
|
||||||
An app with this name already exists. Please choose a
|
|
||||||
different name:
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
<div className="relative">
|
{selectFolderMutation.isPending
|
||||||
<Label className="text-sm ml-2 mb-2">App name</Label>
|
? "Selecting folder..."
|
||||||
<Input
|
: "Select Folder"}
|
||||||
value={customAppName}
|
</Button>
|
||||||
onChange={handleAppNameChange}
|
) : (
|
||||||
placeholder="Enter new app name"
|
<div className="space-y-4">
|
||||||
className="w-full pr-8"
|
<div className="rounded-md border p-3 sm:p-4">
|
||||||
disabled={importAppMutation.isPending}
|
<div className="flex items-start justify-between gap-2">
|
||||||
/>
|
<div className="min-w-0 flex-1 overflow-hidden">
|
||||||
{isCheckingName && (
|
<p className="text-sm font-medium mb-1">
|
||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
Selected folder:
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Accordion type="single" collapsible>
|
|
||||||
<AccordionItem value="advanced-options">
|
|
||||||
<AccordionTrigger className="text-sm hover:no-underline">
|
|
||||||
Advanced options
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="space-y-4">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label className="text-sm ml-2 mb-2">
|
|
||||||
Install command
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={installCommand}
|
|
||||||
onChange={(e) => setInstallCommand(e.target.value)}
|
|
||||||
placeholder="pnpm install"
|
|
||||||
disabled={importAppMutation.isPending}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label className="text-sm ml-2 mb-2">
|
|
||||||
Start command
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={startCommand}
|
|
||||||
onChange={(e) => setStartCommand(e.target.value)}
|
|
||||||
placeholder="pnpm dev"
|
|
||||||
disabled={importAppMutation.isPending}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{!commandsValid && (
|
|
||||||
<p className="text-sm text-red-500">
|
|
||||||
Both commands are required when customizing.
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
<p className="text-xs sm:text-sm text-muted-foreground break-words">
|
||||||
</AccordionContent>
|
{selectedPath}
|
||||||
</AccordionItem>
|
</p>
|
||||||
</Accordion>
|
</div>
|
||||||
|
<Button
|
||||||
{hasAiRules === false && (
|
variant="ghost"
|
||||||
<Alert className="border-yellow-500/20 text-yellow-500 flex items-start gap-2">
|
size="sm"
|
||||||
<TooltipProvider>
|
onClick={handleClear}
|
||||||
<Tooltip>
|
className="h-8 w-8 p-0 flex-shrink-0"
|
||||||
<TooltipTrigger asChild>
|
disabled={importAppMutation.isPending}
|
||||||
<Info className="h-4 w-4 flex-shrink-0 mt-1" />
|
>
|
||||||
</TooltipTrigger>
|
<X className="h-4 w-4" />
|
||||||
<TooltipContent>
|
<span className="sr-only">Clear selection</span>
|
||||||
<p>
|
</Button>
|
||||||
AI_RULES.md lets Dyad know which tech stack to use
|
</div>
|
||||||
for editing the app
|
</div>
|
||||||
</p>
|
|
||||||
</TooltipContent>
|
<div className="space-y-2">
|
||||||
</Tooltip>
|
{nameExists && (
|
||||||
</TooltipProvider>
|
<p className="text-xs sm:text-sm text-yellow-500">
|
||||||
<AlertDescription>
|
An app with this name already exists. Please choose a
|
||||||
No AI_RULES.md found. Dyad will automatically generate
|
different name:
|
||||||
one after importing.
|
</p>
|
||||||
</AlertDescription>
|
)}
|
||||||
</Alert>
|
<div className="relative">
|
||||||
)}
|
<Label className="text-xs sm:text-sm ml-2 mb-2">
|
||||||
|
App name
|
||||||
{importAppMutation.isPending && (
|
</Label>
|
||||||
<div className="flex items-center justify-center space-x-2 text-sm text-muted-foreground animate-pulse">
|
<Input
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
value={customAppName}
|
||||||
<span>Importing app...</span>
|
onChange={handleAppNameChange}
|
||||||
</div>
|
placeholder="Enter new app name"
|
||||||
)}
|
className="w-full pr-8 text-sm"
|
||||||
</div>
|
disabled={importAppMutation.isPending}
|
||||||
)}
|
/>
|
||||||
</div>
|
{isCheckingName && (
|
||||||
|
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
||||||
<DialogFooter>
|
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||||
<Button
|
</div>
|
||||||
variant="outline"
|
)}
|
||||||
onClick={onClose}
|
|
||||||
disabled={importAppMutation.isPending}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleImport}
|
|
||||||
disabled={
|
|
||||||
!selectedPath ||
|
|
||||||
importAppMutation.isPending ||
|
|
||||||
nameExists ||
|
|
||||||
!commandsValid
|
|
||||||
}
|
|
||||||
className="min-w-[80px]"
|
|
||||||
>
|
|
||||||
{importAppMutation.isPending ? <>Importing...</> : "Import"}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="github-repos" className="space-y-4">
|
|
||||||
{!isAuthenticated ? (
|
|
||||||
<UnconnectedGitHubConnector
|
|
||||||
appId={null}
|
|
||||||
folderName=""
|
|
||||||
settings={settings}
|
|
||||||
refreshSettings={refreshSettings}
|
|
||||||
handleRepoSetupComplete={() => undefined}
|
|
||||||
expanded={false}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{loading && (
|
|
||||||
<div className="flex justify-center py-8">
|
|
||||||
<Loader2 className="animate-spin h-6 w-6" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-sm ml-2 mb-2">
|
|
||||||
App name (optional)
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
value={githubAppName}
|
|
||||||
onChange={handleGithubAppNameChange}
|
|
||||||
placeholder="Leave empty to use repository name"
|
|
||||||
className="w-full pr-8"
|
|
||||||
disabled={importing}
|
|
||||||
/>
|
|
||||||
{isCheckingGithubName && (
|
|
||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{githubNameExists && (
|
|
||||||
<p className="text-sm text-yellow-500">
|
|
||||||
An app with this name already exists. Please choose a
|
|
||||||
different name.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col space-y-2 max-h-64 overflow-y-auto">
|
|
||||||
{!loading && repos.length === 0 && (
|
|
||||||
<p className="text-sm text-muted-foreground text-center py-4">
|
|
||||||
No repositories found
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{repos.map((repo) => (
|
|
||||||
<div
|
|
||||||
key={repo.full_name}
|
|
||||||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-accent/50 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<p className="font-semibold truncate">{repo.name}</p>
|
|
||||||
<p className="text-sm text-muted-foreground truncate">
|
|
||||||
{repo.full_name}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleSelectRepo(repo)}
|
|
||||||
disabled={importing}
|
|
||||||
className="ml-2 flex-shrink-0"
|
|
||||||
>
|
|
||||||
{importing ? (
|
|
||||||
<Loader2 className="animate-spin h-4 w-4" />
|
|
||||||
) : (
|
|
||||||
"Import"
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{repos.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Accordion type="single" collapsible>
|
<Accordion type="single" collapsible>
|
||||||
<AccordionItem value="advanced-options">
|
<AccordionItem value="advanced-options">
|
||||||
<AccordionTrigger className="text-sm hover:no-underline">
|
<AccordionTrigger className="text-xs sm:text-sm hover:no-underline">
|
||||||
Advanced options
|
Advanced options
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="space-y-4">
|
<AccordionContent className="space-y-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label className="text-sm">Install command</Label>
|
<Label className="text-xs sm:text-sm ml-2 mb-2">
|
||||||
|
Install command
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
value={installCommand}
|
value={installCommand}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setInstallCommand(e.target.value)
|
setInstallCommand(e.target.value)
|
||||||
}
|
}
|
||||||
placeholder="pnpm install"
|
placeholder="pnpm install"
|
||||||
disabled={importing}
|
className="text-sm"
|
||||||
|
disabled={importAppMutation.isPending}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label className="text-sm">Start command</Label>
|
<Label className="text-xs sm:text-sm ml-2 mb-2">
|
||||||
|
Start command
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
value={startCommand}
|
value={startCommand}
|
||||||
onChange={(e) => setStartCommand(e.target.value)}
|
onChange={(e) => setStartCommand(e.target.value)}
|
||||||
placeholder="pnpm dev"
|
placeholder="pnpm dev"
|
||||||
disabled={importing}
|
className="text-sm"
|
||||||
|
disabled={importAppMutation.isPending}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!commandsValid && (
|
{!commandsValid && (
|
||||||
<p className="text-sm text-red-500">
|
<p className="text-xs sm:text-sm text-red-500">
|
||||||
Both commands are required when customizing.
|
Both commands are required when customizing.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</>
|
|
||||||
|
{hasAiRules === false && (
|
||||||
|
<Alert className="border-yellow-500/20 text-yellow-500 flex items-start gap-2">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Info className="h-4 w-4 flex-shrink-0 mt-1" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p className="text-xs">
|
||||||
|
AI_RULES.md lets Dyad know which tech stack to
|
||||||
|
use for editing the app
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<AlertDescription className="text-xs sm:text-sm">
|
||||||
|
No AI_RULES.md found. Dyad will automatically generate
|
||||||
|
one after importing.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{importAppMutation.isPending && (
|
||||||
|
<div className="flex items-center justify-center space-x-2 text-xs sm:text-sm text-muted-foreground animate-pulse">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
<span>Importing app...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="github-url" className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-sm">Repository URL</Label>
|
|
||||||
<Input
|
|
||||||
placeholder="https://github.com/user/repo.git"
|
|
||||||
value={url}
|
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
|
||||||
disabled={importing}
|
|
||||||
onBlur={handleUrlBlur}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-sm">App name (optional)</Label>
|
|
||||||
<Input
|
|
||||||
value={githubAppName}
|
|
||||||
onChange={handleGithubAppNameChange}
|
|
||||||
placeholder="Leave empty to use repository name"
|
|
||||||
disabled={importing}
|
|
||||||
/>
|
|
||||||
{isCheckingGithubName && (
|
|
||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{githubNameExists && (
|
|
||||||
<p className="text-sm text-yellow-500">
|
|
||||||
An app with this name already exists. Please choose a
|
|
||||||
different name.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Accordion type="single" collapsible>
|
<DialogFooter className="flex-col sm:flex-row gap-2">
|
||||||
<AccordionItem value="advanced-options">
|
<Button
|
||||||
<AccordionTrigger className="text-sm hover:no-underline">
|
variant="outline"
|
||||||
Advanced options
|
onClick={onClose}
|
||||||
</AccordionTrigger>
|
disabled={importAppMutation.isPending}
|
||||||
<AccordionContent className="space-y-4">
|
className="w-full sm:w-auto"
|
||||||
<div className="grid gap-2">
|
>
|
||||||
<Label className="text-sm">Install command</Label>
|
Cancel
|
||||||
<Input
|
</Button>
|
||||||
value={installCommand}
|
<Button
|
||||||
onChange={(e) => setInstallCommand(e.target.value)}
|
onClick={handleImport}
|
||||||
placeholder="pnpm install"
|
disabled={
|
||||||
disabled={importing}
|
!selectedPath ||
|
||||||
/>
|
importAppMutation.isPending ||
|
||||||
</div>
|
nameExists ||
|
||||||
<div className="grid gap-2">
|
!commandsValid
|
||||||
<Label className="text-sm">Start command</Label>
|
}
|
||||||
<Input
|
className="w-full sm:w-auto min-w-[80px]"
|
||||||
value={startCommand}
|
>
|
||||||
onChange={(e) => setStartCommand(e.target.value)}
|
{importAppMutation.isPending ? <>Importing...</> : "Import"}
|
||||||
placeholder="pnpm dev"
|
</Button>
|
||||||
disabled={importing}
|
</DialogFooter>
|
||||||
/>
|
</TabsContent>
|
||||||
</div>
|
<TabsContent value="github-repos" className="space-y-4">
|
||||||
{!commandsValid && (
|
{!isAuthenticated ? (
|
||||||
<p className="text-sm text-red-500">
|
<UnconnectedGitHubConnector
|
||||||
Both commands are required when customizing.
|
appId={null}
|
||||||
</p>
|
folderName=""
|
||||||
)}
|
settings={settings}
|
||||||
</AccordionContent>
|
refreshSettings={refreshSettings}
|
||||||
</AccordionItem>
|
handleRepoSetupComplete={() => undefined}
|
||||||
</Accordion>
|
expanded={false}
|
||||||
|
/>
|
||||||
<Button
|
|
||||||
onClick={handleImportFromUrl}
|
|
||||||
disabled={importing || !url.trim() || !commandsValid}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{importing ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="animate-spin mr-2 h-4 w-4" />
|
|
||||||
Importing...
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
"Import"
|
<>
|
||||||
|
{loading && (
|
||||||
|
<div className="flex justify-center py-8">
|
||||||
|
<Loader2 className="animate-spin h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs sm:text-sm ml-2 mb-2">
|
||||||
|
App name (optional)
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
value={githubAppName}
|
||||||
|
onChange={handleGithubAppNameChange}
|
||||||
|
placeholder="Leave empty to use repository name"
|
||||||
|
className="w-full pr-8 text-sm"
|
||||||
|
disabled={importing}
|
||||||
|
/>
|
||||||
|
{isCheckingGithubName && (
|
||||||
|
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{githubNameExists && (
|
||||||
|
<p className="text-xs sm:text-sm text-yellow-500">
|
||||||
|
An app with this name already exists. Please choose a
|
||||||
|
different name.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col space-y-2 max-h-64 overflow-y-auto overflow-x-hidden">
|
||||||
|
{!loading && repos.length === 0 && (
|
||||||
|
<p className="text-xs sm:text-sm text-muted-foreground text-center py-4">
|
||||||
|
No repositories found
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{repos.map((repo) => (
|
||||||
|
<div
|
||||||
|
key={repo.full_name}
|
||||||
|
className="flex items-center justify-between p-3 border rounded-lg hover:bg-accent/50 transition-colors min-w-0"
|
||||||
|
>
|
||||||
|
<div className="min-w-0 flex-1 overflow-hidden mr-2">
|
||||||
|
<p className="font-semibold truncate text-sm">
|
||||||
|
{repo.name}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground truncate">
|
||||||
|
{repo.full_name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleSelectRepo(repo)}
|
||||||
|
disabled={importing}
|
||||||
|
className="flex-shrink-0 text-xs"
|
||||||
|
>
|
||||||
|
{importing ? (
|
||||||
|
<Loader2 className="animate-spin h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
"Import"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{repos.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="advanced-options">
|
||||||
|
<AccordionTrigger className="text-xs sm:text-sm hover:no-underline">
|
||||||
|
Advanced options
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="space-y-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label className="text-xs sm:text-sm">
|
||||||
|
Install command
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
value={installCommand}
|
||||||
|
onChange={(e) =>
|
||||||
|
setInstallCommand(e.target.value)
|
||||||
|
}
|
||||||
|
placeholder="pnpm install"
|
||||||
|
className="text-sm"
|
||||||
|
disabled={importing}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label className="text-xs sm:text-sm">
|
||||||
|
Start command
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
value={startCommand}
|
||||||
|
onChange={(e) =>
|
||||||
|
setStartCommand(e.target.value)
|
||||||
|
}
|
||||||
|
placeholder="pnpm dev"
|
||||||
|
className="text-sm"
|
||||||
|
disabled={importing}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!commandsValid && (
|
||||||
|
<p className="text-xs sm:text-sm text-red-500">
|
||||||
|
Both commands are required when customizing.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</TabsContent>
|
||||||
</TabsContent>
|
<TabsContent value="github-url" className="space-y-4">
|
||||||
</Tabs>
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs sm:text-sm">Repository URL</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="https://github.com/user/repo.git"
|
||||||
|
value={url}
|
||||||
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
|
disabled={importing}
|
||||||
|
onBlur={handleUrlBlur}
|
||||||
|
className="text-sm break-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs sm:text-sm">
|
||||||
|
App name (optional)
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
value={githubAppName}
|
||||||
|
onChange={handleGithubAppNameChange}
|
||||||
|
placeholder="Leave empty to use repository name"
|
||||||
|
disabled={importing}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
{isCheckingGithubName && (
|
||||||
|
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{githubNameExists && (
|
||||||
|
<p className="text-xs sm:text-sm text-yellow-500">
|
||||||
|
An app with this name already exists. Please choose a
|
||||||
|
different name.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="advanced-options">
|
||||||
|
<AccordionTrigger className="text-xs sm:text-sm hover:no-underline">
|
||||||
|
Advanced options
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="space-y-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label className="text-xs sm:text-sm">
|
||||||
|
Install command
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
value={installCommand}
|
||||||
|
onChange={(e) => setInstallCommand(e.target.value)}
|
||||||
|
placeholder="pnpm install"
|
||||||
|
className="text-sm"
|
||||||
|
disabled={importing}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label className="text-xs sm:text-sm">
|
||||||
|
Start command
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
value={startCommand}
|
||||||
|
onChange={(e) => setStartCommand(e.target.value)}
|
||||||
|
placeholder="pnpm dev"
|
||||||
|
className="text-sm"
|
||||||
|
disabled={importing}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!commandsValid && (
|
||||||
|
<p className="text-xs sm:text-sm text-red-500">
|
||||||
|
Both commands are required when customizing.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleImportFromUrl}
|
||||||
|
disabled={importing || !url.trim() || !commandsValid}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{importing ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="animate-spin mr-2 h-4 w-4" />
|
||||||
|
Importing...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Import"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user