diff --git a/app/bun.lockb b/app/bun.lockb index fe1d36df..1e6814e1 100755 Binary files a/app/bun.lockb and b/app/bun.lockb differ diff --git a/app/common/helpers/index.ts b/app/common/helpers/index.ts index d74b7004..fe870ba5 100644 --- a/app/common/helpers/index.ts +++ b/app/common/helpers/index.ts @@ -65,3 +65,35 @@ export function isOnlookInDoc(doc: Document): boolean { ).booleanValue; return attributeExists; } + +export function timeSince(date: Date): string { + if (!(date instanceof Date) || isNaN(date.getTime())) { + console.error('Invalid date provided'); + return 'Invalid date'; + } + + const now = new Date(); + const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); + let interval = seconds / 31536000; + + if (interval > 1) { + return Math.floor(interval) + 'y'; + } + interval = seconds / 2592000; + if (interval > 1) { + return Math.floor(interval) + 'm'; + } + interval = seconds / 86400; + if (interval > 1) { + return Math.floor(interval) + 'd'; + } + interval = seconds / 3600; + if (interval > 1) { + return Math.floor(interval) + 'h'; + } + interval = seconds / 60; + if (interval > 1) { + return Math.floor(interval) + 'm'; + } + return Math.floor(seconds) + 's'; +} diff --git a/app/package.json b/app/package.json index 7d4f9ec3..530fdc03 100644 --- a/app/package.json +++ b/app/package.json @@ -44,6 +44,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", diff --git a/app/src/components/ui/progress.tsx b/app/src/components/ui/progress.tsx new file mode 100644 index 00000000..be57c156 --- /dev/null +++ b/app/src/components/ui/progress.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import * as ProgressPrimitive from '@radix-ui/react-progress'; + +import { cn } from '@/lib/utils'; + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)); +Progress.displayName = ProgressPrimitive.Root.displayName; + +export { Progress }; diff --git a/app/src/routes/projects/ProjectsTab/Create/ChooseMethod.tsx b/app/src/routes/projects/ProjectsTab/Create/ChooseMethod.tsx new file mode 100644 index 00000000..cd11635b --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/ChooseMethod.tsx @@ -0,0 +1,66 @@ +import { Card } from '@/components/ui/card'; +import { DownloadIcon, FilePlusIcon } from '@radix-ui/react-icons'; +import { CreateMethod } from '../..'; + +export const ChooseMethod = ({ + setCreateMethod, +}: { + setCreateMethod: (method: CreateMethod | null) => void; +}) => { + const MESSAGES = [ + "Ready to make some good lookin' apps", + "What a week... right? Doesn't matter, let's build!", + "These apps aren't gunna design themselves", + 'Time to unleash your inner designer', + 'Release your inner artist today', + "Let's craft some beautiful UIs!", + "*crackles knuckles* Let's get building!", + 'Another day another design', + "Can't wait to see what you create!", + "Let's design something fresh today", + "Let's get to work", + "What time is it? It's time to build!", + "ಠ_ಠ Why aren't you designing? ಠ_ಠ", + ]; + + const OPENING_MESSAGE = MESSAGES[Math.floor(Math.random() * MESSAGES.length)]; + + return ( +
+
+

{'Projects'}

+

{OPENING_MESSAGE}

+
+
+ { + setCreateMethod(CreateMethod.NEW); + }} + > +
+ +
+

+ {'New Onlook project'} +

+

{'Start a React App'}

+
+ { + setCreateMethod(CreateMethod.LOAD); + }} + > +
+ +
+

+ {'Import existing project'} +

+

{'Work on your React UI'}

