Move admin's user list to specific file

This commit is contained in:
Zoe Roux 2024-02-17 19:40:03 +01:00
parent a278e3a565
commit 18ff6fe71b
2 changed files with 168 additions and 144 deletions

View File

@ -18,151 +18,11 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { QueryIdentifier, QueryPage, User, UserP, queryFn } from "@kyoo/models";
import { Alert, Avatar, Icon, IconButton, Menu, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
import { ScrollView, View } from "react-native";
import { QueryPage } from "@kyoo/models";
import { ts } from "@kyoo/primitives";
import { ScrollView } from "react-native";
import { DefaultLayout } from "../layout";
import { SettingsContainer } from "../settings/base";
import { useTranslation } from "react-i18next";
import { InfiniteFetch } from "../fetch-infinite";
import { Layout, WithLoading } from "../fetch";
import { px, useYoshiki } from "yoshiki/native";
import UserI from "@material-symbols/svg-400/rounded/account_circle.svg";
import Admin from "@material-symbols/svg-400/rounded/shield_person.svg";
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
import { useMutation, useQueryClient } from "@tanstack/react-query";
const UserGrid = ({
isLoading,
id,
username,
avatar,
isAdmin,
...props
}: WithLoading<{ id: string; username: string; avatar: string; isAdmin: boolean }>) => {
const { css } = useYoshiki();
const { t } = useTranslation();
const queryClient = useQueryClient();
const { mutateAsync } = useMutation({
mutationFn: async (update: Partial<User>) =>
await queryFn({
path: ["users", id],
method: "PATCH",
body: update,
}),
onSettled: async () => await queryClient.invalidateQueries({ queryKey: ["users"] }),
});
return (
<View {...css({ alignItems: "center" }, props)}>
<Avatar src={avatar} alt={username} placeholder={username} size={UserGrid.layout.size} fill />
<View {...css({ flexDirection: "row" })}>
<Icon
icon={isAdmin ? Admin : UserI}
{...css({
alignSelf: "center",
m: ts(1),
})}
{...tooltip(t(isAdmin ? "admin.users.adminUser" : "admin.users.regularUser"))}
/>
<Skeleton>
<P>{username}</P>
</Skeleton>
<Menu Trigger={IconButton} icon={MoreVert} {...tooltip(t("misc.more"))}>
<Menu.Sub label={t("admin.users.set-permissions")} icon={Admin}>
<Menu.Item
selected={!isAdmin}
label={t("admin.users.regularUser")}
onSelect={() =>
mutateAsync({
permissions: ["overall.read"],
})
}
/>
<Menu.Item
selected={isAdmin}
label={t("admin.users.adminUser")}
onSelect={() =>
mutateAsync({
permissions: [
"overall.read",
"overall.write",
"overall.create",
"overall.delete",
"admin.read",
"admin.write",
"admin.create",
"admin.delete",
],
})
}
/>
</Menu.Sub>
<Menu.Item
label={t("admin.users.delete")}
icon={Delete}
onSelect={async () => {
Alert.alert(
t("admin.users.delete"),
t("login.delete-confirmation"),
[
{ text: t("misc.cancel"), style: "cancel" },
{
text: t("misc.delete"),
onPress: async () => {
await queryFn({ path: ["users", id], method: "DELETE" });
await queryClient.invalidateQueries({ queryKey: ["users"] });
},
style: "destructive",
},
],
{
cancelable: true,
icon: "warning",
},
);
}}
/>
</Menu>
</View>
</View>
);
};
UserGrid.layout = {
size: px(150),
numColumns: { xs: 2, sm: 3, md: 5, lg: 6, xl: 7 },
gap: { xs: ts(1), sm: ts(2), md: ts(4) },
layout: "grid",
} satisfies Layout;
const UserList = () => {
const { t } = useTranslation();
return (
<SettingsContainer title={t("admin.users.label")}>
<InfiniteFetch query={UserList.query()} layout={UserGrid.layout}>
{(user) => (
<UserGrid
isLoading={user.isLoading as any}
id={user.id}
username={user.username}
avatar={user.logo}
isAdmin={user.permissions?.includes("admin.write")}
/>
)}
</InfiniteFetch>
</SettingsContainer>
);
};
UserList.query = (): QueryIdentifier<User> => ({
parser: UserP,
path: ["users"],
infinite: true,
});
import { UserList } from "./users";
export const AdminPage: QueryPage = () => {
return (

View File

@ -0,0 +1,164 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { QueryIdentifier, User, UserP, queryFn } from "@kyoo/models";
import { Alert, Avatar, Icon, IconButton, Menu, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { px, useYoshiki } from "yoshiki/native";
import { Layout, WithLoading } from "../fetch";
import { InfiniteFetch } from "../fetch-infinite";
import { SettingsContainer } from "../settings/base";
import UserI from "@material-symbols/svg-400/rounded/account_circle.svg";
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
import Admin from "@material-symbols/svg-400/rounded/shield_person.svg";
import { useMutation, useQueryClient } from "@tanstack/react-query";
export const UserGrid = ({
isLoading,
id,
username,
avatar,
isAdmin,
...props
}: WithLoading<{ id: string; username: string; avatar: string; isAdmin: boolean }>) => {
const { css } = useYoshiki();
const { t } = useTranslation();
const queryClient = useQueryClient();
const { mutateAsync } = useMutation({
mutationFn: async (update: Partial<User>) =>
await queryFn({
path: ["users", id],
method: "PATCH",
body: update,
}),
onSettled: async () => await queryClient.invalidateQueries({ queryKey: ["users"] }),
});
return (
<View {...css({ alignItems: "center" }, props)}>
<Avatar src={avatar} alt={username} placeholder={username} size={UserGrid.layout.size} fill />
<View {...css({ flexDirection: "row" })}>
<Icon
icon={isAdmin ? Admin : UserI}
{...css({
alignSelf: "center",
m: ts(1),
})}
{...tooltip(t(isAdmin ? "admin.users.adminUser" : "admin.users.regularUser"))}
/>
<Skeleton>
<P>{username}</P>
</Skeleton>
<Menu Trigger={IconButton} icon={MoreVert} {...tooltip(t("misc.more"))}>
<Menu.Sub label={t("admin.users.set-permissions")} icon={Admin}>
<Menu.Item
selected={!isAdmin}
label={t("admin.users.regularUser")}
onSelect={() =>
mutateAsync({
permissions: ["overall.read"],
})
}
/>
<Menu.Item
selected={isAdmin}
label={t("admin.users.adminUser")}
onSelect={() =>
mutateAsync({
permissions: [
"overall.read",
"overall.write",
"overall.create",
"overall.delete",
"admin.read",
"admin.write",
"admin.create",
"admin.delete",
],
})
}
/>
</Menu.Sub>
<Menu.Item
label={t("admin.users.delete")}
icon={Delete}
onSelect={async () => {
Alert.alert(
t("admin.users.delete"),
t("login.delete-confirmation"),
[
{ text: t("misc.cancel"), style: "cancel" },
{
text: t("misc.delete"),
onPress: async () => {
await queryFn({ path: ["users", id], method: "DELETE" });
await queryClient.invalidateQueries({ queryKey: ["users"] });
},
style: "destructive",
},
],
{
cancelable: true,
icon: "warning",
},
);
}}
/>
</Menu>
</View>
</View>
);
};
UserGrid.layout = {
size: px(150),
numColumns: { xs: 2, sm: 3, md: 5, lg: 6, xl: 7 },
gap: { xs: ts(1), sm: ts(2), md: ts(4) },
layout: "grid",
} satisfies Layout;
export const UserList = () => {
const { t } = useTranslation();
return (
<SettingsContainer title={t("admin.users.label")}>
<InfiniteFetch query={UserList.query()} layout={UserGrid.layout}>
{(user) => (
<UserGrid
isLoading={user.isLoading as any}
id={user.id}
username={user.username}
avatar={user.logo}
isAdmin={user.permissions?.includes("admin.write")}
/>
)}
</InfiniteFetch>
</SettingsContainer>
);
};
UserList.query = (): QueryIdentifier<User> => ({
parser: UserP,
path: ["users"],
infinite: true,
});