diff --git a/public/image-630x500.jpeg b/public/image-630x500.jpeg
new file mode 100644
index 00000000..c07c25ed
Binary files /dev/null and b/public/image-630x500.jpeg differ
diff --git a/src/components/Navigation/Header.tsx b/src/components/Navigation/Header.tsx
index 08cd5541..62b72949 100644
--- a/src/components/Navigation/Header.tsx
+++ b/src/components/Navigation/Header.tsx
@@ -63,7 +63,6 @@ const Header = () => {
} else {
setLoaded(true);
}
- console.log(currentUser);
const pathname = router.pathname;
const tabNames = Object.keys(tabLinks);
const index = tabNames.findIndex((name) => tabLinks[name] === pathname);
diff --git a/src/pages/games/create.tsx b/src/pages/games/create.tsx
index e23f473e..8fb8df2c 100644
--- a/src/pages/games/create.tsx
+++ b/src/pages/games/create.tsx
@@ -2,13 +2,12 @@ import React from "react";
import pageAccessHOC from "@/components/HOC/PageAccess";
import { z } from "zod";
import cn from "classnames";
-
+import { Upload } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { TextArea } from "@/components/ui/textarea";
-import { AlertTriangleIcon, MoveLeft, Plus } from "lucide-react";
-
-import { useState, useEffect } from "react";
+import { AlertTriangleIcon, MoveLeft, Plus, X } from "lucide-react";
+import { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import ThemeSelect from "@/components/Themes/ThemeSelect";
import TagSelect from "@/components/Tags/TagSelect";
@@ -28,9 +27,11 @@ import axios from "axios";
const NAME_FORM_KEY = "name";
const TRAILER_FORM_KEY = "videoTrailer";
const DESCR_FORM_KEY = "description";
+const IMAGE_FORM_KEY = "image";
export const createGameSchema = z.object({
name: z.string().min(3, "Title must be at least 3 characters"),
+ image: z.string().min(1, "Image is required"),
videoTrailer: z.string().url("Not a valid URL").or(z.literal("")),
description: z.string().min(1, "Description is required"),
});
@@ -106,7 +107,7 @@ async function uploadBuildFiles(gameId: string, files: Map) {
function CreateGame() {
const router = useRouter();
-
+ const hiddenImagePreviewRef = useRef(null);
const [themes, setThemes] = useState[]>([]);
const [selectedThemes, setSelectedThemes] = useState[]>([]);
@@ -131,6 +132,7 @@ function CreateGame() {
const [codeFile, setCodeFile] = useState(null);
const [frameworkFile, setFrameworkFile] = useState(null);
+ const [imagePreviewFile, setImagePreviewFile] = useState(null);
const [fileValidationError, setFileValidationError] = useState<
string | undefined
>(undefined);
@@ -183,6 +185,7 @@ function CreateGame() {
Record, string | undefined>
>({
name: undefined,
+ image: undefined,
videoTrailer: undefined,
description: undefined,
});
@@ -244,13 +247,47 @@ function CreateGame() {
setFileValidationError(undefined);
}
}
-
+ if (imagePreviewFile) {
+ if (
+ imagePreviewFile.type !== "image/png" &&
+ imagePreviewFile.type !== "image/jpg" &&
+ imagePreviewFile.type !== "image/jpeg"
+ ) {
+ setValidationErrors((prevValidationErrors) => ({
+ ...prevValidationErrors,
+ image: "Invalid Image: Only PNG, JPG, or JPEG permitted.",
+ }));
+ return;
+ }
+ const img = new Image();
+ img.src = URL.createObjectURL(imagePreviewFile);
+ img.onload = () => {
+ const naturalWidth = img.naturalWidth;
+ const naturalHeight = img.naturalHeight;
+ URL.revokeObjectURL(img.src);
+ if (naturalWidth !== 630 || naturalHeight !== 500) {
+ setValidationErrors((prevValidationErrors) => ({
+ ...prevValidationErrors,
+ image: "Image must have dimensions 630x500 pixels.",
+ }));
+ return;
+ }
+ };
+ img.onerror = () => {
+ setValidationErrors((prevValidationErrors) => ({
+ ...prevValidationErrors,
+ image: "Image failed to load",
+ }));
+ return;
+ };
+ }
const formData = new FormData(e.currentTarget);
const input = {
name: formData.get(NAME_FORM_KEY),
videoTrailer: formData.get(TRAILER_FORM_KEY),
description: formData.get(DESCR_FORM_KEY),
builds: builds,
+ image: imagePreviewFile ? "http://dummy-image-url.com" : "", // Temporary image
themes: selectedThemes.map((theme) => theme._id),
tags: [...selectedAccessibilityTags, ...selectedCustomTags].map(
(tag) => tag._id,
@@ -262,6 +299,7 @@ function CreateGame() {
if (parse.success) {
setValidationErrors({
name: undefined,
+ image: undefined,
videoTrailer: undefined,
description: undefined,
});
@@ -294,8 +332,10 @@ function CreateGame() {
} else {
setSubmitting(false);
const errors = parse.error.formErrors.fieldErrors;
+ console.log(errors);
setValidationErrors({
name: errors.name?.at(0),
+ image: errors.image?.at(0),
videoTrailer: errors.videoTrailer?.at(0),
description: errors.description?.at(0),
});
@@ -387,9 +427,67 @@ function CreateGame() {
"h-12",
)}
disabled={submitting}
+ onChange={() => {
+ setValidationErrors({ ...validationErrors, name: undefined });
+ }}
/>
+
+
+
+
+ {imagePreviewFile ? (
+
+
{imagePreviewFile.name}
+
{
+ setImagePreviewFile(null);
+ }}
+ />
+
+ ) : null}
+
+
) => {
+ if (
+ event.target.files === null ||
+ event.target.files.length === 0
+ )
+ return;
+ setImagePreviewFile(event.target.files[0]);
+ }}
+ >
+
@@ -445,6 +549,12 @@ function CreateGame() {
? "border-red-500 focus-visible:ring-red-500"
: "border-input-border focus:border-blue-primary"
}
+ onChange={() => {
+ setValidationErrors({
+ ...validationErrors,
+ description: undefined,
+ });
+ }}
/>
@@ -488,7 +598,9 @@ function CreateGame() {
? validationErrors.name
: validationErrors.videoTrailer
? validationErrors.videoTrailer
- : "All required fields need to be filled."}
+ : validationErrors.image
+ ? validationErrors.image
+ : "All required fields need to be filled."}
)}
diff --git a/src/server/db/actions/__mocks__/GameAction.ts b/src/server/db/actions/__mocks__/GameAction.ts
index 8a61f3e9..998b7481 100644
--- a/src/server/db/actions/__mocks__/GameAction.ts
+++ b/src/server/db/actions/__mocks__/GameAction.ts
@@ -20,6 +20,7 @@ function createRandomGame(): GamesFilterOutput[number] {
lesson: faker.internet.url(),
parentingGuide: faker.internet.url(),
answerKey: faker.internet.url(),
+ image: faker.internet.url(),
videoTrailer: faker.internet.url(),
builds: Array.from({ length: numBuilds }).map(() => {
return {
diff --git a/src/server/db/models/GameModel.ts b/src/server/db/models/GameModel.ts
index 5bbc456c..1dc50592 100644
--- a/src/server/db/models/GameModel.ts
+++ b/src/server/db/models/GameModel.ts
@@ -50,6 +50,7 @@ const GameSchema = new Schema(
answerKey: { type: String },
videoTrailer: { type: String },
preview: { type: Boolean, required: true },
+ image: { type: String, required: true },
},
{ versionKey: false },
);
diff --git a/src/utils/types/index.ts b/src/utils/types/index.ts
index c9cbe9f0..391561a0 100644
--- a/src/utils/types/index.ts
+++ b/src/utils/types/index.ts
@@ -103,6 +103,7 @@ export const gameSchema = z.object({
lesson: z.string().url().optional(),
parentingGuide: z.string().url().optional(),
answerKey: z.string().url().optional(),
+ image: z.string().min(1, "Image is required"),
videoTrailer: z.preprocess(
(val) => (val === "" ? undefined : val),
z.string().url().optional(),