mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
feat(frontend): show user credential expiry
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '@/components/ui'
|
} from '@/components/ui'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { extractErrorMessage, formatDateTime } from '@/utils/format'
|
import { extractErrorMessage, formatDateTime } from '@/utils/format'
|
||||||
|
import { formatUserAuthorizationSummary } from '../dashboard-license'
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
@@ -33,6 +34,10 @@ function requiresUnverifiedEmailOverride(
|
|||||||
return 'requires_override' in result && result.warning_code === 'UNVERIFIED_EMAIL'
|
return 'requires_override' in result && result.warning_code === 'UNVERIFIED_EMAIL'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function userAuthorizationSummary(user: User) {
|
||||||
|
return formatUserAuthorizationSummary(user.jwt_exp)
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
@@ -162,6 +167,9 @@ onMounted(load)
|
|||||||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-sm text-muted-foreground">
|
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-sm text-muted-foreground">
|
||||||
<span>{{ user.email || '未设置邮箱' }}</span>
|
<span>{{ user.email || '未设置邮箱' }}</span>
|
||||||
<span>{{ user.email_verified ? '邮箱已验证' : '邮箱未验证' }}</span>
|
<span>{{ user.email_verified ? '邮箱已验证' : '邮箱未验证' }}</span>
|
||||||
|
<span :class="toneClass(userAuthorizationSummary(user).tone)">{{
|
||||||
|
userAuthorizationSummary(user).label
|
||||||
|
}}</span>
|
||||||
<span>{{ formatDateTime(user.created_at) }}</span>
|
<span>{{ formatDateTime(user.created_at) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { TokenStatus } from '@/api'
|
|||||||
import {
|
import {
|
||||||
canRefreshAuthorization,
|
canRefreshAuthorization,
|
||||||
formatAuthorizationExpiryTooltip,
|
formatAuthorizationExpiryTooltip,
|
||||||
|
formatUserAuthorizationSummary,
|
||||||
formatRemainingDays,
|
formatRemainingDays,
|
||||||
} from './dashboard-license.ts'
|
} from './dashboard-license.ts'
|
||||||
|
|
||||||
@@ -65,3 +66,22 @@ test('formats remaining days label consistently', () => {
|
|||||||
assert.equal(formatRemainingDays(0), '0 天')
|
assert.equal(formatRemainingDays(0), '0 天')
|
||||||
assert.equal(formatRemainingDays(12), '12 天')
|
assert.equal(formatRemainingDays(12), '12 天')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('formats user authorization summary from jwt expiration', () => {
|
||||||
|
assert.deepEqual(formatUserAuthorizationSummary('0'), {
|
||||||
|
label: '未绑定凭证',
|
||||||
|
tone: 'neutral',
|
||||||
|
})
|
||||||
|
assert.deepEqual(formatUserAuthorizationSummary('1000', 2000), {
|
||||||
|
label: '凭证过期',
|
||||||
|
tone: 'danger',
|
||||||
|
})
|
||||||
|
assert.deepEqual(formatUserAuthorizationSummary(String(2000 + 2 * 24 * 60 * 60), 2000), {
|
||||||
|
label: '2 天后过期',
|
||||||
|
tone: 'warning',
|
||||||
|
})
|
||||||
|
assert.deepEqual(formatUserAuthorizationSummary(String(2000 + 9 * 24 * 60 * 60), 2000), {
|
||||||
|
label: '9 天后过期',
|
||||||
|
tone: 'success',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import type { TokenStatus } from '@/api'
|
import type { TokenStatus } from '@/api'
|
||||||
|
import type { Tone } from '@/components/ui'
|
||||||
|
|
||||||
|
const SECONDS_PER_DAY = 24 * 60 * 60
|
||||||
|
|
||||||
|
export interface UserAuthorizationSummary {
|
||||||
|
label: string
|
||||||
|
tone: Tone
|
||||||
|
}
|
||||||
|
|
||||||
export function formatRemainingDays(days?: number | null) {
|
export function formatRemainingDays(days?: number | null) {
|
||||||
return days == null ? '未知' : `${days} 天`
|
return days == null ? '未知' : `${days} 天`
|
||||||
@@ -36,3 +44,23 @@ export function formatAuthorizationExpiryTooltip(
|
|||||||
|
|
||||||
return `过期时间:${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}`
|
return `过期时间:${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatUserAuthorizationSummary(
|
||||||
|
jwtExp?: string | null,
|
||||||
|
nowSeconds = Math.floor(Date.now() / 1000),
|
||||||
|
): UserAuthorizationSummary {
|
||||||
|
const expiresAt = Number(jwtExp)
|
||||||
|
if (!jwtExp || jwtExp === '0' || !Number.isFinite(expiresAt) || expiresAt <= 0) {
|
||||||
|
return { label: '未绑定凭证', tone: 'neutral' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiresAt <= nowSeconds) {
|
||||||
|
return { label: '凭证过期', tone: 'danger' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingDays = Math.ceil((expiresAt - nowSeconds) / SECONDS_PER_DAY)
|
||||||
|
return {
|
||||||
|
label: `${remainingDays} 天后过期`,
|
||||||
|
tone: remainingDays <= 7 ? 'warning' : 'success',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -90,6 +90,13 @@ def test_frontend_admin_approval_policy_warnings_are_visible() -> None:
|
|||||||
assert "未验证邮箱审批警告" not in email_settings
|
assert "未验证邮箱审批警告" not in email_settings
|
||||||
|
|
||||||
|
|
||||||
|
def test_frontend_admin_users_show_authorization_summary() -> None:
|
||||||
|
admin_users = (SRC_ROOT / "views" / "admin" / "AdminUsersView.vue").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "formatUserAuthorizationSummary" in admin_users
|
||||||
|
assert "jwt_exp" in admin_users
|
||||||
|
|
||||||
|
|
||||||
def test_frontend_replaces_starter_component() -> None:
|
def test_frontend_replaces_starter_component() -> None:
|
||||||
app = (SRC_ROOT / "App.vue").read_text(encoding="utf-8")
|
app = (SRC_ROOT / "App.vue").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user