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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion e2e/pages/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export class DashboardPage {
await this.page.goto("/");
await this.createButton.hover();
await this.createButton.click();
await this.page.getByRole("button", { name: "Create from Scratch", exact: true }).click();
await this.page.getByRole("button", { name: "New Project From Scratch" }).hover();
await this.page.getByRole("button", { name: "New Project From Scratch" }).click();
await this.page.getByPlaceholder("Enter project name").fill(randomatic("Aa", 8));
await this.page.getByRole("button", { name: "Create", exact: true }).click();

Expand Down
7 changes: 4 additions & 3 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getPageTitleFromPath } from "@utilities";
import { useFileStore, useOrganizationStore } from "@store";

import { PageTitle } from "@components/atoms";
import { CreateNewProject, DeploymentsTable, EventViewer, ProtectedRoute, SessionsTable } from "@components/organisms";
import { DeploymentsTable, EventViewer, ProtectedRoute, SessionsTable } from "@components/organisms";
import { CodeTable } from "@components/organisms/code";
import { ConnectionsTable, EditConnection } from "@components/organisms/connections";
import { AddConnection } from "@components/organisms/connections/add";
Expand All @@ -31,6 +31,7 @@ import { EventsList } from "@components/organisms/shared";
import { AddTrigger, EditTrigger, TriggersTable } from "@components/organisms/triggers";
import { AddVariable, EditVariable, VariablesTable } from "@components/organisms/variables";
import {
AiLandingPage,
ChatPage,
Connections,
CustomError,
Expand Down Expand Up @@ -108,8 +109,8 @@ export const App = () => {
<Routes>
<Route element={<AppLayout hideTopbar />} path="/">
<Route element={<Dashboard />} index />
<Route element={<CreateNewProject />} path="ai" />
<Route element={<CreateNewProject isWelcomePage />} path="welcome" />
<Route element={<AiLandingPage />} path="ai" />
<Route element={<AiLandingPage />} path="welcome" />

<Route element={<Intro />} path="intro" />

Expand Down
2 changes: 1 addition & 1 deletion src/components/atoms/aiTextarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export const AiTextArea = forwardRef<HTMLTextAreaElement, AiTextAreaProps>(
(ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = element;
}
}}
rows={1}
rows={3}
/>
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<Button
Expand Down
301 changes: 301 additions & 0 deletions src/components/pages/aiLandingPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
import React, { useState, useMemo } from "react";

import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";

import { createAiLandingPagePrompts } from "@constants/aiLandingPagePrompts";
import { ModalName } from "@enums/components";
import { CONFIG, iframeCommService } from "@services/iframeComm.service";
import { TourId } from "@src/enums";
import { useProjectStore, useToastStore, useTourStore, useModalStore } from "@src/store";
import { cn } from "@src/utilities";

import { AiTextArea, Button, Typography } from "@components/atoms";
import { ImportProjectModal, NewProjectModal } from "@components/organisms";
import { ChatbotIframe } from "@components/organisms/chatbotIframe/chatbotIframe";

const pillsPerPage = 4;

export const AiLandingPage = () => {
const { t: tAi } = useTranslation("dashboard", { keyPrefix: "ai" });
const navigate = useNavigate();
const addToast = useToastStore((state) => state.addToast);
const { projectsList } = useProjectStore();
const { openModal } = useModalStore();
const [isModalOpen, setIsModalOpen] = useState(false);
const [pendingMessage, setPendingMessage] = useState<string>();
const [visiblePillsCount, setVisiblePillsCount] = useState(pillsPerPage);
const { startTour } = useTourStore();

const {
register,
handleSubmit,
setValue,
clearErrors,
formState: { errors },
watch,
} = useForm<{ message: string }>({
mode: "onChange",
defaultValues: {
message: "",
},
});
const prompt = watch("message");

const allSuggestionPills = useMemo(() => createAiLandingPagePrompts(tAi), [tAi]);

const visiblePills = allSuggestionPills.slice(0, visiblePillsCount);
const hasMorePills = visiblePillsCount < allSuggestionPills.length;

const handleStartTutorial = async () => {
const { data: newProjectData, error: newProjectError } = await startTour(TourId.quickstart);
if (!newProjectData?.projectId || newProjectError) {
addToast({
message: tAi("projectCreationFailed"),
type: "error",
});
return;
}
const { projectId, defaultFile } = newProjectData;

navigate(`/projects/${projectId}/code`, {
state: {
fileToOpen: defaultFile,
startTour: TourId.quickstart,
},
});
};

const handleStartFromTemplate = () => {
navigate("/templates-library");
};

const handleNewProject = () => {
openModal(ModalName.newProject);
};

const handleLearnMore = () => {
navigate("/intro");
};

const onSubmit = (data: { message: string }) => {
setIsModalOpen(true);
setPendingMessage(data.message);
};

const handleIframeConnect = () => {
if (pendingMessage) {
const messageToSend = pendingMessage;

setPendingMessage(undefined);

iframeCommService.sendMessage({
type: "WELCOME_MESSAGE",
source: CONFIG.APP_SOURCE,
data: {
message: messageToSend,
},
});
}
};

const handleCloseModal = () => {
setIsModalOpen(false);
setPendingMessage(undefined);
};

const onSuggestionClick = (suggestion: string) => {
setValue("message", suggestion);
const textareaElement = document.querySelector('textarea[name="message"]') as HTMLTextAreaElement;
if (textareaElement) {
textareaElement.focus();
}
if (suggestion) {
clearErrors("message");
}
};

const handleLoadMore = () => {
setVisiblePillsCount(allSuggestionPills.length);
};

const showQuickstart = !projectsList.some((project) => project.name.toLowerCase() === "quickstart");

return (
<div className="relative flex min-h-screen flex-col overflow-y-scroll bg-[#1B1C1A] text-white">
<header className="relative z-10 border-b border-gray-900/50 p-4 md:px-6">
<div className="mx-auto flex w-full max-w-6xl flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center sm:gap-3 md:gap-4">
{showQuickstart ? (
<Button
className="w-full rounded-none border border-green-400/30 bg-transparent px-4 py-2 text-base text-[#bcf870] hover:border-green-400/50 hover:bg-green-400/10 sm:w-auto md:px-6 md:text-base"
onClick={handleStartTutorial}
>
<Typography className="font-medium">Start Tutorial</Typography>
</Button>
) : null}
<Button
className="w-full rounded-none border border-green-400/50 bg-transparent px-4 py-2 text-base text-[#bcf870] hover:border-green-400/70 hover:bg-green-400/10 sm:w-auto md:px-6 md:text-base"
onClick={handleStartFromTemplate}
>
<Typography className="font-medium">Start from Template</Typography>
</Button>
<Button
ariaLabel="New Project From Scratch"
className="w-full rounded-none border border-green-400/50 bg-transparent px-4 py-2 text-base text-[#bcf870] hover:border-green-400/70 hover:bg-green-400/10 sm:w-auto md:px-6 md:text-base"
onClick={handleNewProject}
title="New Project From Scratch"
>
<Typography className="font-medium">New Project</Typography>
</Button>
</div>
<Button
className="ml-2 w-full rounded-none bg-transparent text-base text-[#bcf870] hover:underline sm:w-auto"
onClick={handleLearnMore}
>
{tAi("learnMore")}
</Button>
</div>
</header>

<main className="relative mt-[5%] flex flex-1 flex-col items-center p-4 md:px-6">
<div className="relative z-10 flex w-full max-w-6xl flex-col items-center gap-8 md:gap-12">
<div className="mt-8 text-center">
<Typography
className="mb-8 text-2xl font-black leading-tight sm:text-3xl md:mb-12 md:text-4xl lg:text-5xl"
element="h1"
>
<span className="text-white">Build AI Agents & Automations in Minutes</span>
</Typography>
<Typography
className="mb-8 text-xl font-bold text-[#fdfffa] sm:text-2xl md:text-3xl"
element="h2"
>
Prompt, configure, deploy, enhance
</Typography>
</div>

<div className="w-11/12 md:w-3/5">
<form className="mb-4 md:mb-6" onSubmit={handleSubmit(onSubmit)}>
<AiTextArea
className="text-white placeholder:pl-2 placeholder:text-white"
errors={errors}
prompt={prompt}
{...register("message", {
required: tAi("aiPage.requiredMessage"),
onChange: (e) => {
if (errors.message && e.target.value.trim()) {
clearErrors("message");
}
},
})}
/>
</form>
</div>
<div className="flex w-full flex-col items-center gap-4">
<div className="flex w-full max-w-6xl flex-wrap items-center justify-center gap-2 md:gap-3">
{visiblePills.map((suggestion, index) => (
<button
className={cn(
"cursor-pointer rounded-full border border-gray-600/50 bg-[#1b1c1a] px-3 py-1.5",
"w-full text-sm text-gray-600 transition-all duration-300 sm:w-[calc(50%-0.375rem)] sm:px-4 sm:py-3 sm:text-sm md:w-[calc(25%-1.5rem)] md:text-sm",
"hover:border-green-400/50 hover:bg-gray-700/80 hover:text-gray-300",
{
"animate-[fadeIn_0.5s_ease-in-out]": index < pillsPerPage,
}
)}
key={index}
onClick={() => onSuggestionClick(suggestion.text)}
style={
index < pillsPerPage
? {
animationDelay: `${index * 50}ms`,
}
: undefined
}
type="button"
>
<span className="line-clamp-1">{suggestion.title}</span>
</button>
))}
</div>

{hasMorePills ? (
<Button
className="group mt-2 bg-transparent px-6 py-2 text-sm text-gray-400 transition-all duration-300 hover:text-green-400"
onClick={handleLoadMore}
>
<Typography className="flex items-center gap-2 font-medium">
See all
<svg
className="size-4 transition-transform duration-300 group-hover:translate-y-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 9l-7 7-7-7"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
/>
</svg>
</Typography>
</Button>
) : null}
</div>
</div>
</main>
<NewProjectModal />
<ImportProjectModal />
{isModalOpen ? (
<div className="fixed inset-0 z-[99] flex items-center justify-center bg-black/60 p-2 sm:p-4">
<div className="relative h-[95vh] w-full bg-black sm:h-[90vh] sm:w-[90vw] md:h-[85vh] md:w-[85vw]">
<Button
aria-label={tAi("modal.closeLabel")}
className="absolute right-3 top-3 z-10 bg-transparent p-1.5 hover:bg-gray-200 sm:right-6 sm:top-6"
onClick={handleCloseModal}
>
<svg
className="size-4 text-gray-600 sm:size-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 18L18 6M6 6l12 12"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
/>
</svg>
</Button>
<ChatbotIframe
className="size-full"
hideCloseButton
onConnect={handleIframeConnect}
padded
title={tAi("modal.assistantTitle")}
/>
</div>
</div>
) : null}

<style>{`
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`}</style>
</div>
);
};
1 change: 1 addition & 0 deletions src/components/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { AiLandingPage } from "@components/pages/aiLandingPage";
export { ChatPage } from "@components/pages/chat";
export { Connections } from "@components/pages/connections";
export { CustomError } from "@components/pages/customError";
Expand Down
3 changes: 2 additions & 1 deletion src/components/templates/systemLogLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,14 @@ export const SystemLogLayout = ({
const innerLayoutClasses = cn("mr-2 flex flex-1 flex-col md:mb-2", {
"md:mb-0.5": systemLogHeight === 0,
"w-0": ["/", "/intro"].includes(pathname),
"mr-0": isMobile,
});

return (
<div className={layoutClasses}>
{sidebar}
<div className={innerLayoutClasses}>
<div className="flex flex-1 flex-col overflow-hidden" style={{ height: `${100 - systemLogHeight}%` }}>
<div className="flex flex-1 flex-col" style={{ height: `${100 - systemLogHeight}%` }}>
{topbar}
{children}
</div>
Expand Down
Loading