| 1 | import type { Metadata } from "next"; |
| 2 | import { allKtis } from "contentlayer/generated"; |
| 3 | import Link from "next/link"; |
| 4 | import { StructuredData } from "@/components/structured-data"; |
| 5 | import siteMetadata from "@/content/siteMetadata"; |
| 6 | import { absoluteUrl, genPageMetadata } from "../seo"; |
| 7 | |
| 8 | export const metadata: Metadata = { |
| 9 | ...genPageMetadata({ |
| 10 | title: "KTI - Kamus Teknologi Informasi", |
| 11 | description: |
| 12 | "Kamus Teknologi Informasi lengkap untuk tenaga kerja muda Indonesia. Pelajari istilah-istilah IT, pemrograman, cloud computing, dan teknologi terkini dengan penjelasan dalam Bahasa Indonesia.", |
| 13 | path: "/kti", |
| 14 | locale: "id_ID", |
| 15 | }), |
| 16 | keywords: [ |
| 17 | "kamus teknologi informasi", |
| 18 | "istilah IT", |
| 19 | "glosarium teknologi", |
| 20 | "bahasa Indonesia", |
| 21 | "pembelajaran IT", |
| 22 | "karir IT Indonesia", |
| 23 | "cloud computing", |
| 24 | "pemrograman", |
| 25 | "software development", |
| 26 | "teknologi informasi", |
| 27 | ], |
| 28 | openGraph: { |
| 29 | title: "KTI - Kamus Teknologi Informasi untuk Profesional IT Indonesia", |
| 30 | description: |
| 31 | "Panduan lengkap istilah teknologi informasi dalam Bahasa Indonesia untuk membantu karir IT Anda", |
| 32 | url: `${siteMetadata.siteUrl}/kti`, |
| 33 | siteName: siteMetadata.title, |
| 34 | locale: "id_ID", |
| 35 | type: "website", |
| 36 | }, |
| 37 | }; |
| 38 | |
| 39 | const difficultyConfig = { |
| 40 | pemula: { |
| 41 | label: "Pemula", |
| 42 | badge: |
| 43 | "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/20", |
| 44 | bar: "bg-emerald-500", |
| 45 | tag: "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 hover:bg-emerald-100 dark:hover:bg-emerald-500/20", |
| 46 | }, |
| 47 | menengah: { |
| 48 | label: "Menengah", |
| 49 | badge: |
| 50 | "bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20", |
| 51 | bar: "bg-amber-500", |
| 52 | tag: "bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 hover:bg-amber-100 dark:hover:bg-amber-500/20", |
| 53 | }, |
| 54 | lanjut: { |
| 55 | label: "Lanjut", |
| 56 | badge: |
| 57 | "bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/20", |
| 58 | bar: "bg-indigo-500", |
| 59 | tag: "bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-500/20", |
| 60 | }, |
| 61 | } as const; |
| 62 | |
| 63 | type Difficulty = keyof typeof difficultyConfig; |
| 64 | |
| 65 | export default function KtiPage() { |
| 66 | let publishedKtis = allKtis.filter((kti) => kti.published); |
| 67 | |
| 68 | publishedKtis = publishedKtis.sort((a, b) => |
| 69 | a.term.toLowerCase().localeCompare(b.term.toLowerCase()), |
| 70 | ); |
| 71 | |
| 72 | const categories = Array.from( |
| 73 | new Set(publishedKtis.map((kti) => kti.category)), |
| 74 | ).sort(); |
| 75 | |
| 76 | const difficultyGroups = { |
| 77 | pemula: publishedKtis.filter((kti) => kti.difficulty === "pemula"), |
| 78 | menengah: publishedKtis.filter((kti) => kti.difficulty === "menengah"), |
| 79 | lanjut: publishedKtis.filter((kti) => kti.difficulty === "lanjut"), |
| 80 | }; |
| 81 | |
| 82 | const ktiStructuredData = { |
| 83 | "@context": "https://schema.org", |
| 84 | "@graph": [ |
| 85 | { |
| 86 | "@type": "CollectionPage", |
| 87 | "@id": absoluteUrl("/kti#webpage"), |
| 88 | url: absoluteUrl("/kti"), |
| 89 | name: "KTI - Kamus Teknologi Informasi", |
| 90 | description: |
| 91 | "Kamus Teknologi Informasi lengkap untuk tenaga kerja muda Indonesia.", |
| 92 | inLanguage: "id-ID", |
| 93 | isPartOf: { |
| 94 | "@id": absoluteUrl("/#website"), |
| 95 | }, |
| 96 | }, |
| 97 | { |
| 98 | "@type": "BreadcrumbList", |
| 99 | itemListElement: [ |
| 100 | { |
| 101 | "@type": "ListItem", |
| 102 | position: 1, |
| 103 | name: "Home", |
| 104 | item: absoluteUrl("/"), |
| 105 | }, |
| 106 | { |
| 107 | "@type": "ListItem", |
| 108 | position: 2, |
| 109 | name: "KTI", |
| 110 | item: absoluteUrl("/kti"), |
| 111 | }, |
| 112 | ], |
| 113 | }, |
| 114 | { |
| 115 | "@type": "DefinedTermSet", |
| 116 | name: "Kamus Teknologi Informasi", |
| 117 | url: absoluteUrl("/kti"), |
| 118 | description: |
| 119 | "Glosarium istilah teknologi informasi dalam Bahasa Indonesia.", |
| 120 | inLanguage: "id-ID", |
| 121 | hasDefinedTerm: publishedKtis.map((kti) => ({ |
| 122 | "@type": "DefinedTerm", |
| 123 | name: kti.title, |
| 124 | description: kti.description, |
| 125 | url: absoluteUrl(kti.slug), |
| 126 | inLanguage: "id-ID", |
| 127 | })), |
| 128 | }, |
| 129 | ], |
| 130 | }; |
| 131 | |
| 132 | return ( |
| 133 | <div className="mx-auto max-w-5xl px-6 py-16 lg:px-8"> |
| 134 | <StructuredData data={ktiStructuredData} /> |
| 135 | {/* ── Page header ─────────────────────────────────── */} |
| 136 | <div className="mb-16"> |
| 137 | <p className="mb-4 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600"> |
| 138 | Referensi |
| 139 | </p> |
| 140 | <h1 className="text-4xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-5xl"> |
| 141 | Kamus Teknologi Informasi |
| 142 | </h1> |
| 143 | <p className="mt-4 max-w-2xl text-lg text-slate-500 dark:text-slate-400"> |
| 144 | Panduan lengkap istilah teknologi informasi dalam Bahasa Indonesia |
| 145 | untuk tenaga kerja muda dan profesional IT. |
| 146 | </p> |
| 147 | </div> |
| 148 | |
| 149 | {/* ── Stats row ───────────────────────────────────── */} |
| 150 | <div className="mb-16 grid grid-cols-2 gap-4 sm:grid-cols-4"> |
| 151 | {[ |
| 152 | { value: publishedKtis.length, label: "Total Istilah" }, |
| 153 | { value: categories.length, label: "Kategori" }, |
| 154 | { value: difficultyGroups.pemula.length, label: "Level Pemula" }, |
| 155 | { value: difficultyGroups.lanjut.length, label: "Level Lanjut" }, |
| 156 | ].map((stat) => ( |
| 157 | <div |
| 158 | key={stat.label} |
| 159 | className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-5" |
| 160 | > |
| 161 | <p className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white"> |
| 162 | {stat.value} |
| 163 | </p> |
| 164 | <p className="mt-1 text-xs text-slate-500 dark:text-slate-500"> |
| 165 | {stat.label} |
| 166 | </p> |
| 167 | </div> |
| 168 | ))} |
| 169 | </div> |
| 170 | |
| 171 | {/* ── Browse by category ──────────────────────────── */} |
| 172 | <section className="mb-16"> |
| 173 | <p className="mb-8 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600"> |
| 174 | Berdasarkan Kategori |
| 175 | </p> |
| 176 | <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> |
| 177 | {categories.map((category) => { |
| 178 | const categoryKtis = publishedKtis.filter( |
| 179 | (kti) => kti.category === category, |
| 180 | ); |
| 181 | return ( |
| 182 | <div |
| 183 | key={category} |
| 184 | className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-5 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50" |
| 185 | > |
| 186 | <div className="mb-4 flex items-center justify-between"> |
| 187 | <h3 className="font-semibold text-slate-900 dark:text-white"> |
| 188 | {category} |
| 189 | </h3> |
| 190 | <span className="text-xs text-slate-400 dark:text-slate-600"> |
| 191 | {categoryKtis.length} istilah |
| 192 | </span> |
| 193 | </div> |
| 194 | <div className="flex flex-wrap gap-1.5"> |
| 195 | {categoryKtis.slice(0, 6).map((kti) => ( |
| 196 | <Link |
| 197 | key={kti.term} |
| 198 | href={kti.slug} |
| 199 | className="rounded-md bg-slate-100 dark:bg-white/[0.05] px-2.5 py-1 text-xs text-slate-600 dark:text-slate-400 transition-colors hover:bg-indigo-50 dark:hover:bg-indigo-500/10 hover:text-indigo-700 dark:hover:text-indigo-400" |
| 200 | > |
| 201 | {kti.term} |
| 202 | </Link> |
| 203 | ))} |
| 204 | {categoryKtis.length > 6 && ( |
| 205 | <span className="px-2.5 py-1 text-xs text-slate-400 dark:text-slate-600"> |
| 206 | +{categoryKtis.length - 6} lainnya |
| 207 | </span> |
| 208 | )} |
| 209 | </div> |
| 210 | </div> |
| 211 | ); |
| 212 | })} |
| 213 | </div> |
| 214 | </section> |
| 215 | |
| 216 | {/* ── Browse by difficulty ────────────────────────── */} |
| 217 | <section className="mb-16"> |
| 218 | <p className="mb-8 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600"> |
| 219 | Berdasarkan Level |
| 220 | </p> |
| 221 | <div className="space-y-6"> |
| 222 | {(["pemula", "menengah", "lanjut"] as Difficulty[]).map((level) => { |
| 223 | const config = difficultyConfig[level]; |
| 224 | const group = difficultyGroups[level]; |
| 225 | return ( |
| 226 | <div |
| 227 | key={level} |
| 228 | className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-5" |
| 229 | > |
| 230 | <div className="mb-4 flex items-center gap-3"> |
| 231 | <span |
| 232 | className={`rounded-full px-2.5 py-0.5 text-xs font-semibold ${config.badge}`} |
| 233 | > |
| 234 | {config.label} |
| 235 | </span> |
| 236 | <span className="text-xs text-slate-400 dark:text-slate-600"> |
| 237 | {group.length} istilah |
| 238 | </span> |
| 239 | </div> |
| 240 | <div className="flex flex-wrap gap-1.5"> |
| 241 | {group.slice(0, 12).map((kti) => ( |
| 242 | <Link |
| 243 | key={kti.term} |
| 244 | href={kti.slug} |
| 245 | className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${config.tag}`} |
| 246 | > |
| 247 | {kti.term} |
| 248 | </Link> |
| 249 | ))} |
| 250 | {group.length > 12 && ( |
| 251 | <span className="px-2.5 py-1 text-xs text-slate-400 dark:text-slate-600"> |
| 252 | +{group.length - 12} lainnya |
| 253 | </span> |
| 254 | )} |
| 255 | </div> |
| 256 | </div> |
| 257 | ); |
| 258 | })} |
| 259 | </div> |
| 260 | </section> |
| 261 | |
| 262 | {/* ── All terms A–Z ───────────────────────────────── */} |
| 263 | <section className="mb-16"> |
| 264 | <p className="mb-8 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600"> |
| 265 | Semua Istilah (A–Z) |
| 266 | </p> |
| 267 | <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3"> |
| 268 | {publishedKtis.map((kti) => { |
| 269 | const config = |
| 270 | difficultyConfig[kti.difficulty as Difficulty] ?? |
| 271 | difficultyConfig.pemula; |
| 272 | return ( |
| 273 | <Link |
| 274 | key={kti.term} |
| 275 | href={kti.slug} |
| 276 | className="group rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-4 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50 hover:bg-slate-50 dark:hover:bg-white/[0.04]" |
| 277 | > |
| 278 | <div className="mb-2 flex items-start justify-between gap-2"> |
| 279 | <h3 className="font-semibold text-slate-900 dark:text-white transition-colors group-hover:text-indigo-600 dark:group-hover:text-indigo-400"> |
| 280 | {kti.term} |
| 281 | </h3> |
| 282 | <span |
| 283 | className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold ${config.badge}`} |
| 284 | > |
| 285 | {config.label} |
| 286 | </span> |
| 287 | </div> |
| 288 | {kti.englishTerm && ( |
| 289 | <p className="mb-1.5 text-xs text-slate-400 dark:text-slate-600"> |
| 290 | {kti.englishTerm} |
| 291 | </p> |
| 292 | )} |
| 293 | <p className="line-clamp-2 text-sm leading-relaxed text-slate-500 dark:text-slate-400"> |
| 294 | {kti.description} |
| 295 | </p> |
| 296 | </Link> |
| 297 | ); |
| 298 | })} |
| 299 | </div> |
| 300 | </section> |
| 301 | |
| 302 | {/* ── CTA ─────────────────────────────────────────── */} |
| 303 | <section> |
| 304 | <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-14 text-center shadow-2xl shadow-indigo-500/20"> |
| 305 | <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" /> |
| 306 | <h2 className="relative text-2xl font-bold tracking-tight text-white"> |
| 307 | Siap membangun dengan smbCloud? |
| 308 | </h2> |
| 309 | <p className="relative mt-3 text-indigo-200"> |
| 310 | Deploy aplikasi Node.js, Ruby on Rails, dan Swift/Vapor dengan satu |
| 311 | perintah. |
| 312 | </p> |
| 313 | <Link |
| 314 | href="https://console.smbcloud.xyz/login" |
| 315 | target="_blank" |
| 316 | rel="noopener noreferrer" |
| 317 | className="relative mt-8 inline-block rounded-lg bg-white px-8 py-3.5 text-sm font-semibold text-indigo-700 shadow-sm transition-colors hover:bg-indigo-50" |
| 318 | > |
| 319 | Mulai dengan smbCloud → |
| 320 | </Link> |
| 321 | </div> |
| 322 | </section> |
| 323 | </div> |
| 324 | ); |
| 325 | } |