+
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/Load/SelectFolder.tsx b/app/src/routes/projects/ProjectsTab/Create/Load/SelectFolder.tsx new file mode 100644 index 00000000..8e3321e5 --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/Load/SelectFolder.tsx @@ -0,0 +1,84 @@ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { MinusCircledIcon } from '@radix-ui/react-icons'; +import { useState } from 'react'; +import { StepProps } from '..'; +import { MainChannels } from '/common/constants'; + +export const LoadSelectFolder = ({ + props: { currentStep, totalSteps, prevStep, nextStep }, +}: { + props: StepProps; +}) => { + const [projectName, setProjectName] = useState(null); + const [projectPath, setProjectPath] = useState(null); + + async function pickProjectFolder() { + const path = (await window.api.invoke(MainChannels.PICK_COMPONENTS_DIRECTORY)) as + | string + | null; + + if (path == null) { + return; + } + setProjectName('Project Name'); + setProjectPath('/path/to/project'); + } + + return ( + + + {'Select your project folder'} + {'This is where we’ll reference your App'} + + + {projectPath ? ( +
+
+

{projectName}

+

{projectPath}

+
+ +
+ ) : ( + + )} +
+ +

{`${currentStep + 1} of ${totalSteps}`}

+
+ + +
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/Load/SetUrl.tsx b/app/src/routes/projects/ProjectsTab/Create/Load/SetUrl.tsx new file mode 100644 index 00000000..07bcfde4 --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/Load/SetUrl.tsx @@ -0,0 +1,57 @@ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { useState } from 'react'; +import { StepProps } from '..'; + +export const LoadSetUrl = ({ + props: { currentStep, totalSteps, prevStep, nextStep }, +}: { + props: StepProps; +}) => { + const [projectUrl, setProjectUrl] = useState('http://localhost:3000'); + + return ( + + + {'Set your project URL'} + {'Where is your project running locally?'} + + +
+ + setProjectUrl(e.currentTarget.value)} + /> +
+
+ +

{`${currentStep + 1} of ${totalSteps}`}

+
+ + +
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/Load/Verify.tsx b/app/src/routes/projects/ProjectsTab/Create/Load/Verify.tsx new file mode 100644 index 00000000..f59ec6dc --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/Load/Verify.tsx @@ -0,0 +1,77 @@ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { CheckCircledIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons'; +import clsx from 'clsx'; +import { useState } from 'react'; +import { StepProps } from '..'; + +export const LoadVerifyProject = ({ + props: { currentStep, totalSteps, prevStep, nextStep }, +}: { + props: StepProps; +}) => { + const [isInstalled, setIsInstalled] = useState(null); + + async function installOnlook() { + setIsInstalled(true); + } + + return ( + + + + {isInstalled ? 'Onlook is installed' : 'Onlook is not installed'} + + + {isInstalled + ? 'Your project is all set up' + : 'It takes one second to install Onlook on your project'} + + + +
+
+

{'projectName'}

+

{'projectPath'}

+
+ {isInstalled ? ( + + ) : ( + + )} +
+
+ +

{`${currentStep + 1} of ${totalSteps}`}

+
+ + {isInstalled ? ( + + ) : ( + + )} +
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/LoadProject.tsx b/app/src/routes/projects/ProjectsTab/Create/LoadProject.tsx deleted file mode 100644 index c63e4af3..00000000 --- a/app/src/routes/projects/ProjectsTab/Create/LoadProject.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { ProjectData } from '.'; - -// Step components for "Load existing project" path -export const LoadStep1 = ({ - formData, - setProjectData, -}: { - formData: ProjectData; - setProjectData: (data: ProjectData) => void; -}) => ( -
-

Step 1: Select Project

- setProjectData({ ...formData, projectName: e.target.value })} - className="w-full p-2 border rounded" - /> -
-); - -export const LoadStep2 = ({ - formData, - setProjectData, -}: { - formData: ProjectData; - setProjectData: (data: ProjectData) => void; -}) => ( -
-

Step 2: Configure

- -
-); diff --git a/app/src/routes/projects/ProjectsTab/Create/New/Name.tsx b/app/src/routes/projects/ProjectsTab/Create/New/Name.tsx new file mode 100644 index 00000000..dc4cce74 --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/New/Name.tsx @@ -0,0 +1,58 @@ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { useState } from 'react'; +import { StepProps } from '..'; + +export const NewNameProjectStep = ({ + props: { currentStep, totalSteps, prevStep, nextStep }, +}: { + props: StepProps; +}) => { + const [projectName, setProjectName] = useState(null); + + return ( + + + {'Let’s name your project'} + + {'We’ll set you up with a blank template to start designing'} + + + +
+ + setProjectName(e.currentTarget.value)} + /> +
+
+ +

{`${currentStep + 1} of ${totalSteps}`}

+
+ + +
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/New/Run.tsx b/app/src/routes/projects/ProjectsTab/Create/New/Run.tsx new file mode 100644 index 00000000..c426fe97 --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/New/Run.tsx @@ -0,0 +1,71 @@ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { toast } from '@/components/ui/use-toast'; +import { ClipboardCopyIcon } from '@radix-ui/react-icons'; +import { useState } from 'react'; +import { StepProps } from '..'; + +export const NewRunProject = ({ + props: { currentStep, totalSteps, prevStep, nextStep }, +}: { + props: StepProps; +}) => { + const [projectPath, setProjectPath] = useState('path/to/project'); + const [isRunning, setIsRunning] = useState(false); + const codeContent = `cd ${projectPath} && npm run dev`; + + function copyToClipboard(text: string) { + navigator.clipboard.writeText(text); + } + + return ( + + + {'Run your project'} + + {'Run this command in your command line to start'} + + + +
+ {codeContent} + +
+
+ +

{`${currentStep + 1} of ${totalSteps}`}

+
+ + +
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/New/SelectFolder.tsx b/app/src/routes/projects/ProjectsTab/Create/New/SelectFolder.tsx new file mode 100644 index 00000000..85a971ca --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/New/SelectFolder.tsx @@ -0,0 +1,84 @@ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { MinusCircledIcon } from '@radix-ui/react-icons'; +import { useState } from 'react'; +import { StepProps } from '..'; +import { MainChannels } from '/common/constants'; + +export const NewSelectFolder = ({ + props: { currentStep, totalSteps, prevStep, nextStep }, +}: { + props: StepProps; +}) => { + const [projectName, setProjectName] = useState(null); + const [projectPath, setProjectPath] = useState(null); + + async function pickProjectFolder() { + const path = (await window.api.invoke(MainChannels.PICK_COMPONENTS_DIRECTORY)) as + | string + | null; + + if (path == null) { + return; + } + setProjectName('Project Name'); + setProjectPath('/path/to/project'); + } + + return ( + + + {'Select your project folder'} + {'This is where we’ll reference your App'} + + + {projectPath ? ( +
+
+

{projectName}

+

{projectPath}

+
+ +
+ ) : ( + + )} +
+ +

{`${currentStep + 1} of ${totalSteps}`}

+
+ + +
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/New/Setup.tsx b/app/src/routes/projects/ProjectsTab/Create/New/Setup.tsx new file mode 100644 index 00000000..5357377d --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Create/New/Setup.tsx @@ -0,0 +1,80 @@ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { CheckCircledIcon } from '@radix-ui/react-icons'; +import { useEffect, useState } from 'react'; +import { StepProps } from '..'; + +export const NewSetupProject = ({ + props: { currentStep, totalSteps, prevStep, nextStep }, +}: { + props: StepProps; +}) => { + const [isInstalling, setIsInstalling] = useState(false); + const [progress, setProgress] = useState(0); + + useEffect(() => { + if (progress >= 100) { + setIsInstalling(false); + } else { + setTimeout(() => { + setProgress(progress + 10); + }, 1000); + } + }, [progress]); + + async function installOnlook() { + setIsInstalling(true); + } + + return ( + + + + {isInstalling ? 'Setting up project...' : 'Your project is ready'} + + + {isInstalling + ? 'Installing the right files and folders for you.' + : 'Open this project in Onlook any time to start designing'} + + + + {isInstalling ? ( +
+ +

Installing dependencies...

+
+ ) : ( +
+
+

{'projectName'}

+

{'projectPath'}

+
+ +
+ )} +
+ +

{`${currentStep + 1} of ${totalSteps}`}

+
+ {isInstalling && ( + + )} + +
+
+
+ ); +}; diff --git a/app/src/routes/projects/ProjectsTab/Create/NewProject.tsx b/app/src/routes/projects/ProjectsTab/Create/NewProject.tsx deleted file mode 100644 index 42a6ab16..00000000 --- a/app/src/routes/projects/ProjectsTab/Create/NewProject.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { ProjectData } from '.'; - -// Step components for "New Onlook project" path -export const NewStep1 = ({ - formData, - setProjectData, -}: { - formData: ProjectData; - setProjectData: (data: ProjectData) => void; -}) => ( -
-

Step 1: Project Details

- setProjectData({ ...formData, projectName: e.target.value })} - className="w-full p-2 border rounded" - /> - setProjectData({ ...formData, description: e.target.value })} - className="w-full p-2 border rounded" - /> -
-); - -export const NewStep2 = ({ - formData, - setProjectData, -}: { - formData: ProjectData; - setProjectData: (data: ProjectData) => void; -}) => ( -
-

Step 2: React Setup

- -
-); diff --git a/app/src/routes/projects/ProjectsTab/Create/index.tsx b/app/src/routes/projects/ProjectsTab/Create/index.tsx index 3de08619..6b935c0d 100644 --- a/app/src/routes/projects/ProjectsTab/Create/index.tsx +++ b/app/src/routes/projects/ProjectsTab/Create/index.tsx @@ -1,163 +1,88 @@ -import { Button } from '@/components/ui/button'; -import { Card } from '@/components/ui/card'; -import { DownloadIcon, FilePlusIcon } from '@radix-ui/react-icons'; -import { FormEvent, useState } from 'react'; -import { LoadStep1, LoadStep2 } from './LoadProject'; -import { NewStep1, NewStep2 } from './NewProject'; +import { useState } from 'react'; +import { CreateMethod } from '../..'; +import { LoadSelectFolder } from './Load/SelectFolder'; +import { LoadSetUrl } from './Load/SetUrl'; +import { LoadVerifyProject } from './Load/Verify'; +import { NewNameProjectStep } from './New/Name'; +import { NewRunProject } from './New/Run'; +import { NewSelectFolder } from './New/SelectFolder'; +import { NewSetupProject } from './New/Setup'; +import { Project } from '/common/models/project'; -export interface ProjectData { - projectName: string; - projectType: string; - description: string; - reactVersion: string; +export interface StepProps { + projectData: Project; + setProjectData: (data: Project) => void; + currentStep: number; + totalSteps: number; + prevStep: () => void; + nextStep: () => void; } -export enum FormPath { - LOAD = 'load', - NEW = 'new', -} - -const ConfirmationStep = ({ formData }: { formData: ProjectData }) => ( -
-

Confirmation

-

Project Name: {formData.projectName}

- {formData.projectType &&

Project Type: {formData.projectType}

} - {formData.description &&

Description: {formData.description}

} - {formData.reactVersion &&

React Version: {formData.reactVersion}

} -
-); - -const CreateProject = () => { - const [isOpen, setIsOpen] = useState(true); - const [formPath, setFormPath] = useState(null); +const CreateProject = ({ + createMethod, + setCreateMethod, +}: { + createMethod: CreateMethod | null; + setCreateMethod: (method: CreateMethod | null) => void; +}) => { const [currentStep, setCurrentStep] = useState(0); - const [formData, setFormData] = useState({ - projectName: '', - projectType: '', - description: '', - reactVersion: '', + const [totalSteps, setTotalSteps] = useState(createMethod === CreateMethod.NEW ? 4 : 4); + const [projectData, setProjectData] = useState({ + id: '', + name: '', + folderPath: '', + url: '', + onlookEnabled: false, + createdAt: '', + updatedAt: '', }); const nextStep = () => setCurrentStep(currentStep + 1); - const prevStep = () => setCurrentStep(currentStep - 1); - - const handleSubmit = (e: FormEvent) => { - e.preventDefault(); - console.log('Form submitted:', formData); - setIsOpen(false); + const prevStep = () => { + if (currentStep === 0) { + setCreateMethod(null); + return; + } + setCurrentStep(currentStep - 1); }; const renderStep = () => { - if (currentStep === 0) { - return ( -
-
-

{'Projects'}

-

{openingMessage}

-
-
-
- { - setFormPath(FormPath.NEW); - nextStep(); - }} - > -
- -
-

- {' '} - {'New Onlook project'}{' '} -

-

{'Start a React App'}

-
- { - setFormPath(FormPath.LOAD); - nextStep(); - }} - > -
- -
-

- {'Import existing project'} -

-

- {'Work on your React UI'} -

-
-
-
-
- ); - } + const props: StepProps = { + projectData, + setProjectData, + currentStep, + totalSteps, + prevStep, + nextStep, + }; - if (formPath === FormPath.LOAD) { + if (createMethod === CreateMethod.LOAD) { + if (currentStep === 0) { + return ; + } if (currentStep === 1) { - return ; + return ; } if (currentStep === 2) { - return ; + return ; + } + } else if (createMethod === CreateMethod.NEW) { + if (currentStep === 0) { + return ; } - } else if (formPath === FormPath.NEW) { if (currentStep === 1) { - return ; + return ; } if (currentStep === 2) { - return ; + return ; + } + if (currentStep === 3) { + return ; } - } - - if (currentStep === 3) { - return ; } }; - return ( -
-
- {renderStep()} -
- {currentStep > 0 && ( - - )} - {currentStep > 0 && currentStep < 3 && ( - - )} - {currentStep === 3 && } -
-
-
- ); + return
{renderStep()}
; }; -const messages = [ - "Ready to make some good lookin' apps", - "What a week... right? Doesn't matter, let's build!", - "These apps aren't gunna design themselves", - 'Time to unleash your inner designer', - 'Release your inner artist today', - "Let's craft some beautiful UIs!", - "*crackles knuckles* Let's get building!", - 'Another day another design', - "Can't wait to see what you create!", - "Let's design something fresh today", - "Let's get to work", - "What time is it? It's time to build!", - "ಠ_ಠ Why aren't you designing? ಠ_ಠ", -]; - -const openingMessage = messages[Math.floor(Math.random() * messages.length)]; - export default CreateProject; diff --git a/app/src/routes/projects/ProjectsTab/Info.tsx b/app/src/routes/projects/ProjectsTab/Info.tsx deleted file mode 100644 index 3bc4ea3f..00000000 --- a/app/src/routes/projects/ProjectsTab/Info.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Button } from '@/components/ui/button'; -import { DotsVerticalIcon, Pencil2Icon } from '@radix-ui/react-icons'; -import { AnimatePresence, motion } from 'framer-motion'; -import { Project } from '.'; - -export function ProjectInfo({ - projects, - currentProjectIndex, - direction, -}: { - projects: Project[]; - currentProjectIndex: number; - direction: number; -}) { - const variants = { - enter: (direction: number) => ({ - y: direction > 0 ? 20 : -20, - opacity: 0, - }), - center: { - y: 0, - opacity: 1, - }, - exit: (direction: number) => ({ - y: direction < 0 ? 20 : -20, - opacity: 0, - }), - }; - - return ( - <> - - - {projects[currentProjectIndex].title} - - -
-

Last edited 3 days ago

-

localhost: 3000

-
-
- - -
- - ); -} diff --git a/app/src/routes/projects/ProjectsTab/Carousel.tsx b/app/src/routes/projects/ProjectsTab/Select/Carousel.tsx similarity index 96% rename from app/src/routes/projects/ProjectsTab/Carousel.tsx rename to app/src/routes/projects/ProjectsTab/Select/Carousel.tsx index e0db3c18..aa98f317 100644 --- a/app/src/routes/projects/ProjectsTab/Carousel.tsx +++ b/app/src/routes/projects/ProjectsTab/Select/Carousel.tsx @@ -1,7 +1,7 @@ import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; import useEmblaCarousel from 'embla-carousel-react'; import React, { useCallback, useEffect, useState } from 'react'; -import { Project } from '.'; +import { Project } from '/common/models/project'; interface EmblaCarouselProps { slides: Project[]; @@ -86,8 +86,8 @@ const EmblaCarousel: React.FC = ({ slides, onSlideChange }) }} > {slide.title} diff --git a/app/src/routes/projects/ProjectsTab/Select/Info.tsx b/app/src/routes/projects/ProjectsTab/Select/Info.tsx new file mode 100644 index 00000000..49d7e320 --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Select/Info.tsx @@ -0,0 +1,75 @@ +import { Button } from '@/components/ui/button'; +import { DotsVerticalIcon, Pencil2Icon } from '@radix-ui/react-icons'; +import { AnimatePresence, motion } from 'framer-motion'; +import { useEffect, useState } from 'react'; +import { timeSince } from '/common/helpers'; +import { Project } from '/common/models/project'; + +export function ProjectInfo({ + projects, + currentProjectIndex, + direction, +}: { + projects: Project[]; + currentProjectIndex: number; + direction: number; +}) { + const [project, setProject] = useState(null); + useEffect(() => { + setProject(projects[currentProjectIndex]); + }, [currentProjectIndex]); + + const variants = { + enter: (direction: number) => ({ + y: direction > 0 ? 20 : -20, + opacity: 0, + }), + center: { + y: 0, + opacity: 1, + }, + exit: (direction: number) => ({ + y: direction < 0 ? 20 : -20, + opacity: 0, + }), + }; + + return ( + project && ( + <> + + + {project.name} + + +
+

Last edited {timeSince(new Date(project.updatedAt))} ago

+

{project.url}

+
+
+ + +
+ + ) + ); +} diff --git a/app/src/routes/projects/ProjectsTab/Select/index.tsx b/app/src/routes/projects/ProjectsTab/Select/index.tsx new file mode 100644 index 00000000..6e37e33f --- /dev/null +++ b/app/src/routes/projects/ProjectsTab/Select/index.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react'; +import EmblaCarousel from './Carousel'; +import { ProjectInfo } from './Info'; +import { Project } from '/common/models/project'; + +function SelectProject({ projects }: { projects: Project[] }) { + const [currentProjectIndex, setCurrentProjectIndex] = useState(0); + const [direction, setDirection] = useState(0); + + const handleProjectChange = (index: number) => { + if (currentProjectIndex === index) { + return; + } + setDirection(index > currentProjectIndex ? 1 : -1); + setCurrentProjectIndex(index); + }; + return ( + <> +
+ +
+
+ +
+ + ); +} + +export default SelectProject; diff --git a/app/src/routes/projects/ProjectsTab/index.tsx b/app/src/routes/projects/ProjectsTab/index.tsx index d92d5345..6518f742 100644 --- a/app/src/routes/projects/ProjectsTab/index.tsx +++ b/app/src/routes/projects/ProjectsTab/index.tsx @@ -1,52 +1,50 @@ -import { useState } from 'react'; -import EmblaCarousel from './Carousel'; -import CreateProject from './Create'; -import { ProjectInfo } from './Info'; - -export interface Project { - id: number; - img: string; - title: string; -} - -export function ProjectsTab() { - const [currentProjectIndex, setCurrentProjectIndex] = useState(0); - const [direction, setDirection] = useState(0); +import { CreateMethod } from '..'; +import { ChooseMethod } from './Create/ChooseMethod'; +import SelectProject from './Select'; +import { Project } from '/common/models/project'; +export function ProjectsTab({ + setCreateMethod, +}: { + setCreateMethod: (method: CreateMethod | null) => void; +}) { const PROJECTS: Project[] = [ - // { id: 0, img: 'https://picsum.photos/id/237/200/300', title: 'Airbnb.com' }, - // { id: 1, img: 'https://picsum.photos/id/238/300/200', title: 'Netflix Clone' }, - // { id: 2, img: 'https://picsum.photos/id/239/500/500', title: 'Personal Portfolio' }, - // { id: 3, img: 'https://picsum.photos/id/240/100/1000', title: 'Amazon.com' }, - // { id: 4, img: 'https://picsum.photos/id/241/1000/100', title: 'X' }, - // { id: 5, img: 'https://picsum.photos/id/242/1000/1000', title: 'YC' }, + { + id: '0', + previewImg: 'https://picsum.photos/id/237/200/300', + name: 'Airbnb.com', + url: 'http://localhost:3000', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + onlookEnabled: false, + folderPath: '/path/to/folder', + }, + { + id: '1', + previewImg: 'https://picsum.photos/id/238/300/200', + name: 'Netflix Clone', + url: 'http://localhost:5371', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + onlookEnabled: true, + folderPath: '/path/to/folder', + }, + { + id: '2', + previewImg: 'https://picsum.photos/id/239/500/500', + name: 'Personal Portfolio', + url: 'http://localhost:8080', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + onlookEnabled: true, + folderPath: '/path/to/folder', + }, ]; - const handleProjectChange = (index: number) => { - if (currentProjectIndex === index) { - return; - } - setDirection(index > currentProjectIndex ? 1 : -1); - setCurrentProjectIndex(index); - }; - return ( -
- {PROJECTS.length === 0 && } - {PROJECTS.length > 0 && ( - <> -
- -
-
- -
- - )} -
+ <> + {PROJECTS.length === 0 && } + {PROJECTS.length > 0 && } + ); } diff --git a/app/src/routes/projects/SettingsTab/index.tsx b/app/src/routes/projects/SettingsTab/index.tsx index 54a40ff8..a22fdfa1 100644 --- a/app/src/routes/projects/SettingsTab/index.tsx +++ b/app/src/routes/projects/SettingsTab/index.tsx @@ -9,130 +9,125 @@ import { } from '@/components/ui/dropdown-menu'; import { ChevronDownIcon } from '@radix-ui/react-icons'; import { useState } from 'react'; +import { MainChannels } from '/common/constants'; +import { IDE } from '/common/ide'; export function SettingsTab() { const [analyticsActive, setAnalyticsActive] = useState(false); // State for analytics - // function updateIde(ide: IDE) { - // window.api.invoke(MainChannels.UPDATE_USER_SETTINGS, { ideType: ide.type }); - // setIde(ide); - // } + function updateIde(ide: IDE) { + window.api.invoke(MainChannels.UPDATE_USER_SETTINGS, { ideType: ide.type }); + // setIde(ide); + } return ( -
-
-
-

Settings

-

{openingMessage}

-
-
-
-

Editor

-
-

Default Code Editor

- - - - - - - - - VSCode Icon - - VSCode - - - - Cursor Icon - - Cursor - - - -
-
-
-

- Analytics -

-

- This helps our small team of two know what we need to improve - with the product. -

-
- - - - - - setAnalyticsActive(true)}> - Analytics On - - setAnalyticsActive(false)}> - Analytics Off - - - -
+
+
+

Settings

+

{openingMessage}

+
+
+
+

Editor

+
+

Default Code Editor

+ + + + + + + + VSCode Icon + + VSCode + + + + Cursor Icon + + Cursor + + +
-
-
-

Danger Zone

-
-
-

- Delete Account -

-

- We’ll delete all of your actions, your account, and connections - to your projects, but we won’t delete your React projects from - your computer. -

-
- +
+
+

+ Analytics +

+

+ This helps our small team of two know what we need to improve with + the product. +

+ + + + + + setAnalyticsActive(true)}> + {'Analytics On'} + + setAnalyticsActive(false)}> + {'Analytics Off'} + + +
-
-
-

Onlook Studio Version 1.4 •

-

- - Privacy Policy - -

-

and

-

- - Terms of Service - -

+
+
+
+

Danger Zone

+
+
+

+ {'Delete Account'} +

+

+ { + ' We’ll delete all of your actions, your account, and connections to your projects, but we won’t delete your React projects from your computer.' + } +

+
+
+
+
+

{'Onlook Studio Version 1.4 • '}

+

+ + {'Privacy Policy'} + +

+

{'and'}

+

+ + {'Terms of Service'} + +

+
); diff --git a/app/src/routes/projects/TopBar/index.tsx b/app/src/routes/projects/TopBar/index.tsx index cd5168bb..05728a26 100644 --- a/app/src/routes/projects/TopBar/index.tsx +++ b/app/src/routes/projects/TopBar/index.tsx @@ -8,22 +8,26 @@ import { } from '@/components/ui/dropdown-menu'; import { cn } from '@/lib/utils'; import { DownloadIcon, FilePlusIcon, PlusIcon } from '@radix-ui/react-icons'; -import { ProjectsPageTab } from '..'; +import { CreateMethod, ProjectsPageTab } from '..'; import ModeToggle from '../TopBar/ModeToggle'; export default function TopBar({ currentTab, setCurrentTab, + createMethod, + setCreateMethod, }: { currentTab: ProjectsPageTab; setCurrentTab: (tab: ProjectsPageTab) => void; + createMethod: CreateMethod | null; + setCreateMethod: (method: CreateMethod | null) => void; }) { return (
Onlook logo
- + {!createMethod && }
@@ -38,6 +42,7 @@ export default function TopBar({ 'focus:bg-blue-900 focus:text-blue-100', 'hover:bg-blue-900 hover:text-blue-100', )} + onSelect={() => setCreateMethod(CreateMethod.NEW)} > Start from scratch @@ -47,6 +52,7 @@ export default function TopBar({ 'focus:bg-teal-900 focus:text-teal-100', 'hover:bg-teal-900 hover:text-teal-100', )} + onSelect={() => setCreateMethod(CreateMethod.LOAD)} > Import existing project diff --git a/app/src/routes/projects/index.tsx b/app/src/routes/projects/index.tsx index 6145ee1e..369ab254 100644 --- a/app/src/routes/projects/index.tsx +++ b/app/src/routes/projects/index.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { ProjectsTab } from './ProjectsTab'; +import CreateProject from './ProjectsTab/Create'; import { SettingsTab } from './SettingsTab'; import TopBar from './TopBar'; @@ -8,14 +9,35 @@ export enum ProjectsPageTab { SETTINGS = 'settings', } +export enum CreateMethod { + LOAD = 'load', + NEW = 'new', +} + export default function Projects() { const [currentTab, setCurrentTab] = useState(ProjectsPageTab.PROJECTS); + const [createMethod, setCreateMethod] = useState(null); return ( -
- - {currentTab === ProjectsPageTab.PROJECTS && } - {currentTab === ProjectsPageTab.SETTINGS && } +
+ +
+ {createMethod ? ( + + ) : ( + <> + {currentTab === ProjectsPageTab.PROJECTS && ( + + )} + {currentTab === ProjectsPageTab.SETTINGS && } + + )} +
); }