Skip to main content

Import Flow Components

Overview

The import flow components implement a multi-step wizard for importing works from external sources (URLs or git repositories) into Ever Works. The flow supports two modes: "import" (copy data into a new repository) and "link existing" (connect to an already-existing Ever Works repository). All components live in apps/web/src/components/works/import/ and are exported through a barrel index.ts.

Architecture

Import Flow (multi-step wizard)

├── Step 1: ImportSourceStep
│ ├── Source method selection (URL or Repository)
│ ├── URL input with format detection
│ ├── RepositorySelector (from git provider)
│ └── Analyze button → analyzeRepository / analyzeForLinking

├── Step 2: ImportModeSelector
│ ├── ModeOption: "Import Copy" (creates new repo)
│ └── ModeOption: "Link Existing" (connects to existing repo)

├── Step 3 (Import mode): ImportConfigureStep
│ ├── Detected format display
│ ├── Work name input
│ ├── SlugConflictWarning (if slug exists)
│ ├── Sync toggle (Switch component)
│ ├── AI provider selection (for awesome_readme type)
│ ├── Organization selector
│ └── Import button → importWork

└── Step 3 (Link mode): LinkExistingConfirm
├── Repo status display (data, markdown, website)
├── Missing repo warnings
└── Confirm link → createWork with linked repos

The parent page component orchestrates step transitions, holding the current step index and the accumulated form state. Each step component receives its data via props and communicates back through callbacks.

Components

ImportSourceStep

File: apps/web/src/components/works/import/ImportSourceStep.tsx

PropTypeDescription
onAnalyze(result: AnalyzeResult) => voidCallback with analysis results
connectionsOAuthConnectionDto[]Available git provider connections
isAnalyzingbooleanWhether analysis is in progress

The first step in the import flow. It presents two source methods in a two-column layout:

  1. URL source - A text input for pasting a repository or list URL. Supported formats are displayed below the input (GitHub, GitLab, awesome lists, etc.).
  2. Repository source - A RepositorySelector component that connects to the user's git provider (via their OAuth connection) and lets them browse and select a repository from their account.

When the user clicks "Analyze", the component calls either analyzeRepository (for URLs) or analyzeForLinking (for repository selections) server actions. The results include the detected format type, repository metadata, and whether the repo already exists in Ever Works.

<ImportSourceStep
onAnalyze={(result) => {
setAnalysisResult(result);
setStep(2);
}}
connections={userConnections}
isAnalyzing={isPending}
/>

ImportModeSelector

File: apps/web/src/components/works/import/ImportModeSelector.tsx

PropTypeDescription
onSelect(mode: ImportMode) => voidCallback when a mode is chosen
analysisResultAnalyzeResultResults from the analysis step

Presents two mode cards side by side:

ModeValueDescription
Import Copy'import'Creates a new Ever Works repository with the imported data. The original source is copied.
Link Existing'link_existing'Connects to an existing Ever Works repository structure without copying data.

Each mode is rendered as a ModeOption card with an icon, title, description, and a list of what the mode includes. The cards are styled as selectable options with a border highlight on hover.

The link_existing mode is only available when the analysis detects that the repository already has an Ever Works structure.

type ImportMode = 'import' | 'link_existing';
<ImportModeSelector
onSelect={(mode) => {
setImportMode(mode);
setStep(3);
}}
analysisResult={analysisResult}
/>

ImportConfigureStep

File: apps/web/src/components/works/import/ImportConfigureStep.tsx

PropTypeDescription
analysisResultAnalyzeResultResults from the analysis step
modeImportModeSelected import mode
onImport(config: ImportConfig) => voidCallback when import is confirmed
isPendingbooleanWhether import is in progress

The configuration step for the "import" mode. It renders multiple sections:

  1. Detected type display - Shows the auto-detected format (e.g., "awesome_readme", "json_list", "csv") with an icon and description. If detection failed, a manual format selector is shown as a fallback.

  2. Work name - An Input field for naming the new work. Pre-filled from the analysis result.

  3. Slug conflict warning - If the generated slug conflicts with an existing repository, a SlugConflictWarning component is rendered with suggested alternatives.

  4. Sync toggle - A Switch component to enable/disable automatic syncing with the source repository.

  5. AI provider selection - Only shown for awesome_readme type imports. Lets the user select which AI provider to use for content extraction.

  6. Organization selector - If the user belongs to organizations, lets them assign the work to one.

  7. Attribution note - Informational text about source attribution.

  8. Import button - Triggers the importWork server action with the configured options.

<ImportConfigureStep analysisResult={analysisResult} mode="import" onImport={handleImport} isPending={isPending} />

SlugConflictWarning

File: apps/web/src/components/works/import/SlugConflictWarning.tsx

