mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-30 19:54:16 -04:00
Move admin's user list to specific file
This commit is contained in:
parent
a278e3a565
commit
18ff6fe71b
@ -18,151 +18,11 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { QueryIdentifier, QueryPage, User, UserP, queryFn } from "@kyoo/models";
|
import { QueryPage } from "@kyoo/models";
|
||||||
import { Alert, Avatar, Icon, IconButton, Menu, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
|
import { ts } from "@kyoo/primitives";
|
||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView } from "react-native";
|
||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import { SettingsContainer } from "../settings/base";
|
import { UserList } from "./users";
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AdminPage: QueryPage = () => {
|
export const AdminPage: QueryPage = () => {
|
||||||
return (
|
return (
|
||||||
|
164
front/packages/ui/src/admin/users.tsx
Normal file
164
front/packages/ui/src/admin/users.tsx
Normal 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,
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user