mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 00:30:02 +00:00
Use PhosphorIcons instead of FontAwesome
Start implementation of setup process
This commit is contained in:
+7
-4
@@ -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 (
|
||||
<React.StrictMode>
|
||||
<StrictMode>
|
||||
<AuthProvider>
|
||||
<ThemeProvider>
|
||||
<RouterProvider router={router}/>
|
||||
<IconContext.Provider value={{size: 20}}>
|
||||
<RouterProvider router={router}/>
|
||||
</IconContext.Provider>
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</React.StrictMode>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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: <User/>,
|
||||
onClick: () => alert("Profile")
|
||||
},
|
||||
{
|
||||
label: "Administration",
|
||||
icon: faCog,
|
||||
icon: <GearFine/>,
|
||||
onClick: () => alert("Administration"),
|
||||
showIf: state.user?.authorities?.some(a => a?.includes("ADMIN"))
|
||||
},
|
||||
{
|
||||
label: "Help",
|
||||
icon: faQuestionCircle,
|
||||
icon: <Question/>,
|
||||
onClick: () => window.open("https://github.com/gameyfin/gameyfin/tree/v2", "_blank")
|
||||
},
|
||||
{
|
||||
label: "Sign Out",
|
||||
icon: faSignOut,
|
||||
icon: <SignOut/>,
|
||||
onClick: () => logout(),
|
||||
color: "red-500"
|
||||
},
|
||||
@@ -54,10 +46,9 @@ export default function ProfileMenu() {
|
||||
name={state.user?.name}
|
||||
abbr={state.user?.name?.substring(0, 2)}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={isMenuOpen ? faChevronUp : faChevronDown}
|
||||
className="h-2 w-2"
|
||||
/>
|
||||
<IconContext.Provider value={{size: 12}}>
|
||||
{isMenuOpen ? <CaretUp/> : <CaretDown/>}
|
||||
</IconContext.Provider>
|
||||
</Button>
|
||||
</MenuHandler>
|
||||
<MenuList className="p-1">
|
||||
@@ -71,7 +62,7 @@ export default function ProfileMenu() {
|
||||
color ? `hover:${color}/10 focus:${color}/10 active:${color}/10` : ""
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} color={color ? color : ""} className="h-4 w-4"/>
|
||||
{icon}
|
||||
<p color={color ? color : ""}>{label}</p>
|
||||
</MenuItem> : null
|
||||
);
|
||||
|
||||
@@ -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: <LoginView/>
|
||||
},
|
||||
{
|
||||
path: '/setup',
|
||||
element: <SetupView/>
|
||||
}
|
||||
]) as RouteObject[];
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<div className="mt-8 mb-2 w-80 max-w-screen-lg sm:w-96">
|
||||
{ hasError &&
|
||||
{hasError &&
|
||||
<Alert
|
||||
icon={ <FontAwesomeIcon icon={faCircleXmark}/> }
|
||||
icon={<XCircle color="white" weight="fill"/>}
|
||||
className="mb-4 bg-red-500"
|
||||
>
|
||||
Wrong username and/or password
|
||||
</Alert> }
|
||||
</Alert>}
|
||||
<form
|
||||
className="mb-1 flex flex-col gap-6"
|
||||
onSubmit={async e => {
|
||||
|
||||
@@ -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 = [<WelcomeStep/>, <UserStep/>, <SettingsStep/>];
|
||||
|
||||
const handleNext = () => !isLastStep && setActiveStep((cur) => cur + 1);
|
||||
const handlePrev = () => !isFirstStep && setActiveStep((cur) => cur - 1);
|
||||
const finish = () => {
|
||||
alert("Setup finished");
|
||||
}
|
||||
|
||||
function WelcomeStep() {
|
||||
return (
|
||||
<div className="flex flex-col gap-12 w-full items-center">
|
||||
<Typography variant="h4">Welcome to Gameyfin 👋</Typography>
|
||||
<text className="w-1/3 min-w-[500px] text-justify">
|
||||
Gameyfin is a cutting-edge software tailored for gamers seeking efficient management of their video
|
||||
game collections. <br/><br/> 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. <br/><br/> Notably, Gameyfin stands out for its
|
||||
user-friendly
|
||||
design and adaptability, offering ample customization options to meet diverse user preferences.
|
||||
</text>
|
||||
<Typography variant="h5">Let's get started!</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UserStep() {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsStep() {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen">
|
||||
<div className="fixed size-full bg-gradient-to-br from-gf-primary to-gf-secondary"></div>
|
||||
<Card className="w-3/4 h-3/4 m-auto p-8" shadow={true}>
|
||||
<div className="w-full mb-8">
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
isLastStep={(value) => setIsLastStep(value)}
|
||||
isFirstStep={(value) => setIsFirstStep(value)}
|
||||
>
|
||||
<Step onClick={() => setActiveStep(0)}>
|
||||
<HandWaving/>
|
||||
</Step>
|
||||
<Step onClick={() => setActiveStep(1)}>
|
||||
<User/>
|
||||
</Step>
|
||||
<Step onClick={() => setActiveStep(2)}>
|
||||
<GearFine/>
|
||||
</Step>
|
||||
</Stepper>
|
||||
</div>
|
||||
<div className="flex flex-grow justify-center">
|
||||
{steps[activeStep]}
|
||||
</div>
|
||||
<div className="bottom-0 w-full">
|
||||
<div className="flex justify-between">
|
||||
<Button onClick={handlePrev} disabled={isFirstStep} className="rounded-full">
|
||||
<ArrowLeft/>
|
||||
</Button>
|
||||
<Button onClick={isLastStep ? finish : handleNext} className="rounded-full">
|
||||
{isLoading ? <Spinner/> : isLastStep ? <Check/> : <ArrowRight/>}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Generated
+17
-51
@@ -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",
|
||||
|
||||
+3
-7
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> = 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
|
||||
}
|
||||
}
|
||||
@@ -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<String>): List<Role> {
|
||||
return roles.mapNotNull { r -> roleRepository.findByRolename(r) }
|
||||
}
|
||||
|
||||
private fun toAuthorities(roles: Collection<Role>): List<GrantedAuthority> {
|
||||
return roles.map { r -> SimpleGrantedAuthority(r.rolename) }
|
||||
}
|
||||
|
||||
@@ -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<String>
|
||||
)
|
||||
@@ -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<Role> = emptyList()
|
||||
var roles: Collection<Role>
|
||||
)
|
||||
Reference in New Issue
Block a user