feat: add project connection to chat
This commit is contained in:
@@ -58,6 +58,8 @@ export const MobileFlowPage = ({ screen }: MobileFlowPageProps) => {
|
||||
assistantError: projectWorkspace.error?.message,
|
||||
localStatus: workspace.statusMessage,
|
||||
});
|
||||
const handleRepositoryChange =
|
||||
projectWorkspace.projectWorkspace.setRepository;
|
||||
|
||||
const handleOpenUnit = (selectedWorkUnitId?: string) => {
|
||||
const targetId =
|
||||
@@ -135,6 +137,7 @@ export const MobileFlowPage = ({ screen }: MobileFlowPageProps) => {
|
||||
data={projectWorkspace.data}
|
||||
modelId={MODEL_ID}
|
||||
modelLabel={MODEL_LABEL}
|
||||
onAddProject={() => workspace.setProjectManagerOpen(true)}
|
||||
onComposerChange={workspace.handleComposerChange}
|
||||
onComposerSubmit={workspace.handleComposerSubmit}
|
||||
onOpenUnit={(selectedWorkUnitId) =>
|
||||
@@ -145,6 +148,12 @@ export const MobileFlowPage = ({ screen }: MobileFlowPageProps) => {
|
||||
projectId as Id<"projects">
|
||||
)
|
||||
}
|
||||
onProjectManagerClose={() => workspace.setProjectManagerOpen(false)}
|
||||
onRepositoryChange={handleRepositoryChange}
|
||||
onRepositoryConnect={(event) => {
|
||||
event.preventDefault();
|
||||
void projectWorkspace.projectWorkspace.connectRepository();
|
||||
}}
|
||||
onRestoreChecked={handleRestoreChecked}
|
||||
onSettingsClose={() => workspace.setSettingsOpen(false)}
|
||||
onSettingsOpen={() => workspace.setSettingsOpen(true)}
|
||||
@@ -153,6 +162,9 @@ export const MobileFlowPage = ({ screen }: MobileFlowPageProps) => {
|
||||
}}
|
||||
onWorkUnitChecked={handleWorkUnitChecked}
|
||||
onWorkUnitSentBack={handleWorkUnitSentBack}
|
||||
pendingAction={projectWorkspace.projectWorkspace.pendingAction}
|
||||
projectManagerOpen={workspace.projectManagerOpen}
|
||||
repositoryValue={projectWorkspace.projectWorkspace.repository}
|
||||
settingsOpen={workspace.settingsOpen}
|
||||
statusMessage={statusMessage}
|
||||
userEmail={user?.email}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { LoaderCircle, X } from "lucide-react";
|
||||
import type { FormEvent } from "react";
|
||||
|
||||
import type { MobileWorkspaceView } from "./models";
|
||||
|
||||
export interface MobileProjectManagerPanelProps {
|
||||
readonly data: MobileWorkspaceView;
|
||||
readonly onClose: () => void;
|
||||
readonly onProjectSelect: (projectId: string) => void;
|
||||
readonly onRepositoryChange: (value: string) => void;
|
||||
readonly onRepositoryConnect: (event: FormEvent<HTMLFormElement>) => void;
|
||||
readonly pendingAction?: string | null;
|
||||
readonly repositoryValue: string;
|
||||
readonly statusMessage?: string;
|
||||
}
|
||||
|
||||
export const MobileProjectManagerPanel = ({
|
||||
data,
|
||||
onClose,
|
||||
onProjectSelect,
|
||||
onRepositoryChange,
|
||||
onRepositoryConnect,
|
||||
pendingAction,
|
||||
repositoryValue,
|
||||
statusMessage,
|
||||
}: MobileProjectManagerPanelProps) => {
|
||||
const connecting = pendingAction === "connect";
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label="Add project"
|
||||
className="absolute inset-0 z-40 flex flex-col bg-[#f7f8f5] text-[#11110f]"
|
||||
>
|
||||
<header className="flex h-20 shrink-0 items-center border-b border-[#e9eae6] bg-white px-4">
|
||||
<div>
|
||||
<h2 className="text-[18px] font-semibold">Projects</h2>
|
||||
<p className="text-[11px] text-[#807b76]">
|
||||
Connect a repository to Zopu.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Close projects"
|
||||
className="ml-auto flex size-10 items-center justify-center rounded-full bg-[#f2f3ef]"
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-4">
|
||||
<div className="rounded-[24px] bg-white p-4">
|
||||
<label
|
||||
className="text-[10px] font-semibold tracking-[0.08em] text-[#807b76] uppercase"
|
||||
htmlFor="stack-project-selector"
|
||||
>
|
||||
Connected project
|
||||
</label>
|
||||
<select
|
||||
className="mt-2 h-11 w-full rounded-[14px] bg-[#f2f3ef] px-3 text-[13px] font-semibold outline-none focus-visible:ring-2 focus-visible:ring-[#315dc0]"
|
||||
disabled={data.projects.length === 0}
|
||||
id="stack-project-selector"
|
||||
onChange={(event) => onProjectSelect(event.currentTarget.value)}
|
||||
value={data.selectedProjectId ?? ""}
|
||||
>
|
||||
{data.projects.length === 0 ? (
|
||||
<option value="">No connected projects</option>
|
||||
) : (
|
||||
data.projects.map((project) => (
|
||||
<option key={project.id} value={project.id}>
|
||||
{project.repositoryPath ?? project.name}
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
|
||||
<form className="mt-5" onSubmit={onRepositoryConnect}>
|
||||
<label
|
||||
className="text-[10px] font-semibold tracking-[0.08em] text-[#807b76] uppercase"
|
||||
htmlFor="stack-repository-url"
|
||||
>
|
||||
Add project
|
||||
</label>
|
||||
<input
|
||||
aria-label="Git repository URL"
|
||||
className="mt-2 h-12 w-full rounded-[14px] bg-[#f2f3ef] px-3 text-[12px] outline-none placeholder:text-[#aaa7a3] focus-visible:ring-2 focus-visible:ring-[#315dc0]"
|
||||
disabled={connecting}
|
||||
id="stack-repository-url"
|
||||
onChange={(event) =>
|
||||
onRepositoryChange(event.currentTarget.value)
|
||||
}
|
||||
placeholder="https://git.openputer.com/owner/repo.git"
|
||||
required
|
||||
type="url"
|
||||
value={repositoryValue}
|
||||
/>
|
||||
<button
|
||||
className="mt-3 flex h-11 w-full items-center justify-center gap-2 rounded-[14px] bg-[#c8ff00] text-[13px] font-semibold text-black disabled:opacity-50"
|
||||
disabled={connecting || !repositoryValue.trim()}
|
||||
type="submit"
|
||||
>
|
||||
{connecting ? (
|
||||
<LoaderCircle className="size-4 animate-spin" />
|
||||
) : null}
|
||||
{connecting ? "Connecting…" : "Connect project"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{statusMessage ? (
|
||||
<p className="mt-3 text-center text-[11px] text-[#807b76]">
|
||||
{statusMessage}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,12 @@
|
||||
import { cn } from "@code/ui/lib/utils";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { ChevronDown, Plus } from "lucide-react";
|
||||
|
||||
import type { MobileProjectView } from "./models";
|
||||
|
||||
export interface MobileProjectSelectProps {
|
||||
readonly activeCount: number;
|
||||
readonly className?: string;
|
||||
readonly onAddProject: () => void;
|
||||
readonly onProjectSelect: (projectId: string) => void;
|
||||
readonly projects: readonly MobileProjectView[];
|
||||
readonly selectedProjectId?: string;
|
||||
@@ -14,6 +15,7 @@ export interface MobileProjectSelectProps {
|
||||
export const MobileProjectSelect = ({
|
||||
activeCount,
|
||||
className,
|
||||
onAddProject,
|
||||
onProjectSelect,
|
||||
projects,
|
||||
selectedProjectId,
|
||||
@@ -49,5 +51,14 @@ export const MobileProjectSelect = ({
|
||||
</label>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span className="shrink-0 tabular-nums">{activeCount} active</span>
|
||||
<button
|
||||
aria-label="Add project"
|
||||
className="ml-0.5 flex size-5 shrink-0 items-center justify-center rounded-full bg-[#c8ff00] text-black"
|
||||
onClick={onAddProject}
|
||||
title="Add project"
|
||||
type="button"
|
||||
>
|
||||
<Plus className="size-3.5" strokeWidth={2.5} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import type { FormEvent, MouseEvent } from "react";
|
||||
|
||||
import { MobileProjectManagerPanel } from "./mobile-project-manager-panel";
|
||||
import { MobileProjectSelect } from "./mobile-project-select";
|
||||
import { MobileSettingsPanel } from "./mobile-settings-panel";
|
||||
import type {
|
||||
@@ -42,6 +43,7 @@ const dateLabel = new Intl.DateTimeFormat("en", {
|
||||
|
||||
interface WorkStackHeaderProps {
|
||||
readonly activeCount: number;
|
||||
readonly onAddProject: () => void;
|
||||
readonly onOpenSettings?: () => void;
|
||||
readonly onProjectSelect: (projectId: string) => void;
|
||||
readonly projects: readonly MobileProjectView[];
|
||||
@@ -51,6 +53,7 @@ interface WorkStackHeaderProps {
|
||||
|
||||
const WorkStackHeader = ({
|
||||
activeCount,
|
||||
onAddProject,
|
||||
onOpenSettings,
|
||||
onProjectSelect,
|
||||
projects,
|
||||
@@ -67,6 +70,7 @@ const WorkStackHeader = ({
|
||||
</h1>
|
||||
<MobileProjectSelect
|
||||
activeCount={activeCount}
|
||||
onAddProject={onAddProject}
|
||||
onProjectSelect={onProjectSelect}
|
||||
projects={projects}
|
||||
selectedProjectId={selectedProjectId}
|
||||
@@ -298,16 +302,23 @@ export interface MobileWorkStackScreenProps {
|
||||
readonly data: MobileWorkspaceView;
|
||||
readonly modelId: string;
|
||||
readonly modelLabel: string;
|
||||
readonly onAddProject: () => void;
|
||||
readonly onComposerChange: (value: string) => void;
|
||||
readonly onComposerSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
readonly onOpenUnit: (workUnitId: string) => void;
|
||||
readonly onProjectSelect: (projectId: string) => void;
|
||||
readonly onProjectManagerClose: () => void;
|
||||
readonly onRepositoryChange: (value: string) => void;
|
||||
readonly onRepositoryConnect: (event: FormEvent<HTMLFormElement>) => void;
|
||||
readonly onRestoreChecked: () => void;
|
||||
readonly onSettingsClose: () => void;
|
||||
readonly onSettingsOpen: () => void;
|
||||
readonly onSignOut?: () => void;
|
||||
readonly onWorkUnitChecked: (workUnitId: string) => void;
|
||||
readonly onWorkUnitSentBack: (workUnitId: string) => void;
|
||||
readonly pendingAction?: string | null;
|
||||
readonly projectManagerOpen: boolean;
|
||||
readonly repositoryValue: string;
|
||||
readonly settingsOpen: boolean;
|
||||
readonly statusMessage?: string;
|
||||
readonly userEmail?: string;
|
||||
@@ -322,16 +333,23 @@ export const MobileWorkStackScreen = ({
|
||||
data,
|
||||
modelId,
|
||||
modelLabel,
|
||||
onAddProject,
|
||||
onComposerChange,
|
||||
onComposerSubmit,
|
||||
onOpenUnit,
|
||||
onProjectManagerClose,
|
||||
onProjectSelect,
|
||||
onRepositoryChange,
|
||||
onRepositoryConnect,
|
||||
onRestoreChecked,
|
||||
onSettingsClose,
|
||||
onSettingsOpen,
|
||||
onSignOut,
|
||||
onWorkUnitChecked,
|
||||
onWorkUnitSentBack,
|
||||
pendingAction,
|
||||
projectManagerOpen,
|
||||
repositoryValue,
|
||||
settingsOpen,
|
||||
statusMessage,
|
||||
userEmail,
|
||||
@@ -350,6 +368,7 @@ export const MobileWorkStackScreen = ({
|
||||
<div className="relative mx-auto flex h-[100dvh] max-h-[858px] min-h-[700px] w-full max-w-[390px] flex-col overflow-hidden bg-white text-[#0b0b0a]">
|
||||
<WorkStackHeader
|
||||
activeCount={data.activeCount}
|
||||
onAddProject={onAddProject}
|
||||
onOpenSettings={onSettingsOpen}
|
||||
onProjectSelect={onProjectSelect}
|
||||
projects={data.projects}
|
||||
@@ -458,6 +477,18 @@ export const MobileWorkStackScreen = ({
|
||||
statusMessage={statusMessage}
|
||||
value={composerValue}
|
||||
/>
|
||||
{projectManagerOpen ? (
|
||||
<MobileProjectManagerPanel
|
||||
data={data}
|
||||
onClose={onProjectManagerClose}
|
||||
onProjectSelect={onProjectSelect}
|
||||
onRepositoryChange={onRepositoryChange}
|
||||
onRepositoryConnect={onRepositoryConnect}
|
||||
pendingAction={pendingAction}
|
||||
repositoryValue={repositoryValue}
|
||||
statusMessage={statusMessage}
|
||||
/>
|
||||
) : null}
|
||||
{settingsOpen ? (
|
||||
<MobileSettingsPanel
|
||||
modelId={modelId}
|
||||
|
||||
Reference in New Issue
Block a user