Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 47 additions & 46 deletions apps/frontend-demo/src/app/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
'use client'
import { contactContent } from '@/data/contact-content'
import { useContactForm } from '@/hooks/use-contact-form'
import { Button } from '@techsio/ui-kit/atoms/button'
import { Icon, type IconType } from '@techsio/ui-kit/atoms/icon'
import { LinkButton } from '@techsio/ui-kit/atoms/link-button'
import { FormInput } from '@techsio/ui-kit/molecules/form-input'
import { FormTextarea } from '@techsio/ui-kit/molecules/form-textarea'
import { Select } from '@techsio/ui-kit/molecules/select'
import Image from 'next/image'
import contactImage from '/assets/hero/contact.webp'
"use client"
import { Button } from "@techsio/ui-kit/atoms/button"
import { Icon, type IconType } from "@techsio/ui-kit/atoms/icon"
import { LinkButton } from "@techsio/ui-kit/atoms/link-button"
import { FormInput } from "@techsio/ui-kit/molecules/form-input"
import { FormTextarea } from "@techsio/ui-kit/molecules/form-textarea"
import { Select } from "@techsio/ui-kit/molecules/select"
import { slugify } from "@techsio/ui-kit/utils"
import Image from "next/image"
import { contactContent } from "@/data/contact-content"
import { useContactForm } from "@/hooks/use-contact-form"
import contactImage from "/assets/hero/contact.webp"
Comment on lines +1 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃Ч Nitpick | 馃數 Trivial

Scope concern: Multiple unrelated changes included in this PR.

This PR includes several changes that are unrelated to the stated objective of replacing index-based React keys:

  1. Image priority prop (line 28-29): Adding priority={true} is an optimisation change
  2. Form field props (lines 64-65, 74-75, 85-87, etc.): Adding explicit size, value, rows, and type props appears to be form structure refinement
  3. Icon type casting (line 150): The as IconType cast is a TypeScript typing change
  4. LinkButton hover styles (line 183): Adding hover:bg-transparent is a styling change
  5. Quote normalization (lines 61, 71, 81, etc.): Converting single to double quotes is a style change

Including multiple concerns in a single PR makes it harder to:

  • Review each change on its merits
  • Understand the purpose from the commit history
  • Roll back specific changes if needed

Consider splitting these into separate commits or PRs aligned with the conventional commit guidelines (e.g., fix: for keys, style: for quotes, refactor: for form props, perf: for image priority).

Also applies to: 28-29, 57-132, 150-150, 183-183

馃 Prompt for AI Agents
In apps/frontend-demo/src/app/contact/page.tsx (lines 1-12, 28-29, 57-132, 150,
183) this PR mixes the intended key-fix with unrelated changes (image priority,
form prop additions, icon type cast, style tweak, and quote normalization);
revert or remove all unrelated edits from this branch so the PR contains only
the index-key fix, then create separate commits/PRs for: the perf change (add
priority on Image), form refinements (size/value/type/rows), TypeScript cast for
Icon, styling change to LinkButton, and quote/style-only changes鈥攗se focused
branch per concern and clear conventional commit messages (fix:, perf:,
refactor:, style:) before reopening the PR.


