mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 16:38:20 +01:00
refactor: 🛂 Refactor account switching to ask for instance
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 54s
Deploy to GitHub Pages / build (push) Failing after 1m45s
Deploy to GitHub Pages / deploy (push) Has been skipped
Docker / build (push) Failing after 17s
Mirror to Codeberg / Mirror (push) Failing after 1s
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 54s
Deploy to GitHub Pages / build (push) Failing after 1m45s
Deploy to GitHub Pages / deploy (push) Has been skipped
Docker / build (push) Failing after 17s
Mirror to Codeberg / Mirror (push) Failing after 1s
This commit is contained in:
parent
7e9ccbc932
commit
2e67e7858f
|
|
@ -61,7 +61,7 @@ for (const name of [
|
||||||
}
|
}
|
||||||
|
|
||||||
const issuerRedirectUrl = (issuerId: string) => {
|
const issuerRedirectUrl = (issuerId: string) => {
|
||||||
const url = new URL("/oauth/sso", useBaseUrl().value);
|
const url = new URL("/oauth/sso", client.value.url);
|
||||||
|
|
||||||
for (const name of [
|
for (const name of [
|
||||||
"redirect_uri",
|
"redirect_uri",
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<UserPlus />
|
<UserPlus />
|
||||||
{{ m.sunny_pink_hyena_walk() }}
|
{{ m.sunny_pink_hyena_walk() }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" size="lg" @click="signOut()" v-if="identity">
|
<Button variant="secondary" size="lg" @click="signOut(appData, identity)" v-if="identity">
|
||||||
<LogOut />
|
<LogOut />
|
||||||
{{ m.sharp_big_mallard_reap() }}
|
{{ m.sharp_big_mallard_reap() }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -47,7 +47,9 @@
|
||||||
identity.account.display_name
|
identity.account.display_name
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="truncate text-xs">@{{
|
<span class="truncate text-xs">@{{
|
||||||
identity.account.acct
|
identity.account.username
|
||||||
|
}}@{{
|
||||||
|
identity.instance.domain
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -64,7 +66,7 @@
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem @click="signOut()" v-if="identity">
|
<DropdownMenuItem @click="signOut(appData, identity)" v-if="identity">
|
||||||
<LogOut />
|
<LogOut />
|
||||||
{{ m.sharp_big_mallard_reap() }}
|
{{ m.sharp_big_mallard_reap() }}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
@ -98,48 +100,7 @@ import {
|
||||||
const appData = useAppData();
|
const appData = useAppData();
|
||||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||||
|
|
||||||
const signInAction = () => signIn(appData, new URL(useBaseUrl().value));
|
const signInAction = async () => signIn(appData, await askForInstance());
|
||||||
|
|
||||||
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");
|
|
||||||
};
|
|
||||||
|
|
||||||
const switchAccount = async (userId: string) => {
|
const switchAccount = async (userId: string) => {
|
||||||
if (userId === identity.value?.account.id) {
|
if (userId === identity.value?.account.id) {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span class="truncate font-semibold">{{ instance?.title ?? m.short_zippy_felix_kick()
|
<span class="truncate font-semibold">{{ instance?.title ?? m.short_zippy_felix_kick()
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="truncate text-xs">{{ m.top_active_ocelot_cure() }}</span>
|
<span class="truncate text-xs">{{ instance?.description ?? m.top_active_ocelot_cure() }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <ChevronsUpDown class="ml-auto" /> -->
|
<!-- <ChevronsUpDown class="ml-auto" /> -->
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
export const useBaseUrl = () => {
|
|
||||||
// Check if running on Onion URL
|
|
||||||
if (useRequestURL().hostname.endsWith(".onion")) {
|
|
||||||
return ref(
|
|
||||||
useRuntimeConfig().public.onionApiHost ?? useRequestURL().origin,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ref(useRuntimeConfig().public.apiHost ?? useRequestURL().origin);
|
|
||||||
};
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<slot v-if="!route.meta.requiresAuth || identity" />
|
<slot v-if="!route.meta.requiresAuth || identity" />
|
||||||
<Card v-else class="shadow-none bg-transparent border-none p-4 max-w-md mx-auto">
|
<Card v-else class="shadow-none border-none p-4 max-w-md mx-auto">
|
||||||
<CardHeader class="text-center gap-y-4">
|
<CardHeader class="text-center gap-y-4">
|
||||||
<CardTitle>{{ m.sunny_quick_lionfish_flip() }}</CardTitle>
|
<CardTitle>{{ m.sunny_quick_lionfish_flip() }}</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|
@ -35,7 +35,7 @@ import * as m from "~/paraglide/messages.js";
|
||||||
import { SettingIds } from "~/settings";
|
import { SettingIds } from "~/settings";
|
||||||
|
|
||||||
const appData = useAppData();
|
const appData = useAppData();
|
||||||
const signInAction = () => signIn(appData, new URL(useBaseUrl().value));
|
const signInAction = async () => signIn(appData, await askForInstance());
|
||||||
const colorMode = useColorMode();
|
const colorMode = useColorMode();
|
||||||
const themeSetting = useSetting(SettingIds.Theme);
|
const themeSetting = useSetting(SettingIds.Theme);
|
||||||
const { n, d } = useMagicKeys();
|
const { n, d } = useMagicKeys();
|
||||||
|
|
|
||||||
|
|
@ -256,12 +256,6 @@ export default defineNuxtConfig({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
runtimeConfig: {
|
|
||||||
public: {
|
|
||||||
apiHost: "https://beta.versia.social",
|
|
||||||
onionApiHost: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devtools: {
|
devtools: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Client } from "@versia/client";
|
import { Client } from "@versia/client";
|
||||||
import { AlertCircle, AppWindow, Loader } from "lucide-vue-next";
|
import { AlertCircle, Loader } from "lucide-vue-next";
|
||||||
import { confirmModalService } from "~/components/modals/composable";
|
|
||||||
import UserAuthForm from "~/components/oauth/login.vue";
|
import UserAuthForm from "~/components/oauth/login.vue";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
|
|
@ -12,41 +11,19 @@ useHead({
|
||||||
title: m.fuzzy_sea_moth_absorb(),
|
title: m.fuzzy_sea_moth_absorb(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const baseUrl = useBaseUrl();
|
const baseUrl = useRequestURL();
|
||||||
const client = computed(() => new Client(new URL(baseUrl.value)));
|
const client = computed(() => new Client(new URL(baseUrl)));
|
||||||
const instance = useInstanceFromClient(client);
|
const instance = useInstanceFromClient(client);
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
error_description,
|
error_description,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
instance_switch_uri,
|
|
||||||
response_type,
|
response_type,
|
||||||
client_id,
|
client_id,
|
||||||
scope,
|
scope,
|
||||||
} = useUrlSearchParams();
|
} = useUrlSearchParams();
|
||||||
const hasValidUrlSearchParams =
|
const hasValidUrlSearchParams =
|
||||||
redirect_uri && response_type && client_id && scope;
|
redirect_uri && response_type && client_id && scope;
|
||||||
|
|
||||||
const getHost = (uri: string) => new URL(uri).host;
|
|
||||||
|
|
||||||
const changeInstance = async () => {
|
|
||||||
const { confirmed, value } = await confirmModalService.confirm({
|
|
||||||
title: m.sharp_alive_anteater_fade(),
|
|
||||||
inputType: "url",
|
|
||||||
message: m.noble_misty_rook_slide(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (confirmed && value) {
|
|
||||||
// Redirect to the client's instance switch URI
|
|
||||||
const url = new URL(instance_switch_uri as string);
|
|
||||||
|
|
||||||
url.searchParams.set("origin", value);
|
|
||||||
|
|
||||||
await navigateTo(url.toString(), {
|
|
||||||
external: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -92,16 +69,12 @@ const changeInstance = async () => {
|
||||||
{{ m.novel_fine_stork_snap() }}
|
{{ m.novel_fine_stork_snap() }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-sm text-muted-foreground" v-html="m.smug_main_whale_snip({
|
<p class="text-sm text-muted-foreground" v-html="m.smug_main_whale_snip({
|
||||||
host: getHost(baseUrl),
|
host: baseUrl.host,
|
||||||
})">
|
})">
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="instance && hasValidUrlSearchParams">
|
<template v-if="instance && hasValidUrlSearchParams">
|
||||||
<UserAuthForm :instance="instance" />
|
<UserAuthForm :instance="instance" />
|
||||||
<Button variant="ghost" @click="changeInstance" v-if="instance_switch_uri">
|
|
||||||
<AppWindow />
|
|
||||||
{{ m.muddy_topical_pelican_gasp() }}
|
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
<div v-else-if="hasValidUrlSearchParams" class="p-4 flex items-center justify-center h-48">
|
<div v-else-if="hasValidUrlSearchParams" class="p-4 flex items-center justify-center h-48">
|
||||||
<Loader class="size-8 animate-spin" />
|
<Loader class="size-8 animate-spin" />
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ const schema = toTypedSchema(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const instance = useInstanceFromClient(new Client(new URL(useBaseUrl().value)));
|
const instance = useInstanceFromClient(new Client(client.value.url));
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
validationSchema: schema,
|
validationSchema: schema,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,43 @@ import type { Client } from "@versia/client";
|
||||||
import type { ApplicationData } from "@versia/client/types";
|
import type { ApplicationData } from "@versia/client/types";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { toast } from "vue-sonner";
|
import { toast } from "vue-sonner";
|
||||||
|
import { confirmModalService } from "~/components/modals/composable";
|
||||||
|
import pkg from "~/package.json";
|
||||||
import * as m from "~/paraglide/messages.js";
|
import * as m from "~/paraglide/messages.js";
|
||||||
|
|
||||||
|
const getRedirectUri = () => new URL("/", useRequestURL().origin);
|
||||||
|
|
||||||
|
export const askForInstance = async (): Promise<URL> => {
|
||||||
|
const { confirmed, value } = await confirmModalService.confirm({
|
||||||
|
title: m.sharp_alive_anteater_fade(),
|
||||||
|
inputType: "url",
|
||||||
|
message: m.noble_misty_rook_slide(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed && value) {
|
||||||
|
return new URL(URL.canParse(value) ? value : `https://${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("No instance provided");
|
||||||
|
};
|
||||||
|
|
||||||
export const signIn = async (
|
export const signIn = async (
|
||||||
appData: Ref<ApplicationData | null>,
|
appData: Ref<ApplicationData | null>,
|
||||||
origin: URL,
|
origin: URL,
|
||||||
) => {
|
) => {
|
||||||
const id = toast.loading(m.level_due_ox_greet());
|
const id = toast.loading(m.level_due_ox_greet());
|
||||||
|
|
||||||
const redirectUri = new URL("/", useRequestURL().origin);
|
|
||||||
|
|
||||||
const client = useClient(origin);
|
const client = useClient(origin);
|
||||||
|
|
||||||
redirectUri.searchParams.append("origin", client.value.url.origin);
|
const redirectUri = getRedirectUri();
|
||||||
|
|
||||||
const output = await client.value.createApp("Versia", {
|
redirectUri.searchParams.append("origin", origin.toString());
|
||||||
|
|
||||||
|
const output = await client.value.createApp("Versia-FE", {
|
||||||
scopes: ["read", "write", "follow", "push"],
|
scopes: ["read", "write", "follow", "push"],
|
||||||
redirect_uris: redirectUri.toString(),
|
redirect_uris: redirectUri.toString(),
|
||||||
website: useBaseUrl().value,
|
// @ts-expect-error Package.json types are missing this field
|
||||||
|
website: pkg.homepage ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!output?.data) {
|
if (!output?.data) {
|
||||||
|
|
@ -59,9 +78,9 @@ export const signInWithCode = (
|
||||||
origin: URL,
|
origin: URL,
|
||||||
) => {
|
) => {
|
||||||
const client = useClient(origin);
|
const client = useClient(origin);
|
||||||
const redirectUri = new URL("/", useRequestURL().origin);
|
const redirectUri = getRedirectUri();
|
||||||
|
|
||||||
redirectUri.searchParams.append("origin", client.value.url.origin);
|
redirectUri.searchParams.append("origin", origin.toString());
|
||||||
|
|
||||||
client.value
|
client.value
|
||||||
?.fetchAccessToken(
|
?.fetchAccessToken(
|
||||||
|
|
@ -105,3 +124,33 @@ export const signInWithCode = (
|
||||||
window.location.pathname = "/";
|
window.location.pathname = "/";
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const signOut = async (
|
||||||
|
appData: ApplicationData | null,
|
||||||
|
identityToRevoke: Identity,
|
||||||
|
) => {
|
||||||
|
const id = toast.loading("Signing out...");
|
||||||
|
|
||||||
|
if (!appData) {
|
||||||
|
toast.dismiss(id);
|
||||||
|
toast.error("No app or identity data to sign out");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't do anything on error, as Versia Server doesn't implement the revoke endpoint yet
|
||||||
|
await client.value
|
||||||
|
?.revokeToken(
|
||||||
|
appData.client_id,
|
||||||
|
identityToRevoke.tokens.access_token,
|
||||||
|
identityToRevoke.tokens.access_token,
|
||||||
|
)
|
||||||
|
.catch(() => {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
|
|
||||||
|
identities.value = identities.value.filter(
|
||||||
|
(i) => i.id !== identityToRevoke.id,
|
||||||
|
);
|
||||||
|
toast.dismiss(id);
|
||||||
|
toast.success("Signed out");
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue