diff --git a/app-next/messages/de.json b/app-next/messages/de.json
index f8eac243..0547b33b 100644
--- a/app-next/messages/de.json
+++ b/app-next/messages/de.json
@@ -706,6 +706,12 @@
},
"auth": {
"recommended": "EMPFOHLEN",
+ "signInRequired": {
+ "uploadDataset": "Sie müssen sich anmelden, um einen Datensatz hochzuladen",
+ "createTask": "Sie müssen sich anmelden, um eine Aufgabe zu definieren",
+ "createCollection": "Sie müssen sich anmelden, um eine Sammlung zu erstellen",
+ "default": "Sie müssen sich anmelden, um fortzufahren"
+ },
"signIn": {
"title": "Anmelden - OpenML",
"description": "Melden Sie sich bei Ihrem OpenML-Konto an",
diff --git a/app-next/messages/en.json b/app-next/messages/en.json
index fa458f77..ecf2b6ad 100644
--- a/app-next/messages/en.json
+++ b/app-next/messages/en.json
@@ -689,6 +689,12 @@
},
"auth": {
"recommended": "RECOMMENDED",
+ "signInRequired": {
+ "uploadDataset": "You need to sign in to upload a dataset",
+ "createTask": "You need to sign in to define a task",
+ "createCollection": "You need to sign in to create a collection",
+ "default": "You need to sign in to continue"
+ },
"signIn": {
"title": "Sign In - OpenML",
"description": "Sign in to your OpenML account",
diff --git a/app-next/messages/fr.json b/app-next/messages/fr.json
index d72be02e..3a7abf1c 100644
--- a/app-next/messages/fr.json
+++ b/app-next/messages/fr.json
@@ -706,6 +706,12 @@
},
"auth": {
"recommended": "RECOMMANDÉ",
+ "signInRequired": {
+ "uploadDataset": "Vous devez vous connecter pour télécharger un jeu de données",
+ "createTask": "Vous devez vous connecter pour définir une tâche",
+ "createCollection": "Vous devez vous connecter pour créer une collection",
+ "default": "Vous devez vous connecter pour continuer"
+ },
"signIn": {
"title": "Se connecter - OpenML",
"description": "Connectez-vous à votre compte OpenML",
diff --git a/app-next/messages/nl.json b/app-next/messages/nl.json
index 7f4c61c4..86645523 100644
--- a/app-next/messages/nl.json
+++ b/app-next/messages/nl.json
@@ -706,6 +706,12 @@
},
"auth": {
"recommended": "AANBEVOLEN",
+ "signInRequired": {
+ "uploadDataset": "U moet inloggen om een dataset te uploaden",
+ "createTask": "U moet inloggen om een taak te definiëren",
+ "createCollection": "U moet inloggen om een collectie aan te maken",
+ "default": "U moet inloggen om verder te gaan"
+ },
"signIn": {
"title": "Inloggen - OpenML",
"description": "Log in op uw OpenML-account",
diff --git a/app-next/src/app/[locale]/(explore)/collections/create/page.tsx b/app-next/src/app/[locale]/(explore)/collections/create/page.tsx
new file mode 100644
index 00000000..f33adaf2
--- /dev/null
+++ b/app-next/src/app/[locale]/(explore)/collections/create/page.tsx
@@ -0,0 +1,26 @@
+import { getServerSession } from "next-auth";
+import { redirect } from "next/navigation";
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+
+export default async function CollectionCreatePage({
+ params,
+}: {
+ params: Promise<{ locale: string }>;
+}) {
+ const { locale } = await params;
+ const session = await getServerSession(authOptions);
+
+ if (!session) {
+ redirect(
+ `/${locale}/auth/sign-in?reason=createCollection&callbackUrl=/${locale}/collections/create`
+ );
+ }
+
+ // TODO: Implement collection creation form
+ return (
+
+
Create Collection
+
Coming soon.
+
+ );
+}
diff --git a/app-next/src/app/[locale]/(explore)/datasets/[id]/edit/page.tsx b/app-next/src/app/[locale]/(explore)/datasets/[id]/edit/page.tsx
index 0154c364..c64b66b9 100644
--- a/app-next/src/app/[locale]/(explore)/datasets/[id]/edit/page.tsx
+++ b/app-next/src/app/[locale]/(explore)/datasets/[id]/edit/page.tsx
@@ -30,7 +30,7 @@ export default async function DatasetEditPage({
// Auth check — redirect to sign-in if not authenticated
const session = await getServerSession(authOptions);
if (!session?.user) {
- redirect(`/auth/signin?callbackUrl=/datasets/${id}/edit`);
+ redirect(`/${locale}/auth/sign-in?reason=uploadDataset&callbackUrl=/${locale}/datasets/${id}/edit`);
}
const dataset = await fetchDataset(id);
@@ -39,12 +39,20 @@ export default async function DatasetEditPage({
const userId = (session.user as { id?: string }).id;
const isOwner = userId ? Number(userId) === dataset.uploader_id : false;
+ // Check whether the session has a valid OpenML API key
+ const hasApiKey = !!(session as { apikey?: string }).apikey;
+ // OAuth users created in local dev environments don't have a real OpenML API key
+ const isLocalUser =
+ (session.user as { isLocalUser?: boolean }).isLocalUser ?? false;
+
return (
);
}
diff --git a/app-next/src/app/api/datasets/[id]/edit/route.ts b/app-next/src/app/api/datasets/[id]/edit/route.ts
index d73b8bb6..bc5e5546 100644
--- a/app-next/src/app/api/datasets/[id]/edit/route.ts
+++ b/app-next/src/app/api/datasets/[id]/edit/route.ts
@@ -72,8 +72,12 @@ export async function POST(
if (!response.ok) {
const text = await response.text();
console.error(`OpenML API error editing dataset ${id}:`, text);
+ const message =
+ response.status === 401 || response.status === 403
+ ? "Your API key is not accepted by the OpenML server. If you are using a local test account, dataset editing is not supported — only real OpenML accounts can save changes."
+ : "Failed to save changes. Please try again.";
return NextResponse.json(
- { error: "Failed to save changes. Please try again." },
+ { error: message },
{ status: response.status },
);
}
diff --git a/app-next/src/components/dataset/dataset-edit-form.tsx b/app-next/src/components/dataset/dataset-edit-form.tsx
index 4a4270e5..eaba012f 100644
--- a/app-next/src/components/dataset/dataset-edit-form.tsx
+++ b/app-next/src/components/dataset/dataset-edit-form.tsx
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
+import { useLocale } from "next-intl";
import Link from "next/link";
import { ArrowLeft, Save, Loader2, AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
@@ -9,11 +10,14 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
+import { useToast } from "@/hooks/use-toast";
interface DatasetEditFormProps {
datasetId: number;
datasetName: string;
isOwner: boolean;
+ hasApiKey: boolean;
+ isLocalUser: boolean;
initialValues: {
description: string;
creator: string;
@@ -33,29 +37,27 @@ export function DatasetEditForm({
datasetId,
datasetName,
isOwner,
+ hasApiKey,
+ isLocalUser,
initialValues,
features,
}: DatasetEditFormProps) {
const router = useRouter();
+ const locale = useLocale();
+ const { toast } = useToast();
const [values, setValues] = useState(initialValues);
const [saving, setSaving] = useState(false);
- const [error, setError] = useState(null);
- const [success, setSuccess] = useState(false);
const handleChange = (
field: keyof typeof values,
value: string,
) => {
setValues((prev) => ({ ...prev, [field]: value }));
- setError(null);
- setSuccess(false);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
- setError(null);
- setSuccess(false);
try {
const res = await fetch(`/api/datasets/${datasetId}/edit`, {
@@ -72,14 +74,21 @@ export function DatasetEditForm({
throw new Error(data.error || `Failed to save (${res.status})`);
}
- setSuccess(true);
- // Redirect back to dataset page after short delay
+ toast({
+ title: "Changes saved",
+ description: "Redirecting back to dataset...",
+ });
+
setTimeout(() => {
- router.push(`/datasets/${datasetId}`);
+ router.push(`/${locale}/datasets/${datasetId}`);
router.refresh();
}, 1500);
} catch (err) {
- setError(err instanceof Error ? err.message : "Failed to save changes");
+ toast({
+ title: "Failed to save",
+ description: err instanceof Error ? err.message : "Failed to save changes",
+ variant: "destructive",
+ });
} finally {
setSaving(false);
}
@@ -90,7 +99,7 @@ export function DatasetEditForm({
{/* Header */}
@@ -103,16 +112,18 @@ export function DatasetEditForm({
- {/* Status messages */}
- {error && (
-
-
- {error}
-
- )}
- {success && (
-
- Changes saved successfully! Redirecting...
+ {/* Warning: user has no valid OpenML API key (e.g. local dev account) */}
+ {(!hasApiKey || isLocalUser) && (
+
+
+
+
Saving is unavailable in this environment
+
+ {isLocalUser
+ ? "This account was created locally and does not have a valid OpenML API key. Dataset edits cannot be saved to the OpenML backend in a local development environment."
+ : "Your session does not include an OpenML API key. Saving changes requires signing in with a valid OpenML account."}
+