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
50 changes: 19 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ The official website for the DevHub developer community. Built with Next.js, Typ

## What's in here

- Community docs and guides
- rules
- getting started
- how-to's
- Landing page
- Community guides
- Resource library, curated tools and learning materials
- Documentations:
- Partners page
- DevHub Pages:
- Getting Started
- Join Guide
- Server Info
Expand All @@ -20,13 +19,15 @@ The official website for the DevHub developer community. Built with Next.js, Typ
- Moderation Guide
- Staff Roles
- FAQ
- and many many more

## Tech Stack

- **Framework** - Next.js
- **Language** - TypeScript
- **Styling** - Tailwind CSS
- **Animation Libriary** - GSAP and Framer Motion
- **Component Libriary** - React Bits
- **Icons** - Lucide
- **Deployment** - Vercel

Expand All @@ -35,14 +36,14 @@ The official website for the DevHub developer community. Built with Next.js, Typ
### Prerequisites

- Node.js 18+
- npm, yarn, or pnpm
- bun, npm, yarn, or pnpm

### Setup

```bash
# Clone the repo
git clone https://github.com/open-devhub/website
cd devhub-website
cd website

# Install dependencies
npm install
Expand All @@ -63,7 +64,7 @@ Open [http://localhost:3000](http://localhost:3000) and you're in.
│ └── 📄 settings.json
├── 📁 app
│ ├── 📁 api
│ │ └── 📁 link-preview
│ │ └── 📁 preview
│ │ └── 📄 route.ts
│ ├── 📁 pages
│ │ ├── 📁 [slug]
Expand All @@ -73,6 +74,12 @@ Open [http://localhost:3000](http://localhost:3000) and you're in.
│ │ │ └── 📄 page.tsx
│ │ ├── 📄 layout.tsx
│ │ └── 📄 page.tsx
│ ├── 📁 articles
│ │ ├── 📁 [slug]
│ │ │ ├── 📄 ArticleClient.tsx
│ │ │ └── 📄 page.tsx
│ │ ├── 📄 ArticlesListingClient.tsx
│ │ └── 📄 page.tsx
│ ├── 📁 partners
│ │ └── 📄 page.tsx
│ ├── 📁 resources
Expand Down Expand Up @@ -116,28 +123,9 @@ Open [http://localhost:3000](http://localhost:3000) and you're in.
│ └── 📄 LinkPreviewCard.tsx
├── 📁 content
│ ├── 📁 pages
│ │ ├── 📁 bots
│ │ │ ├── 📄 adding-a-bot.md
│ │ │ └── 📄 bots.md
│ │ ├── 📁 community
│ │ │ ├── 📄 acknowledgements.md
│ │ │ ├── 📄 code-of-conduct.md
│ │ │ ├── 📄 faq.md
│ │ │ ├── 📄 getting-started.md
│ │ │ ├── 📄 how-to-ask.md
│ │ │ ├── 📄 how-to-help.md
│ │ │ ├── 📄 join-guide.md
│ │ │ ├── 📄 moderation-guide.md
│ │ │ ├── 📄 server-info.md
│ │ │ └── 📄 staff-roles.md
│ │ ├── 📁 legal
│ │ │ ├── 📄 privacy-policy.md
│ │ │ └── 📄 security-notice.md
│ │ └── 📁 open-source
│ │ ├── 📄 contributing.md
│ │ ├── 📄 github-org.md
│ │ ├── 📄 project-guidelines.md
│ │ └── 📄 submit-project.md
│ │ └── ...
│ ├── 📁 articles
│ │ └── ...
│ ├── 📄 pages-loader.ts
│ ├── 📄 pages-sections.ts
│ ├── 📄 resources.ts
Expand Down Expand Up @@ -180,7 +168,7 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide. The short version:
4. Run `npm run lint`
5. Open a PR with a clear description

First time contributing to open source? Look for issues tagged [`good first issue`](https://github.com/devhub-community/devhub-website/issues?q=is%3Aopen+label%3A%22good+first+issue%22).
First time contributing to open source? Look for issues tagged [`good first issue`](https://github.com/open-devhub/website/issues?q=is%3Aopen+label%3A%22good+first+issue%22).

## Scripts

Expand Down
2 changes: 1 addition & 1 deletion app/articles/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { articles } from "@/content/articles-loader";
import { PREVIEWS } from "@/lib/previews";
import { PREVIEWS } from "@/content/previews";
import { notFound } from "next/navigation";
import ArticleClient from "./ArticleClient";

Expand Down
2 changes: 1 addition & 1 deletion app/pages/[slug]/[subslug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getAdjacentPages, getPage } from "@/content/pages-loader";
import { PREVIEWS } from "@/lib/previews";
import { PREVIEWS } from "@/content/previews";
import { notFound } from "next/navigation";
import PageClient from "../PageClient";

Expand Down
2 changes: 1 addition & 1 deletion app/pages/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getAdjacentPages, getPage } from "@/content/pages-loader";
import { PREVIEWS } from "@/lib/previews";
import { PREVIEWS } from "@/content/previews";
import { notFound } from "next/navigation";
import PageClient from "./PageClient";

Expand Down
95 changes: 32 additions & 63 deletions app/partners/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";

import ShinyText from "@/components/bits/ShinyText";
import { Partner, partners } from "@/content/partners";
import { fadeInUp, staggerContainer } from "@/lib/animations";
import {
accent,
background,
Expand All @@ -11,7 +13,6 @@ import {
semantic,
text,
} from "@/lib/colors";
import { fadeInUp, staggerContainer } from "@/lib/animations";
import { motion } from "framer-motion";
import {
ArrowRight,
Expand All @@ -24,41 +25,6 @@ import {
} from "lucide-react";
import { useEffect, useState } from "react";

type Partner = {
inviteCode: string;
tags?: string[];
websiteUrl?: string;
githubUrl?: string;

// Optional fallback if Discord has no banner
banner?: string;
};

const partners: Partner[] = [
{
inviteCode: "3xKFvKhuGR",
tags: ["Coding", "Programming", "Developer", "Python", "Community"],
websiteUrl: "https://thecodeversehub.tech",
githubUrl: "https://github.com/TheCodeVerseHub",
},
{
inviteCode: "F6Z27BMBhE",
tags: [
"Coding",
"Programming",
"Developing",
"Games and fun",
"Server Not Found",
],
},
{
inviteCode: "BGrCXccWDa",
banner: partnerColors.bannerBrown,
tags: ["Javaceans", "Coding", "DEV Support"],
githubUrl: "https://github.com/drive-for-java",
},
];

type DiscordData = {
guildId: string;

Expand All @@ -71,7 +37,8 @@ type DiscordData = {
iconUrl: string | null;
bannerUrl: string | null;

traits: string[];
traits: { label: string }[];
features: string[];
};

const TRAIT_LABELS: Record<string, { label: string; icon: typeof BadgeCheck }> =
Expand Down Expand Up @@ -220,9 +187,9 @@ function PartnerCard({
const serverDescription =
discord?.description ?? "No server description available.";

const tags = partner.tags ?? [];
const features = (discord?.features ?? []).filter((t) => TRAIT_LABELS[t]);

const traits = (discord?.traits ?? []).filter((t) => TRAIT_LABELS[t]);
const traits = (discord?.traits ?? []).filter((t) => t.label);

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.

The traits filter is checking for truthiness of t.label but should validate that the trait exists in TRAIT_LABELS, consistent with how features are filtered on line 190. Currently, any trait object with a truthy label property will pass through, even if it's not a valid trait label defined in TRAIT_LABELS.

Confidence: 4/5

Suggested Fix
Suggested change
const traits = (discord?.traits ?? []).filter((t) => t.label);
const traits = (discord?.traits ?? []).filter((t) => TRAIT_LABELS[t.label]);

Change the filter to check if t.label exists as a key in TRAIT_LABELS, matching the pattern used for features filtering on line 190.


const fallbackBanner = partner.banner;

Expand Down Expand Up @@ -359,7 +326,7 @@ function PartnerCard({

{/* Name */}
<h2
className="text-base font-semibold mb-1.5"
className="text-base font-semibold mb-3"
style={{
fontFamily: "var(--font-geist-mono)",
color: text.primary,
Expand All @@ -368,14 +335,14 @@ function PartnerCard({
{serverName}
</h2>

{/* Traits */}
{traits.length > 0 && (
<div className="flex flex-wrap gap-2 mb-2.5">
{traits.map((trait) => {
const { label, icon: Icon } = TRAIT_LABELS[trait];
{/* Features */}
{features.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{features.map((feature) => {
const { label, icon: Icon } = TRAIT_LABELS[feature];
return (
<span
key={trait}
key={feature}
className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 uppercase tracking-widest"
style={{
fontFamily: "var(--font-geist-mono)",
Expand Down Expand Up @@ -403,11 +370,11 @@ function PartnerCard({
{serverDescription}
</p>

{/* Tags */}
{/* Traits */}
<div className="flex flex-wrap gap-1.5 mb-5">
{tags.map((tag) => (
{traits.map((trait) => (
<span
key={tag}
key={trait.label}
className="text-[10px] px-2 py-0.5 uppercase tracking-widest"
style={{
fontFamily: "var(--font-geist-mono)",
Expand All @@ -416,15 +383,15 @@ function PartnerCard({
border: `1px solid ${indigo(0.2)}`,
}}
>
{tag}
{trait.label}
</span>
))}
</div>

{/* Buttons */}
<div className="flex flex-wrap gap-3">
<motion.a
href={`https://discord.gg/${partner.inviteCode}`}
href={`https://discord.gg/${partner.code}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-5 py-2.5 text-xs font-mono font-semibold tracking-wider uppercase text-white"
Expand Down Expand Up @@ -502,7 +469,7 @@ export default function PartnersPage() {
Record<string, DiscordData | null>
>({});
const [loadingMap, setLoadingMap] = useState<Record<string, boolean>>(() =>
Object.fromEntries(partners.map((p) => [p.inviteCode, true])),
Object.fromEntries(partners.map((p) => [p.code, true])),
);

useEffect(() => {
Expand All @@ -513,26 +480,27 @@ export default function PartnersPage() {
partners.map(async (p) => {
try {
const res = await fetch(
`https://discord.com/api/v10/invites/${p.inviteCode}?with_counts=true`,
`https://discord.com/api/v10/invites/${p.code}?with_counts=true`,
{ signal: controller.signal },
);

