160 lines
4.8 KiB
TypeScript
160 lines
4.8 KiB
TypeScript
import { Ionicons } from "@expo/vector-icons";
|
|
import { Redirect, router } from "expo-router";
|
|
import { useState } from "react";
|
|
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from "react-native";
|
|
|
|
import { AuthCard } from "@/components/auth/AuthCard";
|
|
import { AuthCardHeader } from "@/components/auth/AuthCardHeader";
|
|
import { AuthScreenLayout } from "@/components/auth/AuthScreenLayout";
|
|
import { Button } from "@/components/ui/Button";
|
|
import { fonts, radii, spacing } from "@/constants/theme";
|
|
import { useAccounts } from "@/contexts/AccountsContext";
|
|
import { useAppTheme } from "@/contexts/ThemeContext";
|
|
import { startAdditionalAccountSignIn } from "@/lib/add-account";
|
|
import { formatServerHost } from "@/lib/server-mode";
|
|
|
|
function initials(name: string, email: string) {
|
|
const source = name.trim() || email.trim();
|
|
const parts = source.split(/\s+/).filter(Boolean);
|
|
if (parts.length >= 2) {
|
|
return `${parts[0]![0] ?? ""}${parts[1]![0] ?? ""}`.toUpperCase();
|
|
}
|
|
return (source[0] ?? "?").toUpperCase();
|
|
}
|
|
|
|
export default function SelectAccountScreen() {
|
|
const { colors } = useAppTheme();
|
|
const { accounts, activeAccountId, switchAccount, clearActiveAccount } = useAccounts();
|
|
const [selectingId, setSelectingId] = useState<string | null>(null);
|
|
const [adding, setAdding] = useState(false);
|
|
|
|
async function handleSelect(accountId: string) {
|
|
if (selectingId) return;
|
|
setSelectingId(accountId);
|
|
try {
|
|
await switchAccount(accountId);
|
|
router.replace("/(auth)/sign-in");
|
|
} finally {
|
|
setSelectingId(null);
|
|
}
|
|
}
|
|
|
|
async function handleAddAccount() {
|
|
if (adding) return;
|
|
setAdding(true);
|
|
try {
|
|
await startAdditionalAccountSignIn(clearActiveAccount);
|
|
} finally {
|
|
setAdding(false);
|
|
}
|
|
}
|
|
|
|
if (activeAccountId || accounts.length === 0) {
|
|
return <Redirect href="/(auth)/sign-in" />;
|
|
}
|
|
|
|
return (
|
|
<AuthScreenLayout>
|
|
<AuthCard>
|
|
<AuthCardHeader
|
|
title="Choose account"
|
|
description="Select the workspace account to use on this device"
|
|
/>
|
|
|
|
<View style={styles.list}>
|
|
{accounts.map((account) => {
|
|
const isSelecting = selectingId === account.id;
|
|
|
|
return (
|
|
<Pressable
|
|
accessibilityRole="button"
|
|
disabled={Boolean(selectingId)}
|
|
key={account.id}
|
|
onPress={() => void handleSelect(account.id)}
|
|
style={({ pressed }) => [
|
|
styles.accountRow,
|
|
{ backgroundColor: colors.muted, borderColor: colors.border },
|
|
pressed && styles.pressed,
|
|
]}
|
|
>
|
|
<View style={[styles.avatar, { backgroundColor: colors.primary }]}>
|
|
<Text style={[styles.avatarText, { color: colors.primaryForeground }]}>
|
|
{initials(account.name, account.email)}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.accountMeta}>
|
|
<Text style={[styles.accountName, { color: colors.foreground }]}>
|
|
{account.name || account.email}
|
|
</Text>
|
|
<Text style={[styles.accountSub, { color: colors.mutedForeground }]}>
|
|
{account.email}
|
|
</Text>
|
|
<Text style={[styles.accountSub, { color: colors.mutedForeground }]}>
|
|
{formatServerHost(account.instanceUrl)}
|
|
</Text>
|
|
</View>
|
|
{isSelecting ? (
|
|
<ActivityIndicator color={colors.primary} size="small" />
|
|
) : (
|
|
<Ionicons name="chevron-forward" size={18} color={colors.mutedForeground} />
|
|
)}
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</View>
|
|
|
|
<Button
|
|
disabled={Boolean(selectingId)}
|
|
loading={adding}
|
|
onPress={() => void handleAddAccount()}
|
|
title="Sign in to another account"
|
|
variant="secondary"
|
|
/>
|
|
</AuthCard>
|
|
</AuthScreenLayout>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
list: {
|
|
gap: spacing.sm,
|
|
},
|
|
accountRow: {
|
|
minHeight: 72,
|
|
borderRadius: radii.lg,
|
|
borderWidth: 1,
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: spacing.md,
|
|
padding: spacing.md,
|
|
},
|
|
avatar: {
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 18,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
},
|
|
avatarText: {
|
|
fontFamily: fonts.bodySemiBold,
|
|
fontSize: 13,
|
|
},
|
|
accountMeta: {
|
|
flex: 1,
|
|
gap: 2,
|
|
},
|
|
accountName: {
|
|
fontFamily: fonts.bodySemiBold,
|
|
fontSize: 15,
|
|
lineHeight: 20,
|
|
},
|
|
accountSub: {
|
|
fontFamily: fonts.body,
|
|
fontSize: 12,
|
|
lineHeight: 16,
|
|
},
|
|
pressed: {
|
|
opacity: 0.9,
|
|
},
|
|
});
|