Minor bugfixes in setup process

Improved UX with small hints
This commit is contained in:
grimsi
2024-09-27 17:51:18 +02:00
parent 61bcd76a9e
commit 71aab506d5
7 changed files with 42 additions and 49 deletions
@@ -7,16 +7,21 @@ import React, {useEffect, useState} from "react";
import {useAuth} from "Frontend/util/auth";
import * as Yup from "yup";
import UserUpdateDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserUpdateDto";
import {EmailConfirmationEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
import {EmailConfirmationEndpoint, MessageEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
import {toast} from "sonner";
import {removeAvatar, uploadAvatar} from "Frontend/endpoints/AvatarEndpoint";
import Avatar from "Frontend/components/general/Avatar";
export default function ProfileManagement() {
const [configSaved, setConfigSaved] = useState(false);
const auth = useAuth();
const [avatar, setAvatar] = useState<any>();
const [configSaved, setConfigSaved] = useState(false);
const [messagesEnabled, setMessagesEnabled] = useState(false);
useEffect(() => {
MessageEndpoint.isEnabled().then(setMessagesEnabled);
}, []);
useEffect(() => {
if (configSaved) {
@@ -122,8 +127,8 @@ export default function ProfileManagement() {
isDisabled={auth.state.user?.managedBySso}/>
<div className="flex flex-row gap-4">
<Input name="email" label="Email" type="email" autocomplete="email"
isDisabled={auth.state.user?.managedBySso}/>
{auth.state.user?.emailConfirmed === false &&
isDisabled={auth.state.user?.managedBySso || !messagesEnabled}/>
{(auth.state.user?.emailConfirmed === false && !auth.state.user.managedBySso) &&
<Tooltip content="Resend email confirmation message">
<Button isIconOnly
onPress={() => {
@@ -131,6 +136,7 @@ export default function ProfileManagement() {
() => toast.success("You will receive an email shortly")
)
}}
isDisabled={!messagesEnabled}
variant="ghost"
className="size-14"
>
@@ -139,6 +145,14 @@ export default function ProfileManagement() {
</Tooltip>
}
</div>
{!messagesEnabled &&
<div className="flex flex-row gap-2 text-warning -mt-5">
<Info/>
<small>
Email services are disabled. Please contact your administrator.
</small>
</div>
}
<Section title="Security"/>
<Input name="newPassword" label="New Password" type="password"
autocomplete="new-password" isDisabled={auth.state.user?.managedBySso}/>
+1 -15
View File
@@ -3,7 +3,7 @@ import * as Yup from 'yup';
import Wizard from "Frontend/components/wizard/Wizard";
import WizardStep from "Frontend/components/wizard/WizardStep";
import Input from "Frontend/components/general/Input";
import {GearFine, HandWaving, Palette, User} from "@phosphor-icons/react";
import {HandWaving, Palette, User} from "@phosphor-icons/react";
import {Card} from "@nextui-org/react";
import {SetupEndpoint} from "Frontend/generated/endpoints";
import {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
@@ -76,17 +76,6 @@ function UserStep() {
);
}
function SettingsStep() {
return (
<div className="flex flex-col size-full items-center">
<div className="flex flex-col w-1/2 min-w-[468px] gap-12 items-center">
<h4>Settings</h4>
<p>Configure your settings</p>
</div>
</div>
);
}
function SetupView() {
const navigate = useNavigate();
@@ -131,9 +120,6 @@ function SetupView() {
>
<UserStep/>
</WizardStep>
<WizardStep icon={<GearFine/>}>
<SettingsStep/>
</WizardStep>
</Wizard>
</Card>
</div>
@@ -3,18 +3,12 @@ package de.grimsi.gameyfin.setup
import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint
import com.vaadin.hilla.exception.EndpointException
import de.grimsi.gameyfin.core.Roles
import de.grimsi.gameyfin.users.RoleService
import de.grimsi.gameyfin.users.UserService
import de.grimsi.gameyfin.users.dto.UserInfoDto
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
import de.grimsi.gameyfin.users.entities.User
@Endpoint
class SetupEndpoint(
private val setupService: SetupService,
private val roleService: RoleService,
private val userService: UserService
private val setupService: SetupService
) {
@AnonymousAllowed
fun isSetupCompleted(): Boolean {
@@ -24,15 +18,6 @@ class SetupEndpoint(
@AnonymousAllowed
fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): UserInfoDto {
if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed")
val user = User(
username = superAdminRegistration.username,
password = superAdminRegistration.password,
email = superAdminRegistration.email,
roles = setOf(roleService.toRole(Roles.SUPERADMIN))
)
val superAdmin = setupService.createInitialAdminUser(user)
return userService.toUserInfo(superAdmin)
return setupService.createInitialAdminUser(superAdminRegistration)
}
}
@@ -3,6 +3,8 @@ package de.grimsi.gameyfin.setup
import de.grimsi.gameyfin.core.Roles
import de.grimsi.gameyfin.users.RoleService
import de.grimsi.gameyfin.users.UserService
import de.grimsi.gameyfin.users.dto.UserInfoDto
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
import de.grimsi.gameyfin.users.entities.User
import org.springframework.stereotype.Service
@@ -24,7 +26,16 @@ class SetupService(
/**
* Creates the initial user with Super-Admin permissions
*/
fun createInitialAdminUser(superAdmin: User): User {
return userService.registerOrUpdateUser(superAdmin, Roles.SUPERADMIN)
fun createInitialAdminUser(registration: UserRegistrationDto): UserInfoDto {
val superAdmin = User(
username = registration.username,
password = registration.password,
email = registration.email,
roles = setOf(roleService.toRole(Roles.SUPERADMIN)),
enabled = true
)
val user = userService.registerOrUpdateUser(superAdmin, Roles.SUPERADMIN)
return userService.toUserInfo(user)
}
}
@@ -2,7 +2,6 @@ package de.grimsi.gameyfin.shared.token
import de.grimsi.gameyfin.users.entities.User
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.transaction.Transactional
abstract class TokenService<T : TokenType>(
private val type: T,
@@ -11,7 +10,6 @@ abstract class TokenService<T : TokenType>(
private val log = KotlinLogging.logger {}
@Transactional
open fun generate(user: User): Token<T> {
val token = Token(
creator = user,
@@ -26,7 +24,6 @@ abstract class TokenService<T : TokenType>(
return tokenRepository.save(token)
}
@Transactional
open fun generateWithPayload(user: User, payload: Map<String, String>): Token<T> {
val token = Token(
creator = user,
@@ -42,7 +39,6 @@ abstract class TokenService<T : TokenType>(
return tokenRepository.save(token)
}
@Transactional
open fun get(secret: String, type: T): Token<T>? {
val token = tokenRepository.findBySecret(secret) ?: return null
@@ -55,12 +51,10 @@ abstract class TokenService<T : TokenType>(
}
}
@Transactional
open fun getPayload(secret: String): Map<String, String>? {
return tokenRepository.findBySecret(secret)?.payload
}
@Transactional
open fun delete(token: Token<T>) {
try {
tokenRepository.delete(token)
@@ -69,7 +63,6 @@ abstract class TokenService<T : TokenType>(
}
}
@Transactional
open fun validate(secret: String): TokenValidationResult {
val token = tokenRepository.findBySecret(secret) ?: return TokenValidationResult.INVALID
return if (token.expired) TokenValidationResult.EXPIRED else TokenValidationResult.VALID
@@ -179,10 +179,10 @@ class UserService(
}
}
fun registerUserFromInvitation(user: UserRegistrationDto, email: String): User {
fun registerUserFromInvitation(registration: UserRegistrationDto, email: String): User {
val user = User(
username = user.username,
password = passwordEncoder.encode(user.password),
username = registration.username,
password = passwordEncoder.encode(registration.password),
email = email,
emailConfirmed = true,
enabled = true,
@@ -2,6 +2,7 @@ package de.grimsi.gameyfin.users.registration
import de.grimsi.gameyfin.core.Utils
import de.grimsi.gameyfin.core.events.UserInvitationEvent
import de.grimsi.gameyfin.core.events.UserRegistrationEvent
import de.grimsi.gameyfin.shared.token.TokenDto
import de.grimsi.gameyfin.shared.token.TokenRepository
import de.grimsi.gameyfin.shared.token.TokenService
@@ -48,7 +49,10 @@ class InvitationService(
val email = invitationToken.payload[EMAIL_KEY] ?: return TokenValidationResult.INVALID
if (invitationToken.expired) return TokenValidationResult.EXPIRED
userService.registerUserFromInvitation(registration, email)
val user = userService.registerUserFromInvitation(registration, email)
super.delete(invitationToken)
eventPublisher.publishEvent(UserRegistrationEvent(this, user, Utils.getBaseUrl()))
return TokenValidationResult.VALID
}
}