Skip to content

Commit 54a6a7e

Browse files
committed
page /sign-in/token
1 parent c0ed9c5 commit 54a6a7e

File tree

4 files changed

+144
-4
lines changed

4 files changed

+144
-4
lines changed

spx-gui/src/pages/sign-in/callback.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
<template>
22
<div class="container">
3-
<h4>Logging in...</h4>
3+
<h4>{{ $t(title) }}</h4>
44
</div>
55
</template>
66
<script setup lang="ts">
7-
import { useUserStore } from '@/stores/user'
7+
import { usePageTitle } from '@/utils/utils'
88
import { useI18n } from '@/utils/i18n'
9+
import { useUserStore } from '@/stores/user'
10+
11+
const title = { en: 'Signing in...', zh: '登录中...' }
12+
13+
usePageTitle(title)
914
1015
const userStore = useUserStore()
1116
const i18n = useI18n()
@@ -27,7 +32,6 @@ try {
2732
}
2833
</script>
2934
<style scoped lang="scss">
30-
// Center the text
3135
.container {
3236
display: flex;
3337
justify-content: center;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<template>
2+
<div class="page">
3+
<UIForm class="form" :form="form" @submit="handleSubmit.fn">
4+
<h1 class="title">{{ $t(title) }}</h1>
5+
<UIFormItem path="token">
6+
<UITextInput
7+
v-model:value="form.value.token"
8+
class="input"
9+
type="textarea"
10+
:placeholder="$t({ en: 'Paste token here', zh: '在此粘贴 Token' })"
11+
/>
12+
</UIFormItem>
13+
<footer class="footer">
14+
<UIButton type="boring" @click="handleCancel">
15+
{{ $t({ en: 'Cancel', zh: '取消' }) }}
16+
</UIButton>
17+
<UIButton type="primary" html-type="submit" :loading="handleSubmit.isLoading.value">
18+
{{ buttonText }}
19+
</UIButton>
20+
</footer>
21+
</UIForm>
22+
</div>
23+
</template>
24+
<script setup lang="ts">
25+
import { computed, ref } from 'vue'
26+
import { useRouter } from 'vue-router'
27+
import { useI18n } from '@/utils/i18n'
28+
import { usePageTitle } from '@/utils/utils'
29+
import { useMessageHandle } from '@/utils/exception'
30+
import { useUserStore, type UserInfo } from '@/stores/user'
31+
import { UIForm, UIFormItem, UITextInput, UIButton, useForm } from '@/components/ui'
32+
33+
const title = {
34+
en: 'Sign in with token',
35+
zh: '使用 Token 登录'
36+
}
37+
38+
usePageTitle(title)
39+
40+
const router = useRouter()
41+
const userStore = useUserStore()
42+
const i18n = useI18n()
43+
44+
const userInfo = ref<UserInfo | null>(null)
45+
const buttonText = computed(() => {
46+
if (userInfo.value == null) return i18n.t({ en: 'Sign in', zh: '登录' })
47+
const username = userInfo.value.displayName || userInfo.value.name
48+
return i18n.t({
49+
en: `Sign in as ${username}`,
50+
zh: `以 ${username} 登录`
51+
})
52+
})
53+
54+
const form = useForm({
55+
token: ['', validateToken]
56+
})
57+
58+
function validateToken(token: string) {
59+
userInfo.value = null
60+
token = token.trim()
61+
if (token === '')
62+
return i18n.t({
63+
en: 'Token is required',
64+
zh: '请提供 Token'
65+
})
66+
try {
67+
userInfo.value = userStore.parseAccessToken(token)
68+
} catch (e) {
69+
return i18n.t({
70+
en: 'Invalid token: ' + e,
71+
zh: '无效的 Token:' + e
72+
})
73+
}
74+
}
75+
76+
function handleCancel() {
77+
router.push('/')
78+
}
79+
80+
const handleSubmit = useMessageHandle(
81+
async () => {
82+
const token = form.value.token.trim()
83+
userStore.signInWithAccessToken(token)
84+
router.push('/')
85+
},
86+
{
87+
en: 'Failed to signin',
88+
zh: '登录失败'
89+
}
90+
)
91+
</script>
92+
<style scoped lang="scss">
93+
.page {
94+
width: 100%;
95+
height: 100%;
96+
display: flex;
97+
justify-content: center;
98+
align-items: center;
99+
}
100+
101+
.form {
102+
width: 320px;
103+
display: flex;
104+
flex-direction: column;
105+
}
106+
107+
.title {
108+
margin-bottom: 1em;
109+
font-size: 16px;
110+
text-align: center;
111+
}
112+
113+
.input {
114+
justify-self: stretch;
115+
height: 160px;
116+
}
117+
118+
.footer {
119+
margin-top: 1em;
120+
display: flex;
121+
justify-content: center;
122+
gap: 1em;
123+
}
124+
</style>

spx-gui/src/router.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ const routes: Array<RouteRecordRaw> = [
112112
path: '/sign-in/callback',
113113
component: () => import('@/pages/sign-in/callback.vue')
114114
},
115+
{
116+
path: '/sign-in/token',
117+
component: () => import('@/pages/sign-in/token.vue')
118+
},
115119
{
116120
path: '/share/:owner/:name',
117121
redirect: (to) => getProjectPageRoute(to.params.owner as string, to.params.name as string)

spx-gui/src/stores/user/signed-in.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,18 @@ export const useUserStore = defineStore('spx-user', {
9595
isSignedIn(): boolean {
9696
return this.isAccessTokenValid() || this.refreshToken != null
9797
},
98+
parseAccessToken(accessToken: string) {
99+
return jwtDecode<UserInfo>(accessToken)
100+
},
98101
// TODO: return type `User` instead of `UserInfo` to keep consistency with `getUser` in `src/apis/user.ts`
99102
getSignedInUser(): UserInfo | null {
100103
if (!this.isSignedIn()) return null
101-
return jwtDecode<UserInfo>(this.accessToken!)
104+
return this.parseAccessToken(this.accessToken!)
105+
},
106+
signInWithAccessToken(accessToken: string) {
107+
this.accessToken = accessToken
108+
this.accessTokenExpiresAt = null
109+
this.refreshToken = null
102110
}
103111
},
104112
persist: true

0 commit comments

Comments
 (0)