Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// @flow

import * as React from 'react';
import { I18n } from '@lingui/react';
import { t, Trans } from '@lingui/macro';
import Dialog from '../../../../UI/Dialog';
import FlatButton from '../../../../UI/FlatButton';
import { CompactToggleField } from '../../../../UI/CompactToggleField';
import { Line } from '../../../../UI/Grid';
import TeamContext from '../../../../Profile/Team/TeamContext';

type Props = {|
onClose: () => void,
|};

const AdvancedStudentOptionsDialog = ({ onClose }: Props): React.Node => {
const { team, onUpdateTeam } = React.useContext(TeamContext);
const [isUpdatingTeam, setIsUpdatingTeam] = React.useState<boolean>(false);

const onToggleStudentsAskAi = React.useCallback(
async (allowed: boolean) => {
setIsUpdatingTeam(true);
try {
await onUpdateTeam({ classrooms: { hideAskAi: !allowed } });
} catch (error) {
console.error(
'An error occurred while updating the team AI setting:',
error
);
} finally {
setIsUpdatingTeam(false);
}
},
[onUpdateTeam]
);

return (
<I18n>
{({ i18n }) => (
<Dialog
title={<Trans>Advanced student options</Trans>}
actions={[
<FlatButton
label={<Trans>Close</Trans>}
disabled={isUpdatingTeam}
key="close"
primary={false}
onClick={onClose}
/>,
]}
maxWidth="sm"
cannotBeDismissed={isUpdatingTeam}
onRequestClose={onClose}
open
>
<Line noMargin>
<CompactToggleField
label={i18n._(
t`Allow students to use GDevelop's AI (disabled by default)`
)}
checked={
!!team &&
!!team.classrooms &&
team.classrooms.hideAskAi === false
}
onCheck={allowed => {
onToggleStudentsAskAi(allowed);
}}
disabled={isUpdatingTeam}
/>
</Line>
</Dialog>
)}
</I18n>
);
};

export default AdvancedStudentOptionsDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ import { EducationCard } from '../LearnSection/EducationCard';
import UserSVG from '../../../../UI/CustomSvgIcons/User';
import { copyTextToClipboard } from '../../../../Utils/Clipboard';
import ManageEducationAccountDialog from './ManageEducationAccountDialog';
import AdvancedStudentOptionsDialog from './AdvancedStudentOptionsDialog';
import TeamAvailableSeats from './TeamAvailableSeats';
import IconButton from '../../../../UI/IconButton';
import Settings from '../../../../UI/CustomSvgIcons/Settings';

const PADDING = 16;

Expand Down Expand Up @@ -112,6 +115,10 @@ const TeamSection = React.forwardRef<Props, TeamSectionInterface>(
manageSeatsDialogOpen,
setManageSeatsDialogOpen,
] = React.useState<boolean>(false);
const [
advancedStudentOptionsDialogOpen,
setAdvancedStudentOptionsDialogOpen,
] = React.useState<boolean>(false);
const forceUpdate = useForceUpdate();
const { isMobile } = useResponsiveWindowSize();
const contextMenu = React.useRef<?ContextMenuInterface>(null);
Expand Down Expand Up @@ -296,14 +303,23 @@ const TeamSection = React.forwardRef<Props, TeamSectionInterface>(
justifyContent="space-between"
>
<TeamAvailableSeats />
<RaisedButton
primary
label={
isMobile ? <Trans>Manage</Trans> : <Trans>Manage seats</Trans>
}
icon={<UserSVG fontSize="small" />}
onClick={() => setManageSeatsDialogOpen(true)}
/>
<LineStackLayout noMargin alignItems="center">
<RaisedButton
primary
label={
isMobile ? <Trans>Manage</Trans> : <Trans>Manage seats</Trans>
}
icon={<UserSVG fontSize="small" />}
onClick={() => setManageSeatsDialogOpen(true)}
/>
<IconButton
size="small"
tooltip={t`Advanced student options`}
onClick={() => setAdvancedStudentOptionsDialogOpen(true)}
>
<Settings />
</IconButton>
</LineStackLayout>
</LineStackLayout>
</div>
);
Expand Down Expand Up @@ -540,6 +556,11 @@ const TeamSection = React.forwardRef<Props, TeamSectionInterface>(
onClose={() => setManageSeatsDialogOpen(false)}
/>
)}
{advancedStudentOptionsDialogOpen && (
<AdvancedStudentOptionsDialog
onClose={() => setAdvancedStudentOptionsDialogOpen(false)}
/>
)}
</>
);
}
Expand Down
4 changes: 4 additions & 0 deletions newIDE/app/src/Profile/Team/TeamContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export type TeamState = {|
newPassword: string
) => Promise<void>,
onEditUser: (editedUserId: string, changes: EditUserChanges) => Promise<void>,
onUpdateTeam: (attributes: {|
classrooms: {| hideAskAi: boolean |},
|}) => Promise<void>,
|};

