diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package-lock.json b/package-lock.json index 320300e..992d12a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "code-universe", "version": "0.1.0", + "hasInstallScript": true, "dependencies": { "@aws-sdk/client-s3": "^3.990.0", "@aws-sdk/s3-request-presigner": "^3.990.0", @@ -24,6 +25,8 @@ "@prisma/client": "6.19", "@swc/helpers": "^0.5.19", "@tanstack/react-query": "^5.90.21", + "@vercel/analytics": "^2.0.1", + "@vercel/speed-insights": "^2.0.0", "bcrypt": "^6.0.0", "clsx": "^2.1.1", "dotenv": "^17.3.1", @@ -5559,6 +5562,86 @@ "win32" ] }, + "node_modules/@vercel/analytics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-2.0.1.tgz", + "integrity": "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==", + "license": "MIT", + "peerDependencies": { + "@remix-run/react": "^2", + "@sveltejs/kit": "^1 || ^2", + "next": ">= 13", + "nuxt": ">= 3", + "react": "^18 || ^19 || ^19.0.0-rc", + "svelte": ">= 4", + "vue": "^3", + "vue-router": "^4" + }, + "peerDependenciesMeta": { + "@remix-run/react": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "nuxt": { + "optional": true + }, + "react": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, + "node_modules/@vercel/speed-insights": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vercel/speed-insights/-/speed-insights-2.0.0.tgz", + "integrity": "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==", + "license": "Apache-2.0", + "peerDependencies": { + "@sveltejs/kit": "^1 || ^2", + "next": ">= 13", + "nuxt": ">= 3", + "react": "^18 || ^19 || ^19.0.0-rc", + "svelte": ">= 4", + "vue": "^3", + "vue-router": "^4" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "nuxt": { + "optional": true + }, + "react": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", diff --git a/package.json b/package.json index e9051df..23590e3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "@prisma/client": "6.19", "@swc/helpers": "^0.5.19", "@tanstack/react-query": "^5.90.21", + "@vercel/analytics": "^2.0.1", + "@vercel/speed-insights": "^2.0.0", "bcrypt": "^6.0.0", "clsx": "^2.1.1", "dotenv": "^17.3.1", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a1fb0de..48a2b9f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,8 +18,6 @@ enum PlanetStatus { enum PlanetCategory { common html - css - javascript } enum SupportedLanguage { diff --git a/src/app/(protected)/admin/planet/add/components/AddPlanetClient.tsx b/src/app/(protected)/admin/planet/add/components/AddPlanetClient.tsx index 86b0be4..4703190 100644 --- a/src/app/(protected)/admin/planet/add/components/AddPlanetClient.tsx +++ b/src/app/(protected)/admin/planet/add/components/AddPlanetClient.tsx @@ -50,48 +50,58 @@ const AddPlanetClient = ({ category }: Props) => { useOrphanedImageCleanup(planetData, setPendingContentImages); - if (previewActive) { - return ( - <> - setPreviewActive(false)} /> - - - ); - } + const labels = { + researchTopics: 'Research Topics', + resources: 'Resources', + contents: 'Contents', + questions: 'Questions', + previous: 'Previous', + next: 'Next', + }; return ( - + <> + {previewActive && ( +
+ setPreviewActive(false)} /> + +
+ )} + + + ); }; diff --git a/src/app/(protected)/admin/planet/update/[planetId]/components/UpdatePlanetClient.tsx b/src/app/(protected)/admin/planet/update/[planetId]/components/UpdatePlanetClient.tsx index d7a6311..6e16a9f 100644 --- a/src/app/(protected)/admin/planet/update/[planetId]/components/UpdatePlanetClient.tsx +++ b/src/app/(protected)/admin/planet/update/[planetId]/components/UpdatePlanetClient.tsx @@ -50,48 +50,58 @@ const UpdatePlanetClient = ({ planetId, step, initialData }: Props) => { // Remove orphaned content images: keep only images referenced by any content in any locale useOrphanedImageCleanup(planetData, setPendingContentImages); - if (previewActive) { - return ( - <> - setPreviewActive(false)} /> - - - ); - } + const labels = { + researchTopics: 'Research Topics', + resources: 'Resources', + contents: 'Contents', + questions: 'Questions', + previous: 'Previous', + next: 'Next', + }; return ( - + <> + {previewActive && ( +
+ setPreviewActive(false)} /> + +
+ )} + + + ); }; diff --git a/src/app/(public)/[locale]/roadmap/[category]/[planetId]/page.tsx b/src/app/(public)/[locale]/roadmap/[category]/[planetId]/page.tsx index f96d660..f691b0e 100644 --- a/src/app/(public)/[locale]/roadmap/[category]/[planetId]/page.tsx +++ b/src/app/(public)/[locale]/roadmap/[category]/[planetId]/page.tsx @@ -40,9 +40,21 @@ export const generateMetadata = async ({ params }: Props) => { export default async function PlanetPage({ params }: Props) { const { planetId, category, locale } = await params; setRequestLocale(locale); - const t = await getTranslations('common'); + if (!isPlanetCategory(category)) notFound(); + const c = await getTranslations('common'); + const t = await getTranslations('planetDetails'); + + const planetDetailsLabels = { + researchTopics: t('researchTopics'), + resources: t('resources'), + questions: t('questions'), + previous: t('previous'), + next: t('next'), + }; + + const planet = await getPlanet( planetId, category, @@ -56,7 +68,7 @@ export default async function PlanetPage({ params }: Props) { className="px-3 py-1 rounded bg-slate-800 border border-white/10 hover:bg-slate-900" href={`/roadmap/${category}`} > - {t('goBack')} + {c('goBack')} @@ -70,7 +82,7 @@ export default async function PlanetPage({ params }: Props) { - + ); } diff --git a/src/app/(public)/[locale]/roadmap/[category]/components/Planet.module.scss b/src/app/(public)/[locale]/roadmap/[category]/components/Planet.module.scss index c6aeac3..76d4375 100644 --- a/src/app/(public)/[locale]/roadmap/[category]/components/Planet.module.scss +++ b/src/app/(public)/[locale]/roadmap/[category]/components/Planet.module.scss @@ -7,6 +7,7 @@ animation: rotate 180s linear infinite; .link { + position: relative; transition: scale 0.3s ease-in-out; z-index: 2; height: 19rem; diff --git a/src/app/(public)/[locale]/roadmap/[category]/components/Planet.tsx b/src/app/(public)/[locale]/roadmap/[category]/components/Planet.tsx index be641fb..aa505bf 100644 --- a/src/app/(public)/[locale]/roadmap/[category]/components/Planet.tsx +++ b/src/app/(public)/[locale]/roadmap/[category]/components/Planet.tsx @@ -8,9 +8,10 @@ import styles from './Planet.module.scss'; interface Props { category: string; planet: PublicPlanetSummary; + index: number; } -const Planet = ({ category, planet }: Props) => { +const Planet = ({ category, planet, index }: Props) => { const { tags } = planet.localized; return ( @@ -20,6 +21,8 @@ const Planet = ({ category, planet }: Props) => { src={planet.image.url} alt={planet.image.alt} fill + priority={index < 2} + sizes="19rem" className={styles.planet} /> diff --git a/src/app/(public)/[locale]/roadmap/[category]/page.tsx b/src/app/(public)/[locale]/roadmap/[category]/page.tsx index 823f809..b2a7daf 100644 --- a/src/app/(public)/[locale]/roadmap/[category]/page.tsx +++ b/src/app/(public)/[locale]/roadmap/[category]/page.tsx @@ -60,8 +60,8 @@ export default async function MapPage({ params }: Props) {
- {planets.map((planet) => ( - + {planets.map((planet, index) => ( + ))}
diff --git a/src/app/error.tsx b/src/app/error.tsx index f34c890..4e220ab 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -76,7 +76,7 @@ export default function GlobalError({ error, reset }: GlobalErrorProps) { href="/" className="w-full sm:w-auto px-10 py-3.5 rounded-xl bg-white/5 border border-white/10 text-white font-bold text-sm hover:bg-white/10 transition-all active:scale-95" > - Back to Dashboard + Home diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 525a668..e2e4931 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,8 @@ import { config } from '@fortawesome/fontawesome-svg-core'; import '@fortawesome/fontawesome-svg-core/styles.css'; import { Toaster } from 'react-hot-toast'; import type { ReactNode } from 'react'; +import { Analytics } from '@vercel/analytics/next'; +import { SpeedInsights } from '@vercel/speed-insights/next'; import NextAuthProvider from '@/components/providers/NextAuthProvider'; import QueryProvider from '@/components/providers/QueryProvider'; @@ -16,7 +18,7 @@ const nunito = Nunito({ }); export const metadata: Metadata = { - metadataBase: new URL('http://localhost:3000'), + metadataBase: new URL('https://code-universe-nu.vercel.app/'), title: { default: 'Code Universe', template: '%s | Code Universe', @@ -43,9 +45,13 @@ export default function RootLayout({ - {children} + + {children} + + + + - ); diff --git a/src/components/admin/planet-editor-layout/ContentSidebar.tsx b/src/components/admin/planet-editor-layout/ContentSidebar.tsx index 6c43739..29f6e5f 100644 --- a/src/components/admin/planet-editor-layout/ContentSidebar.tsx +++ b/src/components/admin/planet-editor-layout/ContentSidebar.tsx @@ -103,7 +103,7 @@ const ContentSidebar = ({ contents, setPlanetData, locale }: Props) => { /> ) : ( -
+
No modules in diff --git a/src/components/shared/planet-details/PlanetDetails.tsx b/src/components/shared/planet-details/PlanetDetails.tsx index 3877776..bbf0238 100644 --- a/src/components/shared/planet-details/PlanetDetails.tsx +++ b/src/components/shared/planet-details/PlanetDetails.tsx @@ -4,14 +4,15 @@ import PlanetHeader from './PlanetHeader'; import ResearchTopics from './ResearchTopics'; import Resources from './Resources'; import ContentList from './ContentList'; -import Questions from './contents/Questions'; +import Questions from './Questions'; import PlanetNavigation from './PlanetNavigation'; interface Props { planet: PublicPlanetResponse; + labels: Record } -const PlanetDetails = ({ planet }: Props) => { +const PlanetDetails = ({ planet, labels }: Props) => { const { category, image, localized } = planet; const { researchTopics, resources, contents, questions } = localized; @@ -24,16 +25,17 @@ const PlanetDetails = ({ planet }: Props) => { /> {researchTopics && researchTopics.length > 0 && ( - + )} - {resources && resources.length > 0 && } + {resources && resources.length > 0 && } {contents && contents.length > 0 && } - {questions && questions.length > 0 && } + {questions && questions.length > 0 && } ); diff --git a/src/components/shared/planet-details/PlanetNavigation.tsx b/src/components/shared/planet-details/PlanetNavigation.tsx index 40c24be..7924004 100644 --- a/src/components/shared/planet-details/PlanetNavigation.tsx +++ b/src/components/shared/planet-details/PlanetNavigation.tsx @@ -1,5 +1,3 @@ -import { getTranslations } from 'next-intl/server'; - import { Link } from '@/lib/next-intl/navigation'; import type { PlanetCategory } from '@/types/planet'; @@ -7,11 +5,10 @@ interface Props { category: PlanetCategory; prevId: string | null; nextId: string | null; + labels: Record } -const PlanetNavigation = async ({ category, prevId, nextId }: Props) => { - const t = await getTranslations('planetDetails'); - +const PlanetNavigation = ({ category, prevId, nextId, labels }: Props) => { return (
{prevId ? ( @@ -19,11 +16,11 @@ const PlanetNavigation = async ({ category, prevId, nextId }: Props) => { href={`/roadmap/${category}/${prevId}`} className="bg-green-600 text-white px-5 py-2 rounded font-semibold hover:bg-green-700 transition-colors" > - ❮ {t('previous')} + ❮ {labels.previous} ) : ( - ❮ {t('previous')} + ❮ {labels.previous} )} @@ -32,11 +29,11 @@ const PlanetNavigation = async ({ category, prevId, nextId }: Props) => { href={`/roadmap/${category}/${nextId}`} className="bg-green-600 text-white px-5 py-2 rounded font-semibold hover:bg-green-700 transition-colors" > - {t('next')} ❯ + {labels.next} ❯ ) : ( - {t('next')} ❯ + {labels.next} ❯ )}
diff --git a/src/components/shared/planet-details/contents/Questions.tsx b/src/components/shared/planet-details/Questions.tsx similarity index 70% rename from src/components/shared/planet-details/contents/Questions.tsx rename to src/components/shared/planet-details/Questions.tsx index 64ad9e9..4d4b197 100644 --- a/src/components/shared/planet-details/contents/Questions.tsx +++ b/src/components/shared/planet-details/Questions.tsx @@ -1,17 +1,14 @@ -import { getTranslations } from 'next-intl/server'; - import type { Question } from '@/types/planet'; interface Props { questions: Question[]; + labels: Record } -const Questions = async ({ questions }: Props) => { - const t = await getTranslations('planetDetails'); - +const Questions = ({ questions, labels }: Props) => { return (
-

{t('questions')}

+

{labels.questions}

    {questions.map((qn, idx) => (
  1. diff --git a/src/components/shared/planet-details/ResearchTopics.tsx b/src/components/shared/planet-details/ResearchTopics.tsx index ce475fb..d68c54f 100644 --- a/src/components/shared/planet-details/ResearchTopics.tsx +++ b/src/components/shared/planet-details/ResearchTopics.tsx @@ -1,16 +1,14 @@ -import { getTranslations } from 'next-intl/server'; - import type { ResearchTopic } from '@/types/planet'; interface Props { researchTopics: ResearchTopic[]; + labels: Record } -const ResearchTopics = async ({ researchTopics }: Props) => { - const t = await getTranslations('planetDetails'); +const ResearchTopics = ({ researchTopics, labels }: Props) => { return (
    -

    {t('researchTopics')}

    +

    {labels.researchTopics}

      {researchTopics.map((topic) => (
    • {topic.topic}
    • diff --git a/src/components/shared/planet-details/Resources.tsx b/src/components/shared/planet-details/Resources.tsx index 48bf2bd..e2df10a 100644 --- a/src/components/shared/planet-details/Resources.tsx +++ b/src/components/shared/planet-details/Resources.tsx @@ -1,19 +1,17 @@ import { ExternalLink } from 'lucide-react'; import Link from 'next/link'; -import { getTranslations } from 'next-intl/server'; import type { Resource } from '@/types/planet'; interface Props { resources: Resource[]; + labels: Record } -const Resources = async ({ resources }: Props) => { - const t = await getTranslations('planetDetails'); - +const Resources = ({ resources, labels }: Props) => { return (
      -

      {t('resources')}

      +

      {labels.resources}

        {resources.map((res) => (
      • diff --git a/src/lib/constants/navbarItems.ts b/src/lib/constants/navbarItems.ts index fea6f57..7e722c1 100644 --- a/src/lib/constants/navbarItems.ts +++ b/src/lib/constants/navbarItems.ts @@ -37,7 +37,7 @@ export const navLinks: NavLinkGroup[] = [ label: 'HTML', icon: html, path: '/roadmap/html', - isActive: false, + isActive: true, }, { label: 'CSS', @@ -49,7 +49,7 @@ export const navLinks: NavLinkGroup[] = [ label: 'JavaScript', icon: js, path: '/roadmap/javascript', - isActive: true, + isActive: false, }, { label: 'React', diff --git a/src/lib/constants/planet.ts b/src/lib/constants/planet.ts index 9bde6e5..15cf0a4 100644 --- a/src/lib/constants/planet.ts +++ b/src/lib/constants/planet.ts @@ -4,8 +4,6 @@ export const PLANET_STATUS = ['draft', 'published'] as const; export const PLANET_CATEGORY = { common: 'Common', html: 'HTML', - css: 'CSS', - javascript: 'Javascript', } as const; export const CONTENT_TYPE = {