diff --git a/backend/models/project.js b/backend/models/project.js index 52fde35..2abdcc5 100644 --- a/backend/models/project.js +++ b/backend/models/project.js @@ -19,6 +19,23 @@ module.exports = (sequelize) => { name: { type: DataTypes.STRING, allowNull: false, + validate: { + notEmpty: { + msg: 'Project name is required', + }, + wordCount(value) { + const MAX_WORDS = 6; + const wordCount = value + .trim() + .split(/\s+/) + .filter((word) => word.length > 0).length; + if (wordCount > MAX_WORDS) { + throw new Error( + `Project name must be ${MAX_WORDS} words or less` + ); + } + }, + }, }, description: { type: DataTypes.TEXT, diff --git a/frontend/components/Project/ProjectBanner.tsx b/frontend/components/Project/ProjectBanner.tsx index 3211620..03d1f0c 100644 --- a/frontend/components/Project/ProjectBanner.tsx +++ b/frontend/components/Project/ProjectBanner.tsx @@ -45,6 +45,23 @@ const ProjectBanner: React.FC = ({ return (
+
{project.image_url ? ( = ({ )}
-
-

+
+

{project.name}

{project.description && ( -

+

{project.description}

)} diff --git a/frontend/components/Project/ProjectModal.tsx b/frontend/components/Project/ProjectModal.tsx index ca8da87..ee7d554 100644 --- a/frontend/components/Project/ProjectModal.tsx +++ b/frontend/components/Project/ProjectModal.tsx @@ -268,6 +268,21 @@ const ProjectModal: React.FC = ({ return; } + const MAX_WORDS = 6; + const wordCount = formData.name + .trim() + .split(/\s+/) + .filter((word) => word.length > 0).length; + if (wordCount > MAX_WORDS) { + setError( + t( + 'errors.projectNameTooLong', + `Project name must be ${MAX_WORDS} words or less` + ) + ); + return; + } + setIsSaving(true); try { // Add new tags to the global store diff --git a/frontend/styles/tailwind.css b/frontend/styles/tailwind.css index a3e0e2c..538d335 100644 --- a/frontend/styles/tailwind.css +++ b/frontend/styles/tailwind.css @@ -170,6 +170,21 @@ select:focus { display: none; /* Chrome/Safari */ } +/* Project name and description line clamp */ +.project-name-clamp { + display: -webkit-box !important; + -webkit-box-orient: vertical !important; + -webkit-line-clamp: 3 !important; + overflow: hidden !important; +} + +.project-desc-clamp { + display: -webkit-box !important; + -webkit-box-orient: vertical !important; + -webkit-line-clamp: 2 !important; + overflow: hidden !important; +} + @layer utilities { .line-clamp-1, .line-clamp-2,