remove the onboarding flow and the accountMode leftover
Onboarding was dead in three layers:
- The OnboardingAdmin screen was only reachable from a route block in App.tsx
that has been commented out, so it never rendered. Its ServerTypeCard carried
accountMode ('organization' | 'single'), inherited from the codebase this was
based on and meaningless for a single-user platform.
- Two /onboarding-complete endpoints, one public and one protected, that no
frontend code called. Both read a server_config key that was never written, so
both answered false while the app's own path defaulted to true.
- HomeScreen gated on settings.onboarding.complete to show a welcome panel, and
seedHomeDir created an Onboarding folder from DATA_PATH/Onboarding and
/Onboarding_Admin — neither seed directory exists, so it only ever produced an
empty folder.
Also drops the onboarding key from UserSettings and the now-empty home-header
panel from the default home layout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
10400310c5
commit
1ac79f9c68
@@ -8,7 +8,7 @@ import { useInitialData } from '@/state/useInitialData';
|
||||
|
||||
export function App() {
|
||||
const { isLoading, isAuthenticated } = useAuth();
|
||||
const { onboardingComplete, plugins, isLoading: isServerSettingsLoading } = useServerSettings();
|
||||
const { plugins, isLoading: isServerSettingsLoading } = useServerSettings();
|
||||
useServerEnvironment();
|
||||
useInitialData();
|
||||
|
||||
@@ -26,14 +26,7 @@ export function App() {
|
||||
</Routes>
|
||||
</Authentication.AuthenticationLayout>
|
||||
)}
|
||||
{/* {isAuthenticated && !onboardingComplete && ( */}
|
||||
{/* <Routes> */}
|
||||
{/* <Route path="/onboarding-admin" element={<OnboardingAdmin />} /> */}
|
||||
{/* <Route path="/auth/signout" element={<SignoutScreen />} /> */}
|
||||
{/* <Route path="*" element={<Navigate to="/onboarding-admin" replace />} /> */}
|
||||
{/* </Routes> */}
|
||||
{/* )} */}
|
||||
{isAuthenticated && onboardingComplete && (
|
||||
{isAuthenticated && (
|
||||
<Dashboard.DashboardLayout>
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard.HomeScreen />} />
|
||||
|
||||
@@ -1,61 +1,14 @@
|
||||
import type { LayoutNode, PanelComponents, DefaultFileSort } from 'officerdev';
|
||||
import { WorkspaceView, useFileViewerPanels } from 'officerdev';
|
||||
import type { LayoutNode } from 'officerdev';
|
||||
import { WorkspaceView } from 'officerdev';
|
||||
import { useDashboardState } from 'state/useDashboardState';
|
||||
import { useSettings } from 'state/useSettings';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { defaultLayout } from './defaultLayout';
|
||||
|
||||
const HomeHeader = () => {
|
||||
const { settings, saveSettings } = useSettings();
|
||||
|
||||
const completeOnboarding = () => {
|
||||
saveSettings({ ...settings, onboarding: { complete: true } });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center p-6 text-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Welcome to your Officer.dev platform</h1>
|
||||
<p className="mt-2 text-sm opacity-70">
|
||||
Please follow the video instructions below in order to get familiar with all that is possible.
|
||||
</p>
|
||||
<Button className="mt-6" onClick={completeOnboarding}>
|
||||
I'm ready
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const components: PanelComponents = {
|
||||
'home-header': HomeHeader,
|
||||
};
|
||||
|
||||
const defaultSort: DefaultFileSort = { field: 'type', direction: 'desc' };
|
||||
|
||||
export const HomeScreen = () => {
|
||||
const workspace = useDashboardState<LayoutNode>('screens/home', defaultLayout);
|
||||
const ephemeral = useFileViewerPanels();
|
||||
const { settings } = useSettings();
|
||||
|
||||
if (settings.onboarding.complete) {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<WorkspaceView workspace={workspace} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<WorkspaceView
|
||||
workspace={workspace}
|
||||
locked
|
||||
initialFilePath="/Onboarding"
|
||||
defaultFileSort={defaultSort}
|
||||
components={components}
|
||||
ephemeral={ephemeral}
|
||||
/>
|
||||
<WorkspaceView workspace={workspace} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,9 +4,5 @@ export const defaultLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'home-root',
|
||||
direction: 'vertical',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'home-header', appType: null }, size: 30 },
|
||||
{ node: { type: 'panel', id: 'home-files', appType: 'officerdev/file-browser' }, size: 70 },
|
||||
],
|
||||
children: [{ node: { type: 'panel', id: 'home-files', appType: 'officerdev/file-browser' }, size: 100 }],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { Terminal, Copy, Check } from 'lucide-react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/Card';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
|
||||
type AIHarnessesCardProps = {
|
||||
onNext: () => void;
|
||||
onBack: () => void;
|
||||
saveSettings: (settings: Record<string, unknown>) => Promise<void>;
|
||||
};
|
||||
|
||||
export const AIHarnessesCard = ({ onNext, onBack, saveSettings }: AIHarnessesCardProps) => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
const [installing, setInstalling] = useState(false);
|
||||
|
||||
type VersionInfo = { version: string | null; path: string | null; globalPath: string | null };
|
||||
type ClaudeAuthInfo = { authenticated: boolean; loggedIn?: boolean; subscriptionType?: string };
|
||||
|
||||
const { data: claudeVersion, isLoading: claudeLoading } = useQuery({
|
||||
queryKey: ['CLAUDE_CODE_VERSION'],
|
||||
queryFn: () => client.get<VersionInfo>('/server-settings/claude-code/version'),
|
||||
refetchInterval: (query) => {
|
||||
const data = query.state.data;
|
||||
return data?.version && !data?.globalPath ? 1000 : false;
|
||||
},
|
||||
});
|
||||
|
||||
const { data: claudeAuth } = useQuery({
|
||||
queryKey: ['CLAUDE_CODE_AUTH'],
|
||||
queryFn: () => client.get<ClaudeAuthInfo>('/server-settings/claude-code/auth'),
|
||||
enabled: !!claudeVersion?.version,
|
||||
refetchInterval: (query) => (!query.state.data?.authenticated ? 2000 : false),
|
||||
});
|
||||
|
||||
const installClaude = async () => {
|
||||
setInstalling(true);
|
||||
try {
|
||||
const result = await client.post<VersionInfo>('/server-settings/claude-code/install');
|
||||
queryClient.setQueryData(['CLAUDE_CODE_VERSION'], result);
|
||||
} finally {
|
||||
setInstalling(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [copied, setCopied] = useState<string | null>(null);
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopied(text);
|
||||
setTimeout(() => setCopied(null), 1500);
|
||||
};
|
||||
|
||||
const CopyCommand = ({ command }: { command: string }) => (
|
||||
<div className="mt-2 text-xs text-amber-600">
|
||||
Not globally accessible. Run:
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
<code className="flex-1 bg-duck-dark/5 rounded px-2 py-1 text-duck-dark/70">{command}</code>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard(command)}
|
||||
className="shrink-0 p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
|
||||
>
|
||||
{copied === command ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-600" />
|
||||
) : (
|
||||
<Copy className="h-3.5 w-3.5 text-duck-dark/50" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const claudeReady = !!claudeVersion?.version && !!claudeVersion?.globalPath && !!claudeAuth?.authenticated;
|
||||
|
||||
const handleNext = async () => {
|
||||
await saveSettings({ aiHarnesses: { claudeCode: true }, onboardingComplete: true });
|
||||
onNext();
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Terminal className="h-5 w-5 text-duck-forest" />
|
||||
<h2 className="text-xl font-bold text-duck-dark">AI Harnesses</h2>
|
||||
</div>
|
||||
<p className="text-duck-dark/70 text-sm mb-6">Set up Claude Code for AI-assisted development.</p>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-duck-dark">Claude Code</span>
|
||||
<div className="ml-7 mt-2 text-xs text-duck-dark/50">
|
||||
{claudeLoading ? (
|
||||
'Checking version...'
|
||||
) : claudeVersion?.version ? (
|
||||
<>
|
||||
<div>{claudeVersion.version}</div>
|
||||
<div>{claudeVersion.path}</div>
|
||||
{claudeAuth && (
|
||||
<div className={`mt-1 ${claudeAuth.authenticated ? 'text-green-600' : 'text-amber-600'}`}>
|
||||
{claudeAuth.authenticated ? (
|
||||
`Logged in (${claudeAuth.subscriptionType ?? 'unknown plan'})`
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<span>Not logged in</span>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-duck-teal text-duck-yellow hover:bg-duck-teal/90"
|
||||
onClick={() => client.post('/server-settings/claude-code/auth/login')}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!claudeVersion.globalPath && claudeVersion.path && (
|
||||
<CopyCommand command={`sudo ln -s ${claudeVersion.path} /usr/local/bin/claude`} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-duck-teal text-duck-yellow hover:bg-duck-teal/90"
|
||||
onClick={installClaude}
|
||||
disabled={installing}
|
||||
>
|
||||
{installing ? 'Installing...' : 'Install'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mt-6">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
Back
|
||||
</Button>
|
||||
<Button disabled={!claudeReady} onClick={handleNext}>
|
||||
Complete Setup
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { Building2, UserRound } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/Card';
|
||||
|
||||
type AccountMode = 'organization' | 'single' | null;
|
||||
|
||||
type ServerTypeCardProps = {
|
||||
onNext: () => void;
|
||||
saveSettings: (settings: Record<string, unknown>) => Promise<void>;
|
||||
};
|
||||
|
||||
export const ServerTypeCard = ({ onNext, saveSettings }: ServerTypeCardProps) => {
|
||||
const [accountMode, setAccountMode] = useState<AccountMode>(null);
|
||||
|
||||
const handleNext = async () => {
|
||||
await saveSettings({ accountMode });
|
||||
onNext();
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h2 className="text-xl font-bold text-duck-dark mb-2">Account Type</h2>
|
||||
<p className="text-duck-dark/70 text-sm mb-6">How will you be using Officer?</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAccountMode('single')}
|
||||
className={`flex flex-col items-center gap-3 p-6 rounded-lg border-2 cursor-pointer transition-colors ${
|
||||
accountMode === 'single'
|
||||
? 'border-duck-teal bg-duck-teal/10'
|
||||
: 'border-duck-dark/20 hover:border-duck-dark/40'
|
||||
}`}
|
||||
>
|
||||
<UserRound className={`h-8 w-8 ${accountMode === 'single' ? 'text-duck-teal' : 'text-duck-dark/50'}`} />
|
||||
<span className={`text-sm font-medium ${accountMode === 'single' ? 'text-duck-teal' : 'text-duck-dark'}`}>
|
||||
Single User
|
||||
</span>
|
||||
<span className="text-xs text-duck-dark/50 text-center">Just me, personal use</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAccountMode('organization')}
|
||||
className={`flex flex-col items-center gap-3 p-6 rounded-lg border-2 cursor-pointer transition-colors ${
|
||||
accountMode === 'organization'
|
||||
? 'border-duck-teal bg-duck-teal/10'
|
||||
: 'border-duck-dark/20 hover:border-duck-dark/40'
|
||||
}`}
|
||||
>
|
||||
<Building2 className={`h-8 w-8 ${accountMode === 'organization' ? 'text-duck-teal' : 'text-duck-dark/50'}`} />
|
||||
<span
|
||||
className={`text-sm font-medium ${accountMode === 'organization' ? 'text-duck-teal' : 'text-duck-dark'}`}
|
||||
>
|
||||
Organization
|
||||
</span>
|
||||
<span className="text-xs text-duck-dark/50 text-center">Multiple users and teams</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-6">
|
||||
<Button disabled={!accountMode} onClick={handleNext}>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useServerSettings } from 'state/useServerSettings';
|
||||
import { ServerTypeCard } from './ServerTypeCard';
|
||||
import { AIHarnessesCard } from './AIHarnessesCard';
|
||||
|
||||
const STEPS = ['server-type', 'ai-harnesses'] as const;
|
||||
type Step = (typeof STEPS)[number];
|
||||
|
||||
const getStepFromHash = (): Step => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (STEPS.includes(hash as Step)) return hash as Step;
|
||||
return STEPS[0]!;
|
||||
};
|
||||
|
||||
const setHash = (step: Step) => {
|
||||
window.location.hash = step;
|
||||
};
|
||||
|
||||
export const OnboardingAdmin = () => {
|
||||
const { saveSettings } = useServerSettings();
|
||||
const [step, setStep] = useState<Step>(getStepFromHash);
|
||||
|
||||
useEffect(() => {
|
||||
const onHashChange = () => setStep(getStepFromHash());
|
||||
window.addEventListener('hashchange', onHashChange);
|
||||
return () => window.removeEventListener('hashchange', onHashChange);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setHash(step);
|
||||
}, [step]);
|
||||
|
||||
const currentIndex = STEPS.indexOf(step);
|
||||
|
||||
const nextStep = () => {
|
||||
if (currentIndex < STEPS.length - 1) {
|
||||
setStep(STEPS[currentIndex + 1]!);
|
||||
}
|
||||
};
|
||||
|
||||
const prevStep = () => {
|
||||
if (currentIndex > 0) {
|
||||
setStep(STEPS[currentIndex - 1]!);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full px-4">
|
||||
<div className="w-full max-w-lg">
|
||||
{step === 'server-type' && <ServerTypeCard onNext={nextStep} saveSettings={saveSettings} />}
|
||||
{step === 'ai-harnesses' && (
|
||||
<AIHarnessesCard onNext={nextStep} onBack={prevStep} saveSettings={saveSettings} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from './Layout';
|
||||
export * from './Home';
|
||||
export * from './OnboardingAdmin';
|
||||
export * from './PasskeyGate';
|
||||
export * from './Plans';
|
||||
export * from './Processes';
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
CREATE TABLE "passkey_challenges" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"origin" text NOT NULL,
|
||||
"challenge" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "passkeys" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"origin" text,
|
||||
"credential_id" text,
|
||||
"public_key" text,
|
||||
"counter" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "token_blacklist" (
|
||||
"jti" text PRIMARY KEY NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"password" text,
|
||||
"status" text DEFAULT 'Unverified' NOT NULL,
|
||||
"name" text,
|
||||
"username" text,
|
||||
"avatar" text,
|
||||
"password_changed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email"),
|
||||
CONSTRAINT "users_username_unique" UNIQUE("username")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "dock_configs" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"paths" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_integrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"server_integration_id" integer,
|
||||
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_user_integrations_user_provider" UNIQUE("user_id","provider")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_settings" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"settings" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_state" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"state" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "dashboard_defaults" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"terminals" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"host_terminals" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "dashboard_defaults_user_id_unique" UNIQUE("user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "dashboards" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"layout" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"terminals" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"host_terminals" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"sort_order" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_dashboards_user_id" UNIQUE("user_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "projects" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"slug" text NOT NULL,
|
||||
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"layout" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"terminals" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"host_terminals" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_projects_user_slug" UNIQUE("user_id","slug")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "screens" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"layout" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"terminals" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"host_terminals" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_screens_user_name" UNIQUE("user_id","name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "queue_jobs" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"lane" text NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"status" text DEFAULT 'queued' NOT NULL,
|
||||
"current_step" integer DEFAULT 0 NOT NULL,
|
||||
"steps" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"meta" jsonb,
|
||||
"error" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "task_logs" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"task_name" text NOT NULL,
|
||||
"task_dir_name" text NOT NULL,
|
||||
"entry_name" text NOT NULL,
|
||||
"entry_type" text NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"model" text NOT NULL,
|
||||
"is_error" boolean DEFAULT false NOT NULL,
|
||||
"messages" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"started_at" timestamp with time zone NOT NULL,
|
||||
"completed_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "terminal_containers" (
|
||||
"user_id" integer PRIMARY KEY NOT NULL,
|
||||
"docker_id" text NOT NULL,
|
||||
"port" integer NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "server_config" (
|
||||
"key" text PRIMARY KEY NOT NULL,
|
||||
"value" jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "server_integrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "server_integrations_provider_unique" UNIQUE("provider")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "email_accounts" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"display_name" text,
|
||||
"imap_host" text NOT NULL,
|
||||
"imap_port" integer NOT NULL,
|
||||
"imap_secure" boolean DEFAULT true NOT NULL,
|
||||
"auth_type" text NOT NULL,
|
||||
"credentials" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"status" text DEFAULT 'connected' NOT NULL,
|
||||
"sync_meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "uq_email_accounts_user_email" UNIQUE("user_id","email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pipeline_jobs" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"task_dir_name" text NOT NULL,
|
||||
"task_name" text NOT NULL,
|
||||
"mode" text DEFAULT 'pipeline' NOT NULL,
|
||||
"status" text DEFAULT 'pending' NOT NULL,
|
||||
"inputs" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"cwd" text,
|
||||
"config" jsonb NOT NULL,
|
||||
"progress" jsonb,
|
||||
"total_cost" jsonb,
|
||||
"error" text,
|
||||
"exit_code" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "passkey_challenges" ADD CONSTRAINT "passkey_challenges_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "passkeys" ADD CONSTRAINT "passkeys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "dock_configs" ADD CONSTRAINT "dock_configs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_integrations" ADD CONSTRAINT "user_integrations_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_integrations" ADD CONSTRAINT "user_integrations_server_integration_id_server_integrations_id_fk" FOREIGN KEY ("server_integration_id") REFERENCES "public"."server_integrations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_state" ADD CONSTRAINT "user_state_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "dashboard_defaults" ADD CONSTRAINT "dashboard_defaults_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "dashboards" ADD CONSTRAINT "dashboards_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "screens" ADD CONSTRAINT "screens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "queue_jobs" ADD CONSTRAINT "queue_jobs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "task_logs" ADD CONSTRAINT "task_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "terminal_containers" ADD CONSTRAINT "terminal_containers_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "email_accounts" ADD CONSTRAINT "email_accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pipeline_jobs" ADD CONSTRAINT "pipeline_jobs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "idx_token_blacklist_expires" ON "token_blacklist" USING btree ("expires_at");--> statement-breakpoint
|
||||
CREATE INDEX "idx_queue_jobs_status_lane" ON "queue_jobs" USING btree ("status","lane");--> statement-breakpoint
|
||||
CREATE INDEX "idx_queue_jobs_user" ON "queue_jobs" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_task_logs_user_started" ON "task_logs" USING btree ("user_id","started_at");--> statement-breakpoint
|
||||
CREATE INDEX "idx_pipeline_jobs_user_created" ON "pipeline_jobs" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "idx_pipeline_jobs_status" ON "pipeline_jobs" USING btree ("status");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1785018112373,
|
||||
"tag": "0000_eager_sir_ram",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,10 +18,8 @@ async function getUserTtsVoice(userId: number): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const DEFAULT_HOME_DIRS = ['Downloads', 'Documents', 'Music', 'Videos', 'Pictures', 'Onboarding'];
|
||||
const DEFAULT_HOME_DIRS = ['Downloads', 'Documents', 'Music', 'Videos', 'Pictures'];
|
||||
const OLD_CACHE_DIRS = ['ocr', 'tts', 'transcriptions', 'audio', 'video'];
|
||||
const ONBOARDING_SEED = join(DATA_PATH, 'Onboarding');
|
||||
const ONBOARDING_ADMIN_SEED = join(DATA_PATH, 'Onboarding_Admin');
|
||||
|
||||
async function cleanOldCacheDirs(userDataDir: string) {
|
||||
for (const dir of OLD_CACHE_DIRS) {
|
||||
@@ -30,28 +28,10 @@ async function cleanOldCacheDirs(userDataDir: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function syncSeedDir(seedDir: string, targetDir: string) {
|
||||
if (!existsSync(seedDir)) return;
|
||||
|
||||
await mkdir(targetDir, { recursive: true });
|
||||
|
||||
const seedEntries = await readdir(seedDir, { withFileTypes: true });
|
||||
for (const entry of seedEntries) {
|
||||
const dest = join(targetDir, entry.name);
|
||||
if (existsSync(dest)) continue;
|
||||
await cp(join(seedDir, entry.name), dest, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function seedHomeDir(homeDir: string) {
|
||||
for (const dir of DEFAULT_HOME_DIRS) {
|
||||
const target = join(homeDir, dir);
|
||||
if (dir === 'Onboarding') {
|
||||
await syncSeedDir(ONBOARDING_ADMIN_SEED, join(homeDir, 'Onboarding'));
|
||||
await syncSeedDir(ONBOARDING_SEED, join(homeDir, 'Onboarding', 'User_Onboarding'));
|
||||
} else if (!existsSync(target)) {
|
||||
await mkdir(target, { recursive: true });
|
||||
}
|
||||
if (!existsSync(target)) await mkdir(target, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,6 @@ serverSettingsRouter.get('/settings', async (ctx) => {
|
||||
return ctx.json(await readServerSettings());
|
||||
});
|
||||
|
||||
serverSettingsRouter.get('/onboarding-complete', async (ctx) => {
|
||||
const settings = await readServerSettings();
|
||||
return ctx.json({ onboardingComplete: !!settings.onboardingComplete });
|
||||
});
|
||||
|
||||
serverSettingsRouter.put('/', async (ctx) => {
|
||||
const body = await ctx.req.json();
|
||||
const settings = await readServerSettings();
|
||||
|
||||
@@ -67,11 +67,6 @@ honoServer.post('/api/hooks/claude-done', async (ctx) => {
|
||||
}
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
honoServer.get('/api/server-settings/onboarding-complete', async (ctx) => {
|
||||
const { readServerSettings } = await import('officerdb');
|
||||
const settings = await readServerSettings();
|
||||
return ctx.json({ onboardingComplete: !!settings.onboardingComplete });
|
||||
});
|
||||
|
||||
const protectedRouter = createRouter();
|
||||
protectedRouter.use(bodyParser());
|
||||
|
||||
@@ -8,8 +8,6 @@ type AIHarnesses = {
|
||||
};
|
||||
|
||||
type ServerSettings = {
|
||||
onboardingComplete?: boolean;
|
||||
accountMode?: 'organization' | 'single';
|
||||
aiHarnesses?: AIHarnesses;
|
||||
plugins?: Record<string, boolean>;
|
||||
};
|
||||
@@ -25,8 +23,6 @@ export const useServerSettings = () => {
|
||||
queryFn: () => client.get<ServerSettings>('/server-settings/settings'),
|
||||
});
|
||||
|
||||
const onboardingComplete = settings?.onboardingComplete ?? true;
|
||||
const accountMode = settings?.accountMode;
|
||||
const aiHarnesses = settings?.aiHarnesses;
|
||||
const plugins = settings?.plugins;
|
||||
const saveSettings = useCallback(
|
||||
@@ -37,5 +33,5 @@ export const useServerSettings = () => {
|
||||
[client, queryClient],
|
||||
);
|
||||
|
||||
return { onboardingComplete, accountMode, aiHarnesses, plugins, isLoading, saveSettings };
|
||||
return { aiHarnesses, plugins, isLoading, saveSettings };
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@ const mergeWithDefaults = (saved: Partial<UserSettings>): UserSettings => ({
|
||||
appearance: { ...DEFAULT_SETTINGS.appearance, ...saved.appearance },
|
||||
languages: { ...DEFAULT_SETTINGS.languages, ...saved.languages },
|
||||
tts: { ...DEFAULT_SETTINGS.tts, ...saved.tts },
|
||||
onboarding: { ...DEFAULT_SETTINGS.onboarding, ...saved.onboarding },
|
||||
});
|
||||
|
||||
export const useSettings = () => {
|
||||
@@ -77,9 +76,6 @@ export type UserSettings = {
|
||||
tts: {
|
||||
voice: string | null;
|
||||
};
|
||||
onboarding: {
|
||||
complete: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type UserState = Record<string, unknown>;
|
||||
@@ -113,7 +109,4 @@ export const DEFAULT_SETTINGS: UserSettings = {
|
||||
tts: {
|
||||
voice: null,
|
||||
},
|
||||
onboarding: {
|
||||
complete: false,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user