Use PhosphorIcons instead of FontAwesome

Start implementation of setup process
This commit is contained in:
grimsi
2024-03-10 00:51:05 +01:00
parent d5c0493e35
commit 647ac97309
12 changed files with 182 additions and 94 deletions
+7 -4
View File
@@ -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>
);
}
+9 -18
View File
@@ -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
);
+5
View File
@@ -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[];
+4 -5
View File
@@ -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 => {
+90
View File
@@ -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>
);
}
+17 -51
View File
@@ -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
View File
@@ -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>
)