export const initialTeamState = {
Expand All @@ -61,6 +64,7 @@ export const initialTeamState = {
onSetMember: async () => {},
onChangeMemberPassword: async () => {},
onEditUser: async () => {},
onUpdateTeam: async () => {},
};

const TeamContext: React.Context<TeamState> = React.createContext<TeamState>(
Expand Down
16 changes: 16 additions & 0 deletions newIDE/app/src/Profile/Team/TeamProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
setUserAsMember,
listTeamInvitations,
editUser,
updateTeam,
type EditUserChanges,
} from '../../Utils/GDevelopServices/User';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
Expand Down Expand Up @@ -399,6 +400,20 @@ const TeamProvider = ({ children }: Props): React.Node => {
[team, getAuthorizationHeader, adminUserId]
);

const onUpdateTeam = React.useCallback(
async (attributes: {| classrooms: {| hideAskAi: boolean |} |}) => {
if (!adminUserId || !team) return;
const updatedTeam = await updateTeam(
getAuthorizationHeader,
adminUserId,
team.id,
attributes
);
setTeam(updatedTeam);
},
[team, getAuthorizationHeader, adminUserId]
);

const getAvailableSeats = React.useCallback(
() =>
team && members && admins && invitations
Expand Down Expand Up @@ -434,6 +449,7 @@ const TeamProvider = ({ children }: Props): React.Node => {
onActivateMembers,
onSetAdmin,
onSetMember,
onUpdateTeam,
}}
>
{children}
Expand Down
29 changes: 28 additions & 1 deletion newIDE/app/src/Utils/GDevelopServices/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,16 @@ export type User = {|
+password?: ?string,
|};

export type Team = {| id: string, createdAt: number, seats: number |};
export type Team = {|
id: string,
createdAt: number,
seats: number,
/**
* Overrides applied to the `classrooms` capability of the students of this
* team. Keys mirror the `classrooms` capability of the limits.
*/
classrooms?: ?{| hideAskAi: boolean |},
|};
export type TeamGroup = {| id: string, name: string |};
export type TeamInvitation = {|
teamId: string,
Expand Down Expand Up @@ -345,6 +354,24 @@ export const updateGroup = async (
});
};

export const updateTeam = async (
getAuthorizationHeader: () => Promise<string>,
userId: string,
teamId: string,
attributes: {| classrooms: {| hideAskAi: boolean |} |}
): Promise<Team> => {
const authorizationHeader = await getAuthorizationHeader();
const response = await client.patch(`/team/${teamId}`, attributes, {
headers: { Authorization: authorizationHeader },
params: { userId },
});
return ensureObjectHasProperty({
data: response.data,
propertyName: 'id',
endpointName: '/team/{id} of User API',
});
};

export const createGroup = async (
getAuthorizationHeader: () => Promise<string>,
userId: string,
Expand Down
12 changes: 11 additions & 1 deletion newIDE/app/src/stories/MockTeamProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ export const MockTeamProvider = ({
noActiveMembers?: boolean,
teamSize?: number,
|}): React.Node => {
const team = { ...initialTeam, seats: teamSize || initialTeam.seats };
const [team, setTeam] = React.useState<Team>({
...initialTeam,
seats: teamSize || initialTeam.seats,
});
const [nameChangeTryCount, setNameChangeTryCount] = React.useState<number>(0);
const [userChangeTryCount, setUserChangeTryCount] = React.useState<number>(0);
const [members, setMembers] = React.useState<?(User[])>(
Expand Down Expand Up @@ -507,6 +510,13 @@ export const MockTeamProvider = ({
onEditUser: editUser,
invitations: null,
onRefreshInvitations: async () => {},
onUpdateTeam: async attributes => {
action('updateTeam')(attributes);
setTeam(currentTeam => ({
...currentTeam,
classrooms: attributes.classrooms,
}));
},
}}
>
<Text allowSelection>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @flow
import * as React from 'react';
import { action } from '@storybook/addon-actions';

import paperDecorator from '../../../PaperDecorator';
import FixedHeightFlexContainer from '../../../FixedHeightFlexContainer';
import AdvancedStudentOptionsDialog from '../../../../MainFrame/EditorContainers/HomePage/TeamSection/AdvancedStudentOptionsDialog';
import { MockTeamProvider } from '../../../MockTeamProvider';

export default {
title: 'HomePage/TeamSection/AdvancedStudentOptionsDialog',
component: AdvancedStudentOptionsDialog,
decorators: [paperDecorator],
};

export const Default = (): React.Node => {
return (
<MockTeamProvider loading={false}>
<FixedHeightFlexContainer height={400}>
<AdvancedStudentOptionsDialog onClose={action('onClose')} />
</FixedHeightFlexContainer>
</MockTeamProvider>
);
};
Loading