[client] Rework OAuth login page

This commit is contained in:
Laura Hausmann 2023-11-03 23:15:58 +01:00
parent be02bc78ad
commit 093f13646e
No known key found for this signature in database
GPG Key ID: D044E84C5BE01605
5 changed files with 164 additions and 52 deletions

View File

@ -1032,6 +1032,7 @@ activeEmailValidationDescription: "Enables stricter validation of email addresse
navbar: "Navigation bar" navbar: "Navigation bar"
shuffle: "Shuffle" shuffle: "Shuffle"
account: "Account" account: "Account"
signedInAs: "Signed in as"
move: "Move" move: "Move"
pushNotification: "Push notifications" pushNotification: "Push notifications"
subscribePushNotification: "Enable push notifications" subscribePushNotification: "Enable push notifications"

View File

@ -150,6 +150,7 @@ export async function openAccountMenu(
opts: { opts: {
includeCurrentAccount?: boolean; includeCurrentAccount?: boolean;
withExtraOperation: boolean; withExtraOperation: boolean;
withoutProfileLink?: boolean;
active?: misskey.entities.UserDetailed["id"]; active?: misskey.entities.UserDetailed["id"];
onChoose?: (account: misskey.entities.UserDetailed) => void; onChoose?: (account: misskey.entities.UserDetailed) => void;
}, },
@ -227,53 +228,56 @@ export async function openAccountMenu(
); );
if (opts.withExtraOperation) { if (opts.withExtraOperation) {
popupMenu( const menu = [
[ ...(!opts.withoutProfileLink ? [
...[ {
type: "link",
text: i18n.ts.profile,
to: `/@${$i.username}`,
avatar: $i,
},
null,
] : []),
...(opts.includeCurrentAccount ? [createItem($i)] : []),
...accountItemPromises,
...(opts.withoutProfileLink ? [null] : []),
{
type: "parent",
icon: "ph-plus ph-bold ph-lg",
text: i18n.ts.addAccount,
children: [
{ {
type: "link", text: i18n.ts.existingAccount,
text: i18n.ts.profile,
to: `/@${$i.username}`,
avatar: $i,
},
null,
...(opts.includeCurrentAccount ? [createItem($i)] : []),
...accountItemPromises,
{
type: "parent",
icon: "ph-plus ph-bold ph-lg",
text: i18n.ts.addAccount,
children: [
{
text: i18n.ts.existingAccount,
action: () => {
showSigninDialog();
},
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
},
},
],
},
{
type: "link",
icon: "ph-users ph-bold ph-lg",
text: i18n.ts.manageAccounts,
to: "/settings/accounts",
},
{
type: "button",
icon: "ph-sign-out ph-bold ph-lg",
text: i18n.ts.logout,
action: () => { action: () => {
signout(); showSigninDialog();
},
},
{
text: i18n.ts.createAccount,
action: () => {
createAccount();
}, },
}, },
], ],
], },
{
type: "link",
icon: "ph-users ph-bold ph-lg",
text: i18n.ts.manageAccounts,
to: "/settings/accounts",
},
{
type: "button",
icon: "ph-sign-out ph-bold ph-lg",
text: i18n.ts.logout,
action: () => {
signout();
},
},
];
popupMenu(
menu,
ev.currentTarget ?? ev.target, ev.currentTarget ?? ev.target,
{ {
align: "left", align: "left",

View File

@ -188,7 +188,9 @@ function checkForSplash() {
}); });
const app = createApp( const app = createApp(
window.location.search === "?zen" window.location.pathname === "/oauth/authorize"
? defineAsyncComponent(() => import("@/ui/oauth.vue"))
: window.location.search === "?zen"
? defineAsyncComponent(() => import("@/ui/zen.vue")) ? defineAsyncComponent(() => import("@/ui/zen.vue"))
: !$i : !$i
? defineAsyncComponent(() => import("@/ui/visitor.vue")) ? defineAsyncComponent(() => import("@/ui/visitor.vue"))

View File

@ -1,35 +1,55 @@
<template> <template>
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<div v-if="$i"> <div v-if="$i">
<div v-if="state == 'waiting'" class="waiting _section"> <div v-if="state == 'waiting'" class="waiting _section" :class="[$style.section]">
<div class="_content"> <div class="_content">
<MkLoading /> <MkLoading />
</div> </div>
</div> </div>
<div v-if="state == 'denied'" class="denied _section"> <div v-if="state == 'denied'" class="denied _section" :class="[$style.section]">
<div class="_content"> <div class="_content">
<p>{{ i18n.ts._auth.denied }}</p> <p>{{ i18n.ts._auth.denied }}</p>
</div> </div>
</div> </div>
<div v-else-if="state == 'error'" class="error _section"> <div v-else-if="state == 'error'" class="error _section" :class="[$style.section]">
<div class="_content"> <div class="_content">
<p>{{ message }}</p> <p>{{ message }}</p>
</div> </div>
</div> </div>
<div v-else-if="state == 'accepted-oob'" class="accepted-oob _section"> <div v-else-if="state == 'accepted-oob'" class="accepted-oob _section" :class="[$style.section]">
<div class="_content"> <div class="_content">
<p>{{ i18n.ts._auth.copyAsk }}</p> <p>{{ i18n.ts._auth.copyAsk }}</p>
<pre>{{ code }}</pre> <pre>{{ code }}</pre>
</div> </div>
</div> </div>
<div v-else-if="state == 'accepted'" class="accepted _section"> <div v-else-if="state == 'accepted'" class="accepted _section" :class="[$style.section]">
<div class="_content"> <div class="_content">
<p> <p>
{{ i18n.ts._auth.callback }}<MkEllipsis /> {{ i18n.ts._auth.callback }}<MkEllipsis />
</p> </p>
</div> </div>
</div> </div>
<div v-else class="_section"> <div v-else class="_section" :class="[$style.section]">
<div :class="[$style.container]">
<button
v-click-anime
class="item _button"
:class="[$style.account]"
@click="openAccountMenu"
>
<MkAvatar
:user="$i"
:class="[$style.icon]"
disableLink
/><!-- <MkAcct class="text" :user="$i"/> -->
</button>
<div :class="[$style.left]">
<div>{{ i18n.ts.signedInAs }}:</div>
<div>@{{ $i.username }}<span :class="[$style.fade]">@{{ config.host }}</span></div>
</div>
</div>
<hr/>
<h2>Authorization required</h2>
<div v-if="name" class="_title"> <div v-if="name" class="_title">
{{ i18n.t("_auth.shareAccess", { name: name }) }} {{ i18n.t("_auth.shareAccess", { name: name }) }}
</div> </div>
@ -70,13 +90,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue";
import MkSignin from "@/components/MkSignin.vue"; import MkSignin from "@/components/MkSignin.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import * as os from "@/os"; import * as os from "@/os";
import { $i, login } from "@/account"; import { $i, login, openAccountMenu as openAccountMenu_ } from "@/account";
import { appendQuery, query } from "@/scripts/url"; import { appendQuery, query } from "@/scripts/url";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import * as config from "@/config.js";
const props = defineProps<{ const props = defineProps<{
response_type: string; response_type: string;
@ -161,6 +181,17 @@ function deny(): void {
async function onLogin(res): Promise<void> { async function onLogin(res): Promise<void> {
await login(res.i); await login(res.i);
} }
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_(
{
includeCurrentAccount: true,
withExtraOperation: true,
withoutProfileLink: true
},
ev,
);
}
</script> </script>
<style lang="scss" module> <style lang="scss" module>
@ -169,6 +200,8 @@ async function onLogin(res): Promise<void> {
} }
.permissions { .permissions {
justify-content: center;
padding-top: var(--margin);
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 1rem;
@ -182,4 +215,36 @@ async function onLogin(res): Promise<void> {
background-color: var(--buttonBg); background-color: var(--buttonBg);
color: var(--fg); color: var(--fg);
} }
</style>
.container {
display: flex;
align-items: center;
justify-content: center;
}
.account {
margin-right: 20px;
}
.icon {
display: inline-block;
width: 55px;
aspect-ratio: 1;
}
.section {
background: var(--panel);
padding: 20px 32px;
border-radius: var(--radius);
font-size: 1.05em;
text-align: center;
}
.fade {
opacity: .5;
}
.left {
text-align: left;
}
</style>

View File

@ -0,0 +1,40 @@
<template>
<div class="mk-app">
<RouterView />
<XCommon />
</div>
</template>
<script lang="ts" setup>
import type { ComputedRef } from "vue";
import { provide } from "vue";
import XCommon from "./_common_/common.vue";
import { mainRouter } from "@/router";
import type { PageMetadata } from "@/scripts/page-metadata";
import {
provideMetadataReceiver,
setPageMetadata,
} from "@/scripts/page-metadata";
import { instanceName } from "@/config";
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
provide("router", mainRouter);
provideMetadataReceiver((info) => {
pageMetadata = info;
if (pageMetadata.value) {
document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
});
document.documentElement.style.overflowY = "scroll";
</script>
<style lang="scss" scoped>
.mk-app {
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
min-height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box;
}
</style>