Onboarding documented, begin consume existing components
This commit is contained in:
parent
dce9a61e6c
commit
7fbfdaf85e
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,3 +15,4 @@ packages/frontend/dist/
|
||||
|
||||
# TypeDoc generated documentation
|
||||
docs/
|
||||
.cursor/
|
||||
|
24
README.md
24
README.md
@ -20,20 +20,20 @@ yarn build --ignore frontend
|
||||
|
||||
### Environment variables, running the development server, and deployment
|
||||
|
||||
Follow the instructions in the README.md files of the [backend](packages/backend/README.md) and [frontend](packages/frontend/README.md) packages.
|
||||
Follow the instructions in the README.md files of the [backend](packages/backend/README.md) and
|
||||
[frontend](packages/frontend/README.md) packages.
|
||||
|
||||
## Documentation
|
||||
## Development Guidelines
|
||||
|
||||
This project uses TSDoc for code documentation and TypeDoc for generating API documentation.
|
||||
### Project Standards
|
||||
|
||||
### Generating Documentation
|
||||
We maintain a set of project-wide standards and conventions in the [standards](./standards)
|
||||
directory. These standards help ensure consistency across the codebase and make it easier for
|
||||
developers to collaborate.
|
||||
|
||||
To generate the API documentation, run:
|
||||
Current standards:
|
||||
|
||||
```zsh
|
||||
yarn docs
|
||||
```
|
||||
|
||||
This will create a `docs` directory with the generated documentation.
|
||||
|
||||
For more information about writing documentation, see [DOCUMENTATION.md](DOCUMENTATION.md).
|
||||
- [Component Documentation Standards](./standards/COMPONENT_DOCUMENTATION.md) - Guidelines for
|
||||
documenting components, hooks, and utilities
|
||||
- [Feature Building Process](./standards/FEATURE_BUILDING.md) - Standardized approach to building
|
||||
new features from design to implementation
|
||||
|
2
packages/frontend/.gitignore
vendored
2
packages/frontend/.gitignore
vendored
@ -25,3 +25,5 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
*storybook.log
|
||||
|
||||
.cursor/
|
@ -32,6 +32,7 @@
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
"@radix-ui/react-hover-card": "^1.1.6",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-menubar": "^1.1.6",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.5",
|
||||
@ -100,7 +101,8 @@
|
||||
"vaul": "^1.1.2",
|
||||
"viem": "^2.7.11",
|
||||
"web-vitals": "^2.1.4",
|
||||
"zod": "^3.24.2"
|
||||
"zod": "^3.24.2",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^1.3.3",
|
||||
|
@ -18,10 +18,10 @@ const router = createBrowserRouter([
|
||||
children: [
|
||||
// {
|
||||
// element: <ProjectSearchLayout />,
|
||||
// children: [
|
||||
// children: [`
|
||||
// {
|
||||
// path: '',
|
||||
// element: <Projects />,
|
||||
// element: <Projects />,`
|
||||
// },
|
||||
// {
|
||||
// path: 'projects',
|
||||
|
17
packages/frontend/src/app/layout.tsx
Normal file
17
packages/frontend/src/app/layout.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import type React from "react"
|
||||
import "@/styles/globals.css"
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="bg-zinc-900 text-white">
|
||||
<div className="flex min-h-screen">{children}</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
74
packages/frontend/src/app/page.tsx
Normal file
74
packages/frontend/src/app/page.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
OnboardingContainer,
|
||||
SidebarNav,
|
||||
StepHeader,
|
||||
StepNavigation,
|
||||
} from '@/components/onboarding-flow';
|
||||
import { ConfigureStep } from '@/components/onboarding-flow/configure-step/configure-step';
|
||||
import { ConnectStep } from '@/components/onboarding-flow/connect-step/connect-step';
|
||||
import { DeployStep } from '@/components/onboarding-flow/deploy-step/deploy-step';
|
||||
import { useOnboarding } from '@/components/onboarding-flow/store';
|
||||
import { FileCog, GitPullRequest, SquareArrowOutDownRight } from 'lucide-react';
|
||||
|
||||
/** Icons for each step in the onboarding flow */
|
||||
const stepIcons = {
|
||||
connect: <GitPullRequest className="h-6 w-6 stroke-2" />,
|
||||
configure: <FileCog className="h-6 w-6 stroke-2" />,
|
||||
deploy: <SquareArrowOutDownRight className="h-6 w-6 stroke-2" />,
|
||||
};
|
||||
|
||||
/** Titles for each step in the onboarding flow */
|
||||
const stepTitles = {
|
||||
connect: 'Connect',
|
||||
configure: 'Configure',
|
||||
deploy: 'Deploy',
|
||||
};
|
||||
|
||||
/** Descriptions for each step in the onboarding flow */
|
||||
const stepDescriptions = {
|
||||
connect: 'Connect and import a GitHub repository to start deploying.',
|
||||
configure: 'Set up your deployment configuration and environment variables.',
|
||||
deploy: 'Review your settings and deploy your project.',
|
||||
};
|
||||
|
||||
/**
|
||||
* Main onboarding page component
|
||||
* Orchestrates the entire onboarding flow and manages step transitions
|
||||
*
|
||||
* Component Hierarchy:
|
||||
* - OnboardingContainer
|
||||
* - SidebarNav (step progress)
|
||||
* - Main content
|
||||
* - StepHeader (current step info)
|
||||
* - Step content (ConnectStep | ConfigureStep | DeployStep)
|
||||
* - StepNavigation (previous/next controls)
|
||||
*
|
||||
* @returns {JSX.Element} Complete onboarding interface
|
||||
*/
|
||||
export default function Page() {
|
||||
const { currentStep, nextStep, previousStep } = useOnboarding();
|
||||
|
||||
return (
|
||||
<OnboardingContainer>
|
||||
<SidebarNav currentStep={currentStep} />
|
||||
<div className="flex-1 bg-primary-foreground rounded-lg p-8 shadow-[0_1px_2px_0_rgba(0,0,0,0.06),0_1px_3px_0_rgba(0,0,0,0.1)] flex flex-col">
|
||||
<StepHeader
|
||||
icon={stepIcons[currentStep]}
|
||||
title={stepTitles[currentStep]}
|
||||
description={stepDescriptions[currentStep]}
|
||||
/>
|
||||
<div className="flex-1 flex items-center justify-center py-8">
|
||||
{currentStep === 'connect' && <ConnectStep />}
|
||||
{currentStep === 'configure' && <ConfigureStep />}
|
||||
{currentStep === 'deploy' && <DeployStep />}
|
||||
</div>
|
||||
<StepNavigation
|
||||
currentStep={currentStep}
|
||||
onPrevious={previousStep}
|
||||
onNext={nextStep}
|
||||
nextLabel={currentStep === 'deploy' ? 'Deploy' : 'Next'}
|
||||
/>
|
||||
</div>
|
||||
</OnboardingContainer>
|
||||
);
|
||||
}
|
@ -9,6 +9,14 @@ export interface Option {
|
||||
label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the Dropdown component.
|
||||
* @interface DropdownProps
|
||||
* @property {Option[]} options - The list of options to display in the dropdown.
|
||||
* @property {(arg: ReactDropdownOption) => void} onChange - Callback fired when an option is selected.
|
||||
* @property {string} [placeholder] - Placeholder text for the dropdown.
|
||||
* @property {Option} [value] - The currently selected option.
|
||||
*/
|
||||
interface DropdownProps {
|
||||
options: Option[];
|
||||
onChange: (arg: ReactDropdownOption) => void;
|
||||
@ -16,6 +24,22 @@ interface DropdownProps {
|
||||
value?: Option;
|
||||
}
|
||||
|
||||
/**
|
||||
* A dropdown component that wraps the ReactDropdown library.
|
||||
*
|
||||
* @component
|
||||
* @param {DropdownProps} props - The props for the Dropdown component.
|
||||
* @returns {React.ReactElement} A dropdown element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Dropdown
|
||||
* options={[{ value: '1', label: 'One' }, { value: '2', label: 'Two' }]}
|
||||
* onChange={(option) => console.log(option)}
|
||||
* placeholder="Select an option"
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
const Dropdown = ({ placeholder, options, onChange, value }: DropdownProps) => {
|
||||
return (
|
||||
<ReactDropdown
|
||||
|
@ -2,11 +2,28 @@ import { cn } from '@/utils/classnames';
|
||||
import { Duration } from 'luxon';
|
||||
import { ComponentPropsWithoutRef } from 'react';
|
||||
|
||||
/**
|
||||
* Props for the FormatMillisecond component.
|
||||
* @interface FormatMilliSecondProps
|
||||
* @property {number} time - The time in milliseconds to format.
|
||||
*/
|
||||
export interface FormatMilliSecondProps
|
||||
extends ComponentPropsWithoutRef<'div'> {
|
||||
time: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that formats a given time in milliseconds into a human-readable format.
|
||||
*
|
||||
* @component
|
||||
* @param {FormatMilliSecondProps} props - The props for the FormatMillisecond component.
|
||||
* @returns {React.ReactElement} A formatted time element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <FormatMillisecond time={3600000} />
|
||||
* ```
|
||||
*/
|
||||
const FormatMillisecond = ({ time, ...props }: FormatMilliSecondProps) => {
|
||||
const formatTime = Duration.fromMillis(time)
|
||||
.shiftTo('days', 'hours', 'minutes', 'seconds')
|
||||
|
@ -1,3 +1,14 @@
|
||||
/**
|
||||
* A simple horizontal line component.
|
||||
*
|
||||
* @component
|
||||
* @returns {React.ReactElement} A horizontal line element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <HorizontalLine />
|
||||
* ```
|
||||
*/
|
||||
const HorizontalLine = () => {
|
||||
return <hr className="h-px bg-gray-100 border-0" />;
|
||||
};
|
||||
|
@ -1,9 +1,26 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* Props for the Logo component.
|
||||
* @interface LogoProps
|
||||
* @property {string} [orgSlug] - The organization slug used for the link.
|
||||
*/
|
||||
interface LogoProps {
|
||||
orgSlug?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that renders the Snowball logo with a link to the organization's page.
|
||||
*
|
||||
* @component
|
||||
* @param {LogoProps} props - The props for the Logo component.
|
||||
* @returns {React.ReactElement} A logo element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Logo orgSlug="my-organization" />
|
||||
* ```
|
||||
*/
|
||||
export const Logo = ({ orgSlug }: LogoProps) => {
|
||||
return (
|
||||
<Link to={`/${orgSlug}`}>
|
||||
|
@ -3,6 +3,18 @@ import React, { forwardRef, RefAttributes } from 'react';
|
||||
import { IconInput, type IconInputProps } from '@/components/ui/extended/input-w-icons';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* A search bar component with an icon input.
|
||||
*
|
||||
* @component
|
||||
* @param {InputProps & RefAttributes<HTMLInputElement>} props - The props for the SearchBar component.
|
||||
* @returns {React.ReactElement} A search bar element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <SearchBar value="search term" onChange={(e) => console.log(e.target.value)} />
|
||||
* ```
|
||||
*/
|
||||
const SearchBar: React.ForwardRefRenderFunction<
|
||||
HTMLInputElement,
|
||||
IconInputProps & RefAttributes<HTMLInputElement>
|
||||
|
@ -4,17 +4,42 @@ const COLOR_COMPLETED = '#059669';
|
||||
const COLOR_ACTIVE = '#CFE6FC';
|
||||
const COLOR_NOT_STARTED = '#F1F5F9';
|
||||
|
||||
/**
|
||||
* Represents a step in the stepper.
|
||||
* @interface StepperValue
|
||||
* @property {number} step - The step number.
|
||||
* @property {string} route - The route associated with the step.
|
||||
* @property {string} label - The label for the step.
|
||||
*/
|
||||
interface StepperValue {
|
||||
step: number;
|
||||
route: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the Stepper component.
|
||||
* @interface StepperProps
|
||||
* @property {number} activeStep - The currently active step.
|
||||
* @property {StepperValue[]} stepperValues - The values for each step.
|
||||
*/
|
||||
interface StepperProps {
|
||||
activeStep: number;
|
||||
stepperValues: StepperValue[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A stepper component that displays a series of steps with different states.
|
||||
*
|
||||
* @component
|
||||
* @param {StepperProps} props - The props for the Stepper component.
|
||||
* @returns {React.ReactElement} A stepper element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Stepper activeStep={1} stepperValues={[{ step: 1, route: '/step1', label: 'Step 1' }]} />
|
||||
* ```
|
||||
*/
|
||||
const Stepper = ({ activeStep, stepperValues }: StepperProps) => {
|
||||
return (
|
||||
<StepperNav
|
||||
|
@ -11,11 +11,29 @@ const setStopWatchOffset = (time: string) => {
|
||||
return currentTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the Stopwatch component.
|
||||
* @interface StopwatchProps
|
||||
* @property {Date} offsetTimestamp - The initial timestamp for the stopwatch.
|
||||
* @property {boolean} isPaused - Whether the stopwatch is paused.
|
||||
*/
|
||||
interface StopwatchProps extends Omit<FormatMilliSecondProps, 'time'> {
|
||||
offsetTimestamp: Date;
|
||||
isPaused: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A stopwatch component that tracks elapsed time.
|
||||
*
|
||||
* @component
|
||||
* @param {StopwatchProps} props - The props for the Stopwatch component.
|
||||
* @returns {React.ReactElement} A stopwatch element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Stopwatch offsetTimestamp={new Date()} isPaused={false} />
|
||||
* ```
|
||||
*/
|
||||
const Stopwatch = ({ offsetTimestamp, isPaused, ...props }: StopwatchProps) => {
|
||||
const { totalSeconds, pause, start } = useStopwatch({
|
||||
autoStart: true,
|
||||
@ -29,4 +47,4 @@ const Stopwatch = ({ offsetTimestamp, isPaused, ...props }: StopwatchProps) => {
|
||||
return <FormatMillisecond time={totalSeconds * 1000} {...props} />;
|
||||
};
|
||||
|
||||
export { Stopwatch, setStopWatchOffset };
|
||||
export { setStopWatchOffset, Stopwatch };
|
||||
|
@ -3,6 +3,14 @@ import * as CSS from 'csstype';
|
||||
//
|
||||
// Nav
|
||||
//
|
||||
/**
|
||||
* Describes a step in the stepper navigation.
|
||||
* @interface IStepDescription
|
||||
* @property {() => JSX.Element} stepContent - The content of the step.
|
||||
* @property {string} [stepStateColor] - The color representing the step's state.
|
||||
* @property {number} [stepStatusCircleSize] - The size of the status circle.
|
||||
* @property {() => void} [onClickHandler] - Handler for click events on the step.
|
||||
*/
|
||||
export interface IStepDescription {
|
||||
stepContent: () => JSX.Element;
|
||||
stepStateColor?: string;
|
||||
@ -10,10 +18,27 @@ export interface IStepDescription {
|
||||
onClickHandler?: () => void | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the StepperNav component.
|
||||
* @interface IStepperNavProps
|
||||
* @property {IStepDescription[]} steps - The steps to display in the navigation.
|
||||
*/
|
||||
export interface IStepperNavProps {
|
||||
steps: IStepDescription[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A navigation component for displaying steps in a vertical layout.
|
||||
*
|
||||
* @component
|
||||
* @param {IStepperNavProps} props - The props for the StepperNav component.
|
||||
* @returns {React.ReactElement} A stepper navigation element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <StepperNav steps={[{ stepContent: () => <div>Step 1</div> }]} />
|
||||
* ```
|
||||
*/
|
||||
export const StepperNav = (props: IStepperNavProps): JSX.Element => {
|
||||
return (
|
||||
<nav>
|
||||
|
@ -9,10 +9,29 @@ import {
|
||||
TabsTrigger,
|
||||
} from '../layout';
|
||||
|
||||
/**
|
||||
* Props for the ExamplePage component.
|
||||
* @interface ExamplePageProps
|
||||
* @property {ReactNode} [children] - The content to display within the example page.
|
||||
*/
|
||||
interface ExamplePageProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* An example page component demonstrating the standard layout components.
|
||||
*
|
||||
* @component
|
||||
* @param {ExamplePageProps} props - The props for the ExamplePage component.
|
||||
* @returns {React.ReactElement} An example page element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ExamplePage>
|
||||
* <div>Custom content here</div>
|
||||
* </ExamplePage>
|
||||
* ```
|
||||
*/
|
||||
export function ExamplePage({ children }: ExamplePageProps) {
|
||||
return (
|
||||
<NavigationWrapper>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { TopNavigation } from './TopNavigation';
|
||||
import { useGitHubAuth } from '@/hooks/useGitHubAuth';
|
||||
|
||||
/**
|
||||
* NavigationWrapperProps interface extends React.HTMLProps<HTMLDivElement> to include all standard HTML div attributes.
|
||||
@ -22,6 +23,8 @@ export function NavigationWrapper({
|
||||
className,
|
||||
...props
|
||||
}: NavigationWrapperProps) {
|
||||
const { isAuthenticated } = useGitHubAuth();
|
||||
|
||||
return (
|
||||
<div className={cn('min-h-screen bg-background', className)} {...props}>
|
||||
<TopNavigation />
|
||||
|
@ -13,6 +13,10 @@ import { WalletSessionId } from './components/WalletSessionId';
|
||||
/**
|
||||
* TopNavigation Component
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=299-1294&m=dev
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=299-1294&t=LEaCSA9ND6svuP3P-4
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=298-242&t=LEaCSA9ND6svuP3P-4
|
||||
* https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=299-1294&m=dev
|
||||
* Renders the top navigation bar, adapting its layout for desktop and mobile views.
|
||||
* It includes the project search bar, navigation links, user authentication via GitHub,
|
||||
* color mode toggle, and wallet session information. On mobile, it utilizes a sheet
|
||||
|
117
packages/frontend/src/components/onboarding-flow/OPTIMIZATION.md
Normal file
117
packages/frontend/src/components/onboarding-flow/OPTIMIZATION.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Onboarding Flow Optimization Recommendations
|
||||
|
||||
Based on the analysis of the current implementation and comparison with the Figma designs, here are recommendations for optimizing the structure and documentation of the onboarding flow components.
|
||||
|
||||
## Implemented Improvements ✅
|
||||
|
||||
1. **Consistent File Naming**
|
||||
|
||||
- ✅ Renamed `ConnectDeployFirstApp.tsx` to `connect-deploy-first-app.tsx` to maintain kebab-case convention
|
||||
- ✅ All component files now follow the same naming pattern
|
||||
|
||||
2. **Step Organization**
|
||||
|
||||
- ✅ Each step directory now has an `index.ts` file that exports all components
|
||||
- ✅ The connect, configure, and deploy steps follow the same pattern
|
||||
|
||||
3. **Common Components**
|
||||
|
||||
- ✅ Moved shared UI elements to a `common/` subdirectory
|
||||
- ✅ These include `background-svg.tsx`, `laconic-icon-lettering.tsx`, etc.
|
||||
|
||||
4. **Types Extraction**
|
||||
|
||||
- ✅ Created a separate `types.ts` file for shared interfaces and types
|
||||
- ✅ Moved the `Step` type and related interfaces from `store.ts` to this file
|
||||
|
||||
5. **Directory Structure**
|
||||
|
||||
- ✅ Reorganized into a more logical structure with `common/`, `sidebar/`, and step-specific directories
|
||||
- ✅ Created appropriate index.ts files to simplify imports
|
||||
|
||||
6. **Documentation Standards**
|
||||
- ✅ Created project-wide documentation standards in `/standards/COMPONENT_DOCUMENTATION.md`
|
||||
|
||||
## Remaining Recommendations 🚧
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
1. **Component Documentation**
|
||||
|
||||
- [ ] Apply JSDoc pattern to all components following the project standards in `/standards/COMPONENT_DOCUMENTATION.md`
|
||||
- [ ] Include description, props interface, examples, and component hierarchy
|
||||
|
||||
2. **Figma References**
|
||||
|
||||
- [ ] Add specific Figma node IDs to each component file
|
||||
- [ ] Example: `/** @see https://www.figma.com/file/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500 */`
|
||||
|
||||
3. **Props Documentation**
|
||||
- [ ] Document all props with JSDoc comments in each component
|
||||
- [ ] Include type information, description, and default values
|
||||
|
||||
### Implementation Recommendations
|
||||
|
||||
1. **State Management**
|
||||
|
||||
- [ ] Add form validation to the store
|
||||
- [ ] Consider adding persistence for the onboarding flow state
|
||||
|
||||
2. **Component Splitting**
|
||||
|
||||
- [ ] Break down large components into smaller, focused ones
|
||||
- [ ] For example, separate form elements in the configure step
|
||||
|
||||
3. **Progressive Enhancement**
|
||||
|
||||
- [ ] Implement loading states for asynchronous operations
|
||||
- [ ] Add error handling for API requests
|
||||
- [ ] Add animations for transitions between steps
|
||||
|
||||
4. **Accessibility**
|
||||
- [ ] Ensure all interactive elements have proper ARIA attributes
|
||||
- [ ] Add keyboard navigation
|
||||
- [ ] Test with screen readers
|
||||
|
||||
## Current Directory Structure
|
||||
|
||||
```
|
||||
onboarding-flow/
|
||||
├── README.md
|
||||
├── OPTIMIZATION.md
|
||||
├── index.ts
|
||||
├── types.ts
|
||||
├── store.ts
|
||||
├── Onboarding.tsx
|
||||
├── common/
|
||||
│ ├── index.ts
|
||||
│ ├── background-svg.tsx
|
||||
│ ├── laconic-icon-lettering.tsx
|
||||
│ ├── onboarding-container.tsx
|
||||
│ ├── step-header.tsx
|
||||
│ └── step-navigation.tsx
|
||||
├── sidebar/
|
||||
│ ├── index.ts
|
||||
│ └── sidebar-nav.tsx
|
||||
├── connect-step/
|
||||
│ ├── index.ts
|
||||
│ ├── connect-step.tsx
|
||||
│ ├── connect-button.tsx
|
||||
│ ├── connect-initial.tsx
|
||||
│ ├── connect-deploy-first-app.tsx
|
||||
│ ├── repository-list.tsx
|
||||
│ └── template-list.tsx
|
||||
├── configure-step/
|
||||
│ ├── index.ts
|
||||
│ └── configure-step.tsx
|
||||
└── deploy-step/
|
||||
├── index.ts
|
||||
└── deploy-step.tsx
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Complete the documentation of all components using the project-wide standards
|
||||
2. Implement the missing features identified in the README.md
|
||||
3. Consider adding more specialized components for the configure and deploy steps
|
||||
4. Add unit tests and Storybook stories for all components
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @component Onboarding
|
||||
* @description Main component that orchestrates the onboarding flow
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
import {
|
||||
OnboardingContainer,
|
||||
StepHeader,
|
||||
StepNavigation,
|
||||
} from '@/components/onboarding-flow/common';
|
||||
import { ConfigureStep } from '@/components/onboarding-flow/configure-step';
|
||||
import { ConnectStep } from '@/components/onboarding-flow/connect-step';
|
||||
import { DeployStep } from '@/components/onboarding-flow/deploy-step';
|
||||
import { SidebarNav } from '@/components/onboarding-flow/sidebar';
|
||||
import { useOnboarding } from '@/components/onboarding-flow/store';
|
||||
import { FileCog, GitPullRequest, SquareArrowOutDownRight } from 'lucide-react';
|
||||
|
||||
/** Icons for each step in the onboarding flow */
|
||||
const stepIcons = {
|
||||
connect: <GitPullRequest className="h-6 w-6 stroke-2" />,
|
||||
configure: <FileCog className="h-6 w-6 stroke-2" />,
|
||||
deploy: <SquareArrowOutDownRight className="h-6 w-6 stroke-2" />,
|
||||
};
|
||||
|
||||
/** Titles for each step in the onboarding flow */
|
||||
const stepTitles = {
|
||||
connect: 'Connect',
|
||||
configure: 'Configure',
|
||||
deploy: 'Deploy',
|
||||
};
|
||||
|
||||
/** Descriptions for each step in the onboarding flow */
|
||||
const stepDescriptions = {
|
||||
connect: 'Connect and import a GitHub repository to start deploying.',
|
||||
configure: 'Set up your deployment configuration and environment variables.',
|
||||
deploy: 'Review your settings and deploy your project.',
|
||||
};
|
||||
|
||||
/**
|
||||
* Main onboarding page component
|
||||
* Orchestrates the entire onboarding flow and manages step transitions
|
||||
*
|
||||
* Component Hierarchy:
|
||||
* - OnboardingContainer
|
||||
* - SidebarNav (step progress)
|
||||
* - Main content
|
||||
* - StepHeader (current step info)
|
||||
* - Step content (ConnectStep | ConfigureStep | DeployStep)
|
||||
* - StepNavigation (previous/next controls)
|
||||
*
|
||||
* @returns {JSX.Element} Complete onboarding interface
|
||||
*/
|
||||
export default function Onboarding() {
|
||||
const { currentStep, nextStep, previousStep } = useOnboarding();
|
||||
|
||||
return (
|
||||
<OnboardingContainer>
|
||||
<SidebarNav currentStep={currentStep} />
|
||||
<div className="flex-1 bg-primary-foreground rounded-lg p-8 shadow-[0_1px_2px_0_rgba(0,0,0,0.06),0_1px_3px_0_rgba(0,0,0,0.1)] flex flex-col">
|
||||
<StepHeader
|
||||
icon={stepIcons[currentStep]}
|
||||
title={stepTitles[currentStep]}
|
||||
description={stepDescriptions[currentStep]}
|
||||
/>
|
||||
<div className="flex-1 flex items-center justify-center py-8">
|
||||
{currentStep === 'connect' && <ConnectStep />}
|
||||
{currentStep === 'configure' && <ConfigureStep />}
|
||||
{currentStep === 'deploy' && <DeployStep />}
|
||||
</div>
|
||||
<StepNavigation
|
||||
currentStep={currentStep}
|
||||
onPrevious={previousStep}
|
||||
onNext={nextStep}
|
||||
nextLabel={currentStep === 'deploy' ? 'Deploy' : 'Next'}
|
||||
/>
|
||||
</div>
|
||||
</OnboardingContainer>
|
||||
);
|
||||
}
|
109
packages/frontend/src/components/onboarding-flow/README.md
Normal file
109
packages/frontend/src/components/onboarding-flow/README.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Laconic Onboarding Flow
|
||||
|
||||
This directory contains the components that make up the onboarding flow for the Laconic platform. The onboarding process guides users through connecting their GitHub repository, configuring their deployment settings, and deploying their application.
|
||||
|
||||
## 📝 Figma Design Reference
|
||||
|
||||
The design for this component is available at:
|
||||
[Laconic Design - Onboarding Flow](https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev)
|
||||
|
||||
## 🏗️ Component Structure
|
||||
|
||||
The onboarding flow is structured as a multi-step wizard with three main steps:
|
||||
|
||||
1. **Connect** - Allows users to connect to GitHub and select a repository
|
||||
2. **Configure** - Lets users configure deployment settings and environment variables
|
||||
3. **Deploy** - Finalizes the setup and deploys the application
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
onboarding-flow/
|
||||
├── README.md # This file
|
||||
├── OPTIMIZATION.md # Optimization recommendations
|
||||
├── DOCUMENTATION_TEMPLATE.md # Documentation templates
|
||||
├── index.ts # Main exports
|
||||
├── types.ts # Shared types
|
||||
├── store.ts # Zustand store for state management
|
||||
├── Onboarding.tsx # Main component that orchestrates the flow
|
||||
├── common/ # Shared components
|
||||
│ ├── index.ts # Exports for common components
|
||||
│ ├── background-svg.tsx # Background visual element
|
||||
│ ├── laconic-icon-lettering.tsx # Laconic branding component
|
||||
│ ├── onboarding-container.tsx # Container layout for the onboarding UI
|
||||
│ ├── step-header.tsx # Header for each step
|
||||
│ └── step-navigation.tsx # Navigation controls (prev/next buttons)
|
||||
├── sidebar/ # Sidebar components
|
||||
│ ├── index.ts # Exports for sidebar components
|
||||
│ └── sidebar-nav.tsx # Sidebar showing all steps and progress
|
||||
├── connect-step/ # Components for the Connect step
|
||||
│ ├── index.ts # Exports for Connect step
|
||||
│ ├── connect-step.tsx # Main component for the Connect step
|
||||
│ ├── connect-button.tsx # GitHub connection button
|
||||
│ ├── connect-initial.tsx # Initial Connect screen
|
||||
│ ├── connect-deploy-first-app.tsx # First app deployment guidance
|
||||
│ ├── repository-list.tsx # List of GitHub repositories
|
||||
│ └── template-list.tsx # List of available templates
|
||||
├── configure-step/ # Components for the Configure step
|
||||
│ ├── index.ts # Exports for Configure step
|
||||
│ └── configure-step.tsx # Main component for the Configure step
|
||||
└── deploy-step/ # Components for the Deploy step
|
||||
├── index.ts # Exports for Deploy step
|
||||
└── deploy-step.tsx # Main component for the Deploy step
|
||||
```
|
||||
|
||||
## 🔄 State Management
|
||||
|
||||
The onboarding flow uses Zustand for state management. The store is defined in `store.ts` and exposed through the `useOnboarding` hook.
|
||||
|
||||
## ✅ Completed Items
|
||||
|
||||
- ✅ Basic component structure following the Figma design
|
||||
- ✅ Step navigation with progress indicators
|
||||
- ✅ Sidebar navigation showing all steps
|
||||
- ✅ Connect step UI with repository and template selection
|
||||
- ✅ Configure step with deployment type selection
|
||||
- ✅ Deploy step with deployment status
|
||||
- ✅ Organized directory structure
|
||||
- ✅ Consistent file naming conventions
|
||||
- ✅ Centralized type definitions
|
||||
- ✅ Main documentation and README
|
||||
|
||||
## 🚧 To-Do Items
|
||||
|
||||
- [ ] Complete JSDoc documentation for all components
|
||||
- [ ] Implement actual GitHub API integration for repository fetching
|
||||
- [ ] Add form validation for configuration inputs
|
||||
- [ ] Implement actual deployment functionality
|
||||
- [ ] Add error handling for API requests
|
||||
- [ ] Add loading states for asynchronous operations
|
||||
- [ ] Implement responsive design for mobile devices
|
||||
- [ ] Add unit tests for all components
|
||||
- [ ] Add Storybook stories for component documentation
|
||||
- [ ] Implement animations for step transitions
|
||||
|
||||
## 📋 Usage Guidelines
|
||||
|
||||
To use the onboarding flow in your application:
|
||||
|
||||
```tsx
|
||||
import { Onboarding } from '@/components/onboarding-flow';
|
||||
|
||||
export default function OnboardingPage() {
|
||||
return <Onboarding />;
|
||||
}
|
||||
```
|
||||
|
||||
The onboarding flow is self-contained and manages its own state through the Zustand store.
|
||||
|
||||
## 🧩 Component Hierarchy
|
||||
|
||||
```
|
||||
Onboarding
|
||||
├── OnboardingContainer
|
||||
│ ├── SidebarNav
|
||||
│ └── Main Content
|
||||
│ ├── StepHeader
|
||||
│ ├── Step Content (ConnectStep | ConfigureStep | DeployStep)
|
||||
│ └── StepNavigation
|
||||
```
|
@ -0,0 +1,13 @@
|
||||
export function BackgroundSVG() {
|
||||
return (
|
||||
<svg width="268" height="270" viewBox="0 0 268 270" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M22.272 155.661C56.6042 121.33 77.8555 73.9178 77.8459 21.5708C77.8586 14.4654 77.4662 7.43923 76.6865 0.5L-27.5 0.509485L-27.4968 200.691C-27.5063 224.957 -18.2546 249.229 0.250641 267.733C18.7573 286.238 43.0469 295.501 67.3161 295.49L267.5 295.5L267.493 191.299C260.569 190.536 253.542 190.141 246.421 190.139C194.088 190.147 146.674 211.395 112.341 245.726C87.3587 270.083 47.3035 270.087 22.6295 245.413C-2.02868 220.754 -2.03989 180.687 22.272 155.661ZM245.89 22.1623C217.117 -6.60845 170.373 -6.61953 141.591 22.1623C112.809 50.9426 112.82 97.687 141.591 126.456C170.379 155.242 217.108 155.238 245.89 126.456C274.671 97.6759 274.677 50.9474 245.89 22.1623Z"
|
||||
fill="#27272A"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Common components for the onboarding flow
|
||||
*
|
||||
* This module exports shared components used across the onboarding flow.
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
export { BackgroundSVG } from './background-svg';
|
||||
export { LaconicIconLettering } from './laconic-icon-lettering';
|
||||
export { OnboardingContainer } from './onboarding-container';
|
||||
export { StepHeader } from './step-header';
|
||||
export { StepNavigation } from './step-navigation';
|
@ -0,0 +1,42 @@
|
||||
export function LaconicIconLettering() {
|
||||
return (
|
||||
<svg width="115" height="20" viewBox="0 0 115 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_498_4195)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z" fill="currentColor" />
|
||||
<path
|
||||
d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3814 1.07196 60.6968 1.07196C56.817 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.817 18.9291 60.6968 18.9291C64.3814 18.9291 65.9636 16.8901 65.9853 12.1468H62.9509C62.9293 15.8599 62.4741 16.7828 60.6968 16.7828C58.6594 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.681 3.19678 60.6968 3.21823C62.4741 3.21823 62.9292 4.18413 62.9292 8.06885Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z" fill="currentColor" />
|
||||
<path
|
||||
d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_498_4195">
|
||||
<rect width="115" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
import type React from "react"
|
||||
|
||||
/**
|
||||
* Props for the OnboardingContainer component
|
||||
* @interface OnboardingContainerProps
|
||||
* @property {React.ReactNode} children - The content to be rendered inside the container
|
||||
*/
|
||||
interface OnboardingContainerProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* A container component that provides the layout structure for the onboarding flow
|
||||
*
|
||||
* @component
|
||||
* @example
|
||||
* ```tsx
|
||||
* <OnboardingContainer>
|
||||
* <SidebarNav currentStep={currentStep} />
|
||||
* <MainContent />
|
||||
* </OnboardingContainer>
|
||||
* ```
|
||||
*
|
||||
* @param {OnboardingContainerProps} props - Component props
|
||||
* @returns {JSX.Element} A container with consistent sizing and layout
|
||||
*/
|
||||
export function OnboardingContainer({ children }: OnboardingContainerProps) {
|
||||
return (
|
||||
<div className="min-h-screen w-full bg-background flex items-center justify-center p-8 relative overflow-hidden">
|
||||
<div className="flex gap-6 w-full max-w-[1200px] min-h-[700px] relative z-10">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
import type React from "react"
|
||||
|
||||
/**
|
||||
* Props for the StepHeader component
|
||||
* @interface StepHeaderProps
|
||||
* @property {React.ReactNode} icon - The icon to display next to the header
|
||||
* @property {string} title - The title of the current step
|
||||
* @property {string} description - A brief description of the current step
|
||||
*/
|
||||
interface StepHeaderProps {
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the header for the current onboarding step
|
||||
* Used at the top of each step's content area
|
||||
*
|
||||
* @component
|
||||
* @example
|
||||
* ```tsx
|
||||
* <StepHeader
|
||||
* icon={<GitPullRequest />}
|
||||
* title="Connect"
|
||||
* description="Connect and import a GitHub repository"
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @param {StepHeaderProps} props - Component props
|
||||
* @returns {JSX.Element} A header with icon, title, and description
|
||||
*/
|
||||
export function StepHeader({ icon, title, description }: StepHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-3.5 mb-8">
|
||||
<div className="flex items-center justify-center w-11 h-11 bg-foreground rounded-lg p-2.5">
|
||||
<div className="text-accent">{icon}</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl font-semibold text-foreground">{title}</h1>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Step } from '../types';
|
||||
|
||||
/**
|
||||
* Props for the StepNavigation component
|
||||
* @interface StepNavigationProps
|
||||
* @property {Step} currentStep - The current active step in the onboarding flow
|
||||
* @property {() => void} [onPrevious] - Callback function for the previous button
|
||||
* @property {() => void} [onNext] - Callback function for the next button
|
||||
* @property {string} [nextLabel] - Custom label for the next button
|
||||
*/
|
||||
interface StepNavigationProps {
|
||||
currentStep: Step;
|
||||
onPrevious?: () => void;
|
||||
onNext?: () => void;
|
||||
nextLabel?: string;
|
||||
}
|
||||
|
||||
/** Order of steps in the onboarding flow */
|
||||
const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy'];
|
||||
|
||||
/**
|
||||
* Navigation component for moving between onboarding steps
|
||||
* Displays progress indicators and previous/next buttons
|
||||
*
|
||||
* @component
|
||||
* @example
|
||||
* ```tsx
|
||||
* <StepNavigation
|
||||
* currentStep="connect"
|
||||
* onPrevious={handlePrevious}
|
||||
* onNext={handleNext}
|
||||
* nextLabel="Continue"
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @param {StepNavigationProps} props - Component props
|
||||
* @returns {JSX.Element} Navigation controls with progress indicators
|
||||
*/
|
||||
export function StepNavigation({
|
||||
currentStep,
|
||||
onPrevious,
|
||||
onNext,
|
||||
nextLabel = 'Next',
|
||||
}: StepNavigationProps) {
|
||||
const currentStepIndex = STEP_ORDER.indexOf(currentStep);
|
||||
const totalSteps = STEP_ORDER.length;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between mt-8">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onPrevious}
|
||||
disabled={currentStepIndex === 0}
|
||||
className={cn(
|
||||
'text-foreground hover:text-foreground/80',
|
||||
currentStepIndex === 0 &&
|
||||
'text-muted-foreground hover:text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
{STEP_ORDER.map((step, index) => (
|
||||
<div
|
||||
key={step}
|
||||
className={cn(
|
||||
'h-1 w-8 rounded-full transition-colors',
|
||||
index === currentStepIndex ? 'bg-zinc-800' : 'bg-zinc-300',
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Button onClick={onNext} disabled={currentStepIndex === totalSteps - 1}>
|
||||
{currentStepIndex === totalSteps - 1 ? 'Deploy' : nextLabel}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import { useOnboarding } from "@/components/onboarding-flow/store"
|
||||
import { FileCog } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
|
||||
/**
|
||||
* Second step in the onboarding flow
|
||||
* Handles deployment configuration and environment setup
|
||||
*
|
||||
* Features:
|
||||
* - Deployment type selection (auction/LRN)
|
||||
* - Environment variable configuration
|
||||
* - Account selection
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
export function ConfigureStep() {
|
||||
const { formData, setFormData } = useOnboarding()
|
||||
const [activeTab, setActiveTab] = useState<"create-auction" | "deployer-lrn">("create-auction")
|
||||
const [environments, setEnvironments] = useState({
|
||||
production: false,
|
||||
preview: false,
|
||||
development: false,
|
||||
})
|
||||
|
||||
// const handleEnvironmentChange = (env: keyof typeof environments) => {
|
||||
// setEnvironments((prev) => ({
|
||||
// ...prev,
|
||||
// [env]: !prev[env],
|
||||
// }))
|
||||
// setFormData({
|
||||
// environmentVars: {
|
||||
// ...formData.environmentVars,
|
||||
// [env]: !environments[env],
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full max-w-[445px] mx-auto">
|
||||
<div className="w-full flex flex-col items-center gap-6">
|
||||
{/* Header section with icon and description */}
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<FileCog className="w-16 h-16 text-foreground" />
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<h2 className="text-2xl font-bold text-foreground">Configure</h2>
|
||||
<p className="text-base text-muted-foreground text-center">
|
||||
Set the deployer LRN for a single deployment or by creating a deployer auction for multiple deployments
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Content sections will be placed here:
|
||||
1. Deployment type tabs (auction/LRN)
|
||||
2. Configuration forms
|
||||
3. Environment variables
|
||||
4. Account selection
|
||||
|
||||
...content here/
|
||||
{/* <Configure/> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Configure step components for the onboarding flow
|
||||
*
|
||||
* This module exports components related to the Configure step of the onboarding flow.
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
export { ConfigureStep } from './configure-step';
|
@ -0,0 +1,52 @@
|
||||
import { Button, ButtonProps } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Github } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
|
||||
interface ConnectButtonProps extends Omit<ButtonProps, 'leftIcon'> {
|
||||
/**
|
||||
* Optional override for the icon
|
||||
*/
|
||||
icon?: React.ReactNode;
|
||||
/**
|
||||
* Optional label for the button
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A specialized button for the Connect step based on Figma design
|
||||
* Used for connecting to services like GitHub
|
||||
*
|
||||
* Figma Design Reference:
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=498-3285&m=dev
|
||||
*/
|
||||
export function ConnectButton({
|
||||
className,
|
||||
icon = <Github />,
|
||||
label = 'Connect to GitHub',
|
||||
children = label,
|
||||
variant = 'outline',
|
||||
size = 'lg',
|
||||
...props
|
||||
}: ConnectButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
'font-medium rounded-md transition-all duration-200',
|
||||
'bg-white text-foreground',
|
||||
'focus:ring-2 focus:ring-primary/25 focus:ring-offset-2',
|
||||
'border border-zinc-200',
|
||||
'py-2 px-4 h-10',
|
||||
'flex items-center justify-center gap-2',
|
||||
className,
|
||||
)}
|
||||
variant={variant}
|
||||
size={size}
|
||||
leftIcon={icon}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* This component is used to connect the first account to the user's GitHub account.
|
||||
* Should only show if not connected and have no projects
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=498-3262&m=dev
|
||||
*/
|
||||
import { Shapes } from 'lucide-react';
|
||||
import { ConnectButton } from './connect-button';
|
||||
|
||||
export default function ConnectDeployFirstApp() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-6 w-full max-w-[573px]">
|
||||
<div className="flex flex-col items-center justify-center gap-6 px-16 w-full">
|
||||
<div className="flex flex-col items-center gap-6 w-full max-w-[445px]">
|
||||
<div className="w-16 h-16">
|
||||
<Shapes className="w-full h-full text-foreground" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<h2 className="text-2xl font-bold text-foreground text-center leading-8">
|
||||
Deploy your first app
|
||||
</h2>
|
||||
<p className="text-base text-muted-foreground text-center leading-6">
|
||||
Once connected, you can import a repository from your account or
|
||||
start with one of our templates.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ConnectButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2.5 w-[276px]">
|
||||
<div className="flex-1 h-1.5 rounded-full bg-primary" />
|
||||
<div className="flex-1 h-1.5 rounded-full bg-muted-foreground/30" />
|
||||
<div className="flex-1 h-1.5 rounded-full bg-muted-foreground/30" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { GitHubLogoIcon } from "@radix-ui/react-icons"
|
||||
|
||||
interface ConnectInitialProps {
|
||||
onConnect: () => void
|
||||
}
|
||||
|
||||
export function ConnectInitial({ onConnect }: ConnectInitialProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center max-w-md mx-auto text-center">
|
||||
<div className="mb-6">
|
||||
// TODO: use lucide icon gitpullrequest in place of svg
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="24" cy="12" r="4" fill="currentColor" />
|
||||
<circle cx="12" cy="36" r="4" fill="currentColor" />
|
||||
<circle cx="36" cy="36" r="4" fill="currentColor" />
|
||||
<line x1="24" y1="16" x2="14" y2="33" stroke="currentColor" strokeWidth="2" />
|
||||
<line x1="24" y1="16" x2="34" y2="33" stroke="currentColor" strokeWidth="2" />
|
||||
<line x1="14" y1="36" x2="34" y2="36" stroke="currentColor" strokeWidth="2" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold mb-3 text-foreground">Deploy your first app</h2>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
Once connected, you can import a repository from your account or start with one of our templates.
|
||||
</p>
|
||||
<Button onClick={onConnect} className="gap-2">
|
||||
<GitHubLogoIcon className="h-4 w-4" />
|
||||
Connect to GitHub
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { useOnboarding } from '@/components/onboarding-flow/store';
|
||||
import ConnectAccount from '@/components/projects/create/ConnectAccount';
|
||||
import { useState } from 'react';
|
||||
import { ConnectButton } from './connect-button';
|
||||
|
||||
type ConnectState = 'initial' | 'repository-select' | 'template-select';
|
||||
|
||||
/**
|
||||
* First step in the onboarding flow
|
||||
* Handles GitHub connection and repository selection
|
||||
*
|
||||
* States:
|
||||
* - initial: Shows GitHub connect button
|
||||
* - repository-select: Shows list of repositories
|
||||
* - template-select: Shows available templates
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
export function ConnectStep() {
|
||||
const [connectState, setConnectState] = useState<ConnectState>('initial');
|
||||
const [projectName, setProjectName] = useState('');
|
||||
const { setFormData, nextStep } = useOnboarding();
|
||||
|
||||
const handleConnect = () => {
|
||||
setConnectState('repository-select');
|
||||
};
|
||||
|
||||
const handleRepositorySelect = (repo: { name: string }) => {
|
||||
setFormData({ githubRepo: repo.name });
|
||||
nextStep();
|
||||
};
|
||||
|
||||
const handleTemplateSelect = (template: { id: string; name: string }) => {
|
||||
setFormData({
|
||||
githubRepo: projectName,
|
||||
deploymentType: template.id,
|
||||
});
|
||||
nextStep();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
{/* <ConnectAccountTabPanel />\ */}
|
||||
{connectState === 'initial' ? (
|
||||
<div className="flex flex-col items-center justify-center gap-6 p-8">
|
||||
<h2 className="text-2xl font-semibold text-center">
|
||||
Connect to GitHub
|
||||
</h2>
|
||||
<p className="text-center text-muted-foreground">
|
||||
Connect your GitHub account to get started
|
||||
</p>
|
||||
<ConnectButton onClick={handleConnect} />
|
||||
</div>
|
||||
) : (
|
||||
<ConnectAccount onAuth={() => {}} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Connect step components for the onboarding flow
|
||||
*
|
||||
* This module exports components related to the Connect step of the onboarding flow.
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
export * from './connect-button';
|
||||
export * from './connect-deploy-first-app';
|
||||
export * from './connect-initial';
|
||||
export * from './connect-step';
|
||||
export * from './repository-list';
|
||||
export * from './template-list';
|
@ -0,0 +1,44 @@
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { GitHubLogoIcon } from '@radix-ui/react-icons';
|
||||
|
||||
interface Repository {
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface RepositoryListProps {
|
||||
repositories: Repository[];
|
||||
onSelect: (repo: Repository) => void;
|
||||
}
|
||||
|
||||
interface RepoCardProps {
|
||||
repo: Repository;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
function RepoCard({ repo, onClick }: RepoCardProps) {
|
||||
return (
|
||||
<Card
|
||||
// as="button"
|
||||
onClick={onClick}
|
||||
className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-accent hover:text-accent-foreground transition-colors text-left"
|
||||
>
|
||||
<GitHubLogoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="flex-1 text-sm">{repo.name}</span>
|
||||
<span className="text-xs text-muted-foreground">{repo.updatedAt}</span>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function RepositoryList({
|
||||
repositories,
|
||||
onSelect,
|
||||
}: RepositoryListProps) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{repositories.map((repo) => (
|
||||
<RepoCard key={repo.name} repo={repo} onClick={() => onSelect(repo)} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import type React from "react"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
interface Template {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
icon: React.ReactNode
|
||||
}
|
||||
|
||||
interface TemplateListProps {
|
||||
templates: Template[]
|
||||
onSelect: (template: Template) => void
|
||||
projectName: string
|
||||
onProjectNameChange: (name: string) => void
|
||||
}
|
||||
|
||||
export function TemplateList({ templates, onSelect, projectName, onProjectNameChange }: TemplateListProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="project-name" className="text-sm font-medium text-foreground">
|
||||
Project Name
|
||||
</label>
|
||||
<Input
|
||||
id="project-name"
|
||||
value={projectName}
|
||||
onChange={(e) => onProjectNameChange(e.target.value)}
|
||||
placeholder="new-repository-name"
|
||||
className="bg-background"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{templates.map((template) => (
|
||||
<button
|
||||
key={template.id}
|
||||
onClick={() => onSelect(template)}
|
||||
className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-accent hover:text-accent-foreground transition-colors text-left"
|
||||
>
|
||||
<div className="h-8 w-8 rounded bg-muted flex items-center justify-center">{template.icon}</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium">{template.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{template.description}</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
"use client"
|
||||
|
||||
import { useOnboarding } from "@/components/onboarding-flow/store"
|
||||
import Deploy from "@/components/projects/create/Deploy"
|
||||
|
||||
/**
|
||||
* Final step in the onboarding flow
|
||||
* Displays deployment summary and triggers deployment
|
||||
*
|
||||
* Features:
|
||||
* - Configuration summary
|
||||
* - Repository display
|
||||
* - Deploy action
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
export function DeployStep() {
|
||||
const { formData } = useOnboarding()
|
||||
|
||||
const handleDeploy = () => {
|
||||
// TODO: Implement actual deployment logic
|
||||
console.log("Deploying with configuration:", formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full max-w-[445px] mx-auto">
|
||||
<div className="w-full flex flex-col items-center gap-6">
|
||||
{/* Header section */}
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<h2 className="text-2xl font-bold text-foreground">Deploy</h2>
|
||||
<p className="text-base text-muted-foreground text-center">
|
||||
Your deployment is configured and ready to go!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Content sections will be placed here:
|
||||
1. Repository info card
|
||||
2. Configuration summary
|
||||
3. Deploy button
|
||||
|
||||
{/* ...content here */}
|
||||
<Deploy/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Deploy step components for the onboarding flow
|
||||
*
|
||||
* This module exports components related to the Deploy step of the onboarding flow.
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
export { DeployStep } from './deploy-step';
|
29
packages/frontend/src/components/onboarding-flow/index.ts
Normal file
29
packages/frontend/src/components/onboarding-flow/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Onboarding Flow
|
||||
*
|
||||
* This module exports all components related to the onboarding flow.
|
||||
* The onboarding process guides users through connecting their GitHub repository,
|
||||
* configuring their deployment settings, and deploying their application.
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
// Main component
|
||||
export { default as Onboarding } from './Onboarding';
|
||||
|
||||
// Step components
|
||||
export { ConfigureStep } from './configure-step';
|
||||
export { ConnectStep } from './connect-step';
|
||||
export { DeployStep } from './deploy-step';
|
||||
|
||||
// Common components
|
||||
export * from './common';
|
||||
|
||||
// Sidebar components
|
||||
export * from './sidebar';
|
||||
|
||||
// Store and hooks
|
||||
export { useOnboarding } from './store';
|
||||
|
||||
// Types
|
||||
export * from './types';
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Sidebar components for the onboarding flow
|
||||
*
|
||||
* This module exports sidebar navigation components used in the onboarding flow.
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
export { SidebarNav } from './sidebar-nav';
|
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* @component SidebarNav
|
||||
* @description Sidebar navigation component showing all steps and their status
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
import {
|
||||
BackgroundSVG,
|
||||
LaconicIconLettering,
|
||||
} from '@/components/onboarding-flow/common';
|
||||
import { type Step } from '@/components/onboarding-flow/types';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { FileCog, GitPullRequest, SquareArrowOutDownRight } from 'lucide-react';
|
||||
import type React from 'react';
|
||||
|
||||
/**
|
||||
* Props for the StepItem subcomponent
|
||||
* @interface StepItemProps
|
||||
* @property {React.ReactNode} icon - Icon representing the step
|
||||
* @property {string} title - Step title
|
||||
* @property {string} description - Step description
|
||||
* @property {boolean} isActive - Whether this step is currently active
|
||||
*/
|
||||
interface StepItemProps {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual step item in the sidebar navigation
|
||||
* @component
|
||||
* @param {StepItemProps} props - Component props
|
||||
*/
|
||||
const StepItem = ({ icon, title, description, isActive }: StepItemProps) => (
|
||||
<div className="flex items-center gap-3.5">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center w-11 h-11 rounded-lg p-2.5 transition-colors',
|
||||
isActive ? 'bg-foreground' : 'bg-primary-foreground border',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'w-6 h-6 stroke-2 transition-colors',
|
||||
isActive ? 'text-accent' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h3
|
||||
className={cn(
|
||||
'text-lg font-semibold leading-7 transition-colors',
|
||||
isActive ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<p
|
||||
className={cn(
|
||||
'text-sm leading-5 transition-colors',
|
||||
isActive ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Props for the SidebarNav component
|
||||
* @interface SidebarNavProps
|
||||
* @property {Step} currentStep - Currently active step in the onboarding flow
|
||||
*/
|
||||
interface SidebarNavProps {
|
||||
currentStep: Step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar navigation component showing all steps and their status
|
||||
*
|
||||
* @component
|
||||
* @example
|
||||
* ```tsx
|
||||
* <SidebarNav currentStep="connect" />
|
||||
* ```
|
||||
*
|
||||
* Interacts with:
|
||||
* - useOnboarding store for step state
|
||||
* - StepItem subcomponent for individual step display
|
||||
*
|
||||
* @param {SidebarNavProps} props - Component props
|
||||
* @returns {JSX.Element} Sidebar with step navigation
|
||||
*/
|
||||
export function SidebarNav({ currentStep }: SidebarNavProps) {
|
||||
return (
|
||||
<div className="w-[379px] p-6 bg-primary-foreground rounded-lg shadow-[0_1px_2px_0_rgba(0,0,0,0.06),0_1px_3px_0_rgba(0,0,0,0.1)] relative overflow-hidden">
|
||||
<div className="h-5 mb-6 text-zinc-800 dark:text-white">
|
||||
<LaconicIconLettering />
|
||||
</div>
|
||||
|
||||
<Separator className="mb-6" />
|
||||
|
||||
<div className="space-y-6">
|
||||
<StepItem
|
||||
icon={<GitPullRequest />}
|
||||
title="Connect"
|
||||
description="Connect and import a GitHub repo"
|
||||
isActive={currentStep === 'connect'}
|
||||
/>
|
||||
|
||||
<StepItem
|
||||
icon={<FileCog />}
|
||||
title="Configure"
|
||||
description="Define the deployment type"
|
||||
isActive={currentStep === 'configure'}
|
||||
/>
|
||||
|
||||
<StepItem
|
||||
icon={<SquareArrowOutDownRight />}
|
||||
title="Deploy"
|
||||
description="Review and confirm deployment"
|
||||
isActive={currentStep === 'deploy'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-0 left-0">
|
||||
<BackgroundSVG />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
61
packages/frontend/src/components/onboarding-flow/store.ts
Normal file
61
packages/frontend/src/components/onboarding-flow/store.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Zustand store for managing onboarding state
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { type OnboardingFormData, type Step } from './types';
|
||||
|
||||
/**
|
||||
* State management for the onboarding flow
|
||||
* @interface OnboardingState
|
||||
* @property {Step} currentStep - Current active step
|
||||
* @property {OnboardingFormData} formData - Collected form data
|
||||
* @property {(step: Step) => void} setCurrentStep - Updates the current step
|
||||
* @property {(data: Partial<OnboardingFormData>) => void} setFormData - Updates form data
|
||||
* @property {() => void} nextStep - Moves to the next step
|
||||
* @property {() => void} previousStep - Moves to the previous step
|
||||
*/
|
||||
interface OnboardingState {
|
||||
currentStep: Step;
|
||||
formData: OnboardingFormData;
|
||||
setCurrentStep: (step: Step) => void;
|
||||
setFormData: (data: Partial<OnboardingFormData>) => void;
|
||||
nextStep: () => void;
|
||||
previousStep: () => void;
|
||||
}
|
||||
|
||||
/** Order of steps in the onboarding flow */
|
||||
const STEP_ORDER: Step[] = ['connect', 'configure', 'deploy'];
|
||||
|
||||
/**
|
||||
* Zustand store for managing onboarding state
|
||||
* Used across all onboarding components to maintain flow state
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { currentStep, formData, nextStep } = useOnboarding()
|
||||
* ```
|
||||
*/
|
||||
export const useOnboarding = create<OnboardingState>((set) => ({
|
||||
currentStep: 'connect',
|
||||
formData: {},
|
||||
setCurrentStep: (step) => set({ currentStep: step }),
|
||||
setFormData: (data) =>
|
||||
set((state) => ({
|
||||
formData: { ...state.formData, ...data },
|
||||
})),
|
||||
nextStep: () =>
|
||||
set((state) => {
|
||||
const currentIndex = STEP_ORDER.indexOf(state.currentStep);
|
||||
const nextStep = STEP_ORDER[currentIndex + 1];
|
||||
return nextStep ? { currentStep: nextStep } : state;
|
||||
}),
|
||||
previousStep: () =>
|
||||
set((state) => {
|
||||
const currentIndex = STEP_ORDER.indexOf(state.currentStep);
|
||||
const previousStep = STEP_ORDER[currentIndex - 1];
|
||||
return previousStep ? { currentStep: previousStep } : state;
|
||||
}),
|
||||
}));
|
83
packages/frontend/src/components/onboarding-flow/types.ts
Normal file
83
packages/frontend/src/components/onboarding-flow/types.ts
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Shared type definitions for the onboarding flow
|
||||
*
|
||||
* @see https://www.figma.com/design/cfMOy1RJasIu3QyzAMBFxB/Laconic?node-id=571-3500&m=dev
|
||||
*/
|
||||
|
||||
/**
|
||||
* Available steps in the onboarding flow
|
||||
*/
|
||||
export type Step = 'connect' | 'configure' | 'deploy';
|
||||
|
||||
/**
|
||||
* Form data collected during the onboarding process
|
||||
* @interface OnboardingFormData
|
||||
* @property {string} [githubRepo] - Selected GitHub repository
|
||||
* @property {string} [deploymentType] - Selected deployment type (e.g., "pwa")
|
||||
* @property {Record<string, string>} [environmentVars] - Environment variables
|
||||
*/
|
||||
export interface OnboardingFormData {
|
||||
githubRepo?: string;
|
||||
deploymentType?: string;
|
||||
environmentVars?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub repository information
|
||||
* @interface Repository
|
||||
* @property {string} id - Unique identifier for the repository
|
||||
* @property {string} name - Repository name
|
||||
* @property {string} fullName - Full repository name including owner
|
||||
* @property {string} [description] - Repository description
|
||||
* @property {boolean} [isPrivate] - Whether the repository is private
|
||||
* @property {string} [url] - Repository URL
|
||||
*/
|
||||
export interface Repository {
|
||||
id: string;
|
||||
name: string;
|
||||
fullName: string;
|
||||
description?: string;
|
||||
isPrivate?: boolean;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template information for new projects
|
||||
* @interface Template
|
||||
* @property {string} id - Unique identifier for the template
|
||||
* @property {string} name - Template name
|
||||
* @property {string} [description] - Template description
|
||||
* @property {string} [thumbnail] - Template thumbnail URL
|
||||
*/
|
||||
export interface Template {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
thumbnail?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deployment type information
|
||||
* @interface DeploymentType
|
||||
* @property {string} id - Unique identifier for the deployment type
|
||||
* @property {string} name - Deployment type name
|
||||
* @property {string} [description] - Deployment type description
|
||||
*/
|
||||
export interface DeploymentType {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment variable definition
|
||||
* @interface EnvironmentVariable
|
||||
* @property {string} key - Environment variable key
|
||||
* @property {string} value - Environment variable value
|
||||
* @property {boolean} [isSecret] - Whether the variable is a secret
|
||||
*/
|
||||
export interface EnvironmentVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
isSecret?: boolean;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { create } from "zustand"
|
||||
|
||||
export type Step = "connect" | "configure" | "deploy"
|
||||
|
||||
interface OnboardingState {
|
||||
currentStep: Step
|
||||
formData: {
|
||||
githubRepo?: string
|
||||
deploymentType?: string
|
||||
environmentVars?: Record<string, string>
|
||||
}
|
||||
setCurrentStep: (step: Step) => void
|
||||
setFormData: (data: Partial<OnboardingState["formData"]>) => void
|
||||
nextStep: () => void
|
||||
previousStep: () => void
|
||||
}
|
||||
|
||||
const STEP_ORDER: Step[] = ["connect", "configure", "deploy"]
|
||||
|
||||
export const useOnboarding = create<OnboardingState>((set) => ({
|
||||
currentStep: "connect",
|
||||
formData: {},
|
||||
setCurrentStep: (step) => set({ currentStep: step }),
|
||||
setFormData: (data) =>
|
||||
set((state) => ({
|
||||
formData: { ...state.formData, ...data },
|
||||
})),
|
||||
nextStep: () =>
|
||||
set((state) => {
|
||||
const currentIndex = STEP_ORDER.indexOf(state.currentStep)
|
||||
const nextStep = STEP_ORDER[currentIndex + 1]
|
||||
return nextStep ? { currentStep: nextStep } : state
|
||||
}),
|
||||
previousStep: () =>
|
||||
set((state) => {
|
||||
const currentIndex = STEP_ORDER.indexOf(state.currentStep)
|
||||
const previousStep = STEP_ORDER[currentIndex - 1]
|
||||
return previousStep ? { currentStep: previousStep } : state
|
||||
}),
|
||||
}))
|
||||
|
@ -67,7 +67,7 @@ const NavigationSidebar: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="w-64 h-full bg-background border-r p-4 flex flex-col">
|
||||
{/* Logo */}
|
||||
{/* Logo */}snowballtools-base
|
||||
<div className="mb-8 p-2">
|
||||
<h1 className="text-xl font-bold">Snowball</h1>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
DialogContent, DialogTrigger
|
||||
} from '@/components/ui/dialog';
|
||||
import React from 'react';
|
||||
import OnboardingLayout from './OnboardingLayout';
|
||||
import Onboarding from '../onboarding-flow/Onboarding';
|
||||
|
||||
interface OnboardingDialogProps {
|
||||
trigger?: React.ReactNode;
|
||||
@ -25,7 +25,8 @@ const OnboardingDialog: React.FC<OnboardingDialogProps> = ({
|
||||
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||
<DialogContent className="max-w-[95vw] max-h-[95vh] w-[1200px] h-[800px] overflow-hidden p-0">
|
||||
<div className="h-full overflow-hidden">
|
||||
<OnboardingLayout isDialog={true} />
|
||||
{/* <OnboardingLayout isDialog={true} /> */}
|
||||
<Onboarding/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -1,3 +1,12 @@
|
||||
/**
|
||||
* @deprecated This theme file is deprecated and will be removed in a future version.
|
||||
* The ProjectCard has been refactored to use component-based styling with shadcn/ui components.
|
||||
* Please use the following components instead:
|
||||
* - ProjectStatusDot for status indicators
|
||||
* - ProjectDeploymentInfo for deployment information
|
||||
* - ProjectCardActions for card actions
|
||||
*/
|
||||
|
||||
import { VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const projectCardTheme = tv({
|
||||
@ -73,20 +82,21 @@ export const projectCardTheme = tv({
|
||||
'group-hover:bg-surface-card-hovered',
|
||||
'dark:group-hover:bg-overlay2',
|
||||
],
|
||||
dot: ['h-2', 'w-2', 'rounded-full'],
|
||||
},
|
||||
variants: {
|
||||
status: {
|
||||
success: {
|
||||
deploymentStatus: ['bg-emerald-500'],
|
||||
dot: ['bg-emerald-500'],
|
||||
},
|
||||
'in-progress': {
|
||||
deploymentStatus: ['bg-orange-400'],
|
||||
dot: ['bg-orange-400'],
|
||||
},
|
||||
failure: {
|
||||
deploymentStatus: ['bg-error'],
|
||||
dot: ['bg-destructive'],
|
||||
},
|
||||
pending: {
|
||||
deploymentStatus: ['bg-gray-500'],
|
||||
dot: ['bg-muted'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1,77 +1,71 @@
|
||||
import { WavyBorder } from '@/components/shared/WavyBorder';
|
||||
import { Button } from '@/components/ui';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { getInitials } from '@/utils/geInitials';
|
||||
import { relativeTimeMs } from '@/utils/time';
|
||||
import { Project } from 'gql-client';
|
||||
import { AlertTriangle, Clock, GitBranch, MoreHorizontal } from 'lucide-react';
|
||||
import { ComponentPropsWithoutRef, MouseEvent, useCallback } from 'react';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { ComponentPropsWithoutRef, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme';
|
||||
import { ProjectCardActions } from './ProjectCardActions';
|
||||
import { ProjectDeploymentInfo } from './ProjectDeploymentInfo';
|
||||
import { ProjectStatusDot } from './ProjectStatusDot';
|
||||
|
||||
export interface ProjectCardProps
|
||||
extends ComponentPropsWithoutRef<'div'>,
|
||||
ProjectCardTheme {
|
||||
/**
|
||||
* Status types for project deployment status
|
||||
*/
|
||||
export type ProjectStatus = 'success' | 'in-progress' | 'failure' | 'pending';
|
||||
|
||||
/**
|
||||
* Props for the ProjectCard component
|
||||
*
|
||||
* @property {Project} project - The project data to display
|
||||
* @property {ProjectStatus} [status='failure'] - The current deployment status of the project
|
||||
*/
|
||||
interface ProjectCardProps extends ComponentPropsWithoutRef<'div'> {
|
||||
project: Project;
|
||||
status?: ProjectStatus;
|
||||
}
|
||||
|
||||
// TODO: Update the whole component to use `Link` from `react-router-dom` and remove the `useNavigate` hook,
|
||||
// currently it's not possible to use `Link` because the dot menu is not a direct child of the `Link` component
|
||||
/**
|
||||
* ProjectCard component
|
||||
*
|
||||
* Displays a card with project information including:
|
||||
* - Project name and icon
|
||||
* - Domain URL (if available)
|
||||
* - Deployment status
|
||||
* - Latest commit information
|
||||
* - Timestamp and branch information
|
||||
* - Actions menu for project settings and deletion
|
||||
*
|
||||
* The card is clickable and navigates to the project details page.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ProjectCard
|
||||
* project={projectData}
|
||||
* status="success"
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export const ProjectCard = ({
|
||||
className,
|
||||
project,
|
||||
status = 'failure',
|
||||
...props
|
||||
}: ProjectCardProps) => {
|
||||
const theme = projectCardTheme();
|
||||
const hasDeployment = project.deployments.length > 0;
|
||||
// TODO: Update this to use the actual status from the API
|
||||
const hasError = status === 'failure';
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleOptionsClick = (
|
||||
e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>,
|
||||
) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles click on the card to navigate to project details
|
||||
*/
|
||||
const handleClick = useCallback(() => {
|
||||
navigate(`projects/${project.id}`);
|
||||
}, [project.id, navigate]);
|
||||
|
||||
const navigateToSettingsOnClick = useCallback(
|
||||
(
|
||||
e: React.MouseEvent<HTMLLIElement> | React.MouseEvent<HTMLButtonElement>,
|
||||
) => {
|
||||
e.stopPropagation();
|
||||
navigate(`projects/${project.id}/settings`);
|
||||
},
|
||||
[project.id, navigate],
|
||||
);
|
||||
|
||||
const handleDeleteClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// TODO: Add delete functionality
|
||||
navigate(`projects/${project.id}/settings`);
|
||||
},
|
||||
[project.id, navigate],
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles click on the settings menu item
|
||||
* Prevents event propagation to avoid triggering card click
|
||||
*/
|
||||
const handleSettingsClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@ -81,95 +75,51 @@ export const ProjectCard = ({
|
||||
[project.id, navigate],
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles click on the delete menu item
|
||||
* Prevents event propagation to avoid triggering card click
|
||||
*/
|
||||
const handleDeleteClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigate(`projects/${project.id}/settings`);
|
||||
},
|
||||
[project.id, navigate],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={theme.wrapper({ className })}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{/* Upper content */}
|
||||
<div className={theme.upperContent()}>
|
||||
{/* Icon container */}
|
||||
<Avatar className="w-12 h-12">
|
||||
<Card className="w-full" onClick={handleClick} {...props}>
|
||||
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-2">
|
||||
<div className="flex gap-2.5">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarImage src={project.icon} alt={project.name} />
|
||||
<AvatarFallback>{getInitials(project.name)}</AvatarFallback>
|
||||
</Avatar>
|
||||
{/* Title and website */}
|
||||
<div className={theme.content()}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<p className={theme.title()}>{project.name}</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{project.name}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<p className={theme.description()}>
|
||||
{project.deployments[0]?.applicationDeploymentRecordData?.url ??
|
||||
'No domain'}
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<p className="text-sm font-semibold text-foreground leading-none">{project.name}</p>
|
||||
<p className="text-sm text-muted-foreground leading-5">
|
||||
{project.deployments[0]?.applicationDeploymentRecordData?.url ?? 'No domain'}
|
||||
</p>
|
||||
</div>
|
||||
{/* Icons */}
|
||||
<div className={theme.icons()}>
|
||||
{hasError && <AlertTriangle className="text-error" />}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
|
||||
<Button variant="ghost">
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenuItem onClick={handleSettingsClick}>
|
||||
Project settings
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={handleDeleteClick}
|
||||
className="text-error"
|
||||
>
|
||||
Delete project
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
{/* Wave */}
|
||||
<WavyBorder variant="stroke-and-fill" className={theme.wavyBorder()} />
|
||||
{/* Lower content */}
|
||||
<div className={theme.lowerContent()}>
|
||||
{/* Latest deployment */}
|
||||
<div className={theme.latestDeployment()}>
|
||||
{/* Dot icon */}
|
||||
<div className={theme.deploymentStatusContainer()}>
|
||||
<div className={theme.deploymentStatus({ status })} />
|
||||
</div>
|
||||
<p className={theme.deploymentText()}>
|
||||
{hasDeployment
|
||||
? project.deployments[0]?.commitMessage
|
||||
: 'No production deployment'}
|
||||
</p>
|
||||
</div>
|
||||
{/* Deployment and branch name */}
|
||||
<div className={theme.deploymentText()}>
|
||||
{hasDeployment ? (
|
||||
<>
|
||||
<GitBranch />
|
||||
<span>{relativeTimeMs(project.deployments[0].createdAt)} on</span>
|
||||
<Clock />
|
||||
<span>{project.deployments[0].branch}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Clock />
|
||||
<span>Created {relativeTimeMs(project.createdAt)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{hasError && <AlertTriangle className="text-destructive h-4 w-4" />}
|
||||
<ProjectCardActions
|
||||
onSettingsClick={handleSettingsClick}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="flex flex-col gap-3 pt-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<ProjectStatusDot status={status} />
|
||||
<ProjectDeploymentInfo project={project} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,69 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ExternalLink, MoreHorizontal, Trash } from 'lucide-react';
|
||||
import { ComponentPropsWithoutRef } from 'react';
|
||||
|
||||
/**
|
||||
* Props for the ProjectCardActions component
|
||||
*
|
||||
* @property {Function} onSettingsClick - Callback function triggered when the settings option is clicked
|
||||
* @property {Function} onDeleteClick - Callback function triggered when the delete option is clicked
|
||||
*/
|
||||
interface ProjectCardActionsProps extends ComponentPropsWithoutRef<'div'> {
|
||||
onSettingsClick: (e: React.MouseEvent) => void;
|
||||
onDeleteClick: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProjectCardActions component
|
||||
*
|
||||
* Displays a dropdown menu with actions that can be performed on a project:
|
||||
* - Project settings: Navigates to the project settings page
|
||||
* - Delete project: Initiates the project deletion process
|
||||
*
|
||||
* The component uses a three-dot menu icon that expands to show available actions.
|
||||
* Each action has an associated icon for better visual recognition.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ProjectCardActions
|
||||
* onSettingsClick={handleSettingsClick}
|
||||
* onDeleteClick={handleDeleteClick}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export const ProjectCardActions = ({
|
||||
onSettingsClick,
|
||||
onDeleteClick,
|
||||
...props
|
||||
}: ProjectCardActionsProps) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuItem onClick={onSettingsClick}>
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
<span>Project settings</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={onDeleteClick} className="text-destructive">
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
<span>Delete project</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,61 @@
|
||||
import { relativeTimeMs } from '@/utils/time';
|
||||
import { Project } from 'gql-client';
|
||||
import { Clock, GitBranch } from 'lucide-react';
|
||||
import { ComponentPropsWithoutRef } from 'react';
|
||||
|
||||
/**
|
||||
* Props for the ProjectDeploymentInfo component
|
||||
*
|
||||
* @property {Project} project - The project data containing deployment information
|
||||
*/
|
||||
interface ProjectDeploymentInfoProps extends ComponentPropsWithoutRef<'div'> {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProjectDeploymentInfo component
|
||||
*
|
||||
* Displays information about the latest deployment for a project, including:
|
||||
* - Commit message (or "No production deployment" if none exists)
|
||||
* - Relative time since deployment or project creation
|
||||
* - Branch name (if a deployment exists)
|
||||
*
|
||||
* The component handles both cases where a project has deployments and where it doesn't,
|
||||
* displaying appropriate information in each case.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ProjectDeploymentInfo project={projectData} />
|
||||
* ```
|
||||
*/
|
||||
export const ProjectDeploymentInfo = ({ project, ...props }: ProjectDeploymentInfoProps) => {
|
||||
const hasDeployment = project.deployments.length > 0;
|
||||
const latestDeployment = project.deployments[0];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3" {...props}>
|
||||
{/* Commit message or no deployment message */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{hasDeployment ? latestDeployment?.commitMessage : 'No production deployment'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Timestamp and branch information */}
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{hasDeployment ? relativeTimeMs(latestDeployment.createdAt) : relativeTimeMs(project.createdAt)} on
|
||||
</span>
|
||||
</div>
|
||||
{hasDeployment && (
|
||||
<div className="flex items-center gap-1">
|
||||
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">{latestDeployment.branch}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,52 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ComponentPropsWithoutRef } from "react";
|
||||
import { ProjectStatus } from "./ProjectCard";
|
||||
|
||||
/**
|
||||
* Mapping of status values to their corresponding CSS classes for styling
|
||||
*
|
||||
* - success: Green color for successful deployments
|
||||
* - in-progress: Orange color for deployments in progress
|
||||
* - failure: Red color for failed deployments
|
||||
* - pending: Gray color for pending deployments
|
||||
*/
|
||||
const statusStyles: Record<ProjectStatus, string> = {
|
||||
success: 'bg-emerald-500',
|
||||
'in-progress': 'bg-orange-400',
|
||||
failure: 'bg-destructive',
|
||||
pending: 'bg-muted'
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the ProjectStatusDot component
|
||||
*
|
||||
* @property {ProjectStatus} status - The current status of the project deployment
|
||||
*/
|
||||
interface ProjectStatusDotProps extends ComponentPropsWithoutRef<'div'> {
|
||||
status: ProjectStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProjectStatusDot component
|
||||
*
|
||||
* A visual indicator that displays the current status of a project deployment.
|
||||
* The color of the dot changes based on the status:
|
||||
* - Green for success
|
||||
* - Orange for in-progress
|
||||
* - Red for failure
|
||||
* - Gray for pending
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ProjectStatusDot status="success" />
|
||||
* ```
|
||||
*/
|
||||
export const ProjectStatusDot = ({ status, className, ...props }: ProjectStatusDotProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn("h-2 w-2 rounded-full", statusStyles[status], className)}
|
||||
aria-label={`Deployment status: ${status}`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
@ -132,7 +132,7 @@ export const OctokitProvider = ({
|
||||
// Remove the interceptor when the component unmounts
|
||||
octokit.hook.remove('request', interceptor);
|
||||
};
|
||||
}, [octokit, client, debouncedUnauthorizedGithubHandler]);
|
||||
}, [octokit, client, debouncedUnauthorizedGithubHandler, fetchUser]);
|
||||
|
||||
return (
|
||||
<OctokitContext.Provider value={{ octokit, updateAuth, isAuth }}>
|
||||
@ -147,7 +147,11 @@ export const OctokitProvider = ({
|
||||
* @returns {object} An object containing the Octokit instance, updateAuth function, and isAuth status.
|
||||
*/
|
||||
export const useOctokit = () => {
|
||||
const { octokit, updateAuth, isAuth } = useContext(OctokitContext);
|
||||
const context = useContext(OctokitContext);
|
||||
|
||||
return { octokit, updateAuth, isAuth };
|
||||
if (!context) {
|
||||
throw new Error('useOctokit must be used within an OctokitProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
@ -36,6 +36,7 @@ export const DashboardLayout = ({
|
||||
<OctokitProviderWithRouter>
|
||||
<NavigationWrapper>
|
||||
<Outlet />
|
||||
|
||||
</NavigationWrapper>
|
||||
</OctokitProviderWithRouter>
|
||||
</section>
|
||||
|
525
standards/COMPONENT_DOCUMENTATION.md
Normal file
525
standards/COMPONENT_DOCUMENTATION.md
Normal file
@ -0,0 +1,525 @@
|
||||
# Component Documentation Standards
|
||||
|
||||
This document outlines the documentation standards for components across the entire project.
|
||||
Following these standards ensures consistency and makes the codebase more maintainable.
|
||||
|
||||
## TypeScript Documentation Approach
|
||||
|
||||
This project uses a **TypeScript-first** documentation approach, focusing on TypeScript's native
|
||||
documentation capabilities rather than traditional JSDoc. While TypeScript supports JSDoc syntax, we
|
||||
prioritize TypeScript-specific documentation patterns where possible.
|
||||
|
||||
### TSDoc vs JSDoc
|
||||
|
||||
- **TSDoc** is a documentation standard specifically designed for TypeScript
|
||||
- We prefer TypeScript's native type annotations over JSDoc type annotations
|
||||
- Use explicit type definitions in code instead of JSDoc type comments when possible
|
||||
- When JSDoc-style comments are needed, use TypeScript-compatible JSDoc tags
|
||||
|
||||
### Documentation Tools
|
||||
|
||||
Our documentation approach is designed to work well with:
|
||||
|
||||
- TypeScript's built-in type checking
|
||||
- IDE integrations like VS Code's IntelliSense
|
||||
- Documentation generators that support TypeScript
|
||||
|
||||
## TypeScript-Specific Documentation Tags
|
||||
|
||||
````tsx
|
||||
/**
|
||||
* This function demonstrates TypeScript-native documentation
|
||||
*
|
||||
* @remarks
|
||||
* The remarks section provides additional details that wouldn't fit in the brief description.
|
||||
*
|
||||
* @typeParam T - Generic type parameter for the input array
|
||||
* @typeParam U - Generic type parameter for the output array
|
||||
*
|
||||
* @param items - Array of items to process (TypeScript infers the type)
|
||||
* @param mapper - Function to transform items (TypeScript infers the signature)
|
||||
* @returns Array of transformed items
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Transform numbers to strings
|
||||
* const numbers = [1, 2, 3];
|
||||
* const strings = mapItems(numbers, n => n.toString());
|
||||
* ```
|
||||
*/
|
||||
function mapItems<T, U>(items: T[], mapper: (item: T) => U): U[] {
|
||||
return items.map(mapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for configuration options
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ConfigOptions {
|
||||
/**
|
||||
* Base URL for API requests
|
||||
*/
|
||||
apiUrl: string;
|
||||
|
||||
/**
|
||||
* Authentication token
|
||||
* @defaultValue undefined
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* Request timeout in milliseconds
|
||||
* @defaultValue 3000
|
||||
*/
|
||||
timeout: number;
|
||||
}
|
||||
````
|
||||
|
||||
## Component JSDoc Template
|
||||
|
||||
````tsx
|
||||
/**
|
||||
* @component ComponentName
|
||||
* @description Brief description of what the component does
|
||||
*
|
||||
* @see [Optional] Link to design reference (e.g., Figma)
|
||||
*
|
||||
* [Optional: Additional context about the component's role in the application]
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ComponentName prop1="value" prop2={value} />
|
||||
* ```
|
||||
*
|
||||
* @dependencies
|
||||
* - DependencyComponent1
|
||||
* - DependencyComponent2
|
||||
*/
|
||||
````
|
||||
|
||||
## Props Interface Template
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Props for the ComponentName component
|
||||
* @interface ComponentNameProps
|
||||
* @property {Type} propName - Description of the prop
|
||||
* @property {Type} [optionalProp] - Description of the optional prop
|
||||
* @property {() => void} [onEvent] - Callback fired when event occurs
|
||||
*/
|
||||
interface ComponentNameProps {
|
||||
propName: Type;
|
||||
optionalProp?: Type;
|
||||
onEvent?: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
## Function Template
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Description of what the function does
|
||||
* @function functionName
|
||||
* @param {ParamType} paramName - Description of the parameter
|
||||
* @returns {ReturnType} Description of the return value
|
||||
* @throws {ErrorType} Description of potential errors
|
||||
*/
|
||||
function functionName(paramName: ParamType): ReturnType {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Type/Interface Template
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Description of what this type/interface represents
|
||||
* @interface InterfaceName
|
||||
* @property {Type} propertyName - Description of the property
|
||||
* @property {Type} [optionalProperty] - Description of the optional property
|
||||
*/
|
||||
interface InterfaceName {
|
||||
propertyName: Type;
|
||||
optionalProperty?: Type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of this type alias
|
||||
* @type TypeName
|
||||
*/
|
||||
export type TypeName = BaseType & {
|
||||
additionalProperty: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type for use with generic components
|
||||
* @template T - Description of the generic parameter
|
||||
*/
|
||||
export type GenericType<T> = {
|
||||
value: T;
|
||||
label: string;
|
||||
};
|
||||
```
|
||||
|
||||
## Enum Template
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Description of what this enum represents
|
||||
* @enum {string|number} EnumName
|
||||
*/
|
||||
export enum EnumName {
|
||||
/**
|
||||
* Description of this enum value
|
||||
*/
|
||||
VALUE_ONE = 'value_one',
|
||||
|
||||
/**
|
||||
* Description of this enum value
|
||||
*/
|
||||
VALUE_TWO = 'value_two',
|
||||
}
|
||||
```
|
||||
|
||||
## Const Assertion Template
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Description of this constant object
|
||||
* @const objectName
|
||||
*/
|
||||
export const objectName = {
|
||||
PROPERTY_ONE: 'value_one',
|
||||
PROPERTY_TWO: 'value_two',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Type derived from const assertion
|
||||
* @type TypeFromConst
|
||||
*/
|
||||
export type TypeFromConst = (typeof objectName)[keyof typeof objectName];
|
||||
```
|
||||
|
||||
## Import/Export Type Guidelines
|
||||
|
||||
When working with TypeScript, use explicit type imports/exports for better clarity:
|
||||
|
||||
```tsx
|
||||
// Preferred: Explicit type imports
|
||||
import type { SomeType, AnotherInterface } from './types';
|
||||
import { Component } from './components';
|
||||
|
||||
// Preferred: Explicit type exports
|
||||
export type { ComponentProps } from './Component';
|
||||
export { Component } from './Component';
|
||||
|
||||
// For re-exporting both the type and value:
|
||||
export { default as Component, type ComponentProps } from './Component';
|
||||
```
|
||||
|
||||
## Barrel File (index.ts) Template
|
||||
|
||||
````tsx
|
||||
/**
|
||||
* @module ModuleName
|
||||
* @description Brief description of the module's purpose
|
||||
*
|
||||
* This barrel file exports all public components, hooks, and types from the module.
|
||||
* When importing from this module, use the following pattern:
|
||||
* ```tsx
|
||||
* import { ComponentA, ComponentB, useFeature } from '@/path/to/module';
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Component exports
|
||||
export { ComponentA } from './ComponentA';
|
||||
export { ComponentB } from './ComponentB';
|
||||
|
||||
// Hook exports
|
||||
export { useFeatureA } from './hooks/useFeatureA';
|
||||
export { useFeatureB } from './hooks/useFeatureB';
|
||||
|
||||
// Type exports - use explicit type exports
|
||||
export type { ComponentAProps } from './ComponentA';
|
||||
export type { ComponentBProps } from './ComponentB';
|
||||
|
||||
// Enum exports
|
||||
export { FeatureEnum } from './types';
|
||||
|
||||
// Re-export all from a sub-module (use sparingly)
|
||||
export * from './submodule';
|
||||
````
|
||||
|
||||
## Component with Subcomponents Template
|
||||
|
||||
````tsx
|
||||
/**
|
||||
* Parent component description
|
||||
* @component ParentComponent
|
||||
* @description Overview of the parent component
|
||||
*
|
||||
* @see [Optional] Link to design reference
|
||||
*
|
||||
* Component Hierarchy:
|
||||
* - ParentComponent
|
||||
* - SubComponent1
|
||||
* - SubComponent2
|
||||
* - NestedComponent
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ParentComponent prop1="value" prop2={value} />
|
||||
* ```
|
||||
*/
|
||||
export function ParentComponent({ prop1, prop2 }: ParentComponentProps) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
/**
|
||||
* Subcomponent description
|
||||
* @component SubComponent
|
||||
* @description Overview of the subcomponent
|
||||
* @private Only used within ParentComponent
|
||||
*/
|
||||
function SubComponent({ subProp }: SubComponentProps) {
|
||||
// Implementation
|
||||
}
|
||||
````
|
||||
|
||||
## Hooks Template
|
||||
|
||||
````tsx
|
||||
/**
|
||||
* Description of what the hook does and when to use it
|
||||
* @hook useHookName
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { value, setValue } = useHookName(initialValue)
|
||||
* ```
|
||||
*
|
||||
* @param {ParamType} initialValue - Description of the parameter
|
||||
* @returns {ReturnType} Description of the return value
|
||||
*/
|
||||
function useHookName(initialValue: ParamType): ReturnType {
|
||||
// Implementation
|
||||
}
|
||||
````
|
||||
|
||||
## Store Documentation Template
|
||||
|
||||
````tsx
|
||||
/**
|
||||
* Description of what the store manages
|
||||
* @store storeName
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { value, setValue } = useStore()
|
||||
* ```
|
||||
*
|
||||
* State Management:
|
||||
* - value: Description of state value
|
||||
* - setValue: Description of state updater
|
||||
*/
|
||||
````
|
||||
|
||||
## Additional Guidelines
|
||||
|
||||
1. **Be concise but complete** - Provide enough information to understand the component without
|
||||
overwhelming the reader
|
||||
2. **Document public API** - Focus on documenting the public API rather than implementation details
|
||||
3. **Keep examples simple** - Examples should demonstrate common use cases
|
||||
4. **Update documentation** - Keep documentation in sync with code changes
|
||||
5. **Document side effects** - Clearly document any side effects or behaviors that might not be
|
||||
obvious
|
||||
|
||||
## When to Document
|
||||
|
||||
- All components, hooks, and utilities exported from a package or module
|
||||
- Complex internal functions that are difficult to understand at a glance
|
||||
- Props, especially those with non-obvious behavior
|
||||
- State management code and side effects
|
||||
|
||||
## TypeScript-Specific JSDoc Tags
|
||||
|
||||
TypeScript supports JSDoc with additional TypeScript-specific tags. Use these tags to enhance your
|
||||
documentation:
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* @typeParam T - Type parameter description (preferred over @template)
|
||||
* @param param - Parameter with TypeScript type inference (no need for {type})
|
||||
* @returns Return value with TypeScript type inference
|
||||
* @defaultValue Default value for a property
|
||||
* @public Indicates this is part of the public API
|
||||
* @private Indicates this is a private member
|
||||
* @protected Indicates this is a protected member
|
||||
* @readonly Indicates this is a readonly property
|
||||
* @deprecated Indicates this is deprecated with optional explanation
|
||||
*/
|
||||
|
||||
// Property documentation in interfaces/classes
|
||||
interface Example {
|
||||
/**
|
||||
* Property description
|
||||
* @defaultValue 'default'
|
||||
*/
|
||||
property: string;
|
||||
}
|
||||
|
||||
// Documentation for React component props using type alias
|
||||
type ButtonProps = {
|
||||
/**
|
||||
* The button's variant style
|
||||
* @defaultValue 'primary'
|
||||
*/
|
||||
variant?: 'primary' | 'secondary' | 'tertiary';
|
||||
|
||||
/**
|
||||
* Content to display inside the button
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Called when the button is clicked
|
||||
*/
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function documentation using modern TypeScript patterns
|
||||
*
|
||||
* @deprecated Use newFunction instead
|
||||
* @throws Error when input is invalid
|
||||
*/
|
||||
function oldFunction(input: string): void {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Mermaid Diagrams
|
||||
|
||||
Use Mermaid diagrams to visualize complex relationships, flows, or processes. Include Mermaid
|
||||
diagrams directly in markdown documentation using the following formats:
|
||||
|
||||
### Component Relationship Diagram
|
||||
|
||||
````markdown
|
||||
/\*\*
|
||||
|
||||
- @component ComplexFeature
|
||||
- @description A complex feature with multiple components
|
||||
-
|
||||
- ## Component Relationships
|
||||
- ```mermaid
|
||||
|
||||
```
|
||||
|
||||
- graph TD
|
||||
- A[ParentComponent] --> B[ChildComponent1]
|
||||
- A --> C[ChildComponent2]
|
||||
- B --> D[GrandchildComponent1]
|
||||
- B --> E[GrandchildComponent2]
|
||||
- C --> F[GrandchildComponent3]
|
||||
- ```
|
||||
*/
|
||||
```
|
||||
````
|
||||
|
||||
### Data Flow Diagram
|
||||
|
||||
````markdown
|
||||
/\*\*
|
||||
|
||||
- @module DataFlow
|
||||
- @description Shows how data flows through the application
|
||||
-
|
||||
- ## Data Flow
|
||||
- ```mermaid
|
||||
|
||||
```
|
||||
|
||||
- graph LR
|
||||
- API[API] --> Store[Store]
|
||||
- Store --> ComponentA[Component A]
|
||||
- Store --> ComponentB[Component B]
|
||||
- ComponentA --> User[User Interface]
|
||||
- ComponentB --> User
|
||||
- ```
|
||||
*/
|
||||
```
|
||||
````
|
||||
|
||||
### State Machine Diagram
|
||||
|
||||
````markdown
|
||||
/\*\*
|
||||
|
||||
- @component StatefulComponent
|
||||
- @description Component with complex state transitions
|
||||
-
|
||||
- ## State Machine
|
||||
- ```mermaid
|
||||
|
||||
```
|
||||
|
||||
- stateDiagram-v2
|
||||
- [*] --> Idle
|
||||
- Idle --> Loading: fetch()
|
||||
- Loading --> Success: data received
|
||||
- Loading --> Error: error thrown
|
||||
- Error --> Loading: retry()
|
||||
- Success --> Idle: reset()
|
||||
- Error --> Idle: reset()
|
||||
- ```
|
||||
*/
|
||||
```
|
||||
````
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
````markdown
|
||||
/\*\*
|
||||
|
||||
- @function authenticateUser
|
||||
- @description Authentication process flow
|
||||
-
|
||||
- ## Authentication Sequence
|
||||
- ```mermaid
|
||||
|
||||
```
|
||||
|
||||
- sequenceDiagram
|
||||
- participant User
|
||||
- participant Client
|
||||
- participant API
|
||||
- participant Database
|
||||
-
|
||||
- User->>Client: Enter credentials
|
||||
- Client->>API: POST /auth/login
|
||||
- API->>Database: Validate credentials
|
||||
- Database-->>API: Valid user
|
||||
- API-->>Client: JWT token
|
||||
- Client-->>User: Login success
|
||||
- ```
|
||||
*/
|
||||
```
|
||||
````
|
||||
|
||||
### When to Use Mermaid Diagrams
|
||||
|
||||
- Component hierarchy diagrams for complex nested components
|
||||
- Data flow diagrams for state management patterns
|
||||
- Process flows for complex business logic
|
||||
- State machines for components with multiple states
|
||||
- Sequence diagrams for asynchronous operations
|
||||
|
||||
### Mermaid Diagram Guidelines
|
||||
|
||||
1. Keep diagrams simple and focused on one aspect of the system
|
||||
2. Use consistent naming conventions in diagrams
|
||||
3. Add concise labels to explain relationships
|
||||
4. Include a brief text description above each diagram
|
||||
5. For complex diagrams, consider breaking them into multiple smaller diagrams
|
232
standards/FEATURE_BUILDING.md
Normal file
232
standards/FEATURE_BUILDING.md
Normal file
@ -0,0 +1,232 @@
|
||||
# Feature Building Process
|
||||
|
||||
This document outlines our standardized approach to building new features. Following this process
|
||||
ensures that features are well-designed, properly structured, thoroughly documented, and
|
||||
consistently implemented.
|
||||
|
||||
## 1. Design and Data Flow Analysis
|
||||
|
||||
Before writing any code, thoroughly analyze the design and data flow requirements:
|
||||
|
||||
### Design Analysis
|
||||
|
||||
- Study the Figma/design mockups thoroughly
|
||||
- Identify all UI components and their states
|
||||
- Note interactions, animations, and transitions
|
||||
- Identify responsive behavior requirements
|
||||
- Document accessibility considerations
|
||||
|
||||
### Data Flow Analysis
|
||||
|
||||
- Map out the data requirements for the feature
|
||||
- Identify data sources and sinks
|
||||
- Document API endpoints that will be used
|
||||
- Define state management needs
|
||||
- Identify where data transformations occur
|
||||
- Document any caching or persistence requirements
|
||||
|
||||
### Output
|
||||
|
||||
Create a Design & Data Requirements document containing:
|
||||
|
||||
- Screenshots/references to relevant design mockups
|
||||
- Component breakdown with states and props
|
||||
- Data flow diagram
|
||||
- API contract expectations
|
||||
- State management approach
|
||||
|
||||
## 2. Structure Planning
|
||||
|
||||
Once the design and data requirements are understood, plan the structure:
|
||||
|
||||
### Routing
|
||||
|
||||
- Define all routes needed for the feature
|
||||
- Document route parameters and query parameters
|
||||
- Specify layout components for each route
|
||||
- Define route guards or access control
|
||||
|
||||
### Component Hierarchy
|
||||
|
||||
- Create a component tree showing parent-child relationships
|
||||
- Identify reusable components vs. feature-specific components
|
||||
- Define prop interfaces for all components
|
||||
- Document component responsibilities and boundaries
|
||||
|
||||
### File Structure
|
||||
|
||||
- Plan the directory structure following project conventions
|
||||
- Define file naming following established patterns
|
||||
- Identify shared utilities, hooks, or helpers needed
|
||||
- Plan test file organization
|
||||
|
||||
### Output
|
||||
|
||||
Create a Structure Plan document containing:
|
||||
|
||||
- Route definitions
|
||||
- Component hierarchy diagram
|
||||
- Directory and file structure plan
|
||||
- List of new files to create with their purpose
|
||||
|
||||
## 3. File Creation with Documentation
|
||||
|
||||
Create skeleton files with comprehensive documentation:
|
||||
|
||||
### For Each Component:
|
||||
|
||||
- Purpose and responsibility
|
||||
- Props interface with detailed documentation
|
||||
- State management approach
|
||||
- Side effects and cleanup
|
||||
- Error handling approach
|
||||
- Expected behaviors for all edge cases
|
||||
- Performance considerations
|
||||
- Testing strategy
|
||||
|
||||
### For Data/API Files:
|
||||
|
||||
- Type definitions
|
||||
- Function signatures with parameters and return types
|
||||
- Error handling approach
|
||||
- Caching strategy
|
||||
- Retry logic
|
||||
|
||||
### For Hooks/Utilities:
|
||||
|
||||
- Purpose and usage examples
|
||||
- Parameters and return values
|
||||
- Side effects
|
||||
- Error scenarios
|
||||
- Performance characteristics
|
||||
|
||||
### Output
|
||||
|
||||
A set of skeleton files with detailed JSDoc comments outlining implementation requirements for each
|
||||
file.
|
||||
|
||||
## 4. Implementation Guide
|
||||
|
||||
Create a comprehensive guide for engineers or AI agents to follow:
|
||||
|
||||
### Implementation Order
|
||||
|
||||
- Dependency graph showing which files should be implemented first
|
||||
- Recommended implementation sequence
|
||||
|
||||
### Critical Requirements
|
||||
|
||||
- Performance requirements
|
||||
- Accessibility requirements
|
||||
- Browser/device compatibility requirements
|
||||
- Error handling expectations
|
||||
|
||||
### Testing Requirements
|
||||
|
||||
- Unit test coverage expectations
|
||||
- Integration test scenarios
|
||||
- E2E test scenarios
|
||||
|
||||
### What NOT to Do
|
||||
|
||||
- Anti-patterns to avoid
|
||||
- Performance pitfalls
|
||||
- Security concerns
|
||||
- Common mistakes
|
||||
|
||||
### Review Checklist
|
||||
|
||||
- Code quality checks
|
||||
- Performance review points
|
||||
- Accessibility review points
|
||||
- Security review points
|
||||
|
||||
## Example: Feature Building for a User Profile Page
|
||||
|
||||
### 1. Design & Data Analysis
|
||||
|
||||
```
|
||||
Design Requirements:
|
||||
- Profile page with user avatar, name, email, and bio
|
||||
- Edit profile form with validation
|
||||
- Activity feed showing recent actions
|
||||
...
|
||||
|
||||
Data Requirements:
|
||||
- User profile data from GET /api/users/:id
|
||||
- Profile updates via PUT /api/users/:id
|
||||
- Activity data from GET /api/users/:id/activity
|
||||
...
|
||||
```
|
||||
|
||||
### 2. Structure Plan
|
||||
|
||||
```
|
||||
Routes:
|
||||
- /profile - Main profile view
|
||||
- /profile/edit - Edit profile form
|
||||
|
||||
Components:
|
||||
- ProfilePage
|
||||
- ProfileHeader
|
||||
- ActivityFeed
|
||||
- ActivityItem
|
||||
- ProfileEditForm
|
||||
- ImageUploader
|
||||
- FormFields
|
||||
...
|
||||
```
|
||||
|
||||
### 3. File Skeleton (Example for ProfileHeader.tsx)
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* @component ProfileHeader
|
||||
* @description Displays the user's profile header with avatar, name, and key information
|
||||
*
|
||||
* Requirements:
|
||||
* - Display user avatar with fallback for missing images
|
||||
* - Show user name, handle, and join date
|
||||
* - Display edit button only if user is viewing their own profile
|
||||
* - Show verified badge if account is verified
|
||||
* - Handle loading and error states
|
||||
* ...
|
||||
*/
|
||||
```
|
||||
|
||||
### 4. Implementation Guide (Excerpt)
|
||||
|
||||
```
|
||||
Implementation Order:
|
||||
1. Types and API functions
|
||||
2. Hooks for data fetching
|
||||
3. Base components (ProfileHeader, ActivityItem)
|
||||
4. Container components (ProfilePage, ActivityFeed)
|
||||
5. Form components
|
||||
|
||||
Do NOT:
|
||||
- Make direct API calls from components - use the defined hooks
|
||||
- Store sensitive user data in localStorage
|
||||
- Use inline styles except for dynamically calculated values
|
||||
- Implement custom form validation - use the specified validation library
|
||||
...
|
||||
```
|
||||
|
||||
## Process Checklist
|
||||
|
||||
- [ ] Complete Design & Data Flow Analysis
|
||||
- [ ] Create Structure Plan
|
||||
- [ ] Create Skeleton Files with Documentation
|
||||
- [ ] Develop Implementation Guide
|
||||
- [ ] Review and Finalize Feature Building Documents
|
||||
- [ ] Implement Feature Following Guide
|
||||
- [ ] Review Implementation Against Requirements
|
||||
|
||||
By following this standardized feature building process, we ensure that features are implemented
|
||||
consistently, with clear documentation, and according to best practices.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To start building a new feature using this process, use the
|
||||
[Feature Building Template](./FEATURE_BUILDING_TEMPLATE.md) as a starting point. This template
|
||||
provides a structured document that you can fill in with the specific details for your feature.
|
239
standards/FEATURE_BUILDING_TEMPLATE.md
Normal file
239
standards/FEATURE_BUILDING_TEMPLATE.md
Normal file
@ -0,0 +1,239 @@
|
||||
# Feature Building: [Feature Name]
|
||||
|
||||
> This is a template for the Feature Building process. Replace placeholder text with actual content
|
||||
> for your feature.
|
||||
|
||||
## 1. Design and Data Flow Analysis
|
||||
|
||||
### Design Analysis
|
||||
|
||||
#### UI Components
|
||||
|
||||
- Component 1: [Description, states, interactions]
|
||||
- Component 2: [Description, states, interactions]
|
||||
- ...
|
||||
|
||||
#### Interactions and Animations
|
||||
|
||||
- Interaction 1: [Description]
|
||||
- Animation 1: [Description]
|
||||
- ...
|
||||
|
||||
#### Responsive Behavior
|
||||
|
||||
- Mobile: [Description]
|
||||
- Tablet: [Description]
|
||||
- Desktop: [Description]
|
||||
|
||||
#### Accessibility Considerations
|
||||
|
||||
- [List accessibility requirements]
|
||||
|
||||
### Data Flow Analysis
|
||||
|
||||
#### Data Requirements
|
||||
|
||||
- Data Entity 1: [Properties, validation rules]
|
||||
- Data Entity 2: [Properties, validation rules]
|
||||
- ...
|
||||
|
||||
#### API Endpoints
|
||||
|
||||
- Endpoint 1: `[METHOD] /path` - [Purpose, request/response format]
|
||||
- Endpoint 2: `[METHOD] /path` - [Purpose, request/response format]
|
||||
- ...
|
||||
|
||||
#### State Management
|
||||
|
||||
- Global State: [What needs to be in global state]
|
||||
- Local State: [What can be kept in component state]
|
||||
- Derived State: [What can be computed from other state]
|
||||
|
||||
#### Data Transformations
|
||||
|
||||
- [Describe any transformations needed between API and UI]
|
||||
|
||||
#### Caching/Persistence
|
||||
|
||||
- [Describe caching or persistence requirements]
|
||||
|
||||
## 2. Structure Planning
|
||||
|
||||
### Routing
|
||||
|
||||
#### Routes
|
||||
|
||||
- `/route1`: [Purpose, parameters]
|
||||
- `/route2`: [Purpose, parameters]
|
||||
- ...
|
||||
|
||||
#### Layouts
|
||||
|
||||
- Route 1 Layout: [Description]
|
||||
- Route 2 Layout: [Description]
|
||||
- ...
|
||||
|
||||
#### Access Control
|
||||
|
||||
- [Describe any route guards or access control]
|
||||
|
||||
### Component Hierarchy
|
||||
|
||||
```
|
||||
ParentComponent
|
||||
├── ChildComponent1
|
||||
│ ├── GrandchildComponent1
|
||||
│ └── GrandchildComponent2
|
||||
└── ChildComponent2
|
||||
```
|
||||
|
||||
#### Component Interfaces
|
||||
|
||||
- Component 1 Props: [Props description]
|
||||
- Component 2 Props: [Props description]
|
||||
- ...
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
feature-name/
|
||||
├── index.ts
|
||||
├── types.ts
|
||||
├── components/
|
||||
│ ├── ComponentOne.tsx
|
||||
│ └── ComponentTwo.tsx
|
||||
├── hooks/
|
||||
│ └── useFeatureData.ts
|
||||
└── utils/
|
||||
└── featureUtils.ts
|
||||
```
|
||||
|
||||
#### New Files to Create
|
||||
|
||||
- `feature-name/index.ts`: [Purpose]
|
||||
- `feature-name/types.ts`: [Purpose]
|
||||
- ...
|
||||
|
||||
## 3. File Skeletons
|
||||
|
||||
### `feature-name/index.ts`
|
||||
|
||||
````typescript
|
||||
/**
|
||||
* @module FeatureName
|
||||
* @description [Brief description of the feature]
|
||||
*
|
||||
* This module exports the main components and hooks for the [Feature Name] feature.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { FeatureComponent } from '@/features/feature-name';
|
||||
*
|
||||
* function MyComponent() {
|
||||
* return <FeatureComponent />;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
export * from './components/ComponentOne';
|
||||
// Add additional exports
|
||||
````
|
||||
|
||||
### `feature-name/components/ComponentOne.tsx`
|
||||
|
||||
````typescript
|
||||
/**
|
||||
* @component ComponentOne
|
||||
* @description [Description of the component]
|
||||
*
|
||||
* Requirements:
|
||||
* - [Requirement 1]
|
||||
* - [Requirement 2]
|
||||
* - ...
|
||||
*
|
||||
* States:
|
||||
* - Loading: [Description]
|
||||
* - Error: [Description]
|
||||
* - Success: [Description]
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ComponentOne prop1="value" />
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Implementation details will go here
|
||||
````
|
||||
|
||||
### [Add skeleton documentation for all planned files]
|
||||
|
||||
## 4. Implementation Guide
|
||||
|
||||
### Implementation Order
|
||||
|
||||
1. Create `types.ts` with all required interfaces
|
||||
2. Implement API utilities and hooks
|
||||
3. Implement base UI components
|
||||
4. Implement container components
|
||||
5. Connect components to data sources
|
||||
6. Implement routing and navigation
|
||||
|
||||
### Critical Requirements
|
||||
|
||||
#### Performance
|
||||
|
||||
- [List performance requirements]
|
||||
|
||||
#### Accessibility
|
||||
|
||||
- [List accessibility requirements]
|
||||
|
||||
#### Compatibility
|
||||
|
||||
- [List browser/device compatibility requirements]
|
||||
|
||||
#### Error Handling
|
||||
|
||||
- [List error handling expectations]
|
||||
|
||||
### Testing Requirements
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
- Component 1: [Test scenarios]
|
||||
- Component 2: [Test scenarios]
|
||||
- ...
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
- [List integration test scenarios]
|
||||
|
||||
#### E2E Tests
|
||||
|
||||
- [List E2E test scenarios]
|
||||
|
||||
### What NOT to Do
|
||||
|
||||
- ❌ [Anti-pattern 1]
|
||||
- ❌ [Anti-pattern 2]
|
||||
- ...
|
||||
|
||||
### Review Checklist
|
||||
|
||||
- [ ] Code follows project style guide
|
||||
- [ ] Components are properly documented
|
||||
- [ ] All critical requirements are met
|
||||
- [ ] Tests cover main functionality
|
||||
- [ ] Accessibility guidelines are followed
|
||||
- [ ] Performance is satisfactory
|
||||
- [ ] Error handling is comprehensive
|
||||
|
||||
## Process Checklist
|
||||
|
||||
- [ ] Complete Design & Data Flow Analysis
|
||||
- [ ] Create Structure Plan
|
||||
- [ ] Create Skeleton Files with Documentation
|
||||
- [ ] Develop Implementation Guide
|
||||
- [ ] Review and Finalize Feature Building Documents
|
||||
- [ ] Implement Feature Following Guide
|
||||
- [ ] Review Implementation Against Requirements
|
41
standards/README.md
Normal file
41
standards/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Project Standards and Conventions
|
||||
|
||||
This directory contains documentation for project-wide standards, conventions, and best practices.
|
||||
These guidelines help ensure consistency across the codebase and make it easier for developers to
|
||||
collaborate.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Component Documentation Standards](./COMPONENT_DOCUMENTATION.md) - Guidelines and templates for
|
||||
documenting components, hooks, and utilities
|
||||
- [Feature Building Process](./FEATURE_BUILDING.md) - Standardized approach to building new features
|
||||
from design to implementation
|
||||
- [Feature Building Template](./FEATURE_BUILDING_TEMPLATE.md) - Template document to use when
|
||||
starting a new feature
|
||||
|
||||
## Purpose
|
||||
|
||||
The standards defined in this directory serve several important purposes:
|
||||
|
||||
1. **Consistency** - Establishes consistent patterns across the codebase
|
||||
2. **Onboarding** - Helps new developers understand project conventions
|
||||
3. **Maintainability** - Makes code easier to maintain and extend
|
||||
4. **Quality** - Encourages best practices that lead to higher quality code
|
||||
|
||||
## Adoption
|
||||
|
||||
These standards should be applied to all new code and, when feasible, retroactively applied to
|
||||
existing code during refactoring efforts.
|
||||
|
||||
## Contributing
|
||||
|
||||
To suggest changes or additions to these standards, please:
|
||||
|
||||
1. Discuss proposed changes with the team
|
||||
2. Update the relevant documentation
|
||||
3. Provide examples demonstrating the benefits of the proposed changes
|
||||
|
||||
## Enforcement
|
||||
|
||||
While these standards are not automatically enforced, developers are encouraged to follow them and
|
||||
code reviewers should check for adherence to these guidelines.
|
10
yarn.lock
10
yarn.lock
@ -4659,6 +4659,11 @@
|
||||
"@radix-ui/react-primitive" "2.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
|
||||
"@radix-ui/react-icons@^1.3.2":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz#09be63d178262181aeca5fb7f7bc944b10a7f441"
|
||||
integrity sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==
|
||||
|
||||
"@radix-ui/react-id@1.1.0", "@radix-ui/react-id@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz"
|
||||
@ -18691,3 +18696,8 @@ zustand@^4.5.2:
|
||||
integrity sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==
|
||||
dependencies:
|
||||
use-sync-external-store "^1.2.2"
|
||||
|
||||
zustand@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.3.tgz#b323435b73d06b2512e93c77239634374b0e407f"
|
||||
integrity sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==
|
||||
|
Loading…
Reference in New Issue
Block a user