Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3afdc6609f | |||
| c0e1747800 | |||
| 7a1652a28c | |||
| 95efb1ecea |
@@ -32,7 +32,7 @@ export default function LandingPage() {
|
||||
navItems={[
|
||||
{ name: "Features", id: "features" },
|
||||
{ name: "How It Works", id: "workflow" },
|
||||
{ name: "Pricing", id: "pricing" },
|
||||
{ name: "Projects", id: "/projects" },
|
||||
{ name: "Resources", id: "resources" },
|
||||
{ name: "Contact", id: "contact" },
|
||||
]}
|
||||
|
||||
334
src/app/projects/page.tsx
Normal file
334
src/app/projects/page.tsx
Normal file
@@ -0,0 +1,334 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleFullscreen from '@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen';
|
||||
import HeroLogoBillboard from '@/components/sections/hero/HeroLogoBillboard';
|
||||
import FooterBase from '@/components/sections/footer/FooterBase';
|
||||
import { Send } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface Node {
|
||||
id: string;
|
||||
type: string;
|
||||
label: string;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface Connection {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
nodes: Node[];
|
||||
connections: Connection[];
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const [projects, setProjects] = useState<Project[]>([
|
||||
{
|
||||
id: "proj-1", name: "Email Marketing Automation", description: "Automated email sequences triggered by user behavior", nodes: [
|
||||
{ id: "node-1", type: "trigger", label: "User Signup", x: 100, y: 100 },
|
||||
{ id: "node-2", type: "action", label: "Send Welcome Email", x: 300, y: 100 },
|
||||
{ id: "node-3", type: "action", label: "Add to List", x: 500, y: 100 },
|
||||
],
|
||||
connections: [
|
||||
{ id: "conn-1", source: "node-1", target: "node-2" },
|
||||
{ id: "conn-2", source: "node-2", target: "node-3" },
|
||||
],
|
||||
createdAt: "2025-01-15"},
|
||||
{
|
||||
id: "proj-2", name: "Data Pipeline Processing", description: "Transform and validate data from multiple sources", nodes: [
|
||||
{ id: "node-1", type: "trigger", label: "Data Received", x: 100, y: 150 },
|
||||
{ id: "node-2", type: "transform", label: "Parse JSON", x: 300, y: 150 },
|
||||
{ id: "node-3", type: "validate", label: "Validate", x: 500, y: 150 },
|
||||
{ id: "node-4", type: "action", label: "Save to DB", x: 700, y: 150 },
|
||||
],
|
||||
connections: [
|
||||
{ id: "conn-1", source: "node-1", target: "node-2" },
|
||||
{ id: "conn-2", source: "node-2", target: "node-3" },
|
||||
{ id: "conn-3", source: "node-3", target: "node-4" },
|
||||
],
|
||||
createdAt: "2025-01-14"},
|
||||
{
|
||||
id: "proj-3", name: "Customer Onboarding", description: "Complete onboarding workflow with document generation", nodes: [
|
||||
{ id: "node-1", type: "trigger", label: "New Customer", x: 100, y: 200 },
|
||||
{ id: "node-2", type: "action", label: "Generate Documents", x: 300, y: 200 },
|
||||
{ id: "node-3", type: "action", label: "Send Package", x: 500, y: 200 },
|
||||
{ id: "node-4", type: "notify", label: "Notify Team", x: 700, y: 200 },
|
||||
],
|
||||
connections: [
|
||||
{ id: "conn-1", source: "node-1", target: "node-2" },
|
||||
{ id: "conn-2", source: "node-2", target: "node-3" },
|
||||
{ id: "conn-3", source: "node-3", target: "node-4" },
|
||||
],
|
||||
createdAt: "2025-01-13"},
|
||||
]);
|
||||
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(projects[0]);
|
||||
const [canvasZoom, setCanvasZoom] = useState(1);
|
||||
const [panX, setPanX] = useState(0);
|
||||
const [panY, setPanY] = useState(0);
|
||||
|
||||
const handleZoom = (direction: 'in' | 'out') => {
|
||||
setCanvasZoom((prev) => direction === 'in' ? Math.min(prev + 0.2, 3) : Math.max(prev - 0.2, 0.5));
|
||||
};
|
||||
|
||||
const handlePan = (dx: number, dy: number) => {
|
||||
setPanX((prev) => prev + dx);
|
||||
setPanY((prev) => prev + dy);
|
||||
};
|
||||
|
||||
const nodeTypeColors: { [key: string]: string } = {
|
||||
trigger: 'bg-blue-500',
|
||||
action: 'bg-green-500',
|
||||
transform: 'bg-purple-500',
|
||||
validate: 'bg-orange-500',
|
||||
notify: 'bg-red-500',
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="elastic-effect"
|
||||
defaultTextAnimation="background-highlight"
|
||||
borderRadius="pill"
|
||||
contentWidth="mediumLarge"
|
||||
sizing="medium"
|
||||
background="noise"
|
||||
cardStyle="glass-depth"
|
||||
primaryButtonStyle="diagonal-gradient"
|
||||
secondaryButtonStyle="layered"
|
||||
headingFontWeight="normal"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleFullscreen
|
||||
navItems={[
|
||||
{ name: "Features", id: "features" },
|
||||
{ name: "How It Works", id: "workflow" },
|
||||
{ name: "Projects", id: "/projects" },
|
||||
{ name: "Resources", id: "resources" },
|
||||
{ name: "Contact", id: "contact" },
|
||||
]}
|
||||
brandName="Ranavo"
|
||||
bottomLeftText="Automation for Everyone"
|
||||
bottomRightText="hello@ranavo.app"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="projects-hero" data-section="projects-hero" className="py-20">
|
||||
<HeroLogoBillboard
|
||||
logoText="Your Projects"
|
||||
description="Manage and edit your automation workflows. Select a project to view the canvas, manage nodes, and collaborate with your team."
|
||||
buttons={[
|
||||
{ text: "Create New Project", href: "#" },
|
||||
{ text: "Back to Home", href: "/" },
|
||||
]}
|
||||
background={{ variant: "plain" }}
|
||||
mediaAnimation="none"
|
||||
frameStyle="card"
|
||||
buttonAnimation="slide-up"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="projects-workspace" data-section="projects-workspace" className="py-12">
|
||||
<div className="w-full max-w-full px-4 md:px-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 h-screen">
|
||||
{/* Projects List Sidebar */}
|
||||
<div className="lg:col-span-1 bg-card rounded-lg p-4 border border-foreground/10 overflow-y-auto">
|
||||
<h3 className="text-lg font-semibold mb-4 text-foreground">Projects</h3>
|
||||
<div className="space-y-2">
|
||||
{projects.map((project) => (
|
||||
<button
|
||||
key={project.id}
|
||||
onClick={() => setSelectedProject(project)}
|
||||
className={`w-full text-left p-3 rounded-md transition-colors ${
|
||||
selectedProject?.id === project.id
|
||||
? 'bg-primary-cta text-primary-cta-text'
|
||||
: 'bg-background/50 text-foreground hover:bg-background/70'
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium text-sm">{project.name}</div>
|
||||
<div className="text-xs mt-1 opacity-75">{project.description}</div>
|
||||
<div className="text-xs mt-2 opacity-60">{project.createdAt}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Canvas Area */}
|
||||
<div className="lg:col-span-3 bg-background rounded-lg border border-foreground/10 relative overflow-hidden">
|
||||
{selectedProject ? (
|
||||
<>
|
||||
{/* Canvas Header */}
|
||||
<div className="absolute top-0 left-0 right-0 bg-card border-b border-foreground/10 p-4 z-10">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-foreground">{selectedProject.name}</h2>
|
||||
<p className="text-sm text-foreground/60 mt-1">{selectedProject.description}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleZoom('in')}
|
||||
className="px-3 py-1 bg-primary-cta text-primary-cta-text rounded text-sm hover:opacity-90"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<span className="px-3 py-1 text-foreground text-sm">{Math.round(canvasZoom * 100)}%</span>
|
||||
<button
|
||||
onClick={() => handleZoom('out')}
|
||||
className="px-3 py-1 bg-primary-cta text-primary-cta-text rounded text-sm hover:opacity-90"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setCanvasZoom(1);
|
||||
setPanX(0);
|
||||
setPanY(0);
|
||||
}}
|
||||
className="px-3 py-1 bg-secondary-cta text-secondary-cta-text rounded text-sm hover:opacity-90 ml-2"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Canvas SVG */}
|
||||
<svg
|
||||
className="w-full h-full mt-24"
|
||||
onMouseMove={(e) => {
|
||||
if (e.buttons === 4) {
|
||||
handlePan(e.movementX, e.movementY);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Grid Background */}
|
||||
<defs>
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeWidth="0.5" opacity="0.1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
|
||||
{/* Connections (Lines) */}
|
||||
<g transform={`translate(${panX}, ${panY}) scale(${canvasZoom})`}>
|
||||
{selectedProject.connections.map((conn) => {
|
||||
const sourceNode = selectedProject.nodes.find((n) => n.id === conn.source);
|
||||
const targetNode = selectedProject.nodes.find((n) => n.id === conn.target);
|
||||
if (!sourceNode || !targetNode) return null;
|
||||
|
||||
return (
|
||||
<line
|
||||
key={conn.id}
|
||||
x1={sourceNode.x + 50}
|
||||
y1={sourceNode.y + 25}
|
||||
x2={targetNode.x + 50}
|
||||
y2={targetNode.y + 25}
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
opacity="0.5"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Nodes */}
|
||||
{selectedProject.nodes.map((node) => (
|
||||
<g key={node.id}>
|
||||
<rect
|
||||
x={node.x}
|
||||
y={node.y}
|
||||
width="100"
|
||||
height="50"
|
||||
className={`${nodeTypeColors[node.type] || 'bg-gray-500'} cursor-move`}
|
||||
rx="4"
|
||||
fill={nodeTypeColors[node.type] || '#6b7280'}
|
||||
opacity="0.8"
|
||||
/>
|
||||
<text
|
||||
x={node.x + 50}
|
||||
y={node.y + 30}
|
||||
textAnchor="middle"
|
||||
fill="white"
|
||||
fontSize="12"
|
||||
fontWeight="500"
|
||||
>
|
||||
{node.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
{/* Canvas Footer */}
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-card border-t border-foreground/10 p-4 text-xs text-foreground/60">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>Nodes: {selectedProject.nodes.length} | Connections: {selectedProject.connections.length}</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-blue-500 rounded"></div>
|
||||
<span>Trigger</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-green-500 rounded"></div>
|
||||
<span>Action</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-purple-500 rounded"></div>
|
||||
<span>Transform</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-foreground/50">
|
||||
Select a project to view
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterBase
|
||||
columns={[
|
||||
{
|
||||
title: "Product", items: [
|
||||
{ label: "Canvas Workspace", href: "#features" },
|
||||
{ label: "Node Library", href: "#workflow" },
|
||||
{ label: "Pricing", href: "#" },
|
||||
{ label: "Security", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Resources", items: [
|
||||
{ label: "Documentation", href: "#" },
|
||||
{ label: "Tutorials", href: "#" },
|
||||
{ label: "Community", href: "#" },
|
||||
{ label: "Blog", href: "#" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Company", items: [
|
||||
{ label: "About Us", href: "#" },
|
||||
{ label: "Contact", href: "#" },
|
||||
{ label: "Careers", href: "#" },
|
||||
{ label: "Status", href: "#" },
|
||||
],
|
||||
},
|
||||
]}
|
||||
logoText="Ranavo"
|
||||
copyrightText="© 2025 Ranavo Canvas. All rights reserved."
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user