diff --git a/frontend/App.tsx b/frontend/App.tsx index 74524c7..cf3a47a 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -3,16 +3,19 @@ import {AuthProvider} from 'Frontend/util/auth.js'; import {RouterProvider} from 'react-router-dom'; import "./main.css"; import {ThemeProvider} from "@material-tailwind/react"; -import React from 'react'; +import {IconContext} from "@phosphor-icons/react"; +import {StrictMode} from "react"; export default function App() { return ( - + - + + + - + ); } diff --git a/frontend/components/ProfileMenu.tsx b/frontend/components/ProfileMenu.tsx index 37542d0..37f966e 100644 --- a/frontend/components/ProfileMenu.tsx +++ b/frontend/components/ProfileMenu.tsx @@ -1,17 +1,9 @@ import {useState} from "react"; import {Button, Menu, MenuHandler, MenuItem, MenuList} from "@material-tailwind/react"; -import { - faChevronDown, - faChevronUp, - faCog, - faQuestionCircle, - faSignOut, - faUser -} from "@fortawesome/free-solid-svg-icons"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {useAuth} from "Frontend/util/auth"; import {Avatar} from "@hilla/react-components/Avatar"; import {useNavigate} from "react-router-dom"; +import {CaretDown, CaretUp, GearFine, IconContext, Question, SignOut, User} from "@phosphor-icons/react"; export default function ProfileMenu() { const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -21,23 +13,23 @@ export default function ProfileMenu() { const profileMenuItems = [ { label: "My Profile", - icon: faUser, + icon: , onClick: () => alert("Profile") }, { label: "Administration", - icon: faCog, + icon: , onClick: () => alert("Administration"), showIf: state.user?.authorities?.some(a => a?.includes("ADMIN")) }, { label: "Help", - icon: faQuestionCircle, + icon: , onClick: () => window.open("https://github.com/gameyfin/gameyfin/tree/v2", "_blank") }, { label: "Sign Out", - icon: faSignOut, + icon: , onClick: () => logout(), color: "red-500" }, @@ -54,10 +46,9 @@ export default function ProfileMenu() { name={state.user?.name} abbr={state.user?.name?.substring(0, 2)} /> - + + {isMenuOpen ? : } + @@ -71,7 +62,7 @@ export default function ProfileMenu() { color ? `hover:${color}/10 focus:${color}/10 active:${color}/10` : "" }`} > - + {icon}

{label}

: null ); diff --git a/frontend/routes.tsx b/frontend/routes.tsx index f0d5b11..87250a9 100644 --- a/frontend/routes.tsx +++ b/frontend/routes.tsx @@ -3,6 +3,7 @@ import {createBrowserRouter, RouteObject} from 'react-router-dom'; import LoginView from "Frontend/views/LoginView"; import MainLayout from "Frontend/views/MainLayout"; import TestView from "Frontend/views/TestView"; +import SetupView from "Frontend/views/SetupView"; export const routes = protectRoutes([ { @@ -15,6 +16,10 @@ export const routes = protectRoutes([ { path: '/login', element: + }, + { + path: '/setup', + element: } ]) as RouteObject[]; diff --git a/frontend/views/LoginView.tsx b/frontend/views/LoginView.tsx index b0810bb..1b4a954 100644 --- a/frontend/views/LoginView.tsx +++ b/frontend/views/LoginView.tsx @@ -2,8 +2,7 @@ import {useAuth} from "Frontend/util/auth"; import {useState} from "react"; import {Link, useNavigate} from "react-router-dom"; import {Alert, Button, Card, Input, Spinner, Typography} from "@material-tailwind/react"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {faCircleInfo, faCircleXmark} from "@fortawesome/free-solid-svg-icons"; +import {XCircle} from "@phosphor-icons/react"; export default function LoginView() { const {state, login} = useAuth(); @@ -28,13 +27,13 @@ export default function LoginView() { src="/images/Logo.svg" />
- { hasError && + {hasError && } + icon={} className="mb-4 bg-red-500" > Wrong username and/or password - } + }
{ diff --git a/frontend/views/SetupView.tsx b/frontend/views/SetupView.tsx new file mode 100644 index 0000000..fa15976 --- /dev/null +++ b/frontend/views/SetupView.tsx @@ -0,0 +1,90 @@ +import {Button, Card, Spinner, Step, Stepper, Typography} from "@material-tailwind/react"; +import {useState} from "react"; +import {ArrowLeft, ArrowRight, Check, GearFine, HandWaving, User} from "@phosphor-icons/react"; + +export default function SetupView() { + const [activeStep, setActiveStep] = useState(0); + const [isLastStep, setIsLastStep] = useState(false); + const [isFirstStep, setIsFirstStep] = useState(false); + const [isLoading, setLoading] = useState(false); + + const steps = [, , ]; + + const handleNext = () => !isLastStep && setActiveStep((cur) => cur + 1); + const handlePrev = () => !isFirstStep && setActiveStep((cur) => cur - 1); + const finish = () => { + alert("Setup finished"); + } + + function WelcomeStep() { + return ( +
+ Welcome to Gameyfin 👋 + + Gameyfin is a cutting-edge software tailored for gamers seeking efficient management of their video + game collections.

With its intuitive interface and comprehensive features, Gameyfin + simplifies the organization of game libraries. Users can effortlessly add games through manual input + or + automated recognition, categorize them based on various criteria like genre or platform, track + in-game + progress, and share achievements with friends.

Notably, Gameyfin stands out for its + user-friendly + design and adaptability, offering ample customization options to meet diverse user preferences. +
+ Let's get started! +
+ ); + } + + function UserStep() { + return ( + <> + + ); + } + + function SettingsStep() { + return ( + <> + + ); + } + + return ( +
+
+ +
+ setIsLastStep(value)} + isFirstStep={(value) => setIsFirstStep(value)} + > + setActiveStep(0)}> + + + setActiveStep(1)}> + + + setActiveStep(2)}> + + + +
+
+ {steps[activeStep]} +
+
+
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9d6e6cf..e211cd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,13 @@ { - "name": "no-name", + "name": "Gameyfin", + "version": "2.0.0-ALPHA", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "no-name", - "license": "UNLICENSED", + "name": "Gameyfin", + "version": "2.0.0-ALPHA", "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.5.1", - "@fortawesome/free-solid-svg-icons": "^6.5.1", - "@fortawesome/react-fontawesome": "^0.2.0", "@hilla/form": "2.5.6", "@hilla/frontend": "2.5.6", "@hilla/generator-typescript-cli": "2.5.6", @@ -26,6 +24,7 @@ "@hilla/react-crud": "2.5.6", "@hilla/react-form": "2.5.6", "@material-tailwind/react": "^2.1.9", + "@phosphor-icons/react": "^2.0.15", "@polymer/polymer": "3.5.1", "@vaadin/bundles": "24.3.0", "@vaadin/common-frontend": "0.0.19", @@ -2259,51 +2258,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", - "hasInstallScript": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", - "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" - } - }, "node_modules/@hilla/form": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/@hilla/form/-/form-2.5.6.tgz", @@ -2976,6 +2930,18 @@ "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.5.tgz", "integrity": "sha512-XCO48r7/l1BMzA0DuB9/osQ6cXWk3PUl90RqFUjR2DB/LIpY98aEpzjYTkiRh2a5nTgEA8kDFDy88O0Kiib/wA==" }, + "node_modules/@phosphor-icons/react": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.0.15.tgz", + "integrity": "sha512-PQKNcRrfERlC8gJGNz0su0i9xVmeubXSNxucPcbCLDd9u0cwJVTEyYK87muul/svf0UXFdL2Vl6bbeOhT1Mwow==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">= 16.8", + "react-dom": ">= 16.8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", diff --git a/package.json b/package.json index e43888c..42468d2 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,6 @@ "version": "2.0.0-ALPHA", "type": "module", "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.5.1", - "@fortawesome/free-solid-svg-icons": "^6.5.1", - "@fortawesome/react-fontawesome": "^0.2.0", "@hilla/form": "2.5.6", "@hilla/frontend": "2.5.6", "@hilla/generator-typescript-cli": "2.5.6", @@ -22,6 +19,7 @@ "@hilla/react-crud": "2.5.6", "@hilla/react-form": "2.5.6", "@material-tailwind/react": "^2.1.9", + "@phosphor-icons/react": "^2.0.15", "@polymer/polymer": "3.5.1", "@vaadin/bundles": "24.3.0", "@vaadin/common-frontend": "0.0.19", @@ -84,9 +82,7 @@ "@hilla/generator-typescript-plugin-backbone": "$@hilla/generator-typescript-plugin-backbone", "@hilla/generator-typescript-cli": "$@hilla/generator-typescript-cli", "@material-tailwind/react": "$@material-tailwind/react", - "@fortawesome/fontawesome-svg-core": "$@fortawesome/fontawesome-svg-core", - "@fortawesome/free-solid-svg-icons": "$@fortawesome/free-solid-svg-icons", - "@fortawesome/react-fontawesome": "$@fortawesome/react-fontawesome" + "@phosphor-icons/react": "$@phosphor-icons/react" }, "vaadin": { "dependencies": { @@ -130,6 +126,6 @@ "workbox-core": "7.0.0", "workbox-precaching": "7.0.0" }, - "hash": "f9e182b004ea4d86a6ca514e8d956df5851830853b0a45b9e40615d51b7d2788" + "hash": "866532fc7368b3b6387e2dee804a0a2f82b8b5c94f00c0a5fa056b2bd6af0220" } } diff --git a/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt index 7ce9985..f6aa22d 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt @@ -32,14 +32,20 @@ class SetupDataLoader( } fun setupUsers() { - val superadmin = User("admin") - superadmin.password = "admin" - superadmin.roles = listOf(roleRepository.findByRolename(Roles.SUPERADMIN.roleName)!!) + val superadmin = User( + username = "admin", + password = "admin", + roles = listOf(roleRepository.findByRolename(Roles.SUPERADMIN.roleName)!!) + ) + userService.registerUser(superadmin) - val user = User("user") - user.password = "user" - user.roles = listOf(roleRepository.findByRolename(Roles.USER.roleName)!!) + val user = User( + username = "user", + password = "user", + roles = listOf(roleRepository.findByRolename(Roles.USER.roleName)!!) + ) + userService.registerUser(user) } diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt index 08fafaa..3ebc9b5 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt @@ -1,6 +1,8 @@ package de.grimsi.gameyfin.users import de.grimsi.gameyfin.users.dto.UserInfo +import de.grimsi.gameyfin.users.dto.UserRegistration +import de.grimsi.gameyfin.users.entities.User import dev.hilla.Endpoint import jakarta.annotation.security.PermitAll import org.springframework.security.core.Authentication @@ -8,7 +10,9 @@ import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.context.SecurityContextHolder @Endpoint -class UserEndpoint { +class UserEndpoint( + private val userService: UserService +) { @PermitAll fun getUserInfo(): UserInfo { @@ -16,4 +20,18 @@ class UserEndpoint { val authorities: List = auth.authorities.map { g: GrantedAuthority -> g.authority } return UserInfo(auth.name, authorities) } + + @PermitAll + fun registerUser(registration: UserRegistration): Boolean { + val user = User( + username = registration.username, + password = registration.password, + email = registration.email, + roles = userService.toRoles(registration.roles) + ) + + userService.registerUser(user) + + return true + } } \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt b/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt index 12b22f5..842a043 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt @@ -2,6 +2,7 @@ package de.grimsi.gameyfin.users import de.grimsi.gameyfin.users.entities.Role import de.grimsi.gameyfin.users.entities.User +import de.grimsi.gameyfin.users.persistence.RoleRepository import de.grimsi.gameyfin.users.persistence.UserRepository import jakarta.transaction.Transactional import org.springframework.security.core.GrantedAuthority @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service @Transactional class UserService( private val userRepository: UserRepository, + private val roleRepository: RoleRepository, private val passwordEncoder: PasswordEncoder ) : UserDetailsService { @@ -40,6 +42,10 @@ class UserService( return userRepository.save(user) } + fun toRoles(roles: Collection): List { + return roles.mapNotNull { r -> roleRepository.findByRolename(r) } + } + private fun toAuthorities(roles: Collection): List { return roles.map { r -> SimpleGrantedAuthority(r.rolename) } } diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserRegistration.kt b/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserRegistration.kt new file mode 100644 index 0000000..929939c --- /dev/null +++ b/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserRegistration.kt @@ -0,0 +1,8 @@ +package de.grimsi.gameyfin.users.dto + +data class UserRegistration( + val username: String, + val password: String, + val email: String, + val roles: List +) \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt b/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt index 291e648..a6dbb93 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt @@ -16,7 +16,7 @@ class User( var id: Long? = null, @field:NotNull - var password: String? = null, + var password: String, @field:Nullable var email: String? = null, @@ -33,5 +33,5 @@ class User( joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")], inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")] ) - var roles: Collection = emptyList() + var roles: Collection ) \ No newline at end of file