mirror of
https://github.com/borbann-platform/backend-api.git
synced 2025-12-19 20:54:05 +01:00
feat: implement multi-step form for creating data pipelines, refactor existing components for improved structure and styling
This commit is contained in:
parent
a687636b07
commit
42b9fdedae
@ -0,0 +1,130 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
import { AddDataSource } from "@/components/pipeline/add-data-source";
|
||||||
|
import { PipelineAiAssistant } from "@/components/pipeline/ai-assistant";
|
||||||
|
import { PipelineDetails } from "@/components/pipeline/details";
|
||||||
|
import { ScheduleAndInformation } from "@/components/pipeline/schedule-and-information";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Form } from "@/components/ui/form";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const TOTAL_STEPS = 5;
|
||||||
|
|
||||||
|
const stepComponents = [
|
||||||
|
<PipelineDetails key="details" />,
|
||||||
|
<AddDataSource key="datasource" />,
|
||||||
|
<PipelineAiAssistant key="ai" />,
|
||||||
|
<ScheduleAndInformation key="schedule" />,
|
||||||
|
<div
|
||||||
|
key="inputs"
|
||||||
|
className="border border-dashed rounded-md flex flex-col items-center justify-center h-[8rem]"
|
||||||
|
>
|
||||||
|
<h3 className="text-base font-semibold text-center">
|
||||||
|
No Inputs Added Yet!
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-muted-foreground text-center">
|
||||||
|
Start building your form by adding input fields.
|
||||||
|
</p>
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
const motionVariants = {
|
||||||
|
enter: { opacity: 0, x: 50 },
|
||||||
|
center: { opacity: 1, x: 0 },
|
||||||
|
exit: { opacity: 0, x: -50 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CratePipelineForm() {
|
||||||
|
const [step, setStep] = useState(0);
|
||||||
|
const isFirstStep = step === 0;
|
||||||
|
const isLastStep = step === TOTAL_STEPS - 1;
|
||||||
|
|
||||||
|
const form = useForm();
|
||||||
|
const { handleSubmit, reset } = form;
|
||||||
|
|
||||||
|
const onSubmit = async (formData: unknown) => {
|
||||||
|
if (!isLastStep) return setStep((s) => s + 1);
|
||||||
|
console.log(formData);
|
||||||
|
reset();
|
||||||
|
setStep(0);
|
||||||
|
toast.success("Form successfully submitted");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBack = () => setStep((s) => (s > 0 ? s - 1 : s));
|
||||||
|
|
||||||
|
const StepForm = (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="grid gap-y-4">
|
||||||
|
{stepComponents[step]}
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
className="font-medium cursor-pointer"
|
||||||
|
onClick={handleBack}
|
||||||
|
disabled={isFirstStep}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
className="font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{isLastStep ? "Create Pipeline" : "Next"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* stepper */}
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
{Array.from({ length: TOTAL_STEPS }).map((_, index) => (
|
||||||
|
<div key={index} className="flex items-center">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-4 h-4 rounded-full transition-all duration-300 ease-in-out",
|
||||||
|
index <= step ? "bg-primary" : "bg-primary/30"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{index < TOTAL_STEPS - 1 && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-8 h-0.5",
|
||||||
|
index < step ? "bg-primary" : "bg-primary/30"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* animated form */}
|
||||||
|
<Card className="shadow-sm">
|
||||||
|
<CardContent>
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={step}
|
||||||
|
variants={motionVariants}
|
||||||
|
initial="enter"
|
||||||
|
animate="center"
|
||||||
|
exit="exit"
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
>
|
||||||
|
{StepForm}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,25 +1,11 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import PageHeader from "@/components/page-header";
|
import CratePipelineForm from "./create-pipeline-multiform";
|
||||||
import { PipelineDetails } from "@/components/pipeline/details";
|
|
||||||
import { PipelineAiAssistant } from "@/components/pipeline/ai-assistant";
|
|
||||||
import { AddDataSource } from "@/components/pipeline/add-data-source";
|
|
||||||
import { ScheduleAndInformation } from "@/components/pipeline/schedule-and-information";
|
|
||||||
|
|
||||||
export default function CreatePipelinePage() {
|
export default function CreatePipelinePage() {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-6">
|
<div className="container mx-auto p-6">
|
||||||
<PageHeader
|
|
||||||
title="Create Data Pipeline"
|
|
||||||
description="Set up a new automated data collection pipeline"
|
|
||||||
breadcrumb={[
|
|
||||||
{ title: "Home", href: "/" },
|
|
||||||
{ title: "Data Pipeline", href: "/data-pipeline" },
|
|
||||||
{ title: "Create", href: "/data-pipeline/create" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Link href="/data-pipeline">
|
<Link href="/data-pipeline">
|
||||||
<Button variant="outline" className="mb-6">
|
<Button variant="outline" className="mb-6">
|
||||||
@ -27,21 +13,10 @@ export default function CreatePipelinePage() {
|
|||||||
Back to Pipelines
|
Back to Pipelines
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
<CratePipelineForm />
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<PipelineDetails />
|
|
||||||
<PipelineAiAssistant />
|
|
||||||
</div>
|
|
||||||
<AddDataSource />
|
|
||||||
<div>
|
|
||||||
<ScheduleAndInformation />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end space-x-4">
|
<div className="mt-6 flex justify-end space-x-4">
|
||||||
<Button variant="outline">Save as Draft</Button>
|
{/* <Button variant="outline">Save as Draft</Button>
|
||||||
<Button>Create Pipeline</Button>
|
<Button>Create Pipeline</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import {
|
|||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "../ui/accordion";
|
} from "../ui/accordion";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@ -12,15 +14,13 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../ui/card";
|
} from "../ui/card";
|
||||||
import { Label } from "../ui/label";
|
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { Badge } from "../ui/badge";
|
import { Label } from "../ui/label";
|
||||||
import { Textarea } from "../ui/textarea";
|
import { Textarea } from "../ui/textarea";
|
||||||
import { Button } from "../ui/button";
|
|
||||||
|
|
||||||
export function AddDataSource() {
|
export function AddDataSource() {
|
||||||
return (
|
return (
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
<Card className="border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Data Sources</CardTitle>
|
<CardTitle>Data Sources</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|||||||
@ -1,22 +1,19 @@
|
|||||||
import { Switch } from "../ui/switch";
|
import {
|
||||||
import { BrainCircuit } from "lucide-react";
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
import { Label } from "../ui/label";
|
import { Label } from "../ui/label";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "../ui/card";
|
|
||||||
import { Textarea } from "../ui/textarea";
|
import { Textarea } from "../ui/textarea";
|
||||||
|
|
||||||
export function PipelineAiAssistant(){
|
export function PipelineAiAssistant() {
|
||||||
return (
|
return (
|
||||||
<Card className="mt-6 border-2 hover:border-highlight-border transition-all duration-200">
|
<Card className="mt-6 border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div>
|
<CardTitle>AI Assistant</CardTitle>
|
||||||
<CardTitle className="text-lg flex items-center">
|
<CardDescription>Customize how AI processes your data</CardDescription>
|
||||||
<BrainCircuit className="mr-2 h-5 w-5 text-primary" />
|
|
||||||
AI Assistant
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Customize how AI processes your data
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@ -33,13 +30,11 @@ export function PipelineAiAssistant(){
|
|||||||
data sources
|
data sources
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{/*
|
||||||
<div className="space-y-3 pt-2">
|
<div className="space-y-3 pt-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="detect-fields">
|
<Label htmlFor="detect-fields">Auto-detect common fields</Label>
|
||||||
Auto-detect common fields
|
|
||||||
</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Automatically identify price, location, etc.
|
Automatically identify price, location, etc.
|
||||||
</p>
|
</p>
|
||||||
@ -49,9 +44,7 @@ export function PipelineAiAssistant(){
|
|||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="suggest-mappings">
|
<Label htmlFor="suggest-mappings">Suggest field mappings</Label>
|
||||||
Suggest field mappings
|
|
||||||
</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Get AI suggestions for matching fields across sources
|
Get AI suggestions for matching fields across sources
|
||||||
</p>
|
</p>
|
||||||
@ -68,7 +61,7 @@ export function PipelineAiAssistant(){
|
|||||||
</div>
|
</div>
|
||||||
<Switch id="deduplicate" defaultChecked />
|
<Switch id="deduplicate" defaultChecked />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import { Label } from "../ui/label";
|
import {
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "../ui/card";
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
import { Textarea } from "../ui/textarea";
|
import { Textarea } from "../ui/textarea";
|
||||||
|
|
||||||
export function PipelineDetails(){
|
export function PipelineDetails() {
|
||||||
return (
|
return (
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
<Card className="border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Pipeline Details</CardTitle>
|
<CardTitle>Pipeline Details</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
|
|
||||||
export function ScheduleAndInformation() {
|
export function ScheduleAndInformation() {
|
||||||
return (
|
return (
|
||||||
<Card className="mt-6 border-2 hover:border-highlight-border transition-all duration-200">
|
<Card className="mt-6 border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Schedule & Automation</CardTitle>
|
<CardTitle>Schedule & Automation</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|||||||
@ -47,6 +47,7 @@
|
|||||||
"cmdk": "1.1.1",
|
"cmdk": "1.1.1",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"embla-carousel-react": "8.5.1",
|
"embla-carousel-react": "8.5.1",
|
||||||
|
"framer-motion": "^12.9.1",
|
||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.487.0",
|
"lucide-react": "^0.487.0",
|
||||||
"next": "15.2.1",
|
"next": "15.2.1",
|
||||||
|
|||||||
@ -123,6 +123,9 @@ dependencies:
|
|||||||
embla-carousel-react:
|
embla-carousel-react:
|
||||||
specifier: 8.5.1
|
specifier: 8.5.1
|
||||||
version: 8.5.1(react@19.0.0)
|
version: 8.5.1(react@19.0.0)
|
||||||
|
framer-motion:
|
||||||
|
specifier: ^12.9.1
|
||||||
|
version: 12.9.1(react-dom@19.0.0)(react@19.0.0)
|
||||||
input-otp:
|
input-otp:
|
||||||
specifier: 1.4.1
|
specifier: 1.4.1
|
||||||
version: 1.4.1(react-dom@19.0.0)(react@19.0.0)
|
version: 1.4.1(react-dom@19.0.0)(react@19.0.0)
|
||||||
@ -3385,6 +3388,27 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/framer-motion@12.9.1(react-dom@19.0.0)(react@19.0.0):
|
||||||
|
resolution: {integrity: sha512-dZBp2TO0a39Cc24opshlLoM0/OdTZVKzcXWuhntfwy2Qgz3t9+N4sTyUqNANyHaRFiJUWbwwsXeDvQkEBPky+g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/is-prop-valid': '*'
|
||||||
|
react: ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/is-prop-valid':
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
motion-dom: 12.9.1
|
||||||
|
motion-utils: 12.8.3
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/function-bind@1.1.2:
|
/function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -4035,6 +4059,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/motion-dom@12.9.1:
|
||||||
|
resolution: {integrity: sha512-xqXEwRLDYDTzOgXobSoWtytRtGlf7zdkRfFbrrdP7eojaGQZ5Go4OOKtgnx7uF8sAkfr1ZjMvbCJSCIT2h6fkQ==}
|
||||||
|
dependencies:
|
||||||
|
motion-utils: 12.8.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/motion-utils@12.8.3:
|
||||||
|
resolution: {integrity: sha512-GYVauZEbca8/zOhEiYOY9/uJeedYQld6co/GJFKOy//0c/4lDqk0zB549sBYqqV2iMuX+uHrY1E5zd8A2L+1Lw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ms@2.1.3:
|
/ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user