2024-12-02 22:55:36 +01:00
|
|
|
<template>
|
|
|
|
|
<DropdownMenu>
|
|
|
|
|
<DropdownMenuTrigger as-child>
|
|
|
|
|
<SidebarMenuButton size="lg"
|
|
|
|
|
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
2024-12-03 14:07:00 +01:00
|
|
|
<Avatar v-if="identity" class="size-8" :src="identity.account.avatar" :name="identity.account.display_name" />
|
|
|
|
|
<Avatar v-else class="size-8" name="AB" />
|
2024-12-02 22:55:36 +01:00
|
|
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
|
|
|
<span class="truncate font-semibold" v-render-emojis="identity?.account.emojis">{{
|
|
|
|
|
identity?.account.display_name ?? "Not signed in"
|
|
|
|
|
}}</span>
|
|
|
|
|
<span class="truncate text-xs" v-if="identity">@{{ identity?.account.acct }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<ChevronsUpDown class="ml-auto size-4" />
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
<DropdownMenuContent class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" side="bottom"
|
|
|
|
|
align="end" :side-offset="4">
|
|
|
|
|
<DropdownMenuLabel class="p-0 font-normal">
|
|
|
|
|
<div v-for="identity of identities" class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
2024-12-03 14:07:00 +01:00
|
|
|
<Avatar class="size-8" :src="identity.account.avatar" :name="identity.account.display_name" />
|
2024-12-02 22:55:36 +01:00
|
|
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
|
|
|
<span class="truncate font-semibold" v-render-emojis="identity.account.emojis">{{
|
|
|
|
|
identity.account.display_name
|
|
|
|
|
}}</span>
|
|
|
|
|
<span class="truncate text-xs">@{{
|
|
|
|
|
identity.account.acct
|
|
|
|
|
}}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<DropdownMenuItem @click="signIn()">
|
|
|
|
|
<UserPlus />
|
|
|
|
|
Add account
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
</DropdownMenuLabel>
|
|
|
|
|
<DropdownMenuSeparator v-if="identity" />
|
|
|
|
|
<DropdownMenuGroup v-if="identity">
|
|
|
|
|
<DropdownMenuItem>
|
|
|
|
|
<BadgeCheck />
|
|
|
|
|
Account
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
</DropdownMenuGroup>
|
|
|
|
|
<DropdownMenuSeparator />
|
2024-12-03 13:44:18 +01:00
|
|
|
<DropdownMenuItem @click="signOut()" v-if="identity">
|
2024-12-02 22:55:36 +01:00
|
|
|
<LogOut />
|
|
|
|
|
Log out
|
|
|
|
|
</DropdownMenuItem>
|
2024-12-03 13:44:18 +01:00
|
|
|
<DropdownMenuItem :as="NuxtLink" href="/register" v-else>
|
|
|
|
|
<LogIn />
|
|
|
|
|
Register
|
|
|
|
|
</DropdownMenuItem>
|
2024-12-02 22:55:36 +01:00
|
|
|
</DropdownMenuContent>
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2024-12-03 13:44:18 +01:00
|
|
|
import {
|
|
|
|
|
BadgeCheck,
|
|
|
|
|
ChevronsUpDown,
|
|
|
|
|
LogOut,
|
|
|
|
|
UserPlus,
|
|
|
|
|
LogIn,
|
|
|
|
|
} from "lucide-vue-next";
|
2024-12-02 22:55:36 +01:00
|
|
|
import { toast } from "vue-sonner";
|
|
|
|
|
import {
|
|
|
|
|
DropdownMenu,
|
|
|
|
|
DropdownMenuContent,
|
|
|
|
|
DropdownMenuGroup,
|
|
|
|
|
DropdownMenuItem,
|
|
|
|
|
DropdownMenuLabel,
|
|
|
|
|
DropdownMenuSeparator,
|
|
|
|
|
DropdownMenuTrigger,
|
|
|
|
|
} from "../ui/dropdown-menu";
|
|
|
|
|
import { SidebarMenuButton } from "../ui/sidebar";
|
2024-12-03 13:44:18 +01:00
|
|
|
import { NuxtLink } from "#components";
|
2024-12-03 14:07:00 +01:00
|
|
|
import Avatar from "../profiles/avatar.vue";
|
2024-12-02 22:55:36 +01:00
|
|
|
|
|
|
|
|
const appData = useAppData();
|
|
|
|
|
|
|
|
|
|
const signIn = async () => {
|
|
|
|
|
const id = toast.loading("Signing in...");
|
|
|
|
|
|
|
|
|
|
const output = await client.value.createApp("Versia", {
|
|
|
|
|
scopes: ["read", "write", "follow", "push"],
|
|
|
|
|
redirect_uris: new URL("/", useRequestURL().origin).toString(),
|
|
|
|
|
website: useBaseUrl().value,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!output?.data) {
|
|
|
|
|
toast.dismiss(id);
|
|
|
|
|
toast.error("Failed to create app");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
appData.value = output.data;
|
|
|
|
|
|
|
|
|
|
const url = await client.value.generateAuthUrl(
|
|
|
|
|
output.data.client_id,
|
|
|
|
|
output.data.client_secret,
|
|
|
|
|
{
|
|
|
|
|
scopes: ["read", "write", "follow", "push"],
|
|
|
|
|
redirect_uri: new URL("/", useRequestURL().origin).toString(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
toast.dismiss(id);
|
|
|
|
|
toast.error("Failed to generate auth URL");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.location.href = url;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const signOut = async (userId?: string) => {
|
|
|
|
|
const id = toast.loading("Signing out...");
|
|
|
|
|
|
|
|
|
|
if (!(appData.value && identity.value)) {
|
|
|
|
|
toast.dismiss(id);
|
|
|
|
|
toast.error("No app or identity data to sign out");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const identityToRevoke = userId
|
|
|
|
|
? identities.value.find((i) => i.account.id === userId)
|
|
|
|
|
: identity.value;
|
|
|
|
|
|
|
|
|
|
if (!identityToRevoke) {
|
|
|
|
|
toast.dismiss(id);
|
|
|
|
|
toast.error("No identity to revoke");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't do anything on error, as Versia Server doesn't implement the revoke endpoint yet
|
|
|
|
|
await client.value
|
|
|
|
|
?.revokeToken(
|
|
|
|
|
appData.value.client_id,
|
|
|
|
|
identityToRevoke.tokens.access_token,
|
|
|
|
|
identityToRevoke.tokens.access_token,
|
|
|
|
|
)
|
|
|
|
|
.catch(() => {
|
|
|
|
|
// Do nothing
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!userId) {
|
|
|
|
|
identity.value = null;
|
|
|
|
|
await navigateTo("/");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
identities.value = identities.value.filter((i) => i.id !== userId);
|
|
|
|
|
toast.dismiss(id);
|
|
|
|
|
toast.success("Signed out");
|
|
|
|
|
};
|
|
|
|
|
</script>
|