182 lines
6.2 KiB
TypeScript
182 lines
6.2 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { Alert } from "react-native";
|
|
import { View } from "react-native";
|
|
|
|
import { useTranslation } from "@turbostarter/i18n";
|
|
import { capitalize } from "@turbostarter/shared/utils";
|
|
import { Button } from "@turbostarter/ui-mobile/button";
|
|
import { Icons } from "@turbostarter/ui-mobile/icons";
|
|
import { Spin } from "@turbostarter/ui-mobile/spin";
|
|
import { Text } from "@turbostarter/ui-mobile/text";
|
|
|
|
import { authConfig } from "~/config/auth";
|
|
import { pathsConfig } from "~/config/paths";
|
|
import { authClient } from "~/lib/auth";
|
|
import { SocialIcons } from "~/modules/auth/form/social-providers";
|
|
import { auth } from "~/modules/auth/lib/api";
|
|
|
|
import type { SocialProvider } from "@turbostarter/auth";
|
|
|
|
export default function AccountsScreen() {
|
|
const { t, i18n } = useTranslation(["auth", "common"]);
|
|
|
|
const { data: session } = authClient.useSession();
|
|
|
|
const queryClient = useQueryClient();
|
|
const { data, isLoading } = useQuery({
|
|
...auth.queries.accounts.getAll,
|
|
enabled: !!session?.user.id,
|
|
});
|
|
|
|
const accounts = data ?? [];
|
|
const socials = accounts.filter((account) =>
|
|
authConfig.providers.oAuth.includes(account.providerId),
|
|
);
|
|
const missing = authConfig.providers.oAuth.filter(
|
|
(provider) => !socials.some((social) => social.providerId === provider),
|
|
);
|
|
|
|
const connect = useMutation({
|
|
...auth.mutations.accounts.connect,
|
|
onSuccess: async () => {
|
|
await queryClient.invalidateQueries(auth.queries.accounts.getAll);
|
|
},
|
|
});
|
|
|
|
const disconnect = useMutation({
|
|
...auth.mutations.accounts.disconnect,
|
|
onSuccess: async () => {
|
|
await queryClient.invalidateQueries(auth.queries.accounts.getAll);
|
|
},
|
|
});
|
|
|
|
const handleDisconnect = (provider: SocialProvider) => {
|
|
Alert.alert(
|
|
t("account.accounts.disconnect.cta", {
|
|
provider: capitalize(provider),
|
|
}),
|
|
t("account.accounts.disconnect.disclaimer", {
|
|
provider: capitalize(provider),
|
|
}),
|
|
[
|
|
{
|
|
text: t("cancel"),
|
|
style: "cancel",
|
|
},
|
|
{
|
|
text: t("continue"),
|
|
onPress: () => disconnect.mutate({ providerId: provider }),
|
|
},
|
|
],
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View className="bg-background flex-1 gap-6 p-6">
|
|
<Text className="text-muted-foreground font-sans-medium text-base">
|
|
{t("account.accounts.description")}
|
|
</Text>
|
|
|
|
{isLoading ? (
|
|
<View className="p-6">
|
|
<Spin>
|
|
<Icons.Loader2 className="text-foreground mx-auto size-6" />
|
|
</Spin>
|
|
</View>
|
|
) : (
|
|
<>
|
|
{socials.length > 0 && (
|
|
<View className="border-border overflow-hidden rounded-lg border">
|
|
{socials.map((social) => {
|
|
const provider = social.providerId as SocialProvider;
|
|
const Icon = SocialIcons[provider];
|
|
|
|
return (
|
|
<View
|
|
key={social.accountId}
|
|
className="border-border flex-row items-center border-b p-4 last:border-b-0"
|
|
>
|
|
<Icon className="text-foreground size-8" />
|
|
<View className="ml-3 flex-1">
|
|
<Text className="font-sans-medium capitalize">
|
|
{social.providerId}
|
|
</Text>
|
|
<Text className="text-muted-foreground text-xs">
|
|
{t("account.accounts.connectedAt", {
|
|
date: social.updatedAt.toLocaleDateString(
|
|
i18n.language,
|
|
),
|
|
})}
|
|
</Text>
|
|
</View>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
disabled={accounts.length === 1 || disconnect.isPending}
|
|
onPress={() => handleDisconnect(provider)}
|
|
>
|
|
{disconnect.isPending &&
|
|
disconnect.variables.providerId === provider ? (
|
|
<Spin>
|
|
<Icons.Loader2 className="text-foreground size-5" />
|
|
</Spin>
|
|
) : (
|
|
<Icons.Trash className="text-foreground" size={20} />
|
|
)}
|
|
</Button>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
)}
|
|
|
|
{missing.length > 0 && (
|
|
<View className="border-border gap-3 rounded-lg border border-dashed px-5 py-4">
|
|
<Text className="font-sans-medium text-sm">{t("addNew")}</Text>
|
|
<View className="bg-border h-px" />
|
|
<View className="flex-row flex-wrap gap-2">
|
|
{missing.map((provider) => {
|
|
const Icon = SocialIcons[provider];
|
|
|
|
return (
|
|
<Button
|
|
key={provider}
|
|
variant="outline"
|
|
disabled={connect.isPending}
|
|
onPress={() =>
|
|
connect.mutate({
|
|
provider,
|
|
callbackURL:
|
|
pathsConfig.dashboard.user.settings.account
|
|
.accounts,
|
|
errorCallbackURL: pathsConfig.setup.auth.error,
|
|
})
|
|
}
|
|
className="h-[44px] flex-row items-center gap-2 px-4"
|
|
>
|
|
{connect.isPending &&
|
|
connect.variables.provider === provider ? (
|
|
<Spin>
|
|
<Icons.Loader2 className="size-5" />
|
|
</Spin>
|
|
) : (
|
|
<Icon className="text-foreground size-5" />
|
|
)}
|
|
<Text className="capitalize">{provider}</Text>
|
|
</Button>
|
|
);
|
|
})}
|
|
</View>
|
|
</View>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
<Text className="text-muted-foreground -mt-3 max-w-80 text-sm">
|
|
{t("account.accounts.info")}
|
|
</Text>
|
|
</View>
|
|
);
|
|
}
|