feat: Add documentation and context for frontend architecture
Some checks failed
Lint / lint (20.x) (pull_request) Has been cancelled

This commit introduces several key improvements to the frontend architecture:

- Created comprehensive documentation for TSDoc and TypeDoc usage
- Added WalletContext for managing wallet connection state
- Updated navigation and project components
- Introduced layout and routing strategy documentation
- Removed unused imports and commented code
- Enhanced project search and navigation components
This commit is contained in:
icld 2025-03-02 06:35:03 -08:00
parent e1c2a8ce2c
commit 6f64513fc8
9 changed files with 635 additions and 60 deletions

165
DOCUMENTATION.md Normal file
View File

@ -0,0 +1,165 @@
# Documentation Guide for Snowball Tools
This guide explains how to write and generate documentation for the Snowball Tools project.
## TSDoc and TypeDoc
We use [TSDoc](https://tsdoc.org/) for documenting our TypeScript code and [TypeDoc](https://typedoc.org/) for generating API documentation from those comments.
## Writing Documentation
### Basic Comment Structure
TSDoc comments start with `/**` and end with `*/`. Each line within the comment block typically starts with a `*`.
```typescript
/**
* This is a TSDoc comment.
*/
```
### Documenting Functions
```typescript
/**
* Calculates the sum of two numbers.
*
* @param a - The first number
* @param b - The second number
* @returns The sum of a and b
*
* @example
* ```ts
* const result = add(1, 2);
* console.log(result); // 3
* ```
*/
function add(a: number, b: number): number {
return a + b;
}
```
### Documenting Classes
```typescript
/**
* Represents a user in the system.
*
* @remarks
* This class is used throughout the application to represent user data.
*
* @example
* ```ts
* const user = new User('John', 'Doe');
* console.log(user.fullName); // "John Doe"
* ```
*/
class User {
/**
* Creates a new User instance.
*
* @param firstName - The user's first name
* @param lastName - The user's last name
*/
constructor(
/** The user's first name */
public firstName: string,
/** The user's last name */
public lastName: string
) {}
/**
* Gets the user's full name.
*/
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
```
### Documenting Interfaces
```typescript
/**
* Configuration options for the application.
*
* @public
*/
interface AppConfig {
/**
* The port number the server should listen on.
* @default 3000
*/
port: number;
/**
* The host the server should bind to.
* @default "localhost"
*/
host: string;
/**
* Whether to enable debug mode.
* @default false
*/
debug?: boolean;
}
```
## Common TSDoc Tags
| Tag | Description |
|-----|-------------|
| `@param` | Documents a function parameter |
| `@returns` | Documents the return value |
| `@throws` | Documents exceptions that might be thrown |
| `@example` | Provides an example of usage |
| `@remarks` | Adds additional information |
| `@deprecated` | Marks an item as deprecated |
| `@see` | Refers to related documentation |
| `@default` | Documents the default value |
| `@public`, `@protected`, `@private` | Visibility modifiers |
| `@internal` | Marks an item as internal (not part of the public API) |
| `@beta` | Marks an item as in beta stage |
| `@alpha` | Marks an item as in alpha stage |
| `@experimental` | Marks an item as experimental |
## Generating Documentation
To generate documentation for the project, run:
```bash
yarn docs
```
This will create a `docs` directory with the generated documentation.
To watch for changes and regenerate documentation automatically:
```bash
yarn docs:watch
```
## Best Practices
1. **Document Public APIs**: Always document public APIs thoroughly.
2. **Include Examples**: Provide examples for complex functions or classes.
3. **Be Concise**: Keep documentation clear and to the point.
4. **Use Proper Grammar**: Use proper grammar and punctuation.
5. **Update Documentation**: Keep documentation in sync with code changes.
6. **Document Parameters**: Document all parameters, including their types and purpose.
7. **Document Return Values**: Document what a function returns.
8. **Document Exceptions**: Document any exceptions that might be thrown.
## Example Files
For reference, check out these example files that demonstrate proper TSDoc usage:
- `packages/backend/src/utils/tsdoc-example.ts`
- `packages/frontend/src/utils/tsdoc-example.ts`
## Resources
- [TSDoc Official Documentation](https://tsdoc.org/)
- [TypeDoc Official Documentation](https://typedoc.org/)
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)

View File

@ -1,6 +1,5 @@
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.
@ -23,7 +22,7 @@ export function NavigationWrapper({
className,
...props
}: NavigationWrapperProps) {
const { isAuthenticated } = useGitHubAuth();
// const { isAuthenticated } = useGitHubAuth();
return (
<div className={cn('min-h-screen bg-background', className)} {...props}>

View File

@ -1,5 +1,5 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { getInitials } from '@/utils/geInitials';
import { Project } from 'gql-client';
import { AlertTriangle } from 'lucide-react';
@ -16,7 +16,7 @@ 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
*/
@ -27,7 +27,7 @@ interface ProjectCardProps extends ComponentPropsWithoutRef<'div'> {
/**
* ProjectCard component
*
*
* Displays a card with project information including:
* - Project name and icon
* - Domain URL (if available)
@ -35,14 +35,14 @@ interface ProjectCardProps extends ComponentPropsWithoutRef<'div'> {
* - 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"
* <ProjectCard
* project={projectData}
* status="success"
* />
* ```
*/
@ -98,16 +98,19 @@ export const ProjectCard = ({
</Avatar>
<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 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'}
{project.deployments[0]?.applicationDeploymentRecordData?.url ??
'No domain'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{hasError && <AlertTriangle className="text-destructive h-4 w-4" />}
<ProjectCardActions
<ProjectCardActions
onSettingsClick={handleSettingsClick}
onDeleteClick={handleDeleteClick}
/>

View File

@ -0,0 +1,122 @@
import { useToast } from '@/hooks/use-toast';
import React, {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from 'react';
/**
* @interface WalletContextType
* @description Defines the structure of the WalletContext value.
* @property {object | null} wallet - The wallet object containing id and address.
* @property {boolean} isConnected - Indicates if the wallet is connected.
* @property {function} connect - Function to connect the wallet.
* @property {function} disconnect - Function to disconnect the wallet.
*/
interface WalletContextType {
wallet: {
id: string;
address?: string;
} | null;
isConnected: boolean;
connect: () => Promise<void>;
disconnect: () => void;
}
/**
* @const WalletContext
* @description Creates a context for managing wallet connection state.
*/
const WalletContext = createContext<WalletContextType | undefined>(undefined);
/**
* @component WalletProvider
* @description Provides the WalletContext to its children.
* @param {Object} props - Component props
* @param {ReactNode} props.children - The children to render.
*/
export const WalletProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const [wallet, setWallet] = useState<WalletContextType['wallet']>(null);
const [isConnected, setIsConnected] = useState(false);
const { toast } = useToast();
useEffect(() => {
const handleWalletMessage = (event: MessageEvent) => {
if (event.origin !== import.meta.env.VITE_WALLET_IFRAME_URL) return;
if (event.data.type === 'WALLET_ACCOUNTS_DATA') {
const address = event.data.data[0].address;
setWallet({
id: address,
address: address,
});
setIsConnected(true);
toast({
title: 'Wallet Connected',
// variant: 'success',
duration: 3000,
// id: '',
});
}
};
window.addEventListener('message', handleWalletMessage);
return () => window.removeEventListener('message', handleWalletMessage);
}, [toast]);
const connect = async () => {
const iframe = document.getElementById('walletIframe') as HTMLIFrameElement;
if (iframe?.contentWindow) {
iframe.contentWindow.postMessage(
{
type: 'REQUEST_WALLET_ACCOUNTS',
chainId: import.meta.env.VITE_LACONICD_CHAIN_ID,
},
import.meta.env.VITE_WALLET_IFRAME_URL,
);
} else {
toast({
title: 'Wallet Connection Failed',
description: 'Wallet iframe not found or not loaded',
// variant: 'error',
duration: 3000,
});
}
};
const disconnect = () => {
setWallet(null);
setIsConnected(false);
toast({
title: 'Wallet Disconnected',
// variant: 'info',
duration: 3000,
});
};
return (
<WalletContext.Provider
value={{ wallet, isConnected, connect, disconnect }}
>
{children}
</WalletContext.Provider>
);
};
/**
* @function useWallet
* @description A hook that provides access to the WalletContext.
* @returns {WalletContextType} The wallet context value.
* @throws {Error} If used outside of a WalletProvider.
*/
export const useWallet = () => {
const context = useContext(WalletContext);
if (context === undefined) {
throw new Error('useWallet must be used within a WalletProvider');
}
return context;
};

View File

@ -1,6 +1,11 @@
import { ScreenWrapper } from '@/components/layout';
import { ProjectSearchBar } from '@/components/projects/ProjectSearchBar';
import { Button } from '@/components/ui/button';
import { useWallet } from '@/context';
import { useGQLClient } from '@/context/GQLClientContext';
import { useWallet } from '@/context/WalletContextProvider';
import { formatAddress } from '@/utils/format';
import { User } from 'gql-client';
import { Bell, Plus } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
@ -28,53 +33,50 @@ const ProjectSearchLayout = () => {
}, [client]);
return (
<>
<Outlet />
</>
// <ScreenWrapper padded={false}>
// <div className="flex flex-col h-full">
// {/* Header */}
// <div className="bg-background dark:bg-overlay hover:z-30 sticky top-0 border-b">
// <div className="flex items-center gap-4 px-6 py-4">
// <div className="flex-1">
// <ProjectSearchBar
// onChange={(project) => {
// navigate(
// `/${project.organization.slug}/projects/${project.id}`,
// );
// }}
// />
// </div>
// <div className="flex items-center gap-3">
// <Button
// variant="secondary"
// size="icon"
// onClick={() => {
// fetchOrgSlug().then((organizationSlug) => {
// navigate(`/${organizationSlug}/projects/create`);
// });
// }}
// >
// <Plus className="w-4 h-4" />
// </Button>
// <Button variant="ghost" size="icon">
// <Bell className="w-4 h-4" />
// </Button>
// {user?.name && (
// <p className="text-sm tracking-[-0.006em] text-muted-foreground">
// {formatAddress(user.name)}
// </p>
// )}
// </div>
// </div>
// </div>
<ScreenWrapper padded={false}>
<div className="flex flex-col h-full">
{/* Header */}
<div className="bg-background dark:bg-overlay hover:z-30 sticky top-0 border-b">
<div className="flex items-center gap-4 px-6 py-4">
<div className="flex-1">
<ProjectSearchBar
onChange={(project) => {
navigate(
`/${project.organization.slug}/projects/${project.id}`,
);
}}
/>
</div>
<div className="flex items-center gap-3">
<Button
variant="secondary"
size="icon"
onClick={() => {
fetchOrgSlug().then((organizationSlug) => {
navigate(`/${organizationSlug}/projects/create`);
});
}}
>
<Plus className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<Bell className="w-4 h-4" />
</Button>
{user?.name && (
<p className="text-sm tracking-[-0.006em] text-muted-foreground">
{formatAddress(user.name)}
</p>
)}
</div>
</div>
</div>
// {/* Content */}
// <div className="flex-1 overflow-y-auto">
// <Outlet />
// </div>
// </div>
// </ScreenWrapper>
{/* Content */}
<div className="flex-1 overflow-y-auto">
<Outlet />
</div>
</div>
</ScreenWrapper>
);
};

View File

@ -0,0 +1,110 @@
# Shadcn Migration Plan
## Overview
Migration from legacy shared components to shadcn/ui components and lucide-react icons.
## 1. Icon Migration Phase
### Lucide Icon Mappings
| Current Custom Icon | Lucide Equivalent |
| ------------------------- | ----------------- |
| ArrowLeftCircleFilledIcon | ArrowLeftCircle |
| ArrowRightCircleIcon | ArrowRight |
| BranchIcon | GitBranch |
| BuildingIcon | Building2 |
| CalendarIcon | Calendar |
| CheckIcon | Check |
| ChevronDownIcon | ChevronDown |
| ClockIcon | Clock |
| CopyIcon | Copy |
| CrossIcon | X |
| EditBigIcon | Edit |
| GearIcon | Settings |
| GithubIcon | Github |
| LoaderIcon | Loader2 |
| LockIcon | Lock |
| PlusIcon | Plus |
| SearchIcon | Search |
| TrashIcon | Trash2 |
| WarningIcon | AlertTriangle |
### Steps
1. Install lucide-react
2. Replace custom icons with Lucide equivalents
3. Remove CustomIcon directory
4. Update all icon imports
## 2. Component Migration Phase
### Direct Replacements
| Legacy Component | Shadcn Component |
| ---------------- | ------------------ |
| Button | ui/button.tsx |
| Input | ui/input.tsx |
| Select | ui/select.tsx |
| Calendar | ui/calendar.tsx |
| Checkbox | ui/checkbox.tsx |
| Modal | ui/dialog.tsx |
| Radio | ui/radio-group.tsx |
| Switch | ui/switch.tsx |
| Table | ui/table.tsx |
| Tabs | ui/tabs.tsx |
| Toast | ui/toast.tsx |
| Tooltip | ui/tooltip.tsx |
### Steps
1. Remove each shared component
2. Update imports to use shadcn components
3. Fix TypeScript errors as they appear
4. Test each component replacement
## 3. Special Components Phase
### Components Requiring Custom Handling
- AsyncSelect (needs custom implementation)
- Any components with specific business logic
### Steps
1. Identify components needing special handling
2. Create custom implementations where needed
3. Test thoroughly
## 4. Theme Migration Phase
### Steps
1. Map theme properties to Tailwind classes
2. Update component styling
3. Remove .theme.ts files
4. Verify visual consistency
## 5. Testing & Validation Phase
### Steps
1. Run TypeScript checks
2. Visual testing of all components
3. Test all interactive features
4. Performance testing
5. Cross-browser testing
## Progress Tracking
- [ ] Icon Migration Complete
- [ ] Component Migration Complete
- [ ] Special Components Handled
- [ ] Theme Migration Complete
- [ ] Testing & Validation Complete
## Notes
- Keep track of any components that need special attention
- Document any deviations from the plan
- Note any breaking changes

View File

@ -0,0 +1,130 @@
# Layout Strategy with Vite & React Router
## Core Principles
1. **NavigationWrapper**
- Top-level container for global navigation
- Handles responsive mobile/desktop navigation
- Manages global navigation state
2. **ScreenWrapper**
- Standard content area with consistent spacing
- Responsive padding and margins
- Handles viewport height calculations
- Flex column layout by default
3. **Header Components**
- Consistent page headers with title and subtitle
- Optional action buttons
- Supports compact and default layouts
- Responsive mobile/desktop behavior
4. **TabWrapper**
- Standardized tabbed navigation
- Supports horizontal and vertical orientations
- Consistent styling and interactions
- Built on shared tab components
## Implementation Guide
### 1. Basic Page Structure
```typescript
import { NavigationWrapper, ScreenWrapper, Header } from '@/components/layout';
function BasicPage() {
return (
<NavigationWrapper>
<ScreenWrapper>
<Header
title="Page Title"
subtitle="Optional subtitle"
actions={[<Button>Action</Button>]}
/>
<main>
{/* Page content */}
</main>
</ScreenWrapper>
</NavigationWrapper>
);
}
```
### 2. Tabbed Page Structure
```typescript
import {
NavigationWrapper,
ScreenWrapper,
Header,
TabWrapper,
TabsList,
TabsTrigger,
TabsContent
} from '@/components/layout';
function TabbedPage() {
return (
<NavigationWrapper>
<ScreenWrapper>
<Header title="Tabbed Page" />
<TabWrapper defaultValue="tab1">
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
</TabsList>
<TabsContent value="tab1">
Content 1
</TabsContent>
<TabsContent value="tab2">
Content 2
</TabsContent>
</TabWrapper>
</ScreenWrapper>
</NavigationWrapper>
);
}
```
## Best Practices
1. **Component Usage**
- Always wrap pages with NavigationWrapper
- Use ScreenWrapper for consistent spacing
- Include Header component for page titles
- Use TabWrapper for tabbed interfaces
2. **Layout Structure**
- Keep layouts shallow and composable
- Use flex layouts for responsive design
- Maintain consistent spacing patterns
- Follow mobile-first approach
3. **Styling Guidelines**
- Use utility classes for minor adjustments
- Leverage component variants for major changes
- Maintain dark mode compatibility
- Follow responsive design breakpoints
4. **Performance**
- Use dynamic imports for route-based code splitting
- Lazy load tab content when possible
- Minimize layout shifts during loading
- Optimize navigation transitions
## Migration Checklist
- [x] Create unified layout components
- [x] Standardize navigation patterns
- [x] Implement consistent header styling
- [x] Create TabWrapper component
- [ ] Update existing pages to use new components
- [ ] Add component documentation
- [ ] Write component tests
- [ ] Create Storybook examples

View File

@ -0,0 +1,37 @@
# Frontend Route Component Mapping
## Core Route Hierarchy
```mermaid
graph TD
A[/:orgSlug/] --> B[projects/]
B --> C[create/]
C --> D[template/]
D --> E[configure]
D --> F[deploy]
C --> G[success/:id]
B --> H[:id/]
H --> I[overview]
H --> J[deployments]
H --> K[settings/]
K --> L[general]
K --> M[git]
K --> N[domains]
K --> O[environment-variables]
```
## Component Mapping Table
| Route Path | Component File | Entry Point |
|------------|----------------|-------------|
| `:orgSlug/projects/create` | `pages/org-slug/projects/create/index.tsx` | NewProject |
| `:orgSlug/projects/create/template` | `pages/org-slug/projects/create/template/index.tsx` | CreateRepo |
| `:orgSlug/projects/create/template/configure` | `pages/org-slug/projects/create/template/Configure.tsx` | Configure |
| `:orgSlug/projects/create/template/deploy` | `pages/org-slug/projects/create/template/Deploy.tsx` | Deploy |
| `:orgSlug/projects/create/success/:id` | `pages/org-slug/projects/create/success/Id.tsx` | Id |
| `:orgSlug/projects/:id/overview` | `pages/org-slug/projects/id/Overview.tsx` | OverviewTabPanel |
| `:orgSlug/projects/:id/deployments` | `pages/org-slug/projects/id/Deployments.tsx` | DeploymentsTabPanel |
| `:orgSlug/projects/:id/settings/general` | `pages/org-slug/projects/id/settings/General.tsx` | GeneralTabPanel |
| `:orgSlug/projects/:id/settings/git` | `pages/org-slug/projects/id/settings/Git.tsx` | GitTabPanel |
| `:orgSlug/projects/:id/settings/domains` | `pages/org-slug/projects/id/settings/Domains.tsx` | Domains |
| `:orgSlug/projects/:id/settings/environment-variables` | `pages/org-slug/projects/id/settings/EnvironmentVariables.tsx` | EnvironmentVariablesTabPanel |

7
standards/init-commit.md Normal file
View File

@ -0,0 +1,7 @@
# Initial Commit for QWRK UX Changes
This is the first commit for QWRK UX changes to a fork of the `snowballtools-base` repository in Gitea. This commit sets the foundation for the upcoming user experience improvements and modifications.
- Repository: `snowballtools-base`
- Platform: Gitea
- Purpose: QWRK UX changes