Add safe user preview for invites
This commit is contained in:
@@ -1174,6 +1174,11 @@ export default function AdminInviteManagementPage() {
|
|||||||
title="Invites"
|
title="Invites"
|
||||||
subtitle="Create access links, apply account profiles, deliver invitations, and see what needs attention."
|
subtitle="Create access links, apply account profiles, deliver invitations, and see what needs attention."
|
||||||
rail={inviteManagementRail}
|
rail={inviteManagementRail}
|
||||||
|
actions={
|
||||||
|
<button type="button" className="ghost-button" onClick={() => router.push('/profile/invites?view=user')}>
|
||||||
|
View as user
|
||||||
|
</button>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<section className="admin-section">
|
<section className="admin-section">
|
||||||
{error && <div className="error-banner">{error}</div>}
|
{error && <div className="error-banner">{error}</div>}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { authFetch, clearToken, getApiBase, getToken } from '../../lib/auth'
|
import { authFetch, clearToken, getApiBase, getToken } from '../../lib/auth'
|
||||||
|
|
||||||
type ProfileInfo = {
|
type ProfileInfo = {
|
||||||
@@ -97,6 +97,7 @@ export default function ProfileInvitesPage() {
|
|||||||
const [inviteManagedByMaster, setInviteManagedByMaster] = useState(false)
|
const [inviteManagedByMaster, setInviteManagedByMaster] = useState(false)
|
||||||
const [masterInviteTemplate, setMasterInviteTemplate] = useState<OwnedInvitesResponse['master_invite']>(null)
|
const [masterInviteTemplate, setMasterInviteTemplate] = useState<OwnedInvitesResponse['master_invite']>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [viewAsUser, setViewAsUser] = useState(false)
|
||||||
|
|
||||||
const signupBaseUrl = useMemo(() => {
|
const signupBaseUrl = useMemo(() => {
|
||||||
if (typeof window === 'undefined') return '/signup'
|
if (typeof window === 'undefined') return '/signup'
|
||||||
@@ -135,6 +136,7 @@ export default function ProfileInvitesPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setViewAsUser(new URLSearchParams(window.location.search).get('view') === 'user')
|
||||||
if (!getToken()) {
|
if (!getToken()) {
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
return
|
return
|
||||||
@@ -307,6 +309,11 @@ export default function ProfileInvitesPage() {
|
|||||||
|
|
||||||
const canManageInvites = profile?.role === 'admin' || inviteAccessEnabled
|
const canManageInvites = profile?.role === 'admin' || inviteAccessEnabled
|
||||||
|
|
||||||
|
const enterUserView = () => {
|
||||||
|
setViewAsUser(true)
|
||||||
|
router.replace('/profile/invites?view=user')
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <main className="card">Loading invite tools...</main>
|
return <main className="card">Loading invite tools...</main>
|
||||||
}
|
}
|
||||||
@@ -315,17 +322,35 @@ export default function ProfileInvitesPage() {
|
|||||||
<main className="card">
|
<main className="card">
|
||||||
<div className="user-directory-panel-header profile-page-header">
|
<div className="user-directory-panel-header profile-page-header">
|
||||||
<div>
|
<div>
|
||||||
<h1>My invites</h1>
|
<h1>{viewAsUser ? 'Invites' : 'My invites'}</h1>
|
||||||
<p className="lede">Create invite links, email them directly, and track who you have invited.</p>
|
<p className="lede">
|
||||||
|
{viewAsUser
|
||||||
|
? 'Preview the invite workspace exactly as a permitted non-admin user sees it.'
|
||||||
|
: 'Create invite links, email them directly, and track who you have invited.'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-inline-actions">
|
<div className="admin-inline-actions">
|
||||||
|
{profile?.role === 'admin' && !viewAsUser ? (
|
||||||
|
<button type="button" className="ghost-button" onClick={enterUserView}>
|
||||||
|
View as user
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{profile?.role === 'admin' && viewAsUser ? (
|
||||||
|
<button type="button" onClick={() => router.push('/admin/invites')}>
|
||||||
|
Return to admin view
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
<button type="button" className="ghost-button" onClick={() => router.push('/profile')}>
|
<button type="button" className="ghost-button" onClick={() => router.push('/profile')}>
|
||||||
Back to profile
|
Back to profile
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{profile ? (
|
{profile && viewAsUser ? (
|
||||||
|
<div className="status-banner">
|
||||||
|
<strong>User view preview.</strong> The non-admin presentation is active. Your admin permissions and invite data remain unchanged.
|
||||||
|
</div>
|
||||||
|
) : profile ? (
|
||||||
<div className="status-banner">
|
<div className="status-banner">
|
||||||
Signed in as <strong>{profile.username}</strong> ({profile.role}).
|
Signed in as <strong>{profile.username}</strong> ({profile.role}).
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user