rolespicker-fix
This commit is contained in:
6
package-lock.json
generated
6
package-lock.json
generated
@@ -3282,9 +3282,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.190",
|
"version": "1.5.191",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz",
|
||||||
"integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
|
"integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "publy-panel",
|
"name": "publy-panel",
|
||||||
"version": "0.7.9",
|
"version": "0.7.9",
|
||||||
"private": true,
|
"type": "module",
|
||||||
|
"description": "Publy, a modern platform for collaborative text creation, offers a user-friendly interface for authors, editors, and readers, supporting real-time collaboration and structured feedback.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
@@ -20,7 +20,7 @@ export interface UserEditModalProps {
|
|||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Доступные роли в системе
|
// Список доступных ролей с сохранением идентификаторов
|
||||||
const AVAILABLE_ROLES = [
|
const AVAILABLE_ROLES = [
|
||||||
{
|
{
|
||||||
id: 'admin',
|
id: 'admin',
|
||||||
@@ -54,13 +54,23 @@ const AVAILABLE_ROLES = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Создаем маппинги для конвертации между ID и названиями
|
||||||
|
const ROLE_ID_TO_NAME = Object.fromEntries(
|
||||||
|
AVAILABLE_ROLES.map(role => [role.id, role.name])
|
||||||
|
)
|
||||||
|
|
||||||
|
const ROLE_NAME_TO_ID = Object.fromEntries(
|
||||||
|
AVAILABLE_ROLES.map(role => [role.name, role.id])
|
||||||
|
)
|
||||||
|
|
||||||
const UserEditModal: Component<UserEditModalProps> = (props) => {
|
const UserEditModal: Component<UserEditModalProps> = (props) => {
|
||||||
|
// Инициализируем форму с использованием ID ролей
|
||||||
const [formData, setFormData] = createSignal({
|
const [formData, setFormData] = createSignal({
|
||||||
id: props.user.id,
|
id: props.user.id,
|
||||||
email: props.user.email || '',
|
email: props.user.email || '',
|
||||||
name: props.user.name || '',
|
name: props.user.name || '',
|
||||||
slug: props.user.slug || '',
|
slug: props.user.slug || '',
|
||||||
roles: props.user.roles || []
|
roles: (props.user.roles || []).map(roleName => ROLE_NAME_TO_ID[roleName] || roleName)
|
||||||
})
|
})
|
||||||
|
|
||||||
const [errors, setErrors] = createSignal<Record<string, string>>({})
|
const [errors, setErrors] = createSignal<Record<string, string>>({})
|
||||||
@@ -89,7 +99,7 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем форму при изменении пользователя
|
// Обновляем эффект для инициализации формы
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.user) {
|
if (props.user) {
|
||||||
setFormData({
|
setFormData({
|
||||||
@@ -97,31 +107,50 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
email: props.user.email || '',
|
email: props.user.email || '',
|
||||||
name: props.user.name || '',
|
name: props.user.name || '',
|
||||||
slug: props.user.slug || '',
|
slug: props.user.slug || '',
|
||||||
roles: props.user.roles || []
|
roles: (props.user.roles || []).map(roleName => ROLE_NAME_TO_ID[roleName] || roleName)
|
||||||
})
|
})
|
||||||
setErrors({})
|
setErrors({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Обновим логику проверки выбранности роли
|
||||||
|
const isRoleSelected = (roleId: string) => {
|
||||||
|
const roles = formData().roles || []
|
||||||
|
const isSelected = roles.includes(roleId)
|
||||||
|
console.log(`Checking role ${roleId}:`, isSelected)
|
||||||
|
return isSelected
|
||||||
|
}
|
||||||
|
|
||||||
const handleRoleToggle = (roleId: string) => {
|
const handleRoleToggle = (roleId: string) => {
|
||||||
|
console.log('Attempting to toggle role:', roleId)
|
||||||
|
console.log('Current roles:', formData().roles)
|
||||||
|
console.log('Is admin:', isAdmin())
|
||||||
|
console.log('Role is admin:', roleId === 'admin')
|
||||||
|
|
||||||
if (roleId === 'admin') {
|
if (roleId === 'admin') {
|
||||||
|
console.log('Admin role cannot be changed')
|
||||||
return // Системная роль не может быть изменена
|
return // Системная роль не может быть изменена
|
||||||
}
|
}
|
||||||
|
|
||||||
setFormData((prev) => {
|
// Создаем новый массив ролей с учетом текущего состояния
|
||||||
|
setFormData(prev => {
|
||||||
const currentRoles = prev.roles || []
|
const currentRoles = prev.roles || []
|
||||||
const isCurrentlySelected = currentRoles.includes(roleId)
|
const isCurrentlySelected = currentRoles.includes(roleId)
|
||||||
|
|
||||||
const newRoles = isCurrentlySelected
|
const newRoles = isCurrentlySelected
|
||||||
? currentRoles.filter((r: string) => r !== roleId) // Убираем роль
|
? currentRoles.filter(r => r !== roleId) // Убираем роль
|
||||||
: [...currentRoles, roleId] // Добавляем роль
|
: [...currentRoles, roleId] // Добавляем роль
|
||||||
|
|
||||||
|
console.log('Current roles before:', currentRoles)
|
||||||
|
console.log('Is currently selected:', isCurrentlySelected)
|
||||||
|
console.log('New roles:', newRoles)
|
||||||
|
|
||||||
return { ...prev, roles: newRoles }
|
return { ...prev, roles: newRoles }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Очищаем ошибки, связанные с ролями
|
// Очищаем ошибки, связанные с ролями
|
||||||
if (errors().roles) {
|
if (errors().roles) {
|
||||||
setErrors((prev) => {
|
setErrors(prev => {
|
||||||
const newErrors = { ...prev }
|
const newErrors = { ...prev }
|
||||||
delete newErrors.roles
|
delete newErrors.roles
|
||||||
return newErrors
|
return newErrors
|
||||||
@@ -162,7 +191,8 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
try {
|
try {
|
||||||
await props.onSave({
|
await props.onSave({
|
||||||
...formData(),
|
...formData(),
|
||||||
roles: (formData().roles || []).join(',')
|
// Конвертируем ID ролей обратно в названия для сервера
|
||||||
|
roles: (formData().roles || []).map(roleId => ROLE_ID_TO_NAME[roleId]).join(',')
|
||||||
})
|
})
|
||||||
props.onClose()
|
props.onClose()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -173,6 +203,84 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обновляем компонент выбора роли
|
||||||
|
const RoleSelector = (props: {
|
||||||
|
role: typeof AVAILABLE_ROLES[0],
|
||||||
|
isSelected: boolean,
|
||||||
|
onToggle: () => void,
|
||||||
|
isDisabled?: boolean
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
class={`${formStyles.roleCard} ${props.isSelected ? formStyles.roleCardSelected : ''} ${props.isDisabled ? formStyles.roleCardDisabled : ''}`}
|
||||||
|
style={{
|
||||||
|
opacity: props.isDisabled ? 0.7 : 1,
|
||||||
|
cursor: props.isDisabled ? 'not-allowed' : 'pointer',
|
||||||
|
background: props.role.id === 'admin' && props.isSelected ? 'rgba(245, 158, 11, 0.1)' : undefined,
|
||||||
|
border: props.role.id === 'admin' && props.isSelected ? '1px solid rgba(245, 158, 11, 0.3)' : undefined
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!props.isDisabled) {
|
||||||
|
props.onToggle()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={formStyles.roleHeader}>
|
||||||
|
<span class={formStyles.roleName}>
|
||||||
|
<span style={{ 'margin-right': '0.5rem', 'font-size': '1.1rem' }}>
|
||||||
|
{props.role.emoji}
|
||||||
|
</span>
|
||||||
|
{props.role.name}
|
||||||
|
<Show when={props.role.id === 'admin'}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
'margin-left': '0.5rem',
|
||||||
|
'font-size': '0.75rem',
|
||||||
|
color: '#d97706',
|
||||||
|
'font-weight': 'normal'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
(системная)
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
'border-radius': '50%',
|
||||||
|
border: `2px solid ${props.isSelected ? '#3b82f6' : '#a1a1aa'}`,
|
||||||
|
'background-color': props.isSelected ? '#3b82f6' : 'transparent',
|
||||||
|
display: 'flex',
|
||||||
|
'align-items': 'center',
|
||||||
|
'justify-content': 'center',
|
||||||
|
cursor: props.isDisabled ? 'not-allowed' : 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Show when={props.isSelected}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="white"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={formStyles.roleDescription}>{props.role.description}</div>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// В основном компоненте модального окна обновляем рендеринг ролей
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={props.isOpen}
|
isOpen={props.isOpen}
|
||||||
@@ -277,80 +385,14 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
|
|
||||||
<div class={formStyles.rolesGrid}>
|
<div class={formStyles.rolesGrid}>
|
||||||
<For each={AVAILABLE_ROLES}>
|
<For each={AVAILABLE_ROLES}>
|
||||||
{(role) => {
|
{(role) => (
|
||||||
const isAdminRole = role.id === 'admin'
|
<RoleSelector
|
||||||
const isSelected = (formData().roles || []).includes(role.id)
|
role={role}
|
||||||
const isDisabled = isAdminRole
|
isSelected={isRoleSelected(role.id)}
|
||||||
const roleInfo = getRoleInfo(role.id)
|
onToggle={() => handleRoleToggle(role.id)}
|
||||||
|
isDisabled={role.id === 'admin'}
|
||||||
return (
|
/>
|
||||||
<label
|
|
||||||
class={`${formStyles.roleCard} ${isSelected ? formStyles.roleCardSelected : ''} ${isDisabled ? formStyles.roleCardDisabled : ''}`}
|
|
||||||
style={{
|
|
||||||
opacity: isDisabled ? 0.7 : 1,
|
|
||||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
||||||
background: isAdminRole && isSelected ? 'rgba(245, 158, 11, 0.1)' : undefined,
|
|
||||||
border: isAdminRole && isSelected ? '1px solid rgba(245, 158, 11, 0.3)' : undefined
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (!isDisabled && role.id !== 'admin') {
|
|
||||||
handleRoleToggle(role.id)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class={formStyles.roleHeader}>
|
|
||||||
<span class={formStyles.roleName}>
|
|
||||||
<span style={{ 'margin-right': '0.5rem', 'font-size': '1.1rem' }}>
|
|
||||||
{roleInfo.emoji}
|
|
||||||
</span>
|
|
||||||
{role.name}
|
|
||||||
{isAdminRole && (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
'margin-left': '0.5rem',
|
|
||||||
'font-size': '0.75rem',
|
|
||||||
color: '#d97706',
|
|
||||||
'font-weight': 'normal'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
(системная)
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '20px',
|
|
||||||
height: '20px',
|
|
||||||
'border-radius': '50%',
|
|
||||||
border: `2px solid ${isSelected ? '#3b82f6' : '#a1a1aa'}`,
|
|
||||||
'background-color': isSelected ? '#3b82f6' : 'transparent',
|
|
||||||
display: 'flex',
|
|
||||||
'align-items': 'center',
|
|
||||||
'justify-content': 'center',
|
|
||||||
cursor: isDisabled ? 'not-allowed' : 'pointer'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Show when={isSelected}>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="12"
|
|
||||||
height="12"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="white"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<polyline points="20 6 9 17 4 12" />
|
|
||||||
</svg>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class={formStyles.roleDescription}>{role.description}</div>
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -2,48 +2,45 @@ import { useAuth } from '../context/auth'
|
|||||||
import { DataProvider } from '../context/data'
|
import { DataProvider } from '../context/data'
|
||||||
import { TableSortProvider } from '../context/sort'
|
import { TableSortProvider } from '../context/sort'
|
||||||
import AdminPage from '../routes/admin'
|
import AdminPage from '../routes/admin'
|
||||||
|
import { Show, createEffect } from 'solid-js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Компонент защищенного маршрута
|
* Компонент защищенного маршрута
|
||||||
*/
|
*/
|
||||||
export const ProtectedRoute = () => {
|
export const ProtectedRoute = () => {
|
||||||
console.log('[ProtectedRoute] Checking authentication...')
|
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
const isReady = auth.isReady()
|
|
||||||
const authenticated = auth.isAuthenticated()
|
|
||||||
|
|
||||||
console.log(`[ProtectedRoute] Auth state: ready=${isReady}, authenticated=${authenticated}`)
|
createEffect(() => {
|
||||||
|
if (auth.isReady() && !auth.isAuthenticated()) {
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Если авторизация еще не готова, показываем загрузку
|
|
||||||
if (!isReady) {
|
|
||||||
console.log('[ProtectedRoute] Auth not ready, showing loading...')
|
|
||||||
return (
|
return (
|
||||||
|
<Show
|
||||||
|
when={auth.isReady()}
|
||||||
|
fallback={
|
||||||
<div class="loading-screen">
|
<div class="loading-screen">
|
||||||
<div class="loading-spinner" />
|
<div class="loading-spinner" />
|
||||||
<div>Инициализация авторизации...</div>
|
<div>Инициализация авторизации...</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
// Если авторизация готова, но пользователь не аутентифицирован
|
<Show
|
||||||
if (!authenticated) {
|
when={auth.isAuthenticated()}
|
||||||
console.log('[ProtectedRoute] Not authenticated, redirecting to login...')
|
fallback={
|
||||||
// Используем window.location.href для редиректа
|
|
||||||
window.location.href = '/login'
|
|
||||||
return (
|
|
||||||
<div class="loading-screen">
|
<div class="loading-screen">
|
||||||
<div class="loading-spinner" />
|
<div class="loading-spinner" />
|
||||||
<div>Перенаправление на страницу входа...</div>
|
<div>Перенаправление на страницу входа...</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
console.log('[ProtectedRoute] Auth ready and authenticated, rendering admin panel...')
|
|
||||||
return (
|
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<TableSortProvider>
|
<TableSortProvider>
|
||||||
<AdminPage apiUrl={`${location.origin}/graphql`} />
|
<AdminPage apiUrl={`${location.origin}/graphql`} />
|
||||||
</TableSortProvider>
|
</TableSortProvider>
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,8 @@ import { defineConfig } from 'vite'
|
|||||||
import solidPlugin from 'vite-plugin-solid'
|
import solidPlugin from 'vite-plugin-solid'
|
||||||
|
|
||||||
// Читаем версию из package.json
|
// Читаем версию из package.json
|
||||||
const packageJsonPath = resolve(__dirname, 'package.json')
|
const currentDir = process.cwd()
|
||||||
|
const packageJsonPath = resolve(currentDir, 'package.json')
|
||||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
||||||
const version = packageJson.version
|
const version = packageJson.version
|
||||||
|
|
Reference in New Issue
Block a user