Skip to content

Commit 6346626

Browse files
feat: add competition details and API integration, including new fields and loading states
1 parent 545ac3f commit 6346626

File tree

7 files changed

+352
-113
lines changed

7 files changed

+352
-113
lines changed

fe-main/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_API_BASE_URL=http://localhost:5000

fe-main/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ dist-ssr
2222
*.njsproj
2323
*.sln
2424
*.sw?
25+
26+
# environment
27+
.env

fe-main/src/components/CompetitionCard.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export interface CompetitionProps {
1515
deadline: string;
1616
teamSize: string;
1717
prize: string;
18+
tkNumber?: string;
19+
t3kysNumber?: string;
20+
years?: number[];
21+
applicationLink?: string;
1822
}
1923

2024
export default function CompetitionCard({ competition, index }: { competition: CompetitionProps; index: number }) {
@@ -82,6 +86,23 @@ export default function CompetitionCard({ competition, index }: { competition: C
8286
>
8387
{statusText[competition.status]}
8488
</Badge>
89+
<div className="absolute top-4 left-4 rtl:right-4 rtl:left-auto z-20 flex flex-wrap gap-2 max-w-[70%]">
90+
{competition.tkNumber && (
91+
<Badge variant="outline" className="backdrop-blur-md border-primary/30 text-primary">
92+
{t("card.tkNumber")}: {competition.tkNumber}
93+
</Badge>
94+
)}
95+
{competition.t3kysNumber && (
96+
<Badge variant="outline" className="backdrop-blur-md border-primary/30 text-primary">
97+
{t("card.t3kysNumber")}: {competition.t3kysNumber}
98+
</Badge>
99+
)}
100+
{competition.years && competition.years.length > 0 && (
101+
<Badge variant="outline" className="backdrop-blur-md border-primary/30 text-primary">
102+
{t("card.years")}: {competition.years.join(", ")}
103+
</Badge>
104+
)}
105+
</div>
85106
<div className="absolute bottom-4 left-4 rtl:right-4 rtl:left-auto z-20">
86107
<span className="text-primary text-xs font-bold uppercase tracking-wider mb-1 block">
87108
{competition.category}
@@ -107,6 +128,16 @@ export default function CompetitionCard({ competition, index }: { competition: C
107128
<span>{t('card.prizePool')}: {competition.prize}</span>
108129
</div>
109130
</div>
131+
{competition.applicationLink && (
132+
<div className="mt-4 flex items-center justify-between text-sm text-muted-foreground">
133+
<span>{t("card.applyLink")}</span>
134+
<Button asChild variant="link" className="px-0 text-primary">
135+
<a href={competition.applicationLink} target="_blank" rel="noreferrer">
136+
{t("card.applyLink")}
137+
</a>
138+
</Button>
139+
</div>
140+
)}
110141
</CardContent>
111142

112143
<CardFooter className="pb-6 pt-0">

fe-main/src/lib/competitions.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { type Language } from "@/lib/translations";
2+
3+
export interface CompetitionApi {
4+
id: number;
5+
created_at: string;
6+
updated_at: string;
7+
deleted_at: string | null;
8+
image_path: string | null;
9+
tk_number: string | null;
10+
t3kys_number: string | null;
11+
application_link_tr: string | null;
12+
application_link_en: string | null;
13+
application_link_ar: string | null;
14+
tr_name: string | null;
15+
tr_description: string | null;
16+
tr_link: string | null;
17+
en_name: string | null;
18+
en_description: string | null;
19+
en_link: string | null;
20+
ar_name: string | null;
21+
ar_description: string | null;
22+
ar_link: string | null;
23+
years: number[];
24+
min_member: number | null;
25+
max_member: number | null;
26+
}
27+
28+
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "";
29+
30+
export const buildApiUrl = (path: string) => {
31+
if (path.startsWith("http")) return path;
32+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
33+
return `${API_BASE_URL}${normalizedPath}`;
34+
};
35+
36+
const localeOrder = (language: Language): Language[] => {
37+
const order: Language[] = [language, "en", "tr", "ar"];
38+
return Array.from(new Set(order));
39+
};
40+
41+
export const pickLocalizedField = (
42+
competition: CompetitionApi,
43+
language: Language,
44+
suffix: "name" | "description" | "link" | "application_link",
45+
): string | null => {
46+
for (const locale of localeOrder(language)) {
47+
const key = `${locale}_${suffix}` as keyof CompetitionApi;
48+
const value = competition[key];
49+
if (typeof value === "string" && value.trim()) {
50+
return value;
51+
}
52+
}
53+
return null;
54+
};
55+
56+
export const formatTeamSize = (
57+
minMember: number | null,
58+
maxMember: number | null,
59+
membersLabel: string,
60+
fallback: string,
61+
) => {
62+
if (minMember && maxMember) return `${minMember}-${maxMember} ${membersLabel}`;
63+
if (minMember && !maxMember) return `${minMember}+ ${membersLabel}`;
64+
if (!minMember && maxMember) return `${membersLabel} <= ${maxMember}`;
65+
return fallback;
66+
};

fe-main/src/lib/translations.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ export const translations = {
3232
transportation: "Transportation",
3333
noResults: "No competitions found",
3434
tryAdjusting: "Try adjusting your search or category filter.",
35-
reset: "Reset Filters"
35+
reset: "Reset Filters",
36+
loading: "Loading competitions...",
37+
error: "Unable to load competitions."
3638
},
3739
card: {
3840
applicationsOpen: "Applications Open",
@@ -42,13 +44,27 @@ export const translations = {
4244
teamSize: "Team Size",
4345
prizePool: "Prize Pool",
4446
viewDetails: "View Details",
45-
members: "Members"
47+
members: "Members",
48+
notSpecified: "Not specified",
49+
tkNumber: "TK No",
50+
t3kysNumber: "T3KYS No",
51+
years: "Years",
52+
applyLink: "Apply"
4653
},
4754
cta: {
4855
title: "Ready to Change the World?",
4956
description: "Don't miss the chance to showcase your projects at the world's biggest technology festival. Applications are closing soon!",
5057
register: "Register Now"
5158
},
59+
detail: {
60+
noDescription: "Description will be added soon.",
61+
noYears: "Years not set",
62+
loading: "Loading competition details...",
63+
error: "Unable to load this competition.",
64+
deadlineFallback: "To be announced",
65+
prizeFallback: "Prize to be announced",
66+
yearsLabel: "Years"
67+
},
5268
footer: {
5369
description: "The world's largest aviation, space, and technology festival. Building a technology-producing Turkey.",
5470
quickLinks: "Quick Links",
@@ -99,7 +115,9 @@ export const translations = {
99115
transportation: "Ulaşım",
100116
noResults: "Yarışma bulunamadı",
101117
tryAdjusting: "Aramanızı veya kategori filtrenizi değiştirmeyi deneyin.",
102-
reset: "Filtreleri Sıfırla"
118+
reset: "Filtreleri Sıfırla",
119+
loading: "Yarışmalar yükleniyor...",
120+
error: "Yarışmalar yüklenemedi."
103121
},
104122
card: {
105123
applicationsOpen: "Başvurular Açık",
@@ -109,13 +127,27 @@ export const translations = {
109127
teamSize: "Takım Boyutu",
110128
prizePool: "Ödül Havuzu",
111129
viewDetails: "Detayları Gör",
112-
members: "Üye"
130+
members: "Üye",
131+
notSpecified: "Belirtilmedi",
132+
tkNumber: "TK No",
133+
t3kysNumber: "T3KYS No",
134+
years: "Yıllar",
135+
applyLink: "Başvur"
113136
},
114137
cta: {
115138
title: "Dünyayı Değiştirmeye Hazır Mısın?",
116139
description: "Projelerinizi dünyanın en büyük teknoloji festivalinde sergileme şansını kaçırmayın. Başvurular yakında kapanıyor!",
117140
register: "Hemen Kayıt Ol"
118141
},
142+
detail: {
143+
noDescription: "Açıklama yakında eklenecek.",
144+
noYears: "Yıllar ayarlanmadı",
145+
loading: "Yarışma detayları yükleniyor...",
146+
error: "Bu yarışma yüklenemedi.",
147+
deadlineFallback: "Duyurulacak",
148+
prizeFallback: "Ödül duyurulacak",
149+
yearsLabel: "Yıllar"
150+
},
119151
footer: {
120152
description: "Dünyanın en büyük havacılık, uzay ve teknoloji festivali. Teknoloji üreten bir Türkiye inşa ediyoruz.",
121153
quickLinks: "Hızlı Bağlantılar",
@@ -166,7 +198,9 @@ export const translations = {
166198
transportation: "النقل",
167199
noResults: "لم يتم العثور على مسابقات",
168200
tryAdjusting: "حاول تعديل البحث أو تصفية الفئات.",
169-
reset: "إعادة تعيين المرشحات"
201+
reset: "إعادة تعيين المرشحات",
202+
loading: "جارٍ تحميل المسابقات...",
203+
error: "تعذر تحميل المسابقات."
170204
},
171205
card: {
172206
applicationsOpen: "التطبيقات مفتوحة",
@@ -176,13 +210,27 @@ export const translations = {
176210
teamSize: "حجم الفريق",
177211
prizePool: "مجموع الجوائز",
178212
viewDetails: "عرض التفاصيل",
179-
members: "أعضاء"
213+
members: "أعضاء",
214+
notSpecified: "غير محدد",
215+
tkNumber: "رقم TK",
216+
t3kysNumber: "رقم T3KYS",
217+
years: "الأعوام",
218+
applyLink: "تقديم"
180219
},
181220
cta: {
182221
title: "هل أنت مستعد لتغيير العالم؟",
183222
description: "لا تفوت فرصة عرض مشاريعك في أكبر مهرجان للتكنولوجيا في العالم. التسجيل يغلق قريباً!",
184223
register: "سجل الآن"
185224
},
225+
detail: {
226+
noDescription: "سيضاف الوصف قريباً.",
227+
noYears: "لم يتم تحديد الأعوام",
228+
loading: "جارٍ تحميل تفاصيل المسابقة...",
229+
error: "تعذر تحميل هذه المسابقة.",
230+
deadlineFallback: "سيتم الإعلان لاحقاً",
231+
prizeFallback: "سيتم الإعلان عن الجائزة لاحقاً",
232+
yearsLabel: "الأعوام"
233+
},
186234
footer: {
187235
description: "أكبر مهرجان للطيران والفضاء والتكنولوجيا في العالم. نبني تركيا منتجة للتكنولوجيا.",
188236
quickLinks: "روابط سريعة",

0 commit comments

Comments
 (0)