mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
1 Commits
cursor/imp
...
feat/full-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7232311fa3 |
@@ -65,7 +65,7 @@ export const AnnouncementsFeed = ({
|
|||||||
|
|
||||||
const classesForClickableContainer = useStylesForClickableContainer();
|
const classesForClickableContainer = useStylesForClickableContainer();
|
||||||
|
|
||||||
if (error) {
|
if (error && !hideLoader) {
|
||||||
return (
|
return (
|
||||||
<PlaceholderError onRetry={fetchAnnouncementsAndPromotions}>
|
<PlaceholderError onRetry={fetchAnnouncementsAndPromotions}>
|
||||||
<Trans>
|
<Trans>
|
||||||
|
@@ -19,7 +19,10 @@ import {
|
|||||||
} from '../../Utils/GDevelopServices/Project';
|
} from '../../Utils/GDevelopServices/Project';
|
||||||
import PlaceholderLoader from '../../UI/PlaceholderLoader';
|
import PlaceholderLoader from '../../UI/PlaceholderLoader';
|
||||||
import PlaceholderError from '../../UI/PlaceholderError';
|
import PlaceholderError from '../../UI/PlaceholderError';
|
||||||
import { getUserPublicProfilesByIds } from '../../Utils/GDevelopServices/User';
|
import {
|
||||||
|
getUserPublicProfilesByIds,
|
||||||
|
type UserPublicProfileByIds,
|
||||||
|
} from '../../Utils/GDevelopServices/User';
|
||||||
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
|
import Dialog, { DialogPrimaryButton } from '../../UI/Dialog';
|
||||||
import FlatButton from '../../UI/FlatButton';
|
import FlatButton from '../../UI/FlatButton';
|
||||||
import LeftLoader from '../../UI/LeftLoader';
|
import LeftLoader from '../../UI/LeftLoader';
|
||||||
@@ -84,9 +87,10 @@ const InviteHome = ({ cloudProjectId }: Props) => {
|
|||||||
| 'unexpected'
|
| 'unexpected'
|
||||||
| null
|
| null
|
||||||
>(null);
|
>(null);
|
||||||
const [userPublicProfileByIds, setUserPublicProfileByIds] = React.useState(
|
const [
|
||||||
{}
|
userPublicProfileByIds,
|
||||||
);
|
setUserPublicProfileByIds,
|
||||||
|
] = React.useState<UserPublicProfileByIds>({});
|
||||||
const [
|
const [
|
||||||
showCollaboratorAddDialog,
|
showCollaboratorAddDialog,
|
||||||
setShowCollaboratorAddDialog,
|
setShowCollaboratorAddDialog,
|
||||||
@@ -312,6 +316,7 @@ const InviteHome = ({ cloudProjectId }: Props) => {
|
|||||||
<ColumnStackLayout expand noMargin>
|
<ColumnStackLayout expand noMargin>
|
||||||
<UserLine
|
<UserLine
|
||||||
username={profile.username}
|
username={profile.username}
|
||||||
|
fullName={profile.fullName}
|
||||||
email={profile.email}
|
email={profile.email}
|
||||||
level={currentUserLevel}
|
level={currentUserLevel}
|
||||||
/>
|
/>
|
||||||
@@ -365,6 +370,7 @@ const InviteHome = ({ cloudProjectId }: Props) => {
|
|||||||
projectUserAcls.map(projectUserAcl => (
|
projectUserAcls.map(projectUserAcl => (
|
||||||
<UserLine
|
<UserLine
|
||||||
username={getCollaboratorUsername(projectUserAcl.userId)}
|
username={getCollaboratorUsername(projectUserAcl.userId)}
|
||||||
|
fullName={null}
|
||||||
email={projectUserAcl.email}
|
email={projectUserAcl.email}
|
||||||
level={projectUserAcl.level}
|
level={projectUserAcl.level}
|
||||||
onDelete={() => {
|
onDelete={() => {
|
||||||
|
@@ -0,0 +1,119 @@
|
|||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import { t, Trans } from '@lingui/macro';
|
||||||
|
import FlatButton from '../../../../../UI/FlatButton';
|
||||||
|
import Dialog, { DialogPrimaryButton } from '../../../../../UI/Dialog';
|
||||||
|
import { ColumnStackLayout } from '../../../../../UI/Layout';
|
||||||
|
import {
|
||||||
|
type User,
|
||||||
|
type EditUserChanges,
|
||||||
|
type UsernameAvailability,
|
||||||
|
} from '../../../../../Utils/GDevelopServices/User';
|
||||||
|
import LeftLoader from '../../../../../UI/LeftLoader';
|
||||||
|
import SemiControlledTextField from '../../../../../UI/SemiControlledTextField';
|
||||||
|
import { UsernameField } from '../../../../../Profile/UsernameField';
|
||||||
|
import AlertMessage from '../../../../../UI/AlertMessage';
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
member: User,
|
||||||
|
onApply: (changes: EditUserChanges) => Promise<void>,
|
||||||
|
onClose: () => void,
|
||||||
|
isSaving: boolean,
|
||||||
|
error: ?Error,
|
||||||
|
|};
|
||||||
|
|
||||||
|
export const EditStudentDialog = ({
|
||||||
|
member,
|
||||||
|
onApply,
|
||||||
|
onClose,
|
||||||
|
isSaving,
|
||||||
|
error,
|
||||||
|
}: Props) => {
|
||||||
|
const [changes, setChanges] = React.useState<EditUserChanges | null>(null);
|
||||||
|
|
||||||
|
const [
|
||||||
|
usernameAvailability,
|
||||||
|
setUsernameAvailability,
|
||||||
|
] = React.useState<?UsernameAvailability>(null);
|
||||||
|
const [
|
||||||
|
isValidatingUsername,
|
||||||
|
setIsValidatingUsername,
|
||||||
|
] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const canSave =
|
||||||
|
!usernameAvailability || (usernameAvailability.isAvailable && !!changes);
|
||||||
|
|
||||||
|
const triggerApply = React.useCallback(
|
||||||
|
async () => {
|
||||||
|
if (!canSave || !changes) return;
|
||||||
|
await onApply(changes);
|
||||||
|
},
|
||||||
|
[changes, canSave, onApply]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open
|
||||||
|
title={<Trans>Edit student</Trans>}
|
||||||
|
actions={[
|
||||||
|
<FlatButton
|
||||||
|
key="close"
|
||||||
|
label={<Trans>Close</Trans>}
|
||||||
|
primary={false}
|
||||||
|
onClick={onClose}
|
||||||
|
disabled={isSaving}
|
||||||
|
id="close-button"
|
||||||
|
/>,
|
||||||
|
<LeftLoader isLoading={isSaving}>
|
||||||
|
<DialogPrimaryButton
|
||||||
|
key="apply"
|
||||||
|
primary
|
||||||
|
label={<Trans>Apply</Trans>}
|
||||||
|
onClick={triggerApply}
|
||||||
|
disabled={isSaving || !canSave}
|
||||||
|
id="apply-button"
|
||||||
|
/>
|
||||||
|
</LeftLoader>,
|
||||||
|
]}
|
||||||
|
onRequestClose={onClose}
|
||||||
|
onApply={triggerApply}
|
||||||
|
maxWidth="sm"
|
||||||
|
>
|
||||||
|
<ColumnStackLayout expand noMargin>
|
||||||
|
{error && (
|
||||||
|
<AlertMessage kind="error">
|
||||||
|
<Trans>
|
||||||
|
An error occurred while editing the student. Please try again.
|
||||||
|
</Trans>
|
||||||
|
</AlertMessage>
|
||||||
|
)}
|
||||||
|
<SemiControlledTextField
|
||||||
|
onChange={fullName => setChanges({ ...changes, fullName })}
|
||||||
|
value={
|
||||||
|
changes && typeof changes.fullName !== 'undefined'
|
||||||
|
? changes.fullName
|
||||||
|
: member.fullName || ''
|
||||||
|
}
|
||||||
|
disabled={isSaving}
|
||||||
|
fullWidth
|
||||||
|
commitOnBlur
|
||||||
|
floatingLabelText={<Trans>Full name</Trans>}
|
||||||
|
/>
|
||||||
|
<UsernameField
|
||||||
|
initialUsername={member.username}
|
||||||
|
value={
|
||||||
|
changes && typeof changes.username !== 'undefined'
|
||||||
|
? changes.username
|
||||||
|
: member.username || ''
|
||||||
|
}
|
||||||
|
onChange={(_, username) => setChanges({ ...changes, username })}
|
||||||
|
disabled={isSaving}
|
||||||
|
allowEmpty
|
||||||
|
onAvailabilityChecked={setUsernameAvailability}
|
||||||
|
onAvailabilityCheckLoading={setIsValidatingUsername}
|
||||||
|
isValidatingUsername={isValidatingUsername}
|
||||||
|
/>
|
||||||
|
</ColumnStackLayout>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
@@ -3,7 +3,10 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { t, Trans } from '@lingui/macro';
|
import { t, Trans } from '@lingui/macro';
|
||||||
import Grid from '@material-ui/core/Grid';
|
import Grid from '@material-ui/core/Grid';
|
||||||
import { type User } from '../../../../../Utils/GDevelopServices/User';
|
import {
|
||||||
|
type User,
|
||||||
|
type EditUserChanges,
|
||||||
|
} from '../../../../../Utils/GDevelopServices/User';
|
||||||
import Text from '../../../../../UI/Text';
|
import Text from '../../../../../UI/Text';
|
||||||
import { LineStackLayout } from '../../../../../UI/Layout';
|
import { LineStackLayout } from '../../../../../UI/Layout';
|
||||||
import Checkbox from '../../../../../UI/Checkbox';
|
import Checkbox from '../../../../../UI/Checkbox';
|
||||||
@@ -12,9 +15,11 @@ import CheckboxChecked from '../../../../../UI/CustomSvgIcons/CheckboxChecked';
|
|||||||
import AsyncSemiControlledTextField from '../../../../../UI/AsyncSemiControlledTextField';
|
import AsyncSemiControlledTextField from '../../../../../UI/AsyncSemiControlledTextField';
|
||||||
import IconButton from '../../../../../UI/IconButton';
|
import IconButton from '../../../../../UI/IconButton';
|
||||||
import Key from '../../../../../UI/CustomSvgIcons/Key';
|
import Key from '../../../../../UI/CustomSvgIcons/Key';
|
||||||
|
import Edit from '../../../../../UI/CustomSvgIcons/Edit';
|
||||||
import { Line } from '../../../../../UI/Grid';
|
import { Line } from '../../../../../UI/Grid';
|
||||||
import { useResponsiveWindowSize } from '../../../../../UI/Responsive/ResponsiveWindowMeasurer';
|
import { useResponsiveWindowSize } from '../../../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||||
import { textEllipsisStyle } from '../../../../../UI/TextEllipsis';
|
import { textEllipsisStyle } from '../../../../../UI/TextEllipsis';
|
||||||
|
import { EditStudentDialog } from './EditStudentDialog';
|
||||||
|
|
||||||
const primaryTextArchivedOpacity = 0.6;
|
const primaryTextArchivedOpacity = 0.6;
|
||||||
const primaryTextPlaceholderOpacity = 0.7;
|
const primaryTextPlaceholderOpacity = 0.7;
|
||||||
@@ -47,6 +52,10 @@ type Props = {|
|
|||||||
userId: string,
|
userId: string,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
|}) => Promise<void>,
|
|}) => Promise<void>,
|
||||||
|
onEdit: ({|
|
||||||
|
editedUserId: string,
|
||||||
|
changes: EditUserChanges,
|
||||||
|
|}) => Promise<void>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
const ManageStudentRow = ({
|
const ManageStudentRow = ({
|
||||||
@@ -55,11 +64,18 @@ const ManageStudentRow = ({
|
|||||||
isArchived,
|
isArchived,
|
||||||
onSelect,
|
onSelect,
|
||||||
onChangePassword,
|
onChangePassword,
|
||||||
|
onEdit,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { isMobile } = useResponsiveWindowSize();
|
const { isMobile } = useResponsiveWindowSize();
|
||||||
const [isEditingPassword, setIsEditingPassword] = React.useState<boolean>(
|
const [isEditingPassword, setIsEditingPassword] = React.useState<boolean>(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
const [isSavingUser, setIsSavingUser] = React.useState<boolean>(false);
|
||||||
|
const [isEditingUser, setIsEditingUser] = React.useState<boolean>(false);
|
||||||
|
const [editedUserError, setEditedUserError] = React.useState<Error | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
passwordEditionError,
|
passwordEditionError,
|
||||||
setPasswordEditionError,
|
setPasswordEditionError,
|
||||||
@@ -110,15 +126,30 @@ const ManageStudentRow = ({
|
|||||||
uncheckedIcon={<CheckboxUnchecked />}
|
uncheckedIcon={<CheckboxUnchecked />}
|
||||||
checkedIcon={<CheckboxChecked />}
|
checkedIcon={<CheckboxChecked />}
|
||||||
/>
|
/>
|
||||||
{member.username ? (
|
{member.username || member.fullName ? (
|
||||||
<Text
|
<Text
|
||||||
allowSelection
|
allowSelection
|
||||||
style={{
|
style={{
|
||||||
...textEllipsisStyle,
|
...textEllipsisStyle,
|
||||||
opacity: isArchived ? primaryTextArchivedOpacity : undefined,
|
opacity: isArchived ? primaryTextArchivedOpacity : undefined,
|
||||||
}}
|
}}
|
||||||
|
tooltip={[member.fullName, member.username]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' - ')}
|
||||||
>
|
>
|
||||||
<b>{member.username}</b>
|
<b>
|
||||||
|
{member.fullName ? (
|
||||||
|
member.username ? (
|
||||||
|
<Trans>
|
||||||
|
{member.fullName} ({member.username})
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
member.fullName
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
member.username
|
||||||
|
)}
|
||||||
|
</b>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text
|
<Text
|
||||||
@@ -136,6 +167,15 @@ const ManageStudentRow = ({
|
|||||||
</i>
|
</i>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
{!isArchived && (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => setIsEditingUser(true)}
|
||||||
|
tooltip={t`Change username or full name`}
|
||||||
|
>
|
||||||
|
<Edit fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
</LineStackLayout>
|
</LineStackLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -205,6 +245,31 @@ const ManageStudentRow = ({
|
|||||||
</LineStackLayout>
|
</LineStackLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const editDialog = isEditingUser && (
|
||||||
|
<EditStudentDialog
|
||||||
|
member={member}
|
||||||
|
isSaving={isSavingUser}
|
||||||
|
error={editedUserError}
|
||||||
|
onApply={async (changes: EditUserChanges) => {
|
||||||
|
try {
|
||||||
|
setEditedUserError(null);
|
||||||
|
setIsSavingUser(true);
|
||||||
|
await onEdit({ editedUserId: member.id, changes });
|
||||||
|
setIsEditingUser(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('An error occurred while editing user:', error);
|
||||||
|
setEditedUserError(error);
|
||||||
|
} finally {
|
||||||
|
setIsSavingUser(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setIsEditingUser(false);
|
||||||
|
setEditedUserError(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -217,21 +282,23 @@ const ManageStudentRow = ({
|
|||||||
<Grid item xs={4} style={isMobile ? styles.mobileCell : styles.cell}>
|
<Grid item xs={4} style={isMobile ? styles.mobileCell : styles.cell}>
|
||||||
{passwordCell}
|
{passwordCell}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{editDialog}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid item xs={5} style={isMobile ? styles.mobileCell : styles.cell}>
|
<Grid item xs={9} style={isMobile ? styles.mobileCell : styles.cell}>
|
||||||
<LineStackLayout noMargin alignItems="center">
|
<LineStackLayout noMargin alignItems="center">
|
||||||
{usernameCell}
|
{usernameCell}
|
||||||
{emailCell}
|
{emailCell}
|
||||||
</LineStackLayout>
|
</LineStackLayout>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={7} style={isMobile ? styles.mobileCell : styles.cell}>
|
<Grid item xs={3} style={isMobile ? styles.mobileCell : styles.cell}>
|
||||||
{passwordCell}
|
{passwordCell}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{editDialog}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -56,6 +56,7 @@ import { selectMessageByLocale } from '../../../../../Utils/i18n/MessageByLocale
|
|||||||
import TextButton from '../../../../../UI/TextButton';
|
import TextButton from '../../../../../UI/TextButton';
|
||||||
import Chip from '../../../../../UI/Chip';
|
import Chip from '../../../../../UI/Chip';
|
||||||
import { SubscriptionSuggestionContext } from '../../../../../Profile/Subscription/SubscriptionSuggestionContext';
|
import { SubscriptionSuggestionContext } from '../../../../../Profile/Subscription/SubscriptionSuggestionContext';
|
||||||
|
import { type EditUserChanges } from '../../../../../Utils/GDevelopServices/User';
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
selectedMembersControlsContainer: {
|
selectedMembersControlsContainer: {
|
||||||
@@ -324,6 +325,7 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
onActivateMembers,
|
onActivateMembers,
|
||||||
onSetAdmin,
|
onSetAdmin,
|
||||||
onRefreshAdmins,
|
onRefreshAdmins,
|
||||||
|
onEditUser,
|
||||||
} = React.useContext(TeamContext);
|
} = React.useContext(TeamContext);
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
@@ -337,6 +339,20 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
[credentialsCopySuccess]
|
[credentialsCopySuccess]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onEditTeamMember = React.useCallback(
|
||||||
|
async ({
|
||||||
|
editedUserId,
|
||||||
|
changes,
|
||||||
|
}: {|
|
||||||
|
editedUserId: string,
|
||||||
|
changes: EditUserChanges,
|
||||||
|
|}) => {
|
||||||
|
await onEditUser(editedUserId, changes);
|
||||||
|
await onRefreshMembers();
|
||||||
|
},
|
||||||
|
[onEditUser, onRefreshMembers]
|
||||||
|
);
|
||||||
|
|
||||||
const onChangeTeamMemberPassword = React.useCallback(
|
const onChangeTeamMemberPassword = React.useCallback(
|
||||||
async ({
|
async ({
|
||||||
userId,
|
userId,
|
||||||
@@ -354,7 +370,7 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
const onCopyActiveCredentials = React.useCallback(
|
const onCopyActiveCredentials = React.useCallback(
|
||||||
() => {
|
() => {
|
||||||
if (!members) return;
|
if (!members) return;
|
||||||
let content = 'Username,Email,Password';
|
let content = 'Username,Full Name,Email,Password';
|
||||||
let membersToConsider = [];
|
let membersToConsider = [];
|
||||||
if (selectedUserIds.length === 0) {
|
if (selectedUserIds.length === 0) {
|
||||||
membersToConsider = members.filter(member => !member.deactivatedAt);
|
membersToConsider = members.filter(member => !member.deactivatedAt);
|
||||||
@@ -364,7 +380,7 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
membersToConsider.forEach(member => {
|
membersToConsider.forEach(member => {
|
||||||
content += `\n${member.username || ''},${
|
content += `\n${member.username || ''},${member.fullName || ''},${
|
||||||
member.email
|
member.email
|
||||||
},${member.password || ''}`;
|
},${member.password || ''}`;
|
||||||
});
|
});
|
||||||
@@ -673,12 +689,13 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
<UserLine
|
<UserLine
|
||||||
key={adminUser.id}
|
key={adminUser.id}
|
||||||
username={adminUser.username}
|
username={adminUser.username}
|
||||||
|
fullName={adminUser.fullName}
|
||||||
email={adminUser.email}
|
email={adminUser.email}
|
||||||
level={null}
|
level={null}
|
||||||
onDelete={() => onRemoveAdmin(adminUser.email)}
|
onDelete={() => onRemoveAdmin(adminUser.email)}
|
||||||
disabled={
|
disabled={
|
||||||
(profile && adminUser.id === profile.id) ||
|
(profile && adminUser.id === profile.id) ||
|
||||||
(adminEmailBeingRemoved &&
|
(!!adminEmailBeingRemoved &&
|
||||||
adminEmailBeingRemoved === adminUser.email)
|
adminEmailBeingRemoved === adminUser.email)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -822,10 +839,10 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
size="small"
|
size="small"
|
||||||
tooltip={
|
tooltip={
|
||||||
selectedUserIds.length === 0
|
selectedUserIds.length === 0
|
||||||
? t`Copy active credentials`
|
? t`Copy active credentials to CSV`
|
||||||
: t`Copy ${
|
: t`Copy ${
|
||||||
selectedUserIds.length
|
selectedUserIds.length
|
||||||
} credentials`
|
} credentials to CSV`
|
||||||
}
|
}
|
||||||
disabled={
|
disabled={
|
||||||
hasNoActiveTeamMembers &&
|
hasNoActiveTeamMembers &&
|
||||||
@@ -847,12 +864,12 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
<Column>
|
<Column>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<GridList cols={2} cellHeight={'auto'}>
|
<GridList cols={2} cellHeight={'auto'}>
|
||||||
<Grid item xs={5}>
|
<Grid item xs={9}>
|
||||||
<Text style={{ opacity: 0.7 }}>
|
<Text style={{ opacity: 0.7 }}>
|
||||||
<Trans>Student</Trans>
|
<Trans>Student</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={7}>
|
<Grid item xs={3}>
|
||||||
<Text style={{ opacity: 0.7 }}>
|
<Text style={{ opacity: 0.7 }}>
|
||||||
<Trans>Password</Trans>
|
<Trans>Password</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -885,6 +902,7 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
onChangePassword={
|
onChangePassword={
|
||||||
onChangeTeamMemberPassword
|
onChangeTeamMemberPassword
|
||||||
}
|
}
|
||||||
|
onEdit={onEditTeamMember}
|
||||||
isSelected={selectedUserIds.includes(
|
isSelected={selectedUserIds.includes(
|
||||||
member.id
|
member.id
|
||||||
)}
|
)}
|
||||||
@@ -966,6 +984,7 @@ const ManageEducationAccountDialog = ({ onClose }: Props) => {
|
|||||||
onChangePassword={
|
onChangePassword={
|
||||||
onChangeTeamMemberPassword
|
onChangeTeamMemberPassword
|
||||||
}
|
}
|
||||||
|
onEdit={onEditTeamMember}
|
||||||
isSelected={selectedUserIds.includes(
|
isSelected={selectedUserIds.includes(
|
||||||
member.id
|
member.id
|
||||||
)}
|
)}
|
||||||
|
@@ -117,9 +117,19 @@ const TeamMemberRow = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LineStackLayout noMargin alignItems="center">
|
<LineStackLayout noMargin alignItems="center">
|
||||||
{member.username && (
|
{(member.username || member.fullName) && (
|
||||||
<Text allowSelection noMargin>
|
<Text allowSelection noMargin>
|
||||||
{member.username}
|
{member.fullName ? (
|
||||||
|
member.username ? (
|
||||||
|
<Trans>
|
||||||
|
{member.fullName} ({member.username})
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
member.fullName
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
member.username
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Text allowSelection noMargin color="secondary">
|
<Text allowSelection noMargin color="secondary">
|
||||||
|
@@ -4,7 +4,6 @@ import {
|
|||||||
type Profile,
|
type Profile,
|
||||||
type LoginForm,
|
type LoginForm,
|
||||||
type RegisterForm,
|
type RegisterForm,
|
||||||
type PatchUserPayload,
|
|
||||||
type ForgotPasswordForm,
|
type ForgotPasswordForm,
|
||||||
type AuthError,
|
type AuthError,
|
||||||
type IdentityProvider,
|
type IdentityProvider,
|
||||||
@@ -13,7 +12,10 @@ import { type PreferencesValues } from '../MainFrame/Preferences/PreferencesCont
|
|||||||
import { type CloudProjectWithUserAccessInfo } from '../Utils/GDevelopServices/Project';
|
import { type CloudProjectWithUserAccessInfo } from '../Utils/GDevelopServices/Project';
|
||||||
import { User as FirebaseUser } from 'firebase/auth';
|
import { User as FirebaseUser } from 'firebase/auth';
|
||||||
import { type Badge, type Achievement } from '../Utils/GDevelopServices/Badge';
|
import { type Badge, type Achievement } from '../Utils/GDevelopServices/Badge';
|
||||||
import { type Recommendation } from '../Utils/GDevelopServices/User';
|
import {
|
||||||
|
type Recommendation,
|
||||||
|
type EditUserChanges,
|
||||||
|
} from '../Utils/GDevelopServices/User';
|
||||||
import { type Notification } from '../Utils/GDevelopServices/Notification';
|
import { type Notification } from '../Utils/GDevelopServices/Notification';
|
||||||
import {
|
import {
|
||||||
type Limits,
|
type Limits,
|
||||||
@@ -62,7 +64,7 @@ export type AuthenticatedUser = {|
|
|||||||
preferences: PreferencesValues
|
preferences: PreferencesValues
|
||||||
) => Promise<void>,
|
) => Promise<void>,
|
||||||
onEditProfile: (
|
onEditProfile: (
|
||||||
payload: PatchUserPayload,
|
changes: EditUserChanges,
|
||||||
preferences: PreferencesValues
|
preferences: PreferencesValues
|
||||||
) => Promise<void>,
|
) => Promise<void>,
|
||||||
onResetPassword: ForgotPasswordForm => Promise<void>,
|
onResetPassword: ForgotPasswordForm => Promise<void>,
|
||||||
|
@@ -8,6 +8,8 @@ import {
|
|||||||
getUserEarningsBalance,
|
getUserEarningsBalance,
|
||||||
} from '../Utils/GDevelopServices/Usage';
|
} from '../Utils/GDevelopServices/Usage';
|
||||||
import {
|
import {
|
||||||
|
editUser,
|
||||||
|
type EditUserChanges,
|
||||||
getUserBadges,
|
getUserBadges,
|
||||||
listDefaultRecommendations,
|
listDefaultRecommendations,
|
||||||
listRecommendations,
|
listRecommendations,
|
||||||
@@ -17,7 +19,6 @@ import { getAchievements } from '../Utils/GDevelopServices/Badge';
|
|||||||
import Authentication, {
|
import Authentication, {
|
||||||
type LoginForm,
|
type LoginForm,
|
||||||
type RegisterForm,
|
type RegisterForm,
|
||||||
type PatchUserPayload,
|
|
||||||
type ChangeEmailForm,
|
type ChangeEmailForm,
|
||||||
type AuthError,
|
type AuthError,
|
||||||
type ForgotPasswordForm,
|
type ForgotPasswordForm,
|
||||||
@@ -622,10 +623,11 @@ export default class AuthenticatedUserProvider extends React.Component<
|
|||||||
if (!userProfile.isCreator) {
|
if (!userProfile.isCreator) {
|
||||||
// If the user is not a creator, then update the profile to say they now are.
|
// If the user is not a creator, then update the profile to say they now are.
|
||||||
try {
|
try {
|
||||||
await authentication.editUserProfile(
|
await editUser(authentication.getAuthorizationHeader, {
|
||||||
authentication.getAuthorizationHeader,
|
editedUserId: userProfile.id,
|
||||||
{ isCreator: true }
|
userId: userProfile.id,
|
||||||
);
|
changes: { isCreator: true },
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Catch the error so that the user profile is still fetched.
|
// Catch the error so that the user profile is still fetched.
|
||||||
console.error('Error while updating the user profile:', error);
|
console.error('Error while updating the user profile:', error);
|
||||||
@@ -1206,11 +1208,13 @@ export default class AuthenticatedUserProvider extends React.Component<
|
|||||||
};
|
};
|
||||||
|
|
||||||
_doEdit = async (
|
_doEdit = async (
|
||||||
payload: PatchUserPayload,
|
payload: EditUserChanges,
|
||||||
preferences: PreferencesValues
|
preferences: PreferencesValues
|
||||||
) => {
|
) => {
|
||||||
const { authentication } = this.props;
|
const { authentication } = this.props;
|
||||||
if (!authentication) return;
|
if (!authentication) return;
|
||||||
|
const { profile } = this.state.authenticatedUser;
|
||||||
|
if (!profile) return;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
editInProgress: true,
|
editInProgress: true,
|
||||||
@@ -1218,9 +1222,10 @@ export default class AuthenticatedUserProvider extends React.Component<
|
|||||||
});
|
});
|
||||||
this._automaticallyUpdateUserProfile = false;
|
this._automaticallyUpdateUserProfile = false;
|
||||||
try {
|
try {
|
||||||
await authentication.editUserProfile(
|
await editUser(authentication.getAuthorizationHeader, {
|
||||||
authentication.getAuthorizationHeader,
|
editedUserId: profile.id,
|
||||||
{
|
userId: profile.id,
|
||||||
|
changes: {
|
||||||
username: payload.username,
|
username: payload.username,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
getGameStatsEmail: payload.getGameStatsEmail,
|
getGameStatsEmail: payload.getGameStatsEmail,
|
||||||
@@ -1231,8 +1236,8 @@ export default class AuthenticatedUserProvider extends React.Component<
|
|||||||
githubUsername: payload.githubUsername,
|
githubUsername: payload.githubUsername,
|
||||||
communityLinks: payload.communityLinks,
|
communityLinks: payload.communityLinks,
|
||||||
survey: payload.survey,
|
survey: payload.survey,
|
||||||
}
|
},
|
||||||
);
|
});
|
||||||
await this._fetchUserProfileWithoutThrowingErrors();
|
await this._fetchUserProfileWithoutThrowingErrors();
|
||||||
} catch (apiCallError) {
|
} catch (apiCallError) {
|
||||||
this.setState({ apiCallError });
|
this.setState({ apiCallError });
|
||||||
@@ -1625,9 +1630,9 @@ export default class AuthenticatedUserProvider extends React.Component<
|
|||||||
badges={this.state.authenticatedUser.badges}
|
badges={this.state.authenticatedUser.badges}
|
||||||
subscription={this.state.authenticatedUser.subscription}
|
subscription={this.state.authenticatedUser.subscription}
|
||||||
onClose={() => this.openEditProfileDialog(false)}
|
onClose={() => this.openEditProfileDialog(false)}
|
||||||
onEdit={async form => {
|
onEdit={async changes => {
|
||||||
try {
|
try {
|
||||||
await this._doEdit(form, this.props.preferencesValues);
|
await this._doEdit(changes, this.props.preferencesValues);
|
||||||
this.openEditProfileDialog(false);
|
this.openEditProfileDialog(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore errors, we will let the user retry in their profile.
|
// Ignore errors, we will let the user retry in their profile.
|
||||||
|
@@ -207,7 +207,6 @@ const CreateAccountDialog = ({
|
|||||||
onChangeOptInNewsletterEmail={setGetNewsletterEmail}
|
onChangeOptInNewsletterEmail={setGetNewsletterEmail}
|
||||||
createAccountInProgress={createAccountInProgress}
|
createAccountInProgress={createAccountInProgress}
|
||||||
error={error}
|
error={error}
|
||||||
usernameAvailability={usernameAvailability}
|
|
||||||
onChangeUsernameAvailability={setUsernameAvailability}
|
onChangeUsernameAvailability={setUsernameAvailability}
|
||||||
isValidatingUsername={isValidatingUsername}
|
isValidatingUsername={isValidatingUsername}
|
||||||
onChangeIsValidatingUsername={setIsValidatingUsername}
|
onChangeIsValidatingUsername={setIsValidatingUsername}
|
||||||
|
@@ -60,7 +60,6 @@ type Props = {|
|
|||||||
onChangeUsername: string => void,
|
onChangeUsername: string => void,
|
||||||
optInNewsletterEmail: boolean,
|
optInNewsletterEmail: boolean,
|
||||||
onChangeOptInNewsletterEmail: boolean => void,
|
onChangeOptInNewsletterEmail: boolean => void,
|
||||||
usernameAvailability: ?UsernameAvailability,
|
|
||||||
onChangeUsernameAvailability: (?UsernameAvailability) => void,
|
onChangeUsernameAvailability: (?UsernameAvailability) => void,
|
||||||
isValidatingUsername: boolean,
|
isValidatingUsername: boolean,
|
||||||
onChangeIsValidatingUsername: boolean => void,
|
onChangeIsValidatingUsername: boolean => void,
|
||||||
@@ -80,7 +79,6 @@ const CreateAccountForm = ({
|
|||||||
onChangeUsername,
|
onChangeUsername,
|
||||||
optInNewsletterEmail,
|
optInNewsletterEmail,
|
||||||
onChangeOptInNewsletterEmail,
|
onChangeOptInNewsletterEmail,
|
||||||
usernameAvailability,
|
|
||||||
onChangeUsernameAvailability,
|
onChangeUsernameAvailability,
|
||||||
isValidatingUsername,
|
isValidatingUsername,
|
||||||
onChangeIsValidatingUsername,
|
onChangeIsValidatingUsername,
|
||||||
|
@@ -7,7 +7,6 @@ import { type MessageDescriptor } from '../Utils/i18n/MessageDescriptor.flow';
|
|||||||
import FlatButton from '../UI/FlatButton';
|
import FlatButton from '../UI/FlatButton';
|
||||||
import Dialog, { DialogPrimaryButton } from '../UI/Dialog';
|
import Dialog, { DialogPrimaryButton } from '../UI/Dialog';
|
||||||
import {
|
import {
|
||||||
type EditForm,
|
|
||||||
type AuthError,
|
type AuthError,
|
||||||
type Profile,
|
type Profile,
|
||||||
type UpdateGitHubStarResponse,
|
type UpdateGitHubStarResponse,
|
||||||
@@ -16,6 +15,7 @@ import {
|
|||||||
type UpdateYoutubeSubscriptionResponse,
|
type UpdateYoutubeSubscriptionResponse,
|
||||||
} from '../Utils/GDevelopServices/Authentication';
|
} from '../Utils/GDevelopServices/Authentication';
|
||||||
import {
|
import {
|
||||||
|
type EditUserChanges,
|
||||||
communityLinksConfig,
|
communityLinksConfig,
|
||||||
donateLinkConfig,
|
donateLinkConfig,
|
||||||
discordUsernameConfig,
|
discordUsernameConfig,
|
||||||
@@ -59,7 +59,7 @@ export type EditProfileDialogProps = {|
|
|||||||
badges: ?Array<Badge>,
|
badges: ?Array<Badge>,
|
||||||
subscription: ?Subscription,
|
subscription: ?Subscription,
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
onEdit: (form: EditForm) => Promise<void>,
|
onEdit: (form: EditUserChanges) => Promise<void>,
|
||||||
onUpdateGitHubStar: (
|
onUpdateGitHubStar: (
|
||||||
githubUsername: string
|
githubUsername: string
|
||||||
) => Promise<UpdateGitHubStarResponse>,
|
) => Promise<UpdateGitHubStarResponse>,
|
||||||
|
@@ -5,6 +5,7 @@ import {
|
|||||||
type TeamGroup,
|
type TeamGroup,
|
||||||
type User,
|
type User,
|
||||||
type TeamMembership,
|
type TeamMembership,
|
||||||
|
type EditUserChanges,
|
||||||
} from '../../Utils/GDevelopServices/User';
|
} from '../../Utils/GDevelopServices/User';
|
||||||
import { type CloudProjectWithUserAccessInfo } from '../../Utils/GDevelopServices/Project';
|
import { type CloudProjectWithUserAccessInfo } from '../../Utils/GDevelopServices/Project';
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ export type TeamState = {|
|
|||||||
userId: string,
|
userId: string,
|
||||||
newPassword: string
|
newPassword: string
|
||||||
) => Promise<void>,
|
) => Promise<void>,
|
||||||
|
onEditUser: (editedUserId: string, changes: EditUserChanges) => Promise<void>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export const initialTeamState = {
|
export const initialTeamState = {
|
||||||
@@ -51,6 +53,7 @@ export const initialTeamState = {
|
|||||||
onRefreshAdmins: async () => {},
|
onRefreshAdmins: async () => {},
|
||||||
onSetAdmin: async () => {},
|
onSetAdmin: async () => {},
|
||||||
onChangeMemberPassword: async () => {},
|
onChangeMemberPassword: async () => {},
|
||||||
|
onEditUser: async () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const TeamContext = React.createContext<TeamState>(initialTeamState);
|
const TeamContext = React.createContext<TeamState>(initialTeamState);
|
||||||
|
@@ -20,6 +20,8 @@ import {
|
|||||||
changeTeamMemberPassword,
|
changeTeamMemberPassword,
|
||||||
activateTeamMembers,
|
activateTeamMembers,
|
||||||
setUserAsAdmin,
|
setUserAsAdmin,
|
||||||
|
editUser,
|
||||||
|
type EditUserChanges,
|
||||||
} from '../../Utils/GDevelopServices/User';
|
} from '../../Utils/GDevelopServices/User';
|
||||||
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
|
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
|
||||||
import { listOtherUserCloudProjects } from '../../Utils/GDevelopServices/Project';
|
import { listOtherUserCloudProjects } from '../../Utils/GDevelopServices/Project';
|
||||||
@@ -150,6 +152,18 @@ const TeamProvider = ({ children }: Props) => {
|
|||||||
[team, getAuthorizationHeader, adminUserId]
|
[team, getAuthorizationHeader, adminUserId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onEditUser = React.useCallback(
|
||||||
|
async (editedUserId: string, changes: EditUserChanges) => {
|
||||||
|
if (!adminUserId) return;
|
||||||
|
await editUser(getAuthorizationHeader, {
|
||||||
|
userId: adminUserId,
|
||||||
|
editedUserId,
|
||||||
|
changes,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[getAuthorizationHeader, adminUserId]
|
||||||
|
);
|
||||||
|
|
||||||
const onChangeMemberPassword = React.useCallback(
|
const onChangeMemberPassword = React.useCallback(
|
||||||
async (userId: string, newPassword: string) => {
|
async (userId: string, newPassword: string) => {
|
||||||
if (!adminUserId) return;
|
if (!adminUserId) return;
|
||||||
@@ -357,6 +371,7 @@ const TeamProvider = ({ children }: Props) => {
|
|||||||
admins,
|
admins,
|
||||||
members,
|
members,
|
||||||
memberships,
|
memberships,
|
||||||
|
onEditUser,
|
||||||
onChangeGroupName,
|
onChangeGroupName,
|
||||||
onChangeUserGroup,
|
onChangeUserGroup,
|
||||||
onListUserProjects,
|
onListUserProjects,
|
||||||
|
@@ -226,11 +226,7 @@ type DialogProps = {|
|
|||||||
|
|
||||||
export const DialogPrimaryButton = RaisedButton;
|
export const DialogPrimaryButton = RaisedButton;
|
||||||
|
|
||||||
/**
|
const DialogWithoutWindowSizeProvider = ({
|
||||||
* A enhanced material-ui Dialog that can have optional secondary actions
|
|
||||||
* and no margins if required.
|
|
||||||
*/
|
|
||||||
const Dialog = ({
|
|
||||||
onApply,
|
onApply,
|
||||||
secondaryActions,
|
secondaryActions,
|
||||||
dangerLevel,
|
dangerLevel,
|
||||||
@@ -481,12 +477,16 @@ const Dialog = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DialogWithTopLevelWindowSize = (props: DialogProps) => {
|
/**
|
||||||
|
* A enhanced material-ui Dialog that can have optional secondary actions
|
||||||
|
* and no margins if required.
|
||||||
|
*/
|
||||||
|
const Dialog = (props: DialogProps) => {
|
||||||
return (
|
return (
|
||||||
<TopLevelWindowSizeProvider>
|
<TopLevelWindowSizeProvider>
|
||||||
<Dialog {...props} />
|
<DialogWithoutWindowSizeProvider {...props} />
|
||||||
</TopLevelWindowSizeProvider>
|
</TopLevelWindowSizeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DialogWithTopLevelWindowSize;
|
export default Dialog;
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
import { I18n } from '@lingui/react';
|
import { I18n } from '@lingui/react';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
import { getGravatarUrl } from '../../UI/GravatarUrl';
|
import { getGravatarUrl } from '../../UI/GravatarUrl';
|
||||||
@@ -6,18 +9,21 @@ import Text from '../Text';
|
|||||||
import IconButton from '../IconButton';
|
import IconButton from '../IconButton';
|
||||||
import Trash from '../CustomSvgIcons/Trash';
|
import Trash from '../CustomSvgIcons/Trash';
|
||||||
import { getTranslatableLevel } from '../../Utils/AclUtils';
|
import { getTranslatableLevel } from '../../Utils/AclUtils';
|
||||||
|
import { type Level } from '../../Utils/GDevelopServices/Project';
|
||||||
|
|
||||||
const UserLine = ({
|
const UserLine = ({
|
||||||
username,
|
username,
|
||||||
|
fullName,
|
||||||
email,
|
email,
|
||||||
level,
|
level,
|
||||||
onDelete,
|
onDelete,
|
||||||
disabled,
|
disabled,
|
||||||
}: {|
|
}: {|
|
||||||
username: ?string,
|
username: ?string,
|
||||||
|
fullName: ?string,
|
||||||
email: string,
|
email: string,
|
||||||
level: ?Level,
|
level: ?Level,
|
||||||
onDelete?: () => void,
|
onDelete?: () => Promise<void> | void,
|
||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
|}) => (
|
|}) => (
|
||||||
<I18n>
|
<I18n>
|
||||||
@@ -26,7 +32,21 @@ const UserLine = ({
|
|||||||
<Line noMargin expand>
|
<Line noMargin expand>
|
||||||
<Avatar src={getGravatarUrl(email, { size: 40 })} />
|
<Avatar src={getGravatarUrl(email, { size: 40 })} />
|
||||||
<Column expand justifyContent="flex-end">
|
<Column expand justifyContent="flex-end">
|
||||||
{username && <Text noMargin>{username}</Text>}
|
{(username || fullName) && (
|
||||||
|
<Text noMargin>
|
||||||
|
{fullName ? (
|
||||||
|
username ? (
|
||||||
|
<Trans>
|
||||||
|
{fullName} ({username})
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
fullName
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
username
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
<Text noMargin color="secondary">
|
<Text noMargin color="secondary">
|
||||||
{email}
|
{email}
|
||||||
</Text>
|
</Text>
|
||||||
|
@@ -35,6 +35,7 @@ export type Profile = {|
|
|||||||
donateLink: ?string,
|
donateLink: ?string,
|
||||||
discordUsername: ?string,
|
discordUsername: ?string,
|
||||||
githubUsername?: ?string,
|
githubUsername?: ?string,
|
||||||
|
fullName?: ?string,
|
||||||
communityLinks?: CommunityLinks,
|
communityLinks?: CommunityLinks,
|
||||||
survey?: UserSurvey,
|
survey?: UserSurvey,
|
||||||
|
|
||||||
@@ -62,31 +63,6 @@ export type RegisterForm = {|
|
|||||||
getNewsletterEmail: boolean,
|
getNewsletterEmail: boolean,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export type EditForm = {|
|
|
||||||
+username: string,
|
|
||||||
+description: string,
|
|
||||||
+getGameStatsEmail: boolean,
|
|
||||||
+getNewsletterEmail: boolean,
|
|
||||||
+donateLink: string,
|
|
||||||
+discordUsername: string,
|
|
||||||
+githubUsername: string,
|
|
||||||
+communityLinks: CommunityLinks,
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type PatchUserPayload = {
|
|
||||||
+username?: string,
|
|
||||||
+description?: string,
|
|
||||||
+getGameStatsEmail?: boolean,
|
|
||||||
+getNewsletterEmail?: boolean,
|
|
||||||
+appLanguage?: string,
|
|
||||||
+isCreator?: boolean,
|
|
||||||
+donateLink?: string,
|
|
||||||
+discordUsername?: string,
|
|
||||||
+githubUsername?: string,
|
|
||||||
+communityLinks?: CommunityLinks,
|
|
||||||
+survey?: UserSurvey,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChangeEmailForm = {|
|
export type ChangeEmailForm = {|
|
||||||
email: string,
|
email: string,
|
||||||
|};
|
|};
|
||||||
@@ -388,60 +364,6 @@ export default class Authentication {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
editUserProfile = async (
|
|
||||||
getAuthorizationHeader: () => Promise<string>,
|
|
||||||
{
|
|
||||||
username,
|
|
||||||
description,
|
|
||||||
getGameStatsEmail,
|
|
||||||
getNewsletterEmail,
|
|
||||||
appLanguage,
|
|
||||||
isCreator,
|
|
||||||
donateLink,
|
|
||||||
discordUsername,
|
|
||||||
githubUsername,
|
|
||||||
communityLinks,
|
|
||||||
survey,
|
|
||||||
}: PatchUserPayload
|
|
||||||
) => {
|
|
||||||
const { currentUser } = this.auth;
|
|
||||||
if (!currentUser)
|
|
||||||
throw new Error('Tried to edit user profile while not authenticated.');
|
|
||||||
|
|
||||||
return getAuthorizationHeader()
|
|
||||||
.then(authorizationHeader => {
|
|
||||||
return axios.patch(
|
|
||||||
`${GDevelopUserApi.baseUrl}/user/${currentUser.uid}`,
|
|
||||||
{
|
|
||||||
username,
|
|
||||||
description,
|
|
||||||
getGameStatsEmail,
|
|
||||||
getNewsletterEmail,
|
|
||||||
appLanguage,
|
|
||||||
isCreator,
|
|
||||||
donateLink,
|
|
||||||
discordUsername,
|
|
||||||
githubUsername,
|
|
||||||
communityLinks,
|
|
||||||
survey,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params: {
|
|
||||||
userId: currentUser.uid,
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Authorization: authorizationHeader,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(response => response.data)
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error while editing user:', error);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
updateGitHubStar = async (
|
updateGitHubStar = async (
|
||||||
getAuthorizationHeader: () => Promise<string>
|
getAuthorizationHeader: () => Promise<string>
|
||||||
): Promise<UpdateGitHubStarResponse> => {
|
): Promise<UpdateGitHubStarResponse> => {
|
||||||
|
@@ -142,6 +142,21 @@ export type UserPublicProfile = {|
|
|||||||
iconUrl: string,
|
iconUrl: string,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
|
export type EditUserChanges = {|
|
||||||
|
username?: string,
|
||||||
|
fullName?: string,
|
||||||
|
description?: string,
|
||||||
|
getGameStatsEmail?: boolean,
|
||||||
|
getNewsletterEmail?: boolean,
|
||||||
|
appLanguage?: string,
|
||||||
|
isCreator?: boolean,
|
||||||
|
donateLink?: string,
|
||||||
|
discordUsername?: string,
|
||||||
|
githubUsername?: string,
|
||||||
|
communityLinks?: CommunityLinks,
|
||||||
|
survey?: UserSurvey,
|
||||||
|
|};
|
||||||
|
|
||||||
export type UserPublicProfileByIds = {|
|
export type UserPublicProfileByIds = {|
|
||||||
[key: string]: UserPublicProfile,
|
[key: string]: UserPublicProfile,
|
||||||
|};
|
|};
|
||||||
@@ -801,3 +816,24 @@ export const generateCustomAuthToken = async (
|
|||||||
);
|
);
|
||||||
return response.data.customAuthToken;
|
return response.data.customAuthToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editUser = async (
|
||||||
|
getAuthorizationHeader: () => Promise<string>,
|
||||||
|
{
|
||||||
|
editedUserId,
|
||||||
|
userId,
|
||||||
|
changes,
|
||||||
|
}: {|
|
||||||
|
editedUserId: string,
|
||||||
|
userId: string,
|
||||||
|
changes: EditUserChanges,
|
||||||
|
|}
|
||||||
|
): Promise<User> => {
|
||||||
|
const authorizationHeader = await getAuthorizationHeader();
|
||||||
|
const response = await client.patch(`/user/${editedUserId}`, changes, {
|
||||||
|
headers: { Authorization: authorizationHeader },
|
||||||
|
params: { userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
type TeamGroup,
|
type TeamGroup,
|
||||||
type TeamMembership,
|
type TeamMembership,
|
||||||
type User,
|
type User,
|
||||||
|
type EditUserChanges,
|
||||||
} from '../../../Utils/GDevelopServices/User';
|
} from '../../../Utils/GDevelopServices/User';
|
||||||
import { type CloudProjectWithUserAccessInfo } from '../../../Utils/GDevelopServices/Project';
|
import { type CloudProjectWithUserAccessInfo } from '../../../Utils/GDevelopServices/Project';
|
||||||
import { testProject } from '../../GDevelopJsInitializerDecorator';
|
import { testProject } from '../../GDevelopJsInitializerDecorator';
|
||||||
@@ -57,6 +58,7 @@ const initialMembers: Array<User> = [
|
|||||||
email: 'user1@hotmail.com',
|
email: 'user1@hotmail.com',
|
||||||
username: null,
|
username: null,
|
||||||
password: 'blue-chair-34',
|
password: 'blue-chair-34',
|
||||||
|
fullName: 'John Doe',
|
||||||
},
|
},
|
||||||
// $FlowIgnore - the whole user object is not needed for this component
|
// $FlowIgnore - the whole user object is not needed for this component
|
||||||
{
|
{
|
||||||
@@ -94,6 +96,7 @@ const initialMembers: Array<User> = [
|
|||||||
id: 'user7',
|
id: 'user7',
|
||||||
email: 'user7@mail.ru',
|
email: 'user7@mail.ru',
|
||||||
username: 'Bayonetta',
|
username: 'Bayonetta',
|
||||||
|
fullName: 'Jane Smith',
|
||||||
},
|
},
|
||||||
// $FlowIgnore - the whole user object is not needed for this component
|
// $FlowIgnore - the whole user object is not needed for this component
|
||||||
{
|
{
|
||||||
@@ -435,6 +438,18 @@ const MockTeamProvider = ({
|
|||||||
setMembers(newMembers);
|
setMembers(newMembers);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editUser = async (userId: string, changes: EditUserChanges) => {
|
||||||
|
if (!members) return;
|
||||||
|
const newMembers = [...members];
|
||||||
|
const memberIndex = newMembers.findIndex(member => member.id === userId);
|
||||||
|
if (memberIndex === -1) return;
|
||||||
|
newMembers.splice(memberIndex, 1, {
|
||||||
|
...newMembers[memberIndex],
|
||||||
|
...changes,
|
||||||
|
});
|
||||||
|
setMembers(newMembers);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertProvider>
|
<AlertProvider>
|
||||||
<DragAndDropContextProvider>
|
<DragAndDropContextProvider>
|
||||||
@@ -460,6 +475,7 @@ const MockTeamProvider = ({
|
|||||||
onActivateMembers: action('activateMembers'),
|
onActivateMembers: action('activateMembers'),
|
||||||
onChangeMemberPassword: changeMemberPassword,
|
onChangeMemberPassword: changeMemberPassword,
|
||||||
onSetAdmin: setAdmin,
|
onSetAdmin: setAdmin,
|
||||||
|
onEditUser: editUser,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text allowSelection>
|
<Text allowSelection>
|
||||||
@@ -474,19 +490,21 @@ const MockTeamProvider = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = () => (
|
export const Default = () => {
|
||||||
<MockTeamProvider loading={false} teamSize={12}>
|
return (
|
||||||
<FixedHeightFlexContainer height={600}>
|
<MockTeamProvider loading={false} teamSize={12}>
|
||||||
<TeamSection
|
<FixedHeightFlexContainer height={600}>
|
||||||
project={testProject.project}
|
<TeamSection
|
||||||
currentFileMetadata={null}
|
project={testProject.project}
|
||||||
onOpenRecentFile={action('onOpenRecentFile')}
|
currentFileMetadata={null}
|
||||||
storageProviders={[CloudStorageProvider]}
|
onOpenRecentFile={action('onOpenRecentFile')}
|
||||||
onOpenTeachingResources={action('onOpenTeachingResources')}
|
storageProviders={[CloudStorageProvider]}
|
||||||
/>
|
onOpenTeachingResources={action('onOpenTeachingResources')}
|
||||||
</FixedHeightFlexContainer>
|
/>
|
||||||
</MockTeamProvider>
|
</FixedHeightFlexContainer>
|
||||||
);
|
</MockTeamProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const WithNoGroupsYet = () => (
|
export const WithNoGroupsYet = () => (
|
||||||
<MockTeamProvider loading={false} noGroups>
|
<MockTeamProvider loading={false} noGroups>
|
||||||
|
Reference in New Issue
Block a user