From 20ca3f2f5fcf4a9eb0d6a8be671bb4fb1f5e6178 Mon Sep 17 00:00:00 2001 From: Jakob Ankarhem Date: Wed, 27 Jan 2021 00:09:42 +0100 Subject: [PATCH] feat(users): add editable usernames (#715) --- server/entity/User.ts | 15 +- server/lib/notifications/agents/discord.ts | 8 +- server/lib/notifications/agents/email.ts | 10 +- server/lib/notifications/agents/pushover.ts | 12 +- server/lib/notifications/agents/slack.ts | 8 +- server/lib/notifications/agents/telegram.ts | 2 +- server/lib/notifications/agents/webhook.ts | 2 +- .../1611508672722-AddDisplayNameToUser.ts | 43 +++ server/routes/auth.ts | 14 +- server/routes/user.ts | 24 +- src/components/RequestBlock/index.tsx | 4 +- src/components/RequestCard/index.tsx | 2 +- .../RequestList/RequestItem/index.tsx | 5 +- .../RequestModal/MovieRequestModal.tsx | 2 +- src/components/UserEdit/index.tsx | 290 ++++++++++-------- src/components/UserList/index.tsx | 2 +- src/context/UserContext.tsx | 2 +- src/hooks/useUser.ts | 8 +- src/i18n/locale/en.json | 2 +- 19 files changed, 282 insertions(+), 173 deletions(-) create mode 100644 server/migration/1611508672722-AddDisplayNameToUser.ts diff --git a/server/entity/User.ts b/server/entity/User.ts index 0b05efb2..078fe15e 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -6,6 +6,7 @@ import { UpdateDateColumn, OneToMany, RelationCount, + AfterLoad, } from 'typeorm'; import { Permission, hasPermission } from '../lib/permissions'; import { MediaRequest } from './MediaRequest'; @@ -25,14 +26,19 @@ export class User { static readonly filteredFields: string[] = ['plexToken', 'password']; + public displayName: string; + @PrimaryGeneratedColumn() public id: number; @Column({ unique: true }) public email: string; - @Column() - public username: string; + @Column({ nullable: true }) + public plexUsername: string; + + @Column({ nullable: true }) + public username?: string; @Column({ nullable: true, select: false }) public password?: string; @@ -125,4 +131,9 @@ export class User { }); } } + + @AfterLoad() + public setDisplayName(): void { + this.displayName = this.username || this.plexUsername; + } } diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index de6ed7ba..798f6525 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -104,7 +104,7 @@ class DiscordAgent fields.push( { name: 'Requested By', - value: payload.notifyUser.username ?? '', + value: payload.notifyUser.displayName ?? '', inline: true, }, { @@ -126,7 +126,7 @@ class DiscordAgent fields.push( { name: 'Requested By', - value: payload.notifyUser.username ?? '', + value: payload.notifyUser.displayName ?? '', inline: true, }, { @@ -148,7 +148,7 @@ class DiscordAgent fields.push( { name: 'Requested By', - value: payload.notifyUser.username ?? '', + value: payload.notifyUser.displayName ?? '', inline: true, }, { @@ -170,7 +170,7 @@ class DiscordAgent fields.push( { name: 'Requested By', - value: payload.notifyUser.username ?? '', + value: payload.notifyUser.displayName ?? '', inline: true, }, { diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts index a74a4c18..ccb42802 100644 --- a/server/lib/notifications/agents/email.ts +++ b/server/lib/notifications/agents/email.ts @@ -60,7 +60,7 @@ class EmailAgent mediaName: payload.subject, imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.username, + requestedBy: payload.notifyUser.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -106,7 +106,7 @@ class EmailAgent mediaName: payload.subject, imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.username, + requestedBy: payload.notifyUser.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -144,7 +144,7 @@ class EmailAgent mediaName: payload.subject, imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.username, + requestedBy: payload.notifyUser.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -181,7 +181,7 @@ class EmailAgent mediaName: payload.subject, imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.username, + requestedBy: payload.notifyUser.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -218,7 +218,7 @@ class EmailAgent mediaName: payload.subject, imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.username, + requestedBy: payload.notifyUser.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts index 158d01c7..5b7713b0 100644 --- a/server/lib/notifications/agents/pushover.ts +++ b/server/lib/notifications/agents/pushover.ts @@ -48,42 +48,42 @@ class PushoverAgent const title = payload.subject; const plot = payload.message; - const user = payload.notifyUser.username; + const username = payload.notifyUser.displayName; switch (type) { case Notification.MEDIA_PENDING: messageTitle = 'New Request'; message += `${title}\n\n`; message += `${plot}\n\n`; - message += `Requested By\n${user}\n\n`; + message += `Requested By\n${username}\n\n`; message += `Status\nPending Approval\n`; break; case Notification.MEDIA_APPROVED: messageTitle = 'Request Approved'; message += `${title}\n\n`; message += `${plot}\n\n`; - message += `Requested By\n${user}\n\n`; + message += `Requested By\n${username}\n\n`; message += `Status\nProcessing Request\n`; break; case Notification.MEDIA_AVAILABLE: messageTitle = 'Now available!'; message += `${title}\n\n`; message += `${plot}\n\n`; - message += `Requested By\n${user}\n\n`; + message += `Requested By\n${username}\n\n`; message += `Status\nAvailable\n`; break; case Notification.MEDIA_DECLINED: messageTitle = 'Request Declined'; message += `${title}\n\n`; message += `${plot}\n\n`; - message += `Requested By\n${user}\n\n`; + message += `Requested By\n${username}\n\n`; message += `Status\nDeclined\n`; break; case Notification.TEST_NOTIFICATION: messageTitle = 'Test Notification'; message += `${title}\n\n`; message += `${plot}\n\n`; - message += `Requested By\n${user}\n`; + message += `Requested By\n${username}\n`; break; } diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts index df338884..f6ca6856 100644 --- a/server/lib/notifications/agents/slack.ts +++ b/server/lib/notifications/agents/slack.ts @@ -69,7 +69,7 @@ class SlackAgent fields.push( { type: 'mrkdwn', - text: `*Requested By*\n${payload.notifyUser.username ?? ''}`, + text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`, }, { type: 'mrkdwn', @@ -85,7 +85,7 @@ class SlackAgent fields.push( { type: 'mrkdwn', - text: `*Requested By*\n${payload.notifyUser.username ?? ''}`, + text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`, }, { type: 'mrkdwn', @@ -101,7 +101,7 @@ class SlackAgent fields.push( { type: 'mrkdwn', - text: `*Requested By*\n${payload.notifyUser.username ?? ''}`, + text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`, }, { type: 'mrkdwn', @@ -117,7 +117,7 @@ class SlackAgent fields.push( { type: 'mrkdwn', - text: `*Requested By*\n${payload.notifyUser.username ?? ''}`, + text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`, }, { type: 'mrkdwn', diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index 62de93ec..a2b09c1c 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -51,7 +51,7 @@ class TelegramAgent const title = this.escapeText(payload.subject); const plot = this.escapeText(payload.message); - const user = this.escapeText(payload.notifyUser.username); + const user = this.escapeText(payload.notifyUser.displayName); /* eslint-disable no-useless-escape */ switch (type) { diff --git a/server/lib/notifications/agents/webhook.ts b/server/lib/notifications/agents/webhook.ts index d0e502e8..796593da 100644 --- a/server/lib/notifications/agents/webhook.ts +++ b/server/lib/notifications/agents/webhook.ts @@ -16,7 +16,7 @@ const KeyMap: Record = { subject: 'subject', message: 'message', image: 'image', - notifyuser_username: 'notifyUser.username', + notifyuser_username: 'notifyUser.displayName', notifyuser_email: 'notifyUser.email', notifyuser_avatar: 'notifyUser.avatar', media_tmdbid: 'media.tmdbId', diff --git a/server/migration/1611508672722-AddDisplayNameToUser.ts b/server/migration/1611508672722-AddDisplayNameToUser.ts new file mode 100644 index 00000000..cacea059 --- /dev/null +++ b/server/migration/1611508672722-AddDisplayNameToUser.ts @@ -0,0 +1,43 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDisplayNameToUser1611508672722 implements MigrationInterface { + name = 'AddDisplayNameToUser1611508672722'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "temporary_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar NOT NULL, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), "plexUsername" varchar, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))` + ); + await queryRunner.query( + `INSERT INTO "temporary_user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername") SELECT "id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "username" FROM "user"` + ); + await queryRunner.query(`DROP TABLE "user"`); + await queryRunner.query(`ALTER TABLE "temporary_user" RENAME TO "user"`); + await queryRunner.query( + `CREATE TABLE "temporary_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), "plexUsername" varchar, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))` + ); + await queryRunner.query( + `INSERT INTO "temporary_user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername") SELECT "id", "email", "", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername" FROM "user"` + ); + await queryRunner.query(`DROP TABLE "user"`); + await queryRunner.query(`ALTER TABLE "temporary_user" RENAME TO "user"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" RENAME TO "temporary_user"`); + await queryRunner.query( + `CREATE TABLE "user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar NOT NULL, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), "plexUsername" varchar, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))` + ); + await queryRunner.query( + `INSERT INTO "user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername") SELECT "id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType", "plexUsername" FROM "temporary_user"` + ); + await queryRunner.query(`DROP TABLE "temporary_user"`); + await queryRunner.query(`ALTER TABLE "user" RENAME TO "temporary_user"`); + await queryRunner.query( + `CREATE TABLE "user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar NOT NULL, "username" varchar NOT NULL, "plexId" integer, "plexToken" varchar, "permissions" integer NOT NULL DEFAULT (0), "avatar" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "password" varchar, "userType" integer NOT NULL DEFAULT (1), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))` + ); + await queryRunner.query( + `INSERT INTO "user"("id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType") SELECT "id", "email", "username", "plexId", "plexToken", "permissions", "avatar", "createdAt", "updatedAt", "password", "userType" FROM "temporary_user"` + ); + await queryRunner.query(`DROP TABLE "temporary_user"`); + } +} diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 8c2a90fd..00d1ef9d 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -48,13 +48,17 @@ authRoutes.post('/login', async (req, res, next) => { // Let's check if their plex token is up to date if (user.plexToken !== body.authToken) { user.plexToken = body.authToken; - await userRepository.save(user); } // Update the users avatar with their plex thumbnail (incase it changed) user.avatar = account.thumb; user.email = account.email; - user.username = account.username; + user.plexUsername = account.username; + + if (user.username === account.username) { + user.username = ''; + } + await userRepository.save(user); } else { // Here we check if it's the first user. If it is, we create the user with no check // and give them admin permissions @@ -63,7 +67,7 @@ authRoutes.post('/login', async (req, res, next) => { if (totalUsers === 0) { user = new User({ email: account.email, - username: account.username, + plexUsername: account.username, plexId: account.id, plexToken: account.authToken, permissions: Permission.ADMIN, @@ -86,7 +90,7 @@ authRoutes.post('/login', async (req, res, next) => { if (await mainPlexTv.checkUserAccess(account)) { user = new User({ email: account.email, - username: account.username, + plexUsername: account.username, plexId: account.id, plexToken: account.authToken, permissions: settings.main.defaultPermissions, @@ -141,7 +145,7 @@ authRoutes.post('/local', async (req, res, next) => { try { const user = await userRepository.findOne({ select: ['id', 'password'], - where: { email: body.email, userType: UserType.LOCAL }, + where: { email: body.email }, }); const isCorrectCredentials = await user?.passwordMatch(body.password); diff --git a/server/routes/user.ts b/server/routes/user.ts index 5b3887dc..896278ef 100644 --- a/server/routes/user.ts +++ b/server/routes/user.ts @@ -138,7 +138,11 @@ router.put<{ id: string }>('/:id', async (req, res, next) => { }); } - Object.assign(user, req.body); + Object.assign(user, { + username: req.body.username, + permissions: req.body.permissions, + }); + await userRepository.save(user); return res.status(200).json(user.filter()); @@ -213,20 +217,32 @@ router.post('/import-from-plex', async (req, res, next) => { const createdUsers: User[] = []; for (const rawUser of plexUsersResponse.MediaContainer.User) { const account = rawUser.$; + const user = await userRepository.findOne({ - where: { plexId: account.id }, + where: [{ plexId: account.id }, { email: account.email }], }); + if (user) { // Update the users avatar with their plex thumbnail (incase it changed) user.avatar = account.thumb; user.email = account.email; - user.username = account.username; + user.plexUsername = account.username; + + // in-case the user was previously a local account + if (user.userType === UserType.LOCAL) { + user.userType = UserType.PLEX; + user.plexId = parseInt(account.id); + + if (user.username === account.username) { + user.username = ''; + } + } await userRepository.save(user); } else { // Check to make sure it's a real account if (account.email && account.username) { const newUser = new User({ - username: account.username, + plexUsername: account.username, email: account.email, permissions: settings.main.defaultPermissions, plexId: parseInt(account.id), diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index e154f7e5..aa925aef 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -82,7 +82,7 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => { /> - {request.requestedBy.username} + {request.requestedBy.displayName} {request.modifiedBy && ( @@ -101,7 +101,7 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => { /> - {request.modifiedBy?.username} + {request.modifiedBy?.displayName} )} diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 7a1a082a..196db861 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -108,7 +108,7 @@ const RequestCard: React.FC = ({ request }) => {
{intl.formatMessage(messages.requestedby, { - username: requestData.requestedBy.username, + username: requestData.requestedBy.displayName, })}
{requestData.media.status && ( diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 9c337c0f..53eb2d6a 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -165,7 +165,7 @@ const RequestItem: React.FC = ({
{intl.formatMessage(messages.requestedby, { - username: requestData.requestedBy.username, + username: requestData.requestedBy.displayName, })}
{requestData.seasons.length > 0 && ( @@ -206,7 +206,8 @@ const RequestItem: React.FC = ({
{requestData.modifiedBy ? ( - {requestData.modifiedBy.username} ( + {requestData.modifiedBy.displayName} + ( = ({ {intl.formatMessage( is4k ? messages.request4kfrom : messages.requestfrom, { - username: activeRequest.requestedBy.username, + username: activeRequest.requestedBy.displayName, } )} {hasPermission(Permission.REQUEST_ADVANCED) && ( diff --git a/src/components/UserEdit/index.tsx b/src/components/UserEdit/index.tsx index d5b2a56c..d8798dea 100644 --- a/src/components/UserEdit/index.tsx +++ b/src/components/UserEdit/index.tsx @@ -8,10 +8,14 @@ import axios from 'axios'; import { useToasts } from 'react-toast-notifications'; import Header from '../Common/Header'; import PermissionEdit from '../PermissionEdit'; +import { Field, Form, Formik } from 'formik'; +import * as Yup from 'yup'; +import { UserType } from '../../../server/constants/user'; export const messages = defineMessages({ edituser: 'Edit User', - username: 'Username', + plexUsername: 'Plex Username', + username: 'Display Name', avatar: 'Avatar', email: 'Email', permissions: 'Permissions', @@ -25,7 +29,6 @@ const UserEdit: React.FC = () => { const router = useRouter(); const intl = useIntl(); const { addToast } = useToasts(); - const [isUpdating, setIsUpdating] = useState(false); const { user: currentUser } = useUser(); const { user, error, revalidate } = useUser({ id: Number(router.query.userId), @@ -40,155 +43,184 @@ const UserEdit: React.FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [user]); - const updateUser = async () => { - try { - setIsUpdating(true); - - await axios.put(`/api/v1/user/${user?.id}`, { - permissions: currentPermission, - email: user?.email, - }); - - addToast(intl.formatMessage(messages.usersaved), { - appearance: 'success', - autoDismiss: true, - }); - } catch (e) { - addToast(intl.formatMessage(messages.userfail), { - appearance: 'error', - autoDismiss: true, - }); - throw new Error(`Something went wrong saving the user: ${e.message}`); - } finally { - revalidate(); - setIsUpdating(false); - } - }; - if (!user && !error) { return ; } + const UserEditSchema = Yup.object().shape({ + username: Yup.string(), + }); + return ( - <> -
- -
-
-
-
-
- -
- -
-
-
- -
- + { + try { + await axios.put(`/api/v1/user/${user?.id}`, { + permissions: currentPermission, + email: user?.email, + username: values.username, + }); + addToast(intl.formatMessage(messages.usersaved), { + appearance: 'success', + autoDismiss: true, + }); + } catch (e) { + addToast(intl.formatMessage(messages.userfail), { + appearance: 'error', + autoDismiss: true, + }); + throw new Error(`Something went wrong saving the user: ${e.message}`); + } finally { + revalidate(); + } + }} + > + {({ isSubmitting, handleSubmit }) => ( +
+
+ +
+
+
+
+ {user?.userType === UserType.PLEX && ( +
+ +
+ +
+
+ )} +
+ +
+ +
+
+
+ +
+ +
+
-
-
-
- -
-
-
+ +
+
+ +
+
+ +
- -
- -
-
-
-
-
-
-
-
-
- +
+
+
+
+
+
+ +
+
+
+
+ + setCurrentPermission(newPermission) + } + /> +
+
-
-
- - setCurrentPermission(newPermission) - } - /> -
+
+
+
+ + +
-
-
- - - -
-
-
-
- + + )} + ); }; diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 4c98db27..aa5d844e 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -452,7 +452,7 @@ const UserList: React.FC = () => {
- {user.username} + {user.displayName}
{user.email} diff --git a/src/context/UserContext.tsx b/src/context/UserContext.tsx index 24809a18..ba1bc1a1 100644 --- a/src/context/UserContext.tsx +++ b/src/context/UserContext.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react'; -import { User, useUser } from '../hooks/useUser'; +import { useUser, User } from '../hooks/useUser'; import { useRouter } from 'next/dist/client/router'; interface UserContextProps { diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts index c2102a00..18d67bf0 100644 --- a/src/hooks/useUser.ts +++ b/src/hooks/useUser.ts @@ -2,17 +2,19 @@ import useSwr from 'swr'; import { hasPermission, Permission } from '../../server/lib/permissions'; import { UserType } from '../../server/constants/user'; +export { Permission, UserType }; + export interface User { id: number; - username: string; + plexUsername?: string; + username?: string; + displayName: string; email: string; avatar: string; permissions: number; userType: number; } -export { Permission, UserType }; - interface UserHookResponse { user?: User; loading: boolean; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index be576879..d04a0141 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -508,7 +508,7 @@ "components.UserEdit.save": "Save", "components.UserEdit.saving": "Saving…", "components.UserEdit.userfail": "Something went wrong saving the user.", - "components.UserEdit.username": "Username", + "components.UserEdit.username": "Display Name", "components.UserEdit.usersaved": "User saved", "components.UserList.admin": "Admin", "components.UserList.autogeneratepassword": "Automatically generate password",