PropTypeDescription
conflictsstring[]List of conflicting repository names/slugs
suggestedSlugstringAutomatically generated alternative slug
onUseSuggested() => voidCallback to apply the suggested slug

An amber-colored warning banner displayed when the desired work slug already exists. It shows:

  • A warning icon with explanatory text.
  • The list of conflicting repository names.
  • A clickable button to apply the suggested alternative slug.
<SlugConflictWarning
conflicts={['my-work', 'my-work-data']}
suggestedSlug="my-work-2"
onUseSuggested={() => setSlug('my-work-2')}
/>

LinkExistingConfirm

File: apps/web/src/components/works/import/LinkExistingConfirm.tsx

PropTypeDescription
analysisResultAnalyzeResultResults from the analysis step
onConfirm(config: LinkConfig) => voidCallback when link is confirmed
onCancel() => voidCallback to go back
isPendingbooleanWhether linking is in progress

A confirmation dialog for the "link existing" mode. It displays the status of each repository type:

RepositoryStatus Indicators
Data repoFound (green check) or Missing (amber warning)
Markdown repoFound (green check) or Missing (amber warning)
Website repoFound (green check) or Missing (amber warning)

If any repositories are missing, the dialog offers two options:

  1. Continue without missing repos - Links only the found repositories.
  2. Create missing repos - Creates the missing repositories as part of the link operation.
<LinkExistingConfirm
analysisResult={analysisResult}
onConfirm={handleLink}
onCancel={() => setStep(2)}
isPending={isPending}
/>

Implementation Details

Analysis Flow

The analysis step calls server actions that hit the backend API:

  1. analyzeRepository(url) - For URL-based sources. The backend clones or fetches the repository, detects the format, counts items, and returns metadata.
  2. analyzeForLinking(repoId) - For repository selections. Checks if the repo has an Ever Works structure (data repo, markdown repo, website repo).

The analysis result contains:

interface AnalyzeResult {
type: string; // e.g., 'awesome_readme', 'json_list', 'csv'
name: string; // Suggested work name
slug: string; // Generated slug
itemCount: number; // Detected number of items
repoUrl: string; // Source repository URL
hasExistingStructure: boolean; // Whether link_existing is available
repos: {
data: RepoStatus;
markdown: RepoStatus;
website: RepoStatus;
};
}

The two modes result in different server action calls:

  • Import calls importWork which clones the source, parses the content, creates a new Ever Works work structure, and optionally sets up sync.
  • Link calls createWork with pre-existing repository references, connecting the work to already-existing repos without copying data.

Slug Generation

Work slugs are auto-generated from the work name using a kebab-case transformation. If a conflict is detected (the slug or its variants like slug-data or slug-website already exist), the SlugConflictWarning is displayed and a numeric suffix is suggested (e.g., my-work-2).

Styling & Theming

ElementClasses
Step containerspace-y-6 with card-like sections
Source method cardsborder border-border rounded-lg p-6 cursor-pointer hover:border-primary
Active source cardborder-primary bg-primary/5
Mode option cardsborder-2 rounded-xl p-6 with hover and selection states
Selected modeborder-primary bg-primary/5
Warning bannerbg-warning/10 border border-warning/20 rounded-lg p-3 with amber text
Status indicatorsGreen check (text-success) or amber warning (text-warning)
Section dividersborder-t border-border dark:border-border-dark

The import flow uses generous spacing (space-y-6, gap-6) for readability and clear visual separation between configuration sections.

Usage Examples

Complete Import Flow Page

'use client';

import { useState, useTransition } from 'react';
import {
ImportSourceStep,
ImportModeSelector,
ImportConfigureStep,
LinkExistingConfirm
} from '@/components/works/import';
import type { ImportMode } from '@/components/works/import';

export function ImportPage({ connections }) {
const [step, setStep] = useState(1);
const [analysisResult, setAnalysisResult] = useState(null);
const [importMode, setImportMode] = useState<ImportMode>('import');
const [isPending, startTransition] = useTransition();

return (
<div className="max-w-3xl mx-auto">
{step === 1 && (
<ImportSourceStep
onAnalyze={(result) => {
setAnalysisResult(result);
setStep(result.hasExistingStructure ? 2 : 3);
}}
connections={connections}
isAnalyzing={isPending}
/>
)}
{step === 2 && (
<ImportModeSelector
onSelect={(mode) => {
setImportMode(mode);
setStep(3);
}}
analysisResult={analysisResult}
/>
)}
{step === 3 && importMode === 'import' && (
<ImportConfigureStep
analysisResult={analysisResult}
mode={importMode}
onImport={handleImport}
isPending={isPending}
/>
)}
{step === 3 && importMode === 'link_existing' && (
<LinkExistingConfirm
analysisResult={analysisResult}
onConfirm={handleLink}
onCancel={() => setStep(2)}
isPending={isPending}
/>
)}
</div>
);
}