if (!res.ok) {
setDiscordData((prev) => ({ ...prev, [p.inviteCode]: null }));
setDiscordData((prev) => ({ ...prev, [p.code]: null }));
return;
}

const data = await res.json();
const guild = data.guild;
const profile = data.profile;

if (!guild) {
setDiscordData((prev) => ({ ...prev, [p.inviteCode]: null }));
setDiscordData((prev) => ({ ...prev, [p.code]: null }));
return;
}

setDiscordData((prev) => ({
...prev,
[p.inviteCode]: {
[p.code]: {
guildId: guild.id,

name: guild.name,
Expand All @@ -549,18 +517,19 @@ export default function PartnersPage() {
? `https://cdn.discordapp.com/banners/${guild.id}/${guild.banner}.png?size=1024`
: null,

traits: guild.features ?? [],
features: guild.features ?? [],
traits: profile.traits ?? [],

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.

The code accesses profile.traits without validating that profile exists. If the Discord API response doesn't include a profile object, this will cause a runtime error ("Cannot read property 'traits' of undefined").

Confidence: 4/5

Suggested Fix
Suggested change
traits: profile.traits ?? [],
traits: profile?.traits ?? [],

Use optional chaining to safely access profile.traits with a fallback to an empty array if profile is undefined.

},
}));
} catch (error) {
const aborted =
error instanceof DOMException && error.name === "AbortError";
if (!aborted) {
setDiscordData((prev) => ({ ...prev, [p.inviteCode]: null }));
setDiscordData((prev) => ({ ...prev, [p.code]: null }));
}
} finally {
if (!controller.signal.aborted) {
setLoadingMap((prev) => ({ ...prev, [p.inviteCode]: false }));
setLoadingMap((prev) => ({ ...prev, [p.code]: false }));
}
}
}),
Expand Down Expand Up @@ -727,13 +696,13 @@ export default function PartnersPage() {
{/* ─── Cards ─── */}
<div className="flex flex-col gap-6">
{partners.map((partner, i) =>
loadingMap[partner.inviteCode] ? (
<PartnerCardSkeleton key={partner.inviteCode} index={i} />
loadingMap[partner.code] ? (
<PartnerCardSkeleton key={partner.code} index={i} />
) : (
<PartnerCard
key={partner.inviteCode}
key={partner.code}
partner={partner}
discord={discordData[partner.inviteCode] ?? null}
discord={discordData[partner.code] ?? null}
index={i}
/>
),
Expand Down
2 changes: 1 addition & 1 deletion components/LinkPreviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface PreviewData {
image?: string | null;
}

function truncate(desc: string, maxLength = 300) {
function truncate(desc: string, maxLength = 200) {
if (desc.length <= maxLength) return desc;
const cut = desc.slice(0, maxLength);
const lastSpace = cut.lastIndexOf(" ");
Expand Down
27 changes: 27 additions & 0 deletions content/partners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { partner as partnerColors } from "@/lib/colors";

export type Partner = {
code: string;
tags?: string[];
websiteUrl?: string;
githubUrl?: string;

// Optional fallback if Discord has no banner
banner?: string;
};

export const partners: Partner[] = [
{
code: "3xKFvKhuGR",
websiteUrl: "https://thecodeversehub.tech",
githubUrl: "https://github.com/TheCodeVerseHub",
},
{
code: "F6Z27BMBhE",
},
{
code: "BGrCXccWDa",
banner: partnerColors.bannerBrown,
githubUrl: "https://github.com/drive-for-java",
},
];
Loading
Loading