mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-06-04 13:30:32 +08:00
feat(auth): rich workspace-aware notification on successful login
Replace the plain "Login successful" toast with a TDesign Notify card that surfaces the workspace the user just landed in and their role there, so users immediately see which space they're working in — especially useful now that login can drop them into a remembered non-home tenant. A small shared helper (utils/loginNotify.ts) handles both the password login path (views/auth/Login.vue) and the OIDC callback path (App.vue handleGlobalOIDCCallback), so the two flows stay consistent. Role label goes through the same useRoleLabel composable used by the rest of the role-aware UI, so locale + future role-name tweaks live in one place. The membership lookup falls back gracefully (no role line) when the active tenant isn't in the response's memberships list — e.g. the auto-setup path that synthesises a single owner row. The legacy auth.loginSuccess key is left in place; nothing else references it, so a future cleanup PR can drop it safely.
This commit is contained in:
@@ -8,6 +8,8 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
import { getCurrentUser } from '@/api/auth'
|
||||
import { consumePendingTenantSwitchToast } from '@/utils/tenantSwitch'
|
||||
import { useRoleLabel } from '@/composables/useRoleLabel'
|
||||
import { notifyLoginSuccess } from '@/utils/loginNotify'
|
||||
|
||||
// TDesign locale configs
|
||||
import enUSConfig from 'tdesign-vue-next/esm/locale/en_US'
|
||||
@@ -16,6 +18,7 @@ import koKRConfig from 'tdesign-vue-next/esm/locale/ko_KR'
|
||||
import ruRUConfig from 'tdesign-vue-next/esm/locale/ru_RU'
|
||||
|
||||
const { locale, t } = useI18n()
|
||||
const { formatRole } = useRoleLabel()
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -139,8 +142,8 @@ const handleGlobalOIDCCallback = async () => {
|
||||
const response = decodeOIDCResult(oidcResult)
|
||||
if (response.success) {
|
||||
clearOIDCCallbackState('/')
|
||||
MessagePlugin.success('Login successful')
|
||||
await persistOIDCLoginResponse(response)
|
||||
notifyLoginSuccess(response, t, formatRole)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1296,6 +1296,9 @@ export default {
|
||||
rememberMe: 'Remember Me',
|
||||
forgotPassword: 'Forgot Password?',
|
||||
loginSuccess: 'Login successful!',
|
||||
loginSuccessTitle: 'Login successful',
|
||||
loginSuccessContent: 'Welcome back. You are now in "{name}"',
|
||||
loginSuccessContentWithRole: 'Welcome back. You are now in "{name}" as {role}',
|
||||
loginFailed: 'Login failed',
|
||||
loggingIn: 'Logging in...',
|
||||
register: 'Register',
|
||||
|
||||
@@ -1147,6 +1147,9 @@ export default {
|
||||
rememberMe: "로그인 상태 유지",
|
||||
forgotPassword: "비밀번호를 잊으셨나요?",
|
||||
loginSuccess: "로그인 성공!",
|
||||
loginSuccessTitle: "로그인 성공",
|
||||
loginSuccessContent: "환영합니다. 「{name}」에 입장했습니다",
|
||||
loginSuccessContentWithRole: "환영합니다. 「{name}」에 입장했습니다 · 역할: {role}",
|
||||
loginFailed: "로그인 실패",
|
||||
loggingIn: "로그인 중...",
|
||||
oidcLogin: "OIDC로 로그인",
|
||||
|
||||
@@ -1248,6 +1248,9 @@ export default {
|
||||
rememberMe: 'Запомнить меня',
|
||||
forgotPassword: 'Забыли пароль?',
|
||||
loginSuccess: 'Вход выполнен успешно!',
|
||||
loginSuccessTitle: 'Вход выполнен',
|
||||
loginSuccessContent: 'С возвращением. Вы вошли в «{name}»',
|
||||
loginSuccessContentWithRole: 'С возвращением. Вы вошли в «{name}» · роль: {role}',
|
||||
loginFailed: 'Ошибка входа',
|
||||
loggingIn: 'Вход...',
|
||||
register: 'Регистрация',
|
||||
|
||||
@@ -1142,6 +1142,9 @@ export default {
|
||||
rememberMe: "记住我",
|
||||
forgotPassword: "忘记密码?",
|
||||
loginSuccess: "登录成功!",
|
||||
loginSuccessTitle: "登录成功",
|
||||
loginSuccessContent: "欢迎,你已进入「{name}」",
|
||||
loginSuccessContentWithRole: "欢迎,你已进入「{name}」,身份:{role}",
|
||||
loginFailed: "登录失败",
|
||||
loggingIn: "登录中...",
|
||||
oidcLogin: "使用 OIDC 登录",
|
||||
|
||||
47
frontend/src/utils/loginNotify.ts
Normal file
47
frontend/src/utils/loginNotify.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// Rich "you're now in {workspace} as {role}" notification for the
|
||||
// post-login moment. Shared between the password (views/auth/Login.vue)
|
||||
// and OIDC (App.vue handleGlobalOIDCCallback) login paths so the two
|
||||
// flows feel identical to the user.
|
||||
//
|
||||
// Kept as a free function — not a composable — because the caller
|
||||
// already has `t` and `formatRole` in scope from useI18n / useRoleLabel
|
||||
// and there is no per-instance state worth tracking.
|
||||
|
||||
import { NotifyPlugin } from 'tdesign-vue-next'
|
||||
|
||||
type Translator = (key: string, params?: Record<string, unknown>) => string
|
||||
type RoleFormatter = (role: string | null | undefined) => string
|
||||
|
||||
interface LoginResponseLike {
|
||||
// Password-login response uses `active_tenant`; the OIDC callback
|
||||
// response uses `tenant` (legacy backward-compat name on the Go side).
|
||||
// Accept either so callers don't have to normalise.
|
||||
active_tenant?: { id?: number | string; name?: string }
|
||||
tenant?: { id?: number | string; name?: string }
|
||||
memberships?: Array<{ tenant_id?: number | string; role?: string }>
|
||||
}
|
||||
|
||||
export function notifyLoginSuccess(
|
||||
response: LoginResponseLike | null | undefined,
|
||||
t: Translator,
|
||||
formatRole: RoleFormatter,
|
||||
): void {
|
||||
const activeTenant = response?.active_tenant || response?.tenant
|
||||
if (!activeTenant) return
|
||||
|
||||
const tenantName = activeTenant.name || String(activeTenant.id || '')
|
||||
const activeTenantId = Number(activeTenant.id)
|
||||
const membership = Array.isArray(response?.memberships)
|
||||
? response!.memberships!.find((m) => Number(m?.tenant_id) === activeTenantId)
|
||||
: null
|
||||
const roleLabel = membership?.role ? formatRole(membership.role) : ''
|
||||
|
||||
NotifyPlugin.success({
|
||||
title: t('auth.loginSuccessTitle'),
|
||||
content: roleLabel
|
||||
? t('auth.loginSuccessContentWithRole', { name: tenantName, role: roleLabel })
|
||||
: t('auth.loginSuccessContent', { name: tenantName }),
|
||||
duration: 6000,
|
||||
closeBtn: true,
|
||||
})
|
||||
}
|
||||
@@ -372,6 +372,8 @@
|
||||
import { ref, reactive, nextTick, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { MessagePlugin } from 'tdesign-vue-next'
|
||||
import { useRoleLabel } from '@/composables/useRoleLabel'
|
||||
import { notifyLoginSuccess } from '@/utils/loginNotify'
|
||||
import { Swiper, SwiperSlide } from 'swiper/vue'
|
||||
import { Autoplay, EffectFade, Pagination } from 'swiper/modules'
|
||||
import 'swiper/css'
|
||||
@@ -389,6 +391,7 @@ import screenshot4 from '@/assets/img/screenshot-4.svg'
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
const { t, locale } = useI18n()
|
||||
const { formatRole } = useRoleLabel()
|
||||
|
||||
// Swiper modules
|
||||
const modules = [Autoplay, EffectFade, Pagination]
|
||||
@@ -662,8 +665,8 @@ const handleLogin = async () => {
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
MessagePlugin.success(t('auth.loginSuccess'))
|
||||
await persistLoginResponse(response)
|
||||
notifyLoginSuccess(response, t, formatRole)
|
||||
} else {
|
||||
MessagePlugin.error(response.message || t('auth.loginError'))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user