Skip to content

Commit 75f6be0

Browse files
committed
Implement Technology create/edit forms with full functionality
Add comprehensive technology management: Forms & Pages: - TechnologyForm component with create and edit modes - /tech/new - Create new technology page - /tech/[slug]/edit - Edit existing technology page - Full form validation and error handling - File upload for logos with preview - Tier/category selection Features: - Auto-generate slug from name for new technologies - Logo upload with preview - Field validation (name, slug, vendor, description, URLs) - Lock technology to prevent edits by others - Delete functionality for existing technologies - Proper authentication checks - Error display from API responses UI Improvements: - Updated Input component to support label prop - '+ Add Technology' button on /tech listing page - 'Edit' button on technology detail pages (authenticated users) - Responsive forms with grid layouts - Character counters on text fields Tech Stack Detail Page Fix: - Fixed technology display - now shows actual tech logos grouped by tier - Supports both logo display and text fallback - Proper tier grouping (Programming Languages, Server, Data, etc.) - Displays detailsHtml if available
1 parent 2e3f215 commit 75f6be0

File tree

7 files changed

+412
-33
lines changed

7 files changed

+412
-33
lines changed

nextjs-app/src/app/stacks/[slug]/page.tsx

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default function TechStackDetailPage({
1717
const [stack, setStack] = useState<any>(null);
1818
const [loading, setLoading] = useState(true);
1919
const { isAuthenticated } = useAuth();
20-
const { favoriteTechStackIds, addFavoriteTechStack, removeFavoriteTechStack } =
20+
const { favoriteTechStackIds, addFavoriteTechStack, removeFavoriteTechStack, config } =
2121
useAppStore();
2222

2323
useEffect(() => {
@@ -124,31 +124,50 @@ export default function TechStackDetailPage({
124124

125125
{stack.technologyChoices && stack.technologyChoices.length > 0 && (
126126
<div className="mt-8">
127-
<h2 className="text-2xl font-semibold mb-4">Technology Stack</h2>
128-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
129-
{stack.technologyChoices.map((choice: any) => (
130-
<div key={choice.technologyId} className="bg-gray-50 rounded-lg p-4">
131-
<div className="flex items-center gap-3">
132-
{choice.technology?.logoUrl && (
133-
<img
134-
src={choice.technology.logoUrl}
135-
alt={choice.technology.name}
136-
className="w-12 h-12 object-contain"
137-
/>
138-
)}
139-
<div className="flex-1">
127+
<h2 className="text-2xl font-semibold mb-6">Technologies used by {stack.name}</h2>
128+
{/* Group by tier */}
129+
{config?.allTiers?.map((tier: any) => {
130+
const tierTechs = stack.technologyChoices.filter((tech: any) => tech.tier === tier.name);
131+
if (tierTechs.length === 0) return null;
132+
133+
return (
134+
<div key={tier.name} className="mb-8">
135+
<h3 className="text-xl font-semibold text-gray-700 mb-4">{tier.title}</h3>
136+
<div className="flex flex-wrap gap-6 items-center">
137+
{tierTechs.map((tech: any) => (
140138
<Link
141-
href={routes.tech(choice.technology?.slug || '')}
142-
className="font-semibold text-gray-900 hover:text-primary-600"
139+
key={tech.id}
140+
href={routes.tech(tech.slug)}
141+
className="hover:opacity-80 transition-opacity"
142+
title={tech.name}
143143
>
144-
{choice.technology?.name}
144+
{tech.logoApproved && tech.logoUrl && (
145+
<img
146+
src={tech.logoUrl}
147+
alt={tech.name}
148+
className="max-w-[300px] max-h-20 object-contain"
149+
/>
150+
)}
151+
{(!tech.logoApproved || !tech.logoUrl) && (
152+
<div className="px-4 py-2 bg-gray-100 rounded">
153+
<span className="font-semibold text-gray-900">{tech.name}</span>
154+
</div>
155+
)}
145156
</Link>
146-
<p className="text-xs text-gray-600">{choice.tier}</p>
147-
</div>
157+
))}
148158
</div>
149159
</div>
150-
))}
151-
</div>
160+
);
161+
})}
162+
</div>
163+
)}
164+
165+
{stack.detailsHtml && (
166+
<div className="mt-8">
167+
<div
168+
className="prose max-w-none"
169+
dangerouslySetInnerHTML={{ __html: stack.detailsHtml }}
170+
/>
152171
</div>
153172
)}
154173
</div>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client';
2+
3+
import { use } from 'react';
4+
import { TechnologyForm } from '@/components/forms/TechnologyForm';
5+
6+
export default function EditTechnologyPage({
7+
params,
8+
}: {
9+
params: Promise<{ slug: string }>;
10+
}) {
11+
const { slug } = use(params);
12+
13+
return (
14+
<div className="container mx-auto px-4 py-8">
15+
<div className="max-w-4xl mx-auto">
16+
<TechnologyForm slug={slug} />
17+
</div>
18+
</div>
19+
);
20+
}

nextjs-app/src/app/tech/[slug]/page.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,26 @@ export default function TechnologyDetailPage({
102102
</a>
103103
)}
104104
</div>
105-
<button
106-
onClick={handleFavoriteToggle}
107-
className={`px-4 py-2 rounded ${
108-
isFavorite
109-
? 'bg-yellow-500 text-white'
110-
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
111-
}`}
112-
>
113-
{isFavorite ? '★ Favorited' : '☆ Favorite'}
114-
</button>
105+
<div className="flex gap-2">
106+
<button
107+
onClick={handleFavoriteToggle}
108+
className={`px-4 py-2 rounded ${
109+
isFavorite
110+
? 'bg-yellow-500 text-white'
111+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
112+
}`}
113+
>
114+
{isFavorite ? '★ Favorited' : '☆ Favorite'}
115+
</button>
116+
{isAuthenticated && (
117+
<Link
118+
href={`/tech/${slug}/edit`}
119+
className="px-4 py-2 bg-primary-600 text-white rounded hover:bg-primary-700"
120+
>
121+
Edit
122+
</Link>
123+
)}
124+
</div>
115125
</div>
116126

117127
{tech.description && (
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { TechnologyForm } from '@/components/forms/TechnologyForm';
2+
3+
export default function NewTechnologyPage() {
4+
return (
5+
<div className="container mx-auto px-4 py-8">
6+
<div className="max-w-4xl mx-auto">
7+
<TechnologyForm />
8+
</div>
9+
</div>
10+
);
11+
}

nextjs-app/src/app/tech/page.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,15 @@ export default function TechnologiesPage() {
3737
return (
3838
<div className="container mx-auto px-4 py-8">
3939
<div className="max-w-6xl mx-auto">
40-
<h1 className="text-4xl font-bold text-gray-900 mb-8">Technologies</h1>
40+
<div className="flex justify-between items-center mb-8">
41+
<h1 className="text-4xl font-bold text-gray-900">Technologies</h1>
42+
<Link
43+
href="/tech/new"
44+
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700"
45+
>
46+
+ Add Technology
47+
</Link>
48+
</div>
4149

4250
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
4351
{technologies.map((tech: any) => (

0 commit comments

Comments
 (0)