mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-09-02 23:16:28 +00:00
feat(app): management foundation - client exposure, manage hub, shared shell
Groundwork for the full admin surface on mobile: the API client gains the user-account and Claude-management types (skills, connectors, plugins, permissions, users/auth, owner fields) plus an unauthenticated authApi helper; AppState exposes the client and the new management screen names; ManageShell/Field/ErrorNotice and a useResource hook give the subscreens one shared page/fetch pattern; Settings gains Manage and Account rows leading to the new hub. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
This commit is contained in:
@@ -12,9 +12,12 @@ import {
|
||||
SectionLabel,
|
||||
StatusDot,
|
||||
} from "../components/primitives";
|
||||
import { Icon } from "../components/Icon";
|
||||
import { TabBar } from "../components/TabBar";
|
||||
import { useAppState } from "../state/AppState";
|
||||
import { useServerConfig } from "../state/ServerConfig";
|
||||
import { createClient } from "../api/client";
|
||||
import { Pressable } from "react-native";
|
||||
|
||||
type Ping =
|
||||
| { state: "checking" }
|
||||
@@ -25,6 +28,7 @@ export function SettingsScreen() {
|
||||
const { colors } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { config, clear } = useServerConfig();
|
||||
const { go } = useAppState();
|
||||
|
||||
// Notification toggles stay local (no server-side counterpart yet).
|
||||
const [pushWait, setPushWait] = useState(true);
|
||||
@@ -94,6 +98,21 @@ export function SettingsScreen() {
|
||||
</View>
|
||||
</Card>
|
||||
|
||||
<SectionLabel style={{ marginBottom: 10 }}>Control</SectionLabel>
|
||||
<Card style={{ marginBottom: 20 }}>
|
||||
<NavRow
|
||||
title="Manage"
|
||||
subtitle="Models, skills, repos, users — the full admin surface"
|
||||
onPress={() => go("manage")}
|
||||
/>
|
||||
<Divider />
|
||||
<NavRow
|
||||
title="Account"
|
||||
subtitle="Who you're signed in as, change password"
|
||||
onPress={() => go("account")}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<SectionLabel style={{ marginBottom: 10 }}>Notifications</SectionLabel>
|
||||
<Card style={{ marginBottom: 20 }}>
|
||||
<ToggleRow
|
||||
@@ -125,6 +144,27 @@ export function SettingsScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
function NavRow({
|
||||
title,
|
||||
subtitle,
|
||||
onPress,
|
||||
}: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
onPress: () => void;
|
||||
}) {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<Pressable style={[styles.infoRow, { paddingVertical: 12 }]} onPress={onPress}>
|
||||
<View style={{ flex: 1, gap: 2 }}>
|
||||
<Text style={[text.label, { color: colors.textHeading }]}>{title}</Text>
|
||||
<Text style={[text.caption, { color: colors.textMuted }]}>{subtitle}</Text>
|
||||
</View>
|
||||
<Icon name="chevronRight" size={16} color={colors.textMuted} />
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
function InfoRow({ label, value }: { label: string; value: string }) {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import React from "react";
|
||||
import { Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import { text } from "../../theme/tokens";
|
||||
import { useTheme } from "../../theme/useTheme";
|
||||
import { Icon } from "../../components/Icon";
|
||||
import { ManageShell } from "../../components/ManageShell";
|
||||
import { Card, Divider, SectionLabel } from "../../components/primitives";
|
||||
import { useAppState, type Screen } from "../../state/AppState";
|
||||
|
||||
/**
|
||||
* The management hub (Settings → Manage): the mobile counterpart of the web
|
||||
* dashboard's admin surface. Most actions here need the admin token (or an
|
||||
* admin account session); non-admin sessions get inline 403s on writes.
|
||||
*/
|
||||
|
||||
interface Row {
|
||||
screen: Screen;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
const CLAUDE_ROWS: Row[] = [
|
||||
{ screen: "models", title: "Models", subtitle: "Model backends for spawns + schedules" },
|
||||
{ screen: "skills", title: "Skills", subtitle: "Managed skills synced to every worker" },
|
||||
{ screen: "connectors", title: "Connectors", subtitle: "MCP servers agents may reach" },
|
||||
{ screen: "plugins", title: "Plugins", subtitle: "Marketplace plugins installed on boot" },
|
||||
{ screen: "permissions", title: "Permissions", subtitle: "Default mode + allow/deny/ask rules" },
|
||||
{ screen: "claudeLogin", title: "Claude login", subtitle: "Sign the worker into Claude Code" },
|
||||
];
|
||||
|
||||
const SERVER_ROWS: Row[] = [
|
||||
{ screen: "repositories", title: "Repositories", subtitle: "Register + sync project repos" },
|
||||
{ screen: "gitServers", title: "Git servers", subtitle: "Forge hosts, tokens, deploy keys" },
|
||||
{ screen: "approvals", title: "Approvals", subtitle: "Approve or reject protected branches" },
|
||||
{ screen: "shared", title: "Shared context", subtitle: "Key/value context all agents see" },
|
||||
{ screen: "users", title: "Users", subtitle: "Invite, promote, disable, reset" },
|
||||
];
|
||||
|
||||
export function ManageScreen() {
|
||||
return (
|
||||
<ManageShell
|
||||
title="Manage"
|
||||
subtitle="The full control surface — everything the web dashboard can do."
|
||||
backTo="settings"
|
||||
>
|
||||
<SectionLabel style={{ marginBottom: 8 }}>Claude</SectionLabel>
|
||||
<RowsCard rows={CLAUDE_ROWS} />
|
||||
<SectionLabel style={{ marginTop: 20, marginBottom: 8 }}>Server</SectionLabel>
|
||||
<RowsCard rows={SERVER_ROWS} />
|
||||
</ManageShell>
|
||||
);
|
||||
}
|
||||
|
||||
function RowsCard({ rows }: { rows: Row[] }) {
|
||||
const { colors } = useTheme();
|
||||
const { go } = useAppState();
|
||||
return (
|
||||
<Card>
|
||||
{rows.map((r, i) => (
|
||||
<View key={r.screen}>
|
||||
{i > 0 && <Divider />}
|
||||
<Pressable style={styles.row} onPress={() => go(r.screen)}>
|
||||
<View style={{ flex: 1, gap: 2 }}>
|
||||
<Text style={[text.label, { color: colors.textHeading }]}>{r.title}</Text>
|
||||
<Text style={[text.caption, { color: colors.textMuted }]}>
|
||||
{r.subtitle}
|
||||
</Text>
|
||||
</View>
|
||||
<Icon name="chevronRight" size={16} color={colors.textMuted} />
|
||||
</Pressable>
|
||||
</View>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
minHeight: 44,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user