Skip to content

Commit bb9494e

Browse files
Fix language as per #31, add error styling, add pasting to login
1 parent 6bd371c commit bb9494e

File tree

12 files changed

+108
-56
lines changed

12 files changed

+108
-56
lines changed
File renamed without changes.
File renamed without changes.

workshop-ui/prompts/exercises.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@
6767
"data_files": [],
6868
"image": "/images/covers/06-improve-p5js-code.svg"
6969
},
70-
"test": {
71-
"title": "Test Exercise",
72-
"folder": "99-test",
70+
"chatbot": {
71+
"title": "Chatbot",
72+
"folder": "99-chatbot",
7373
"difficulty": "hard",
74-
"summary": "You should not see this exercise in the UI.",
74+
"summary": "Das ist ein einfacher Chatbot, den du nutzen kannst, um irgendetwas zu fragen oder zu besprechen.",
7575
"reference": "tasksheet.md",
7676
"system_prompt_file": "system-prompt.md",
7777
"data_files": [],
78-
"image": "/images/covers/99-test.svg"
78+
"image": "/images/covers/99-chatbot.svg"
7979
}
8080
}
8181
}

workshop-ui/src/app/chat/[exercise]/page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ export default function Home() {
220220
}
221221
if (parsed.encryptedResponseId) {
222222
setResponseId(parsed.encryptedResponseId);
223-
console.log('Received encryptedResponseId:', parsed.encryptedResponseId);
224223
}
225224
} catch {
226225
// Ignore parsing errors for SSE data
@@ -230,7 +229,7 @@ export default function Home() {
230229
}
231230
}
232231
} catch (error) {
233-
console.error('Error:', error);
232+
console.error('Error processing message:', error);
234233
setMessages((prev) => [
235234
...prev,
236235
{
@@ -275,7 +274,7 @@ export default function Home() {
275274
</button>
276275
<h1 className={styles.exerciseTitle}>{exerciseTitle}</h1>
277276
<div className={styles.dropdown} ref={dropdownRef} onMouseEnter={handleToggleDropdown} onMouseLeave={handleToggleDropdown}>
278-
<button className={styles.dropdownButton} title="Options">
277+
<button className={styles.dropdownButton}>
279278
<ChevronDown size={20} />
280279
</button>
281280
{isDropdownOpen && (
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.errorContainer {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
justify-content: center;
6+
height: 100vh;
7+
text-align: center;
8+
background-color: #ffa4afaa;
9+
color: #721c24;
10+
font-family: Arial, sans-serif;
11+
}
12+
13+
.errorTitle {
14+
font-size: 2rem;
15+
margin-bottom: 1rem;
16+
}
17+
18+
.errorMessage {
19+
font-size: 1.2rem;
20+
margin-bottom: 2rem;
21+
}
22+
23+
.errorButton {
24+
padding: 0.75rem 1.5rem;
25+
font-size: 1rem;
26+
color: #fff;
27+
background-color: #721c24;
28+
border: none;
29+
border-radius: 5px;
30+
cursor: pointer;
31+
}

workshop-ui/src/app/error.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
'use client';
22

3-
export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
3+
import styles from './error.module.css';
4+
5+
type ErrorProps = {
6+
error: Error & { digest?: string };
7+
reset: () => void;
8+
}
9+
10+
export default function Error({ error, reset }: Readonly<ErrorProps>) {
411
return (
5-
<div>
6-
<h2>Something went wrong</h2>
7-
<p>{process.env.NODE_ENV === 'development' ? error.message : 'Please try again.'}</p>
8-
<button onClick={() => reset()}>Retry</button>
12+
<div className={styles.errorContainer}>
13+
<h2 className={styles.errorTitle}>Etwas ist schiefgelaufen</h2>
14+
<p className={styles.errorMessage}>{process.env.NODE_ENV === 'development' ? error.message : 'Bitte versuche es erneut.'}</p>
15+
<button onClick={() => reset()} className={styles.errorButton}>Wiederholen</button>
916
</div>
1017
);
1118
}

workshop-ui/src/app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import "./globals.css";
33

44
export const metadata: Metadata = {
55
title: "CoderDojo AI Workshops",
6-
description: "The AI-powered coding platform for CoderDojo workshops",
6+
description: "Die besten AI Workshops für Kinder und Jugendliche",
77
icons: {
88
icon: "/favicon.ico", // default favicon
99
apple: "/apple-icon.png", // Apple touch icon
@@ -25,7 +25,7 @@ export default function RootLayout({
2525
children: React.ReactNode;
2626
}>) {
2727
return (
28-
<html lang="en">
28+
<html lang="de">
2929
<body>{children}</body>
3030
</html>
3131
);

workshop-ui/src/app/login/page.tsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export default function LoginPage() {
2525

2626
if (data.authenticated) {
2727
// User is already authenticated, redirect to home
28-
console.log('User already authenticated, redirecting to home.')
2928

3029
// Get the "from" parameter if it exists
3130
const urlParams = new URLSearchParams(window.location.search)
@@ -109,12 +108,34 @@ export default function LoginPage() {
109108
}
110109
}
111110

111+
const handlePaste = (pasteIndex: number, e: React.ClipboardEvent<HTMLDivElement>) => {
112+
e.preventDefault()
113+
const pasteData = e.clipboardData.getData('Text').toUpperCase().slice(0, 6).split('')
114+
const newCode = [...code]
115+
116+
for (let i = 0; i < pasteData.length; i++) {
117+
if (pasteIndex + i < 6) {
118+
newCode[pasteIndex + i] = pasteData[i]
119+
}
120+
}
121+
122+
setCode(newCode)
123+
124+
// Focus the next empty input or login button
125+
const firstEmptyIndex = newCode.findIndex((digit) => digit === '')
126+
if (firstEmptyIndex !== -1) {
127+
inputRefs.current[firstEmptyIndex]?.focus()
128+
} else {
129+
loginButtonRef.current?.focus()
130+
}
131+
}
132+
112133
// Show loading while checking authentication
113134
if (isLoading) {
114135
return (
115136
<div className={styles.container}>
116137
<div className={styles.loginPrompt}>
117-
<p>Checking authentication...</p>
138+
<p>Prüfe Login-Status...</p>
118139
</div>
119140
</div>
120141
)
@@ -126,12 +147,12 @@ export default function LoginPage() {
126147
<h1>Login</h1>
127148
<p>Bitte logge dich ein, um auf die Workshops zuzugreifen. Du erhältst den Zugangscode von einem Mentor.</p>
128149
<div className={styles.inputForm}>
129-
<input type="text" maxLength={1} value={code[0]} onChange={(e) => setCodeDigit(0, e.target.value)} onKeyDown={(e) => handleKeyDown(0, e)} ref={(el) => { inputRefs.current[0] = el }} />
130-
<input type="text" maxLength={1} value={code[1]} onChange={(e) => setCodeDigit(1, e.target.value)} onKeyDown={(e) => handleKeyDown(1, e)} ref={(el) => { inputRefs.current[1] = el }} />
131-
<input type="text" maxLength={1} value={code[2]} onChange={(e) => setCodeDigit(2, e.target.value)} onKeyDown={(e) => handleKeyDown(2, e)} ref={(el) => { inputRefs.current[2] = el }} />
132-
<input type="text" maxLength={1} value={code[3]} onChange={(e) => setCodeDigit(3, e.target.value)} onKeyDown={(e) => handleKeyDown(3, e)} ref={(el) => { inputRefs.current[3] = el }} />
133-
<input type="text" maxLength={1} value={code[4]} onChange={(e) => setCodeDigit(4, e.target.value)} onKeyDown={(e) => handleKeyDown(4, e)} ref={(el) => { inputRefs.current[4] = el }} />
134-
<input type="text" maxLength={1} value={code[5]} onChange={(e) => setCodeDigit(5, e.target.value)} onKeyDown={(e) => handleKeyDown(5, e)} ref={(el) => { inputRefs.current[5] = el }} />
150+
<input type="text" maxLength={1} value={code[0]} onChange={(e) => setCodeDigit(0, e.target.value)} onKeyDown={(e) => handleKeyDown(0, e)} ref={(el) => { inputRefs.current[0] = el }} onPaste={(e) => handlePaste(0, e)}/>
151+
<input type="text" maxLength={1} value={code[1]} onChange={(e) => setCodeDigit(1, e.target.value)} onKeyDown={(e) => handleKeyDown(1, e)} ref={(el) => { inputRefs.current[1] = el }} onPaste={(e) => handlePaste(1, e)}/>
152+
<input type="text" maxLength={1} value={code[2]} onChange={(e) => setCodeDigit(2, e.target.value)} onKeyDown={(e) => handleKeyDown(2, e)} ref={(el) => { inputRefs.current[2] = el }} onPaste={(e) => handlePaste(2, e)}/>
153+
<input type="text" maxLength={1} value={code[3]} onChange={(e) => setCodeDigit(3, e.target.value)} onKeyDown={(e) => handleKeyDown(3, e)} ref={(el) => { inputRefs.current[3] = el }} onPaste={(e) => handlePaste(3, e)}/>
154+
<input type="text" maxLength={1} value={code[4]} onChange={(e) => setCodeDigit(4, e.target.value)} onKeyDown={(e) => handleKeyDown(4, e)} ref={(el) => { inputRefs.current[4] = el }} onPaste={(e) => handlePaste(4, e)}/>
155+
<input type="text" maxLength={1} value={code[5]} onChange={(e) => setCodeDigit(5, e.target.value)} onKeyDown={(e) => handleKeyDown(5, e)} ref={(el) => { inputRefs.current[5] = el }} onPaste={(e) => handlePaste(5, e)}/>
135156
</div>
136157
{errorMessage && <p className={styles.errorMessage}>{errorMessage}</p>}
137158
<button
@@ -140,7 +161,7 @@ export default function LoginPage() {
140161
disabled={isSubmitting}
141162
className={styles.loginButton}
142163
>
143-
{isSubmitting ? 'Logging in...' : 'Log In'}
164+
{isSubmitting ? 'Wird angemeldet...' : 'Anmelden'}
144165
</button>
145166
</div>
146167
</div>

workshop-ui/src/app/page.tsx

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import Link from 'next/link';
2-
import styles from './page.module.css';
3-
import { getExercises } from '@/lib/exercise-file-manager';
4-
import { trace } from '@opentelemetry/api';
5-
import LogoutButton from '@/components/LogoutButton';
6-
import { getAppSession, validateAppSession } from '@/lib/session';
72
import { redirect } from 'next/navigation';
83
import { headers } from 'next/headers';
4+
import { trace } from '@opentelemetry/api';
5+
6+
import { getExercises } from '@/lib/exercise-file-manager';
7+
import { getAppSession, validateAppSession } from '@/lib/session';
8+
import LogoutButton from '@/components/LogoutButton';
9+
10+
import styles from './page.module.css';
11+
12+
type Difficulty = {
13+
class: string;
14+
label: string;
15+
}
916

1017
export default async function Home() {
1118
// redirect to login if not authenticated
@@ -24,45 +31,32 @@ export default async function Home() {
2431
}
2532
const exercisesData = exercisesResult.value.exercises;
2633

27-
function difficultyToClass(difficulty: string) {
28-
switch (difficulty) {
29-
case 'easy':
30-
return styles.easy;
31-
case 'medium':
32-
return styles.medium;
33-
case 'hard':
34-
return styles.hard;
35-
default:
36-
return '';
37-
}
38-
}
39-
40-
function difficultyToName(difficulty: string) {
34+
function parseDifficulty(difficulty: string): Difficulty {
4135
switch (difficulty) {
4236
case 'easy':
43-
return 'Beginner';
37+
return { class: styles.easy, label: 'Anfänger' };
4438
case 'medium':
45-
return 'Advanced';
39+
return { class: styles.medium, label: 'Fortgeschritten' };
4640
case 'hard':
47-
return 'Expert';
41+
return { class: styles.hard, label: 'Experte' };
4842
default:
49-
return '';
43+
return { class: '', label: '' };
5044
}
5145
}
5246

5347
return (
5448
<>
5549
<LogoutButton className={styles.logoutContainer} />
56-
<img src="/images/background1.svg" alt="Decorative image" className={styles.vectorBg1} />
57-
<img src="/images/background2.svg" alt="Decorative image" className={styles.vectorBg2} />
50+
<img src="/images/background1.svg" alt="Dekoratives Bild" className={styles.vectorBg1} />
51+
<img src="/images/background2.svg" alt="Dekoratives Bild" className={styles.vectorBg2} />
5852
<div className={styles.container}>
5953
<h1 className={styles.title}>AI Workshop Exercises</h1>
6054
<div className={styles.exerciseGrid}>
6155
{Object.entries(exercisesData).map(([key, exercise]) => (
6256
<div key={key} className={styles.exerciseCard}>
6357
<Link href={`/chat/${key}`} className={styles.exerciseLink}>
64-
<span className={`${styles.exerciseDifficulty} ${difficultyToClass(exercise.difficulty)}`}>{difficultyToName(exercise.difficulty)}</span>
65-
<img src={exercise.image || '/images/elementor-placeholder-image.png'} alt={`${exercise.title}'s descriptive image`} />
58+
<span className={`${styles.exerciseDifficulty} ${parseDifficulty(exercise.difficulty).class}`}>{parseDifficulty(exercise.difficulty).label}</span>
59+
<img src={exercise.image || '/images/elementor-placeholder-image.png'} alt={`${exercise.title}'s beschreibendes Bild`} />
6660
<div className={styles.exerciseContent}>
6761
<h2 className={styles.exerciseTitle}>{exercise.title}</h2>
6862
<p className={styles.exerciseDescription}>{exercise.summary}</p>

0 commit comments

Comments
 (0)