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
13 changes: 13 additions & 0 deletions infra/smalruby-classroom/lambda/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ async function handleListClassrooms(teacherSub: string): Promise<APIGatewayProxy
joinCode: item.joinCode,
studentCount: item.studentCount,
googleClassroomCourseId: item.googleClassroomCourseId || null,
googleClassroomAlternateLink: item.googleClassroomAlternateLink || null,
createdAt: item.createdAt,
expiresAt: item.ttl ? new Date((item.ttl as number) * 1000).toISOString() : null,
}));
Expand Down Expand Up @@ -342,6 +343,7 @@ async function handleGetClassroom(teacherSub: string, classroomId: string): Prom
joinCode: result.Item.joinCode,
studentCount: result.Item.studentCount,
googleClassroomCourseId: result.Item.googleClassroomCourseId || null,
googleClassroomAlternateLink: result.Item.googleClassroomAlternateLink || null,
status: result.Item.status,
createdAt: result.Item.createdAt,
expiresAt: result.Item.ttl ? new Date((result.Item.ttl as number) * 1000).toISOString() : null,
Expand Down Expand Up @@ -1149,6 +1151,17 @@ async function handlePostAssignment(
materials: [{ link: { url: link, title: 'スモウルビーで開く' } }],
}) as { id: string; alternateLink: string };

// Persist courseWork info to prevent duplicate posting
await docClient.send(new UpdateCommand({
TableName: CLASSROOMS_TABLE,
Key: { classroomId },
UpdateExpression: 'SET googleClassroomCourseWorkId = :cwId, googleClassroomAlternateLink = :link',
ExpressionAttributeValues: {
':cwId': courseWork.id,
':link': courseWork.alternateLink,
},
}));

