Skip to content

Commit c09e80c

Browse files
committed
Initial commit
0 parents  commit c09e80c

File tree

64 files changed

+10324
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+10324
-0
lines changed

.eslintrc.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": [
3+
"next/core-web-vitals",
4+
"plugin:prettier/recommended"
5+
]
6+
}

.gitignore

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts

.husky/pre-commit

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
npx lint-staged
5+
npx lint-staged
6+
npx lint-staged

.prettierrc.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "https://json.schemastore.org/prettierrc",
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"semi": true,
6+
"bracketSpacing": true,
7+
"tabWidth": 4,
8+
"printWidth": 100,
9+
"plugins": [
10+
"prettier-plugin-tailwindcss"
11+
]
12+
}

.vscode/settings.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"editor.codeActionsOnSave": {
3+
"source.fixAll.eslint": "explicit"
4+
}
5+
}

README.md

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# PBL Platform - Learn Programming by Building Real Projects
2+
3+
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4+
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/megoxv/pbl-platform/pulls)
5+
6+
Learn programming through hands-on project development with community-curated tutorials. 🚀
7+
8+
## Features
9+
10+
**Learn by Building**
11+
Gain practical experience by creating real-world applications from scratch
12+
13+
📚 **Curated Tutorials**
14+
High-quality guides across multiple languages from experienced developers
15+
16+
🌍 **Community Driven**
17+
Open source platform powered by developer contributions
18+
19+
## Contributing Tutorials
20+
21+
We welcome community contributions! Here's how to add new tutorials:
22+
23+
### 1. Create Markdown File
24+
Create a new `.md` file in the appropriate language directory:
25+
```bash
26+
content/javascript/my-tutorial.md
27+
```
28+
29+
### 2. Frontmatter Template
30+
```markdown
31+
---
32+
title: "Your Tutorial Title"
33+
description: "Brief description (150-200 chars)"
34+
technologies: Tech1, Tech2
35+
difficulty: "beginner"
36+
url: "https://youtube.com/..."
37+
image: "https://example.com/preview.jpg"
38+
---
39+
40+
## Tutorial Content
41+
Your tutorial content here...
42+
```
43+
44+
### 3. Required Fields
45+
| Field | Description |
46+
|----------------|----------------------------------------------|
47+
| `title` | Tutorial name |
48+
| `description` | Concise summary (150-200 characters) |
49+
| `technologies` | Comma-separated technologies used |
50+
| `difficulty` | beginner/intermediate/advanced |
51+
| `url` | YouTube/article URL |
52+
| `image` | Preview image URL (16:9 ratio recommended) |
53+
54+
### Submission Process
55+
1. Fork the repository
56+
2. Create feature branch: `git checkout -b feat/awesome-tutorial`
57+
3. Commit changes: `git commit -m 'Add awesome tutorial'`
58+
4. Push to branch: `git push origin feat/awesome-tutorial`
59+
5. Open a Pull Request
60+
61+
## Need Help?
62+
💬 **Get support through:**
63+
- [Open an Issue](https://github.com/megoxv/pbl-platform/issues)
64+
65+
## License
66+
Distributed under MIT License. See `LICENSE` for more information.

app/api/languages/route.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { readdirSync } from "fs";
2+
import { join } from "path";
3+
import { NextResponse } from "next/server";
4+
5+
const CONTENT_DIR = join(process.cwd(), "content");
6+
7+
export async function GET() {
8+
try {
9+
const directories = readdirSync(CONTENT_DIR, { withFileTypes: true })
10+
.filter(dirent => dirent.isDirectory())
11+
.map(dirent => ({
12+
name: dirent.name,
13+
icon: `/icons/${dirent.name.toLowerCase()}.svg`,
14+
count: readdirSync(join(CONTENT_DIR, dirent.name))
15+
.filter(file => file.endsWith(".md"))
16+
.length
17+
}));
18+
19+
return NextResponse.json(directories);
20+
} catch {
21+
return NextResponse.json({ error: "Failed to fetch languages" }, { status: 500 });
22+
}
23+
}

app/api/tutorials/[language]/route.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { readdirSync, readFileSync } from "fs";
2+
import { join } from "path";
3+
import matter from "gray-matter";
4+
import { NextResponse } from "next/server";
5+
6+
const CONTENT_DIR = join(process.cwd(), "content");
7+
8+
export async function generateStaticParams() {
9+
try {
10+
return readdirSync(CONTENT_DIR, { withFileTypes: true })
11+
.filter(dirent => dirent.isDirectory())
12+
.map(dirent => ({
13+
language: dirent.name.toLowerCase(),
14+
}));
15+
} catch (error) {
16+
console.error("Error generating static params:", error);
17+
return [];
18+
}
19+
}
20+
21+
type Params = Promise<{ language: string }>
22+
23+
export async function GET(
24+
request: Request,
25+
props: { params: Params }
26+
) {
27+
try {
28+
const params = props.params;
29+
30+
const languageDir = join(CONTENT_DIR, (await params).language);
31+
const files = readdirSync(languageDir).filter(file => file.endsWith(".md"));
32+
33+
const tutorials = files.map(filename => {
34+
const fileContent = readFileSync(join(languageDir, filename), "utf-8");
35+
const { data } = matter(fileContent);
36+
return {
37+
slug: filename.replace(".md", ""),
38+
frontmatter: data,
39+
};
40+
});
41+
42+
return NextResponse.json(tutorials);
43+
} catch {
44+
return NextResponse.json(
45+
{ error: "Failed to fetch tutorials" },
46+
{ status: 500 }
47+
);
48+
}
49+
}

app/contribute/page.tsx

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
export default function ContributePage() {
2+
return (
3+
<div className="container mx-auto px-4 py-8 max-w-4xl">
4+
<h1 className="text-4xl font-bold mb-8">How to Contribute</h1>
5+
6+
<div className="prose dark:prose-invert max-w-none">
7+
<h2>Adding New Tutorials</h2>
8+
<p>
9+
We welcome contributions from the community! Here&apos;s how you can add new tutorials:
10+
</p>
11+
12+
<h3>1. Create a Markdown File</h3>
13+
<p>
14+
Create a new markdown file in the appropriate language directory under <code>content/</code>.
15+
For example: <code>content/javascript/my-tutorial.md</code>
16+
</p>
17+
18+
<div className="bg-muted p-4 rounded-lg my-4">
19+
<pre className="text-sm">
20+
{`---
21+
title: "Tutorial Title"
22+
description: "A brief description of the tutorial"
23+
technologies: Tech1, Tech2
24+
difficulty: "beginner"
25+
url: "https://youtube.com/..."
26+
image: "https://example.com/preview.jpg"
27+
---
28+
29+
## Tutorial Content
30+
31+
Your tutorial content here...`}
32+
</pre>
33+
</div>
34+
35+
<h3>2. Required Fields</h3>
36+
<ul>
37+
<li><code>title</code>: The name of your tutorial</li>
38+
<li><code>description</code>: A brief description (150-200 characters)</li>
39+
<li><code>technologies</code>: Comma-separated list of technologies used</li>
40+
<li><code>difficulty</code>: One of: beginner, intermediate, advanced</li>
41+
<li><code>url</code>: YouTube video or article URL</li>
42+
<li><code>image</code>: Preview image URL (16:9 ratio recommended)</li>
43+
</ul>
44+
45+
<h3>3. Content Guidelines</h3>
46+
<ul>
47+
<li>Use clear, concise language</li>
48+
<li>Include code examples where relevant</li>
49+
<li>Break down complex concepts</li>
50+
<li>Add screenshots or diagrams when helpful</li>
51+
<li>Link to additional resources</li>
52+
</ul>
53+
54+
<h3>4. Submit Your Contribution</h3>
55+
<p>
56+
1. Fork the repository<br />
57+
2. Create a new branch<br />
58+
3. Add your tutorial<br />
59+
4. Submit a pull request
60+
</p>
61+
62+
<div className="bg-muted p-4 rounded-lg my-4">
63+
<h4 className="text-sm font-semibold mb-2">Need Help?</h4>
64+
<p className="text-sm">
65+
If you need assistance or have questions, feel free to:
66+
</p>
67+
<ul className="text-sm mt-2">
68+
<li>Open an issue on GitHub</li>
69+
<li>Join our Discord community</li>
70+
<li>Check our detailed contribution guide</li>
71+
</ul>
72+
</div>
73+
</div>
74+
</div>
75+
);
76+
}

app/favicon.ico

25.3 KB
Binary file not shown.

app/globals.css

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
body {
6+
font-family: Arial, Helvetica, sans-serif;
7+
}
8+
9+
@layer base {
10+
:root {
11+
--background: 0 0% 100%;
12+
--foreground: 0 0% 3.9%;
13+
--card: 0 0% 100%;
14+
--card-foreground: 0 0% 3.9%;
15+
--popover: 0 0% 100%;
16+
--popover-foreground: 0 0% 3.9%;
17+
--primary: 0 0% 9%;
18+
--primary-foreground: 0 0% 98%;
19+
--secondary: 0 0% 96.1%;
20+
--secondary-foreground: 0 0% 9%;
21+
--muted: 0 0% 96.1%;
22+
--muted-foreground: 0 0% 45.1%;
23+
--accent: 0 0% 96.1%;
24+
--accent-foreground: 0 0% 9%;
25+
--destructive: 0 84.2% 60.2%;
26+
--destructive-foreground: 0 0% 98%;
27+
--border: 0 0% 89.8%;
28+
--input: 0 0% 89.8%;
29+
--ring: 0 0% 3.9%;
30+
--chart-1: 12 76% 61%;
31+
--chart-2: 173 58% 39%;
32+
--chart-3: 197 37% 24%;
33+
--chart-4: 43 74% 66%;
34+
--chart-5: 27 87% 67%;
35+
--radius: 0.5rem;
36+
}
37+
38+
.dark {
39+
--background: 0 0% 3.9%;
40+
--foreground: 0 0% 98%;
41+
--card: 0 0% 3.9%;
42+
--card-foreground: 0 0% 98%;
43+
--popover: 0 0% 3.9%;
44+
--popover-foreground: 0 0% 98%;
45+
--primary: 0 0% 98%;
46+
--primary-foreground: 0 0% 9%;
47+
--secondary: 0 0% 14.9%;
48+
--secondary-foreground: 0 0% 98%;
49+
--muted: 0 0% 14.9%;
50+
--muted-foreground: 0 0% 63.9%;
51+
--accent: 0 0% 14.9%;
52+
--accent-foreground: 0 0% 98%;
53+
--destructive: 0 62.8% 30.6%;
54+
--destructive-foreground: 0 0% 98%;
55+
--border: 0 0% 14.9%;
56+
--input: 0 0% 14.9%;
57+
--ring: 0 0% 83.1%;
58+
--chart-1: 220 70% 50%;
59+
--chart-2: 160 60% 45%;
60+
--chart-3: 30 80% 55%;
61+
--chart-4: 280 65% 60%;
62+
--chart-5: 340 75% 55%;
63+
}
64+
}
65+
66+
@layer base {
67+
* {
68+
@apply border-border;
69+
}
70+
71+
body {
72+
@apply bg-background text-foreground;
73+
}
74+
}

0 commit comments

Comments
 (0)