export default function ContactPage() {
const { hero, form, info, hours, help } = contactContent
const { hero, form, info, help } = contactContent
const { formData, updateField, handleSubmit, isSubmitting } = useContactForm()

return (
<>
{/* Hero Section */}
<section className="relative flex h-about-hero-height items-center bg-center bg-cover md:h-about-hero-height-md">
<Image
src={contactImage}
alt="Na拧e kancel谩艡"
className="-z-1 brightness-60"
fill
objectFit="cover"
priority={true}
placeholder="blur"
priority={true}
src={contactImage}
/>
<div className="inset-0 mx-auto max-w-container-max px-container-x text-white">
<div className="text-center">
Expand All @@ -40,14 +41,14 @@ export default function ContactPage() {
</section>

{/* Contact Content */}
<section id="contact-form" className="py-contact-content-y">
<section className="py-contact-content-y" id="contact-form">
<div className="mx-auto max-w-container-max px-container-x">
<div className="grid grid-cols-1 gap-contact-grid-gap lg:grid-cols-3">
{/* Contact Form */}
<div className="lg:col-span-2">
<form
onSubmit={handleSubmit}
className="grid rounded-contact-form bg-fill-base/20 p-contact-form-padding shadow-contact-form"
onSubmit={handleSubmit}
>
<h2 className="mb-contact-form-title-bottom font-contact-form-title text-contact-form-title-fg text-contact-form-title-size">
{form.title}
Expand All @@ -56,78 +57,78 @@ export default function ContactPage() {
<FormInput
id="firstName"
label={form.labels.firstName}
size="sm"
value={formData.firstName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateField('firstName', e.target.value)
updateField("firstName", e.target.value)
}
required
size="sm"
value={formData.firstName}
/>
<FormInput
id="lastName"
label={form.labels.lastName}
size="sm"
value={formData.lastName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateField('lastName', e.target.value)
updateField("lastName", e.target.value)
}
required
size="sm"
value={formData.lastName}
/>
<FormInput
id="email"
label={form.labels.email}
type="email"
size="sm"
value={formData.email}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateField('email', e.target.value)
updateField("email", e.target.value)
}
required
size="sm"
type="email"
value={formData.email}
/>
<FormInput
id="phone"
label={form.labels.phone}
type="tel"
size="sm"
value={formData.phone}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateField('phone', e.target.value)
updateField("phone", e.target.value)
}
required
size="sm"
type="tel"
value={formData.phone}
/>
<div className="space-y-contact-field-gap md:col-span-2">
<Select
label={form.labels.subject}
options={form.subjects}
value={[formData.subject]}
onValueChange={(details) =>
updateField('subject', details.value[0] || 'general')
updateField("subject", details.value[0] || "general")
}
size="sm"
options={form.subjects}
placeholder="Vyberte t茅ma"
size="sm"
value={[formData.subject]}
/>
</div>
<div className="md:col-span-2">
<FormTextarea
id="message"
label={form.labels.message}
rows={6}
size="sm"
value={formData.message}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
updateField('message', e.target.value)
updateField("message", e.target.value)
}
required
rows={6}
size="sm"
value={formData.message}
/>
</div>
</div>
<Button
type="submit"
className="mt-6 place-self-end"
size="sm"
disabled={isSubmitting}
size="sm"
type="submit"
>
{isSubmitting ? 'Odes铆l谩m...' : form.labels.submit}
{isSubmitting ? "Odes铆l谩m..." : form.labels.submit}
</Button>
</form>
</div>
Expand All @@ -139,23 +140,23 @@ export default function ContactPage() {
<h3 className="mb-contact-info-title-bottom font-contact-info-title text-contact-info-title-fg text-contact-info-title-size">
{info.title}
</h3>
{info.items.map((item, index) => (
{info.items.map((item) => (
<div
key={index}
className="mb-contact-info-item-gap flex items-start space-x-contact-info-icon-gap"
key={slugify(item.label)}
>
<Icon
icon={item.icon as IconType}
className="h-6 text-md"
icon={item.icon as IconType}
/>
<div>
<p className="text-contact-info-text-fg text-contact-info-text-size">
{item.label}
</p>
{item.link ? (
<a
href={item.link}
className="text-contact-info-link-fg transition-colors hover:text-contact-info-link-fg-hover"
href={item.link}
>
{item.value}
</a>
Expand All @@ -179,10 +180,10 @@ export default function ContactPage() {
ot谩zky pro okam啪itou pomoc.
</p>
<LinkButton
className="px-0 py-0 hover:bg-transparent"
href="/faq"
size="sm"
theme="borderless"
className="px-0 py-0 hover:bg-transparent"
>
<span className="underline">膶asto kladen茅 dotazy</span>
</LinkButton>
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend-demo/src/components/account/orders-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function OrdersSkeleton({ itemsCount }: { itemsCount: number }) {
<div className="block space-y-3 sm:hidden">
{maxItems.map((_, i) => (
<div
key={i}
key={`mobile-skeleton-${i}`}
className="rounded-sm border border-orders-border bg-orders-card-bg p-sm"
>
<div className="mb-2xs flex items-start justify-between">
Expand Down Expand Up @@ -67,7 +67,7 @@ export function OrdersSkeleton({ itemsCount }: { itemsCount: number }) {
</div>
{maxItems.map((_, i) => (
<div
key={i}
key={`desktop-skeleton-${i}`}
className="hidden grid-cols-12 gap-orders-card border-orders-border border-b p-sm sm:grid"
>
<SkeletonLoader className="col-span-12 h-12" />
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend-demo/src/components/molecules/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export function Navigation({ items, className, ...props }: NavigationProps) {
return (
<nav className="bg-nav-bg" {...props}>
<ul className="flex items-center gap-nav-gap">
{items.map((item, index) => (
<NavigationItem key={index} item={item} />
{items.map((item) => (
<NavigationItem key={slugify(item.title)} item={item} />
))}
Comment on lines +80 to 82

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃Ч Nitpick | 馃數 Trivial

馃З Analysis chain

馃弫 Script executed:

#!/bin/bash
# Find navigation-related files and data definitions
find apps/frontend-demo -type f -name "*.ts" -o -name "*.tsx" -o -name "*.json" | \
  xargs grep -l "navigation\|nav.*item" -i | head -20

Repository: NMIT-WR/new-engine

Length of output: 769


馃弫 Script executed:

#!/bin/bash
# Look for the navigation.tsx file and check its structure
if [ -f "apps/frontend-demo/src/components/molecules/navigation.tsx" ]; then
  echo "=== Navigation.tsx file found ==="
  cat "apps/frontend-demo/src/components/molecules/navigation.tsx"
else
  echo "File not found. Searching for navigation files..."
  find . -name "navigation.tsx" -o -name "navigation.ts" | grep -v node_modules
fi

Repository: NMIT-WR/new-engine

Length of output: 2806


馃弫 Script executed:

#!/bin/bash
# Search for where navigation items/data are defined
rg "export.*nav.*items|navigation.*:.*\[" --type ts --type tsx -A 10 | head -100

Repository: NMIT-WR/new-engine

Length of output: 89


馃弫 Script executed:

#!/bin/bash
# Find files that import/use Navigation component
rg "import.*Navigation|<Navigation" --type ts --type tsx -B 2 -A 5 | head -150

Repository: NMIT-WR/new-engine

Length of output: 89


馃弫 Script executed:

#!/bin/bash
# Find files that import or use the Navigation component
grep -r "import.*Navigation\|<Navigation" apps/frontend-demo/src --include="*.ts" --include="*.tsx" -B 2 -A 5 | head -200

Repository: NMIT-WR/new-engine

Length of output: 2103


馃弫 Script executed:

#!/bin/bash
# Find where navigationItems is defined in header.tsx
grep -n "navigationItems" apps/frontend-demo/src/components/header.tsx -B 10 -A 2 | head -100

Repository: NMIT-WR/new-engine

Length of output: 1576


馃弫 Script executed:

#!/bin/bash
# Find where Header component is used and where navigationItems come from
grep -r "<Header\|navigationItems:" apps/frontend-demo/src --include="*.ts" --include="*.tsx" -B 3 -A 10 | head -250

Repository: NMIT-WR/new-engine

Length of output: 4722


馃弫 Script executed:

#!/bin/bash
# Find the full definition of categoryItems
grep -n "categoryItems" apps/frontend-demo/src/components/header-wrapper.tsx -B 30 | head -50

Repository: NMIT-WR/new-engine

Length of output: 1454


馃弫 Script executed:

#!/bin/bash
# Check the full header-wrapper.tsx file to see the complete context
cat apps/frontend-demo/src/components/header-wrapper.tsx

Repository: NMIT-WR/new-engine

Length of output: 1607


馃弫 Script executed:

#!/bin/bash
# Check if there are any other places where Navigation items might be used dynamically
grep -r "navigationItems\|NavItem\[\]" apps/frontend-demo/src --include="*.ts" --include="*.tsx" | grep -v "node_modules"

Repository: NMIT-WR/new-engine

Length of output: 1250


馃弫 Script executed:

#!/bin/bash
# Extract all navigation titles to verify uniqueness
cat > /tmp/check_slugs.js << 'EOF'
const titles = [
  'Dom暖',
  'Produkty',
  'Obl铆ben茅',
  'O n谩s',
  'Kontakt',
  'M臎sto',
  'Zimn铆',
  'Obuv',
  'Sport'
];

// Simple slugify approximation
function slugify(str) {
  return str
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .trim();
}

const slugs = titles.map(t => ({ title: t, slug: slugify(t) }));
console.log('Title -> Slug mapping:');
slugs.forEach(s => console.log(`  ${s.title} -> ${s.slug}`));

const uniqueSlugs = new Set(slugs.map(s => s.slug));
console.log(`\nTotal titles: ${titles.length}`);
console.log(`Unique slugs: ${uniqueSlugs.size}`);
if (titles.length !== uniqueSlugs.size) {
  console.log('鈿狅笍  DUPLICATES FOUND!');
  slugs.forEach(s => {
    const count = slugs.filter(x => x.slug === s.slug).length;
    if (count > 1) {
      console.log(`  "${s.title}" produces slug "${s.slug}" (${count} items)`);
    }
  });
}
EOF
node /tmp/check_slugs.js

Repository: NMIT-WR/new-engine

Length of output: 282


Consider augmenting the key with additional context to prevent potential collisions.

The current implementation with slugify(item.title) as a key works correctly for the existing navigation data (all titles are unique). However, to guard against future issues if duplicate titles are added, consider using a composite key. Since each NavItem has an href property, you could strengthen the key: key={slugify(item.title)}-${item.href} or similar. This follows React's requirement that keys be unique among siblings more defensively and would prevent unexpected behaviour if the navigation data changes.

Note: This pattern also applies to the submenu children at line 43.

馃 Prompt for AI Agents
In apps/frontend-demo/src/components/molecules/navigation.tsx around lines 80-82
(and apply similarly to submenu children near line 43), the key uses only
slugify(item.title) which can collide if titles repeat; change the key to a
composite value that includes another stable field such as item.href (for
example combine slugified title and href) so each sibling key is unique and
resistant to future duplicate titles; update both the top-level NavigationItem
and submenu child keys accordingly to use the composite key.

</ul>
</nav>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function ProductGridSkeleton({
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
{[...Array(numberOfItems)].map((_, i) => (
<div key={i} className="space-y-3">
<div key={`product-skeleton-${i}`} className="space-y-3">
<SkeletonLoader
variant="box"
size="fit"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function ProductInfo({
{badges.length > 0 && (
<div className="mb-product-info-badge-margin flex flex-wrap gap-product-info-badge-gap">
{badges.map((badge, idx) => (
<Badge key={idx} {...badge} />
<Badge key={`badge-${badge.variant}-${idx}`} {...badge} />
))}
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend-demo/src/components/stats-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function StatsSection() {
colorClasses[stat.color as keyof typeof colorClasses]

return (
<div key={index} className="relative">
<div key={`stat-${stat.year}`} className="relative">
{/* Main content */}
<div className="relative flex h-[20rem] flex-col justify-between overflow-hidden rounded-[1rem] border border-border-subtle bg-fill-base/50 p-[2rem] text-center shadow-sm md:h-[22rem] lg:h-[20rem]">
{/* Top section */}
Expand Down
78 changes: 39 additions & 39 deletions apps/frontend-demo/src/data/contact-content.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface ContactInfo {
type: 'email' | 'phone' | 'address'
type: "email" | "phone" | "address"
label: string
value: string
link?: string
Expand Down Expand Up @@ -52,66 +52,66 @@ export interface ContactContent {

export const contactContent: ContactContent = {
hero: {
title: 'Kontaktujte n谩s',
title: "Kontaktujte n谩s",
subtitle:
'M谩te ot谩zku nebo pot艡ebujete pomoc? Jsme tu pro v谩s! Obra钮te se na n谩拧 p艡谩telsk媒 t媒m podpory a ozv臎me se v谩m co nejd艡铆ve.',
"M谩te ot谩zku nebo pot艡ebujete pomoc? Jsme tu pro v谩s! Obra钮te se na n谩拧 p艡谩telsk媒 t媒m podpory a ozv臎me se v谩m co nejd艡铆ve.",
},
form: {
title: 'Po拧lete n谩m zpr谩vu',
title: "Po拧lete n谩m zpr谩vu",
subjects: [
{ value: 'general', label: 'Obecn媒 dotaz' },
{ value: 'order', label: 'Podpora objedn谩vek' },
{ value: 'shipping', label: 'Ot谩zka o doru膷en铆' },
{ value: 'returns', label: 'Vracen铆 a v媒m臎ny' },
{ value: 'wholesale', label: 'Velkoobchodn铆 dotaz' },
{ value: 'other', label: 'Jin茅' },
{ value: "general", label: "Obecn媒 dotaz" },
{ value: "order", label: "Podpora objedn谩vek" },
{ value: "shipping", label: "Ot谩zka o doru膷en铆" },
{ value: "returns", label: "Vracen铆 a v媒m臎ny" },
{ value: "wholesale", label: "Velkoobchodn铆 dotaz" },
{ value: "other", label: "Jin茅" },
],
labels: {
firstName: 'Jm茅no',
lastName: 'P艡铆jmen铆',
email: 'E-mailov谩 adresa',
phone: 'Telefonn铆 膷铆slo',
subject: 'P艡edm臎t',
message: 'Zpr谩va',
submit: 'Odeslat zpr谩vu',
firstName: "Jm茅no",
lastName: "P艡铆jmen铆",
email: "E-mailov谩 adresa",
phone: "Telefonn铆 膷铆slo",
subject: "P艡edm臎t",
message: "Zpr谩va",
submit: "Odeslat zpr谩vu",
},
successMessage: {
title: 'Zpr谩va odesl谩na!',
description: 'Ozv臎me se v谩m co nejd艡铆ve.',
title: "Zpr谩va odesl谩na!",
description: "Ozv臎me se v谩m co nejd艡铆ve.",
},
},
info: {
title: 'Spojte se s n谩mi',
title: "Spojte se s n谩mi",
items: [
{
type: 'email',
label: 'Napi拧te n谩m na:',
value: 'pavel.koudelka@naucme.it',
link: 'mailto:pavel.koudelka@naucme.it',
icon: 'token-icon-email',
type: "email",
label: "Napi拧te n谩m na:",
value: "pavel.koudelka@naucme.it",
link: "mailto:pavel.koudelka@naucme.it",
icon: "token-icon-email",
},
{
type: 'phone',
label: 'Zavolejte n谩m na:',
value: '+420 731 472 822',
link: 'tel:+420731472822',
icon: 'token-icon-phone',
type: "phone",
label: "Zavolejte n谩m na:",
value: "+420 731 472 822",
link: "tel:+420731472822",
icon: "token-icon-phone",
},
],
},
hours: {
title: 'Provozn铆 doba',
title: "Provozn铆 doba",
schedule: [
{ day: 'Pond臎l铆 - P谩tek', hours: '9:00 - 18:00' },
{ day: 'Sobota', hours: '10:00 - 16:00' },
{ day: 'Ned臎le', hours: 'Zav艡eno' },
{ day: "Pond臎l铆 - P谩tek", hours: "9:00 - 18:00" },
{ day: "Sobota", hours: "10:00 - 16:00" },
{ day: "Ned臎le", hours: "Zav艡eno" },
],
timezone: 'V拧echny 膷asy jsou ve st艡edoevropsk茅m 膷ase (SE膶)',
timezone: "V拧echny 膷asy jsou ve st艡edoevropsk茅m 膷ase (SE膶)",
},
help: {
title: 'Rychl谩 pomoc',
description: 'Hled谩te rychl茅 odpov臎di? Pod铆vejte se na na拧e',
linkText: '膶asto kladen茅 ot谩zky',
linkHref: '/faq',
title: "Rychl谩 pomoc",
description: "Hled谩te rychl茅 odpov臎di? Pod铆vejte se na na拧e",
linkText: "膶asto kladen茅 ot谩zky",
linkHref: "/faq",
},
}
2 changes: 1 addition & 1 deletion libs/ui/src/atoms/rating.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function Rating({
<input {...api.getHiddenInputProps()} />
<div className={control()} {...api.getControlProps()}>
{api.items.map((index) => (
<div key={index} className={itemWrapper()}>
<div key={`star-${index}`} className={itemWrapper()}>
<span className={item()} {...api.getItemProps({ index })} />
</div>
))}
Expand Down
2 changes: 1 addition & 1 deletion libs/ui/src/atoms/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Skeleton.Text = function SkeletonText({

return (
<div
key={index}
key={`skeleton-text-${index}`}
className={styles.root({
className: styles.textLine({ className }),
})}
Expand Down
Loading