return {
statusCode: 201,
body: JSON.stringify({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@
}

.primary-button {
display: inline-flex;
align-items: center;
gap: 0.3rem;
background-color: #4c97ff;
color: white;
border: none;
Expand All @@ -149,13 +152,17 @@
}

.secondary-button {
display: inline-flex;
align-items: center;
gap: 0.3rem;
background-color: white;
color: #575e75;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 0.6rem 1.2rem;
font-size: 0.9rem;
cursor: pointer;
text-decoration: none;
}

.secondary-button:hover {
Expand Down Expand Up @@ -353,6 +360,27 @@
font-size: 1.1rem;
}

.post-assignment-header {
font-size: 1rem;
font-weight: bold;
color: #575e75;
margin-bottom: 0.6rem;
}

.post-assignment-target {
font-size: 0.9rem;
color: #575e75;
margin-bottom: 1rem;
}

.post-assignment-hint {
font-size: 0.8rem;
color: #888;
line-height: 1.5;
margin-top: 0.5rem;
margin-bottom: 1rem;
}

.class-item-main {
display: flex;
justify-content: space-between;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ClassCodeDisplay from './class-code-display.jsx';
import ErrorDisplay from './error-display.jsx';
import TeacherMemberDetail from './teacher-member-detail.jsx';

import googleClassroomIcon from '../classroom-teacher-modal/google-classroom-icon.png';
import styles from './classroom-modal.css';

const TeacherClassDetail = ({
Expand Down Expand Up @@ -198,23 +199,60 @@ const TeacherClassDetail = ({
onBlur={handleAssignmentNameBlur}
onChange={handleAssignmentNameChange}
/>
{selectedClassroom.googleClassroomCourseId && (
<button
className={
styles.secondaryButton
}
data-testid="classroom-post-assignment"
onClick={
onShowPostAssignment
}
>
<FormattedMessage
defaultMessage="Post Assignment"
description="Post assignment to Google Classroom"
id="gui.classroom.postAssignment.title"
/>
</button>
)}
{selectedClassroom.googleClassroomCourseId &&
(selectedClassroom.googleClassroomAlternateLink ? (
<a
className={
styles.secondaryButton
}
data-testid="classroom-view-assignment"
href={
selectedClassroom.googleClassroomAlternateLink
}
rel="noopener noreferrer"
target="_blank"
>
<img
alt=""
className={
styles.gcImportIcon
}
src={
googleClassroomIcon
}
/>
<FormattedMessage
defaultMessage="View Assignment"
description="View posted assignment on Google Classroom"
id="gui.classroom.postAssignment.viewAssignment"
/>
</a>
) : (
<button
className={
styles.secondaryButton
}
data-testid="classroom-post-assignment"
onClick={
onShowPostAssignment
}
>
<img
alt=""
className={
styles.gcImportIcon
}
src={
googleClassroomIcon
}
/>
<FormattedMessage
defaultMessage="Post Assignment"
description="Post assignment to Google Classroom"
id="gui.classroom.postAssignment.title"
/>
</button>
))}
</div>

{/* Join code with expand button */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useCallback, useState } from 'react';

import ErrorDisplay from './error-display.jsx';

import googleClassroomIcon from '../classroom-teacher-modal/google-classroom-icon.png';
import styles from './classroom-modal.css';

const TeacherPostAssignment = ({
Expand All @@ -15,9 +16,7 @@ const TeacherPostAssignment = ({
onBack,
onPostAssignment,
}) => {
const defaultTitle = selectedClassroom
? `${selectedClassroom.className}${selectedClassroom.assignmentName ? ` (${selectedClassroom.assignmentName})` : ''}`
: '';
const defaultTitle = selectedClassroom?.assignmentName || '';
const [title, setTitle] = useState(defaultTitle);
const [description, setDescription] = useState('');
const [posted, setPosted] = useState(false);
Expand Down Expand Up @@ -66,9 +65,6 @@ const TeacherPostAssignment = ({
id="gui.classroom.postAssignment.title"
/>
</div>
<div className={styles.detailLabel}>
{selectedClassroom?.className}
</div>
{posted ? (
<div
className={styles.successMessage}
Expand All @@ -82,26 +78,43 @@ const TeacherPostAssignment = ({
</div>
) : (
<>
<div className={styles.postAssignmentHeader}>
<FormattedMessage
defaultMessage="Create an assignment on Google Classroom."
description="Post assignment form header"
id="gui.classroom.postAssignment.header"
/>
</div>
<div className={styles.postAssignmentTarget}>
<FormattedMessage
defaultMessage="Target: {className}"
description="Target class name for assignment"
id="gui.classroom.postAssignment.target"
values={{
className: selectedClassroom?.className,
}}
/>
</div>
<div className={styles.formGroup}>
<label className={styles.formLabel}>
<label className={styles.label}>
<FormattedMessage
defaultMessage="Title"
defaultMessage="Title:"
description="Assignment title label"
id="gui.classroom.postAssignment.titleLabel"
/>
</label>
<input
className={styles.formInput}
className={styles.input}
data-testid="classroom-post-assignment-title"
type="text"
value={title}
onChange={handleTitleChange}
/>
</div>
<div className={styles.formGroup}>
<label className={styles.formLabel}>
<label className={styles.label}>
<FormattedMessage
defaultMessage="Description (optional)"
defaultMessage="Assignment details (optional)"
description="Assignment description label"
id="gui.classroom.postAssignment.descriptionLabel"
/>
Expand All @@ -114,12 +127,24 @@ const TeacherPostAssignment = ({
onChange={handleDescriptionChange}
/>
</div>
<div className={styles.postAssignmentHint}>
<FormattedMessage
defaultMessage="After creating the assignment, you can set formatting, assignees, points, etc. on Google Classroom."
description="Hint about Google Classroom settings"
id="gui.classroom.postAssignment.hint"
/>
</div>
<button
className={styles.primaryButton}
data-testid="classroom-post-assignment-submit"
disabled={!title.trim() || isLoading}
onClick={handlePost}
>
<img
alt=""
className={styles.gcImportIcon}
src={googleClassroomIcon}
/>
<FormattedMessage
defaultMessage="Post to Google Classroom"
description="Post assignment button"
Expand Down
18 changes: 18 additions & 0 deletions packages/scratch-gui/src/containers/use-teacher-classroom.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,24 @@ const useTeacherClassroom = ({
link,
description,
);
// Update selectedClassroom with the alternateLink to prevent double posting
if (result.alternateLink) {
setSelectedClassroom(prev => ({
...prev,
googleClassroomAlternateLink: result.alternateLink,
}));
// Also update classrooms list
setClassrooms(prev =>
prev.map(c =>
c.classroomId === selectedClassroom.classroomId
? {
...c,
googleClassroomAlternateLink: result.alternateLink,
}
: c,
),
);
}
return result;
} catch (err) {
if (err.status === 401) {
Expand Down
9 changes: 7 additions & 2 deletions packages/scratch-gui/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,15 @@ export default {
'gui.classroom.googleCourses.empty': 'No courses found',
'gui.classroom.googleCourses.students': '{count} students',
'gui.classroom.postAssignment.title': 'Post Assignment',
'gui.classroom.postAssignment.titleLabel': 'Title',
'gui.classroom.postAssignment.descriptionLabel': 'Description (optional)',
'gui.classroom.postAssignment.header': 'Create an assignment on Google Classroom.',
'gui.classroom.postAssignment.target': 'Target: {className}',
'gui.classroom.postAssignment.titleLabel': 'Title:',
'gui.classroom.postAssignment.descriptionLabel': 'Assignment details (optional)',
'gui.classroom.postAssignment.hint':
'After creating the assignment, you can set formatting, assignees, points, etc. on Google Classroom.',
'gui.classroom.postAssignment.post': 'Post to Google Classroom',
'gui.classroom.postAssignment.success': 'Assignment posted!',
'gui.classroom.postAssignment.viewAssignment': 'View Assignment',
'gui.classroom.error.fileTooLarge': 'Project is too large ({size}MB). Maximum size is 10MB.',
'gui.classroom.studentStatus.returned': 'Returned',
'gui.classroom.studentStatus.teacherComment': "Teacher's Comment",
Expand Down
9 changes: 7 additions & 2 deletions packages/scratch-gui/src/locales/ja-Hira.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,15 @@ export default {
'gui.classroom.googleCourses.empty': 'クラスがみつかりません',
'gui.classroom.googleCourses.students': '{count}にん',
'gui.classroom.postAssignment.title': 'かだいをはいしん',
'gui.classroom.postAssignment.titleLabel': 'タイトル',
'gui.classroom.postAssignment.descriptionLabel': 'せつめい(にんい)',
'gui.classroom.postAssignment.header': 'Google Classroom にかだいをさくせいします。',
'gui.classroom.postAssignment.target': 'たいしょう: {className}',
'gui.classroom.postAssignment.titleLabel': 'タイトル:',
'gui.classroom.postAssignment.descriptionLabel': 'かだいのしょうさい(しょうりゃくか)',
'gui.classroom.postAssignment.hint':
'かだいのしょうさいのそうしょく、わりあてさき、てんすうなどのせっていは、かだいのさくせいごに Google Classroom でおこなってください。',
'gui.classroom.postAssignment.post': 'Google Classroom にはいしん',
'gui.classroom.postAssignment.success': 'はいしんしました!',
'gui.classroom.postAssignment.viewAssignment': 'かだいをかくにん',
'gui.classroom.error.fileTooLarge': 'プロジェクトがおおきすぎます({size}MB)。じょうげんは10MBです。',
'gui.classroom.studentStatus.returned': 'へんきゃくずみ',
'gui.classroom.studentStatus.teacherComment': 'せんせいからのコメント',
Expand Down
9 changes: 7 additions & 2 deletions packages/scratch-gui/src/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,15 @@ export default {
'gui.classroom.googleCourses.empty': 'クラスが見つかりません',
'gui.classroom.googleCourses.students': '{count}人',
'gui.classroom.postAssignment.title': '課題を配信',
'gui.classroom.postAssignment.titleLabel': 'タイトル',
'gui.classroom.postAssignment.descriptionLabel': '説明(任意)',
'gui.classroom.postAssignment.header': 'Google Classroom に課題を作成します。',
'gui.classroom.postAssignment.target': '対象: {className}',
'gui.classroom.postAssignment.titleLabel': 'タイトル:',
'gui.classroom.postAssignment.descriptionLabel': '課題の詳細(省略可)',
'gui.classroom.postAssignment.hint':
'課題の詳細の装飾、割当先、点数などの設定は、課題の作成後に Google Classroom で行ってください。',
'gui.classroom.postAssignment.post': 'Google Classroom に配信',
'gui.classroom.postAssignment.success': '配信しました!',
'gui.classroom.postAssignment.viewAssignment': '課題を確認',
'gui.classroom.error.fileTooLarge': 'プロジェクトが大きすぎます({size}MB)。上限は10MBです。',
'gui.classroom.studentStatus.returned': '返却済み',
'gui.classroom.studentStatus.teacherComment': '先生からのコメント',
Expand Down
Loading