feat: whyrating - initial project from turbostarter boilerplate
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user