├── .all-contributorsrc
├── .editorconfig
├── .eslintrc.json
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── submit-ui-design.md
├── .gitignore
├── .husky
├── pre-commit
└── pre-push
├── .lintstagedrc.js
├── .prettier.config.js
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CONTRIBUTING_DESIGN.md
├── FUNDING.yml
├── LICENSE
├── README.md
├── app
├── (authenticated)
│ ├── layout.tsx
│ └── me
│ │ ├── page.tsx
│ │ ├── submission
│ │ └── [[...params]]
│ │ │ └── page.tsx
│ │ └── submissions
│ │ └── [[...params]]
│ │ └── page.tsx
├── actions.ts
├── auth
│ ├── callback
│ │ └── route.ts
│ └── login
│ │ └── page.tsx
├── challenge
│ └── [slug]
│ │ ├── head.tsx
│ │ ├── page.tsx
│ │ └── submission
│ │ └── page.tsx
├── challenges
│ └── page.tsx
├── code-the-design.ico
├── construction
│ └── page.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
├── manual
│ └── page.tsx
├── page.tsx
├── submissions
│ └── page.tsx
├── test-1
│ └── page.tsx
├── test-2
│ └── page.tsx
└── test
│ └── route.ts
├── commitlint.config.js
├── components
├── Analytics.tsx
├── AuthNav.tsx
├── Box.tsx
├── Button.tsx
├── CategoriesSelector.tsx
├── CategoriesSelectorClient.tsx
├── Challenge.tsx
├── Challenges.tsx
├── Combobox.tsx
├── Designers.tsx
├── DifficultiesSelector.tsx
├── DifficultiesSelectorClient.tsx
├── Dropdown.tsx
├── EmptyState.tsx
├── FigmaPreview.tsx
├── FileUpload.tsx
├── Form.tsx
├── Header.tsx
├── LoadMore.tsx
├── Meta.tsx
├── ScrollToTop.tsx
├── SectionTitle.tsx
├── Select.tsx
├── Sidebar.tsx
├── SidebarNav.tsx
├── Steps.tsx
├── Submission.tsx
├── SubmissionForm.tsx
├── SubmissionModeCheckbox.tsx
└── TechSelector.tsx
├── data
├── challenges.ts
└── difficulties.ts
├── drizzle.config.ts
├── drizzle
├── 0000_aromatic_mathemanic.sql
├── 0001_icy_aqueduct.sql
├── 0002_black_otto_octavius.sql
├── 0003_happy_jack_power.sql
├── 0004_steep_luckman.sql
├── 0005_white_arachne.sql
├── 0006_luxuriant_electro.sql
├── 0007_curvy_blazing_skull.sql
├── 0008_shiny_nuke.sql
├── 0009_keen_frightful_four.sql
├── 0010_zippy_rick_jones.sql
├── 0011_complete_sabra.sql
├── 0012_sharp_senator_kelly.sql
├── 0013_nifty_ender_wiggin.sql
├── 0014_illegal_machine_man.sql
└── meta
│ ├── 0000_snapshot.json
│ ├── 0001_snapshot.json
│ ├── 0002_snapshot.json
│ ├── 0003_snapshot.json
│ ├── 0004_snapshot.json
│ ├── 0005_snapshot.json
│ ├── 0006_snapshot.json
│ ├── 0007_snapshot.json
│ ├── 0008_snapshot.json
│ ├── 0009_snapshot.json
│ ├── 0010_snapshot.json
│ ├── 0011_snapshot.json
│ ├── 0012_snapshot.json
│ ├── 0013_snapshot.json
│ ├── 0014_snapshot.json
│ └── _journal.json
├── lib
├── db.ts
├── hooks
│ ├── usePageChanges.ts
│ └── useSearchParamsManipulator.ts
├── loader-image.ts
├── migrate.ts
├── session.ts
├── storage.ts
├── supabase.ts
└── utils.ts
├── middleware.ts
├── middlewares
└── auth-middleware.ts
├── next-env.d.ts
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── DumpingDoodle.png
├── alfian.jpeg
├── challenges
│ ├── 5
│ │ ├── adoptme.webp
│ │ ├── agency.webp
│ │ ├── agenone.webp
│ │ ├── al-nasr.webp
│ │ ├── authed.webp
│ │ ├── bertumbuh.webp
│ │ ├── bubble-bash.webp
│ │ ├── chat-n-rechat.webp
│ │ ├── chatflow-landing.webp
│ │ ├── chatflow.webp
│ │ ├── clone-netflix.webp
│ │ ├── collosal.webp
│ │ ├── comments.webp
│ │ ├── construction.webp
│ │ ├── destinize.webp
│ │ ├── digidaw.webp
│ │ ├── dressly.webp
│ │ ├── edufree.webp
│ │ ├── enlighten.webp
│ │ ├── epictetus.webp
│ │ ├── evenity.webp
│ │ ├── faceless.webp
│ │ ├── grolin.webp
│ │ ├── hangeulin.webp
│ │ ├── holadok.webp
│ │ ├── hoster-support.webp
│ │ ├── jobless.webp
│ │ ├── kourse.webp
│ │ ├── lidia.webp
│ │ ├── movies.webp
│ │ ├── mstskp.webp
│ │ ├── musix-player.webp
│ │ ├── nft.webp
│ │ ├── nowted-app.webp
│ │ ├── nowted.webp
│ │ ├── omah.webp
│ │ ├── online-learning.webp
│ │ ├── priced.webp
│ │ ├── pricy.webp
│ │ ├── profile-hover.webp
│ │ ├── suxz.webp
│ │ ├── swiftship.webp
│ │ ├── tailor.webp
│ │ ├── team-collaboration.webp
│ │ ├── testimoni.webp
│ │ ├── the-malaka.webp
│ │ ├── the-starter.webp
│ │ ├── the-sugihartos.webp
│ │ ├── the-zeitplan.webp
│ │ ├── twitter-embed.webp
│ │ └── wumbo.webp
│ ├── 1000
│ │ ├── adoptme.webp
│ │ ├── agency.webp
│ │ ├── agenone.webp
│ │ ├── al-nasr.webp
│ │ ├── authed.webp
│ │ ├── bertumbuh.webp
│ │ ├── bubble-bash.webp
│ │ ├── chat-n-rechat.webp
│ │ ├── chatflow-landing.webp
│ │ ├── chatflow.webp
│ │ ├── clone-netflix.webp
│ │ ├── collosal.webp
│ │ ├── comments.webp
│ │ ├── construction.webp
│ │ ├── destinize.webp
│ │ ├── digidaw.webp
│ │ ├── dressly.webp
│ │ ├── edufree.webp
│ │ ├── enlighten.webp
│ │ ├── epictetus.webp
│ │ ├── evenity.webp
│ │ ├── faceless.webp
│ │ ├── grolin.webp
│ │ ├── hangeulin.webp
│ │ ├── holadok.webp
│ │ ├── hoster-support.webp
│ │ ├── jobless.webp
│ │ ├── kourse.webp
│ │ ├── lidia.webp
│ │ ├── movies.webp
│ │ ├── mstskp.webp
│ │ ├── musix-player.webp
│ │ ├── nft.webp
│ │ ├── nowted-app.webp
│ │ ├── nowted.webp
│ │ ├── omah.webp
│ │ ├── online-learning.webp
│ │ ├── priced.webp
│ │ ├── pricy.webp
│ │ ├── profile-hover.webp
│ │ ├── suxz.webp
│ │ ├── swiftship.webp
│ │ ├── tailor.webp
│ │ ├── team-collaboration.webp
│ │ ├── testimoni.webp
│ │ ├── the-malaka.webp
│ │ ├── the-starter.webp
│ │ ├── the-sugihartos.webp
│ │ ├── the-zeitplan.webp
│ │ ├── twitter-embed.webp
│ │ └── wumbo.webp
│ ├── adoptme.webp
│ ├── agency.webp
│ ├── agenone.webp
│ ├── al-nasr.webp
│ ├── authed.webp
│ ├── bertumbuh.webp
│ ├── bubble-bash.webp
│ ├── chat-n-rechat.webp
│ ├── chatflow-landing.webp
│ ├── chatflow.webp
│ ├── clone-netflix.webp
│ ├── collosal.webp
│ ├── comments.webp
│ ├── construction.webp
│ ├── destinize.webp
│ ├── digidaw.webp
│ ├── dressly.webp
│ ├── edufree.webp
│ ├── enlighten.webp
│ ├── epictetus.webp
│ ├── evenity.webp
│ ├── faceless.webp
│ ├── grolin.webp
│ ├── hangeulin.webp
│ ├── holadok.webp
│ ├── hoster-support.webp
│ ├── jobless.webp
│ ├── kourse.webp
│ ├── lidia.webp
│ ├── movies.webp
│ ├── mstskp.webp
│ ├── musix-player.webp
│ ├── nft.webp
│ ├── nowted-app.webp
│ ├── nowted.webp
│ ├── omah.webp
│ ├── online-learning.webp
│ ├── priced.webp
│ ├── pricy.webp
│ ├── profile-hover.webp
│ ├── suxz.webp
│ ├── swiftship.webp
│ ├── tailor.webp
│ ├── team-collaboration.webp
│ ├── testimoni.webp
│ ├── the-malaka.webp
│ ├── the-starter.webp
│ ├── the-sugihartos.webp
│ ├── the-zeitplan.webp
│ ├── thumbs
│ │ ├── adoptme.webp
│ │ ├── agency.webp
│ │ ├── agenone.webp
│ │ ├── al-nasr.webp
│ │ ├── authed.webp
│ │ ├── bertumbuh.webp
│ │ ├── bubble-bash.webp
│ │ ├── chat-n-rechat.webp
│ │ ├── chatflow-landing.webp
│ │ ├── chatflow.webp
│ │ ├── clone-netflix.webp
│ │ ├── collosal.webp
│ │ ├── comments.webp
│ │ ├── construction.webp
│ │ ├── destinize.webp
│ │ ├── digidaw.webp
│ │ ├── dressly.webp
│ │ ├── edufree.webp
│ │ ├── enlighten.webp
│ │ ├── epictetus.webp
│ │ ├── evenity.webp
│ │ ├── faceless.webp
│ │ ├── grolin.webp
│ │ ├── hangeulin.webp
│ │ ├── holadok.webp
│ │ ├── hoster-support.webp
│ │ ├── jobless.webp
│ │ ├── kourse.webp
│ │ ├── lidia.webp
│ │ ├── movies.webp
│ │ ├── mstskp.webp
│ │ ├── musix-player.webp
│ │ ├── nft.webp
│ │ ├── nowted-app.webp
│ │ ├── nowted.webp
│ │ ├── omah.webp
│ │ ├── online-learning.webp
│ │ ├── priced.webp
│ │ ├── pricy.webp
│ │ ├── profile-hover.webp
│ │ ├── suxz.webp
│ │ ├── swiftship.webp
│ │ ├── tailor.webp
│ │ ├── team-collaboration.webp
│ │ ├── testimoni.webp
│ │ ├── the-malaka.webp
│ │ ├── the-starter.webp
│ │ ├── the-sugihartos.webp
│ │ ├── the-zeitplan.webp
│ │ ├── twitter-embed.webp
│ │ └── wumbo.webp
│ ├── twitter-embed.webp
│ └── wumbo.webp
├── code-design.png
├── code-the-design.svg
├── codedesign-bg.png
├── codedesign-logo.png
├── lidia.png
├── nav-sfx-2.mp3
├── nav-sfx-3.mp3
├── nav-sfx.mp3
├── section-header-quests.png
└── users
│ ├── afin.jpeg
│ ├── alfian.jpeg
│ ├── arie.jpeg
│ ├── brainstew.jpeg
│ ├── dimas.jpeg
│ ├── dzaki.jpeg
│ ├── fauzan.jpeg
│ ├── glenn.jpeg
│ ├── irham.jpeg
│ ├── irvan.jpeg
│ ├── mahyu.jpeg
│ ├── nauval.jpg
│ ├── rafiq.png
│ ├── syauqi.jpeg
│ ├── taufik.jpeg
│ └── yohana.jpeg
├── schemas
├── badges.ts
├── categories.ts
├── challenge_designers.tsx
├── challenges.ts
├── difficulties.ts
├── submission_techs.ts
├── submission_users.ts
├── submissions.ts
├── techs.ts
├── user_badges.ts
├── user_links.ts
├── user_points.ts
└── users.ts
├── seeds
├── data
│ └── tech-data.ts
├── seed.ts
└── tech-seed.ts
├── services
├── auth-service.ts
├── category-service.ts
├── challenge-service.ts
├── difficulty-service.ts
├── point_service.tsx
├── submission-service.ts
├── tech-service.ts
└── user-service.ts
├── tailwind.config.js
├── tsconfig.json
└── types
└── css.d.ts
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "commitConvention": "angular",
8 | "contributors": [
9 | {
10 | "login": "nauvalazhar",
11 | "name": "Nauval",
12 | "avatar_url": "https://avatars.githubusercontent.com/u/14899175?v=4",
13 | "profile": "http://nauv.al",
14 | "contributions": [
15 | "code",
16 | "design",
17 | "ideas",
18 | "doc"
19 | ]
20 | },
21 | {
22 | "login": "wiscaksono",
23 | "name": "Wisnu Wicaksono",
24 | "avatar_url": "https://avatars.githubusercontent.com/u/63142229?v=4",
25 | "profile": "https://wiscaksono.me/",
26 | "contributions": [
27 | "code",
28 | "design"
29 | ]
30 | },
31 | {
32 | "login": "fiqryq",
33 | "name": "Fiqry choerudin",
34 | "avatar_url": "https://avatars.githubusercontent.com/u/25787603?v=4",
35 | "profile": "http://fiqry.dev",
36 | "contributions": [
37 | "code"
38 | ]
39 | },
40 | {
41 | "login": "mrtampan",
42 | "name": "Achmad Rivaldi",
43 | "avatar_url": "https://avatars.githubusercontent.com/u/33930946?v=4",
44 | "profile": "https://mrtampan.github.io/",
45 | "contributions": [
46 | "code"
47 | ]
48 | },
49 | {
50 | "login": "perdiDev",
51 | "name": "Perdi",
52 | "avatar_url": "https://avatars.githubusercontent.com/u/108800230?v=4",
53 | "profile": "http://perdidev.github.io",
54 | "contributions": [
55 | "code",
56 | "design",
57 | "doc"
58 | ]
59 | },
60 | {
61 | "login": "hendraaagil",
62 | "name": "Hendra Agil",
63 | "avatar_url": "https://avatars.githubusercontent.com/u/54741166?v=4",
64 | "profile": "https://hendraaagil.dev",
65 | "contributions": [
66 | "code"
67 | ]
68 | },
69 | {
70 | "login": "up2dul",
71 | "name": "Abdul Malik",
72 | "avatar_url": "https://avatars.githubusercontent.com/u/36098718?v=4",
73 | "profile": "https://up2dul.com",
74 | "contributions": [
75 | "code"
76 | ]
77 | },
78 | {
79 | "login": "haikelz",
80 | "name": "ハキム",
81 | "avatar_url": "https://avatars.githubusercontent.com/u/77146709?v=4",
82 | "profile": "https://haikel.my.id",
83 | "contributions": [
84 | "code"
85 | ]
86 | },
87 | {
88 | "login": "iamyuu",
89 | "name": "Yusuf",
90 | "avatar_url": "https://avatars.githubusercontent.com/u/45778229?v=4",
91 | "profile": "https://github.com/iamyuu",
92 | "contributions": [
93 | "code",
94 | "design"
95 | ]
96 | },
97 | {
98 | "login": "mahdanidn",
99 | "name": "M. Mahdani Rahmatullah",
100 | "avatar_url": "https://avatars.githubusercontent.com/u/50037482?v=4",
101 | "profile": "https://github.com/mahdanidn",
102 | "contributions": [
103 | "code"
104 | ]
105 | },
106 | {
107 | "login": "alfanjauhari",
108 | "name": "Alfan Jauhari",
109 | "avatar_url": "https://avatars.githubusercontent.com/u/57592351?v=4",
110 | "profile": "https://alfanjauhari.com",
111 | "contributions": [
112 | "code"
113 | ]
114 | },
115 | {
116 | "login": "sglkc",
117 | "name": "Seya",
118 | "avatar_url": "https://avatars.githubusercontent.com/u/31957516?v=4",
119 | "profile": "https://sglkc.my.id",
120 | "contributions": [
121 | "code"
122 | ]
123 | },
124 | {
125 | "login": "altafsyah",
126 | "name": "Altaf Syah",
127 | "avatar_url": "https://avatars.githubusercontent.com/u/57579846?v=4",
128 | "profile": "https://github.com/altafsyah",
129 | "contributions": [
130 | "design"
131 | ]
132 | }
133 | ],
134 | "contributorsPerLine": 7,
135 | "skipCi": true,
136 | "repoType": "github",
137 | "repoHost": "https://github.com",
138 | "projectName": "code-design",
139 | "projectOwner": "nauvalazhar",
140 | "commitType": "docs"
141 | }
142 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Top-most EditorConfig file
2 | root = true
3 |
4 | # Set default charset to UTF-8
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 |
10 | # End of EditorConfig file
11 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/submit-ui-design.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Submit UI Design
3 | about: Submit your UI design
4 | title: New UI Design Contribution
5 | labels: ui design contribs
6 | assignees: nauvalazhar
7 |
8 | ---
9 | Make sure you have read our [contributing guidelines](https://github.com/nauvalazhar/code-design/blob/main/CONTRIBUTING_DESIGN.md) before submitting this issue.
10 |
11 | **Name**
12 | Your UI design project name. Must be short.
13 |
14 | **Figma Community URL**
15 | Your UI design URL to the Figma community.
16 |
17 | **Your Figma Profile**
18 | The URL of your Figma profile. For example, https://figma.com/@mhd
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | if [ -t 2 ]; then
5 | exec >/dev/tty 2>&1
6 | fi
7 |
8 | yarn lint-staged
9 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | if [ -t 2 ]; then
5 | exec >/dev/tty 2>&1
6 | fi
7 |
8 | yarn build
--------------------------------------------------------------------------------
/.lintstagedrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Lint & Prettify TSX, TS, JSX, and JS files
3 | '**/*.(tsx|ts|jsx|js)': filenames => [
4 | `yarn lint`,
5 | `yarn prettier --config ./.prettier.config.js -w ${filenames.join(' ')}`
6 | ],
7 |
8 | // Prettify only JSON files
9 | '**/*.(json)': filenames => `yarn prettier -w ${filenames.join(' ')}`
10 | };
11 |
--------------------------------------------------------------------------------
/.prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'none',
4 | arrowParens: 'avoid',
5 | proseWrap: 'preserve',
6 | quoteProps: 'as-needed',
7 | bracketSameLine: false,
8 | bracketSpacing: true,
9 | tabWidth: 2,
10 | importOrder: ['^data/(.*)$', '^components/(.*)$', '^[./]'],
11 | importOrderBuiltinModulesToTop: true,
12 | importOrderCaseInsensitive: true,
13 | importOrderParserPlugins: ['jsx', 'decorators-legacy', 'typescript'],
14 | importOrderMergeDuplicateImports: true,
15 | importOrderCombineTypeAndValueImports: true,
16 | importOrderSeparation: true,
17 | importOrderSortSpecifiers: true,
18 | plugins: [
19 | require('prettier-plugin-tailwindcss'),
20 | require('@ianvs/prettier-plugin-sort-imports'),
21 | ],
22 | };
23 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "typescript.enablePromptUseWorkspaceTsdk": true
4 | }
5 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | nauvalazhar2@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | First of all, thank you for taking your valuable time to contribute.
4 |
5 | Here, we would explain everything about all you need to know to start your first contribution to this project.
6 |
7 | ## UI Design Contribution
8 | For UI design contributions, please consult the [UI Design Contribution Guideline document](https://github.com/nauvalazhar/code-design/blob/main/CONTRIBUTING_DESIGN.md).
9 |
10 | ## Code Contribution
11 |
12 | ### Tech Stacks
13 |
14 | We use Next JS as our front-end framework, there are several reasons why we use Next JS. Objectively, Next JS has a better developer experience compared to others that are available on the market. Subjectively, because it's React, we all love React and so do you.
15 | Besides React, we use Tailwind CSS for our styling tool. We don’t use a UI kit library here, since we design every UIs from scratch. Using a UI kit library in this kind of situation would only bring us to tears.
16 | For any interactive components, we use Radix UI as our headless components.
17 |
18 | ### Requirements
19 |
20 | Since the project is still not Dockerized yet, so you have to install all the following requirements into your machine:
21 |
22 | - Node JS v16
23 | - Yarn
24 |
25 | ### How To Run The Front-end App
26 |
27 | Since we use Next JS as our framework, the development workflow is simple:
28 |
29 | 1. Fork this repository
30 | 2. Run the `cd` command into the project directory like `cd code-design`
31 | 3. Run the `yarn` command to start installing all the Node JS dependencies
32 | 4. To start the development server, run the `yarn dev` command
33 |
34 | ### How To Contribute
35 |
36 | There are no formal steps that you need to follow to contribute, but here are some tips:
37 | - Find some opening issues
38 | - Have some discussion about the issue before you working on
39 | - Confirm to the maintainers that you will working on the issue
40 |
41 | Worth noting, please **do not** implement any new feature without opening a new issue first and have disscussion with the maintainers.
42 |
43 | ### Commit Your Changes
44 |
45 | You need to follow [conventional commits](https://www.conventionalcommits.org/) for your commit message before you push all the changes.
46 |
47 | ### Submit Pull Request
48 |
49 | To help the maintainers review your changes, you could follow the following tips:
50 | - Write a detailed description if you made several changes in a single pull request
51 | - You may make changes to some component's interactivity, please attach a related Gif or video so that the maintainers know the expected behavior
52 | - Please attach a screenshot if your changes related to the UI in general
53 | - Please put any link reference if you think that would help the maintainers
54 |
55 | Currently, the only maintainer is [@nauvalazhar](https://github.com/nauvalazhar), so if your pull request has not been reviewed within 24 hours, feel free to mention him on Twitter or any other platform that he lives on.
56 |
57 | ---
58 |
59 | Thank you for any kind of contribution to the project, it helps us to grow the project faster!
60 |
--------------------------------------------------------------------------------
/CONTRIBUTING_DESIGN.md:
--------------------------------------------------------------------------------
1 | # UI Design Contribution Guideline
2 | Thank you for your interest in contributing to the design collection!
3 | The purpose of this guide is to provide you with the information you need to make meaningful contributions to the project.
4 |
5 | ## Design Guidelines
6 | In order to maintain a consistent style and user experience across the designs in the collection, please follow these design guidelines:
7 | - Use Figma. We all here love Figma, so you are.
8 | - Create at least two pages:
9 | - **Thumbnail**: Contains all of your design thumbnail information, such as Figma Community file cover, Dribbble shot cover, etc.
10 | - **UI Design** or **Design**: Contains all of your UI design frames, contents, components, etc.
11 | - Publish your UI design as a Figma Community file.
12 |
13 | We don't have any design standards for now, but you may follow these general design tips from us:
14 | - Use a clean design style that focuses on readability and usability.
15 | - Use a limited typography that is consistent with your overall UI design.
16 | - Avoid clutter and distractions. Keep the UI design focused on the most important information.
17 | - Consider the user's experience and how they will interact with the design.
18 |
19 | ## Submitting a New Design
20 | To contribute a new design to the collection, follow these steps:
21 | 1. Go to the [New Issue page](https://github.com/nauvalazhar/code-design/issues/new/choose)
22 | 2. Select the "Submit UI Design" template
23 | 3. Fill out all the questions
24 | 4. Wait for the review process
25 |
26 | ## Review Process
27 | The repository maintainers will review your issue and provide feedback or suggestions for changes. If your design is accepted, it will be published into our site. If changes are needed, you'll be asked to make them and let the maintainers know.
28 |
29 | ## Conclusion
30 | Thank you for your contributions! By submitting a new design, you are helping to make the collection even better and more valuable to users. We look forward to reviewing your UI design contribution!
31 |
32 | > This guideline is new, you may see old UI designs are not following these guidelines.
33 |
--------------------------------------------------------------------------------
/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [nauvalazhar]
2 | custom: https://trakteer.id/mhdnauvalazhar
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Muhamad Nauval Azhar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/(authenticated)/layout.tsx:
--------------------------------------------------------------------------------
1 | import AuthNavs from 'components/AuthNav';
2 | import Box, { BoxTab, BoxTabs } from 'components/Box';
3 | import Link from 'next/link';
4 |
5 | export default function Layout({ children }: { children: React.ReactNode }) {
6 | return (
7 |
8 |
9 | {children}
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/app/(authenticated)/me/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation';
2 | import { getAuthUser } from 'services/auth-service';
3 |
4 | import { BoxTitle } from 'components/Box';
5 | import { Field, Input, Label } from 'components/Form';
6 |
7 | export const metadata = {
8 | title: 'Profile'
9 | };
10 |
11 | export default async function Page() {
12 | const user = await getAuthUser();
13 |
14 | if (true) {
15 | redirect('/construction');
16 | }
17 |
18 | return (
19 | <>
20 | Profile
21 |
22 | Name
23 |
24 |
25 |
26 | Username
27 |
28 |
29 |
30 | Registered at
31 |
32 |
33 |
34 | We're still working on this page. Currently you can only view your
35 | profile.
36 |
37 | >
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/app/(authenticated)/me/submission/[[...params]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SubmissionMode } from 'app/actions';
2 | import { notFound } from 'next/navigation';
3 | import { getAuthId, isUserReviewer } from 'services/auth-service';
4 | import { getSubmissionBySlug } from 'services/submission-service';
5 |
6 | import Box, { BoxTitle } from 'components/Box';
7 | import SubmissionForm from 'components/SubmissionForm';
8 |
9 | export const metadata = {
10 | title: 'Submission'
11 | };
12 |
13 | export default async function Page({
14 | params: { params }
15 | }: {
16 | params: { params: string[] };
17 | }) {
18 | const slug = params[0];
19 | let mode = (params[1] || 'edit') as SubmissionMode;
20 | const submission = await getSubmissionBySlug(slug);
21 | const isReviewer = isUserReviewer();
22 | const userId = await getAuthId();
23 |
24 | if (isReviewer) {
25 | mode = 'review';
26 | }
27 |
28 | if (
29 | (mode === 'review' && !isReviewer) ||
30 | (submission.reviewer && submission.reviewer?.userId !== userId)
31 | ) {
32 | notFound();
33 | }
34 |
35 | return (
36 |
37 | {mode} Submission
38 |
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/app/(authenticated)/me/submissions/[[...params]]/page.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import { dateFormat } from 'lib/utils';
3 | import Link from 'next/link';
4 | import { notFound } from 'next/navigation';
5 | import { getAuthUser, isUserReviewer } from 'services/auth-service';
6 | import { getSubmissions } from 'services/submission-service';
7 |
8 | import { BoxDescription, BoxTitle } from 'components/Box';
9 | import { Button } from 'components/Button';
10 | import SubmissionModeCheckbox from 'components/SubmissionModeCheckbox';
11 |
12 | export const metadata = {
13 | title: 'Submissions'
14 | };
15 |
16 | export default async function Page({
17 | params: { params }
18 | }: {
19 | params: { params: string[] };
20 | }) {
21 | const mode = params ? params[0] : 'owner';
22 | const submissions = await getSubmissions({
23 | where: {
24 | mode: mode === 'reviewer' ? 'reviewer' : 'owner'
25 | }
26 | });
27 |
28 | const user = await getAuthUser();
29 | const isReviewer = await isUserReviewer();
30 |
31 | if (mode === 'reviewer' && !isReviewer) {
32 | notFound();
33 | }
34 |
35 | return (
36 | <>
37 |
38 | Submissions
39 |
40 | {mode === 'reviewer'
41 | ? 'Here you can review the submissions that are pending for your review.'
42 | : 'Here you can see all your submissions.'}
43 |
44 |
45 |
46 | {submissions.map(submission => (
47 |
55 |
56 |
{submission.challenge.name}
57 |
58 | {dateFormat(submission.createdAt)} - {submission.phase} -{' '}
59 | {submission.user.name}
60 |
61 |
62 |
63 | {isReviewer && submission.phase === 'submitted' && (
64 |
65 |
66 | Review
67 |
68 |
69 | )}
70 |
71 |
72 | Edit
73 |
74 |
75 |
76 | ))}
77 |
78 | >
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/app/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { redirect } from 'next/navigation';
4 | import { techIdsSchema } from 'schemas/submission_techs';
5 | import { insertSubmissionSchema, NewSubmission } from 'schemas/submissions';
6 | import { Tech } from 'schemas/techs';
7 | import { isUserReviewer } from 'services/auth-service';
8 | import {
9 | createSubmission,
10 | updateSubmission
11 | } from 'services/submission-service';
12 | import { getTechs } from 'services/tech-service';
13 |
14 | export type SubmissionMode = 'create' | 'edit' | 'review';
15 |
16 | export async function handleCreateSubmission(
17 | challenge: string,
18 | mode: SubmissionMode,
19 | prevState: any,
20 | formData: FormData
21 | ) {
22 | const demo = formData.get('demo') as string;
23 | const repository = formData.get('repository') as string;
24 | const description = formData.get('description') as string;
25 | const techs = formData.get('techs') as string;
26 |
27 | const parse = insertSubmissionSchema.safeParse({
28 | demo,
29 | repository,
30 | description
31 | });
32 |
33 | if (parse.success === false) {
34 | return {
35 | success: false,
36 | error: parse.error.flatten().fieldErrors
37 | };
38 | }
39 |
40 | const parseTechs = techIdsSchema.safeParse({
41 | techs
42 | });
43 |
44 | if (parseTechs.success === false) {
45 | return {
46 | success: false,
47 | error: parseTechs.error.flatten().fieldErrors
48 | };
49 | }
50 |
51 | await createSubmission({
52 | demo,
53 | repository,
54 | description,
55 | challenge,
56 | techs: parseTechs.data.techs
57 | });
58 |
59 | return {
60 | success: true
61 | };
62 | }
63 |
64 | export async function handleUpdateSubmission(
65 | submissionId: NewSubmission['id'],
66 | mode: SubmissionMode,
67 | prevState: any,
68 | formData: FormData
69 | ) {
70 | const demo = formData.get('demo') as string;
71 | const repository = formData.get('repository') as string;
72 | const description = formData.get('description') as string;
73 | const techs = formData.get('techs') as string;
74 | const image = formData.get('image') as File;
75 | const phase = formData.get('phase') as NewSubmission['phase'];
76 | const note = formData.get('note') as string;
77 | const imageOld = formData.get('image_old') as string;
78 |
79 | const parse = insertSubmissionSchema.safeParse({
80 | demo,
81 | repository,
82 | description,
83 | image,
84 | phase,
85 | note
86 | });
87 |
88 | if (parse.success === false) {
89 | return {
90 | success: false,
91 | error: parse.error.flatten().fieldErrors
92 | };
93 | }
94 |
95 | const parseTechs = techIdsSchema.safeParse({
96 | techs
97 | });
98 |
99 | if (parseTechs.success === false) {
100 | return {
101 | success: false,
102 | error: parseTechs.error.flatten().fieldErrors
103 | };
104 | }
105 |
106 | await updateSubmission(
107 | {
108 | id: submissionId,
109 | demo,
110 | repository,
111 | description,
112 | phase,
113 | note,
114 | image,
115 | techs: parseTechs.data.techs,
116 | imageOld
117 | },
118 | mode === 'review'
119 | );
120 |
121 | const isReviewer = await isUserReviewer();
122 |
123 | if (isReviewer) {
124 | redirect('/me/submissions/reviewer');
125 | }
126 | redirect('/me/submissions');
127 | }
128 |
129 | export async function loadTechs(search): Promise {
130 | 'use server';
131 |
132 | return getTechs({
133 | limit: 5,
134 | where: {
135 | search
136 | }
137 | });
138 | }
139 |
--------------------------------------------------------------------------------
/app/auth/callback/route.ts:
--------------------------------------------------------------------------------
1 | import { encryptData, getSession } from 'lib/session';
2 | import { redirect } from 'next/navigation';
3 | import { getOrRegisterUser } from 'services/user-service';
4 |
5 | export async function GET(request: Request) {
6 | const session = await getSession();
7 |
8 | const query = new URL(request.url).searchParams;
9 | const code = query.get('code');
10 |
11 | const accessToken = await fetch(
12 | 'https://github.com/login/oauth/access_token',
13 | {
14 | method: 'POST',
15 | headers: {
16 | 'Content-Type': 'application/json',
17 | Accept: 'application/json'
18 | },
19 | body: JSON.stringify({
20 | client_id: process.env.GITHUB_ID,
21 | client_secret: process.env.GITHUB_SECRET,
22 | code
23 | })
24 | }
25 | );
26 |
27 | const result = await accessToken.json();
28 |
29 | if (result.error) {
30 | return new Response(result.error, { status: 400 });
31 | }
32 |
33 | const resultUser = await fetch('https://api.github.com/user', {
34 | headers: {
35 | Authorization: `Bearer ${result.access_token}`
36 | }
37 | });
38 | const userInfo = await resultUser.json();
39 |
40 | const resultEmails = await fetch('https://api.github.com/user/emails', {
41 | headers: {
42 | Authorization: `Bearer ${result.access_token}`
43 | }
44 | });
45 | const emails = await resultEmails.json();
46 |
47 | const [user] = await getOrRegisterUser({
48 | username: userInfo.login,
49 | email: emails.find((email: any) => email.primary)?.email,
50 | name: userInfo.name || userInfo.login,
51 | bio: userInfo.bio,
52 | avatar: userInfo.avatar_url,
53 | githubId: userInfo.id,
54 | hireable: userInfo.hireable,
55 | refreshToken: await encryptData({
56 | token: result.refresh_token,
57 | expires: result.refresh_token_expires_in
58 | })
59 | });
60 |
61 | session.token = result.access_token;
62 | session.expires = result.expires_in;
63 | session.userId = user.id;
64 |
65 | await session.save();
66 |
67 | redirect('/');
68 | }
69 |
--------------------------------------------------------------------------------
/app/auth/login/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from 'next/navigation';
2 | import { isAuth } from 'services/auth-service';
3 |
4 | import Box, { BoxDescription, BoxTitle } from 'components/Box';
5 | import { Button } from 'components/Button';
6 |
7 | export const metadata = {
8 | title: 'Login'
9 | };
10 |
11 | export default async function Page() {
12 | const isLogin = await isAuth();
13 |
14 | if (isLogin) {
15 | redirect('/me');
16 | }
17 |
18 | return (
19 |
20 | Login
21 | You can only login with GitHub for now.
22 |
23 |
26 | Login with github
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/app/challenge/[slug]/head.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import challengeData from 'data/challenges';
3 |
4 | export default async function Head({ params }) {
5 | const challenge = challengeData.find(
6 | challenge => challenge.slug === params.slug
7 | );
8 |
9 | return (
10 | <>
11 | {`${challenge?.name} – Code The Design`}
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
33 |
34 |
38 |
42 |
46 |
47 |
48 |
52 |
56 |
60 |
64 | >
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/app/challenge/[slug]/submission/page.tsx:
--------------------------------------------------------------------------------
1 | import { notFound } from 'next/navigation';
2 |
3 | import Box, { BoxTitle } from 'components/Box';
4 | import SubmissionForm from 'components/SubmissionForm';
5 |
6 | export const metadata = {
7 | title: 'Submission'
8 | };
9 |
10 | export default function Page({
11 | params: { slug }
12 | }: {
13 | params: { slug: string };
14 | }) {
15 | if (true) {
16 | notFound();
17 | }
18 |
19 | return (
20 |
21 | Submission
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/app/challenges/page.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | getChallenges,
3 | type GetChallengesParams
4 | } from 'services/challenge-service';
5 |
6 | import CategoriesSelector from 'components/CategoriesSelector';
7 | import Challenges from 'components/Challenges';
8 | import DifficultiesSelector from 'components/DifficultiesSelector';
9 | import LoadMore from 'components/LoadMore';
10 | import { SectionTitle } from 'components/SectionTitle';
11 |
12 | export const metadata = {
13 | title: 'Challenges'
14 | };
15 |
16 | async function loadMore(offset, params) {
17 | 'use server';
18 |
19 | const { challenges } = await getChallenges({
20 | ...params,
21 | offset
22 | });
23 |
24 | return ;
25 | }
26 |
27 | export default async function Page({
28 | searchParams
29 | }: {
30 | searchParams: { [key: string]: string | string[] | undefined };
31 | }) {
32 | const dataParams: GetChallengesParams = {
33 | where: {
34 | difficultyId: Number(searchParams.difficulty) || undefined,
35 | categoryId: Number(searchParams.category) || undefined
36 | }
37 | };
38 |
39 | const { challenges, limit, total } = await getChallenges(dataParams);
40 |
41 | return (
42 |
43 |
44 |
Challenges
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/app/code-the-design.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/app/code-the-design.ico
--------------------------------------------------------------------------------
/app/construction/page.tsx:
--------------------------------------------------------------------------------
1 | import Box, { BoxDescription, BoxTitle } from 'components/Box';
2 |
3 | export default function Page() {
4 | return (
5 |
6 | Construction
7 | This page is under construction.
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #191919;
3 | background-image: url('/codedesign-bg.png');
4 | background-position: center;
5 | background-attachment: fixed;
6 | background-size: cover;
7 | background-repeat: no-repeat;
8 |
9 | @apply text-body;
10 | }
11 |
12 | .gradient-blue {
13 | background-image: linear-gradient(90.34deg, #4375f2 0%, #4375f2 100%);
14 | }
15 |
16 | .button-shadow {
17 | box-shadow: 0px 8px 4px rgba(0, 0, 0, 0.15), 0px 4px 0px #2854c3;
18 | }
19 |
20 | .button-text-shadow {
21 | text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.2);
22 | }
23 |
24 | .text-shadow {
25 | text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
26 | }
27 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css';
2 |
3 | import clsx from 'clsx';
4 | import { Metadata } from 'next';
5 | import { Fredoka, Patrick_Hand } from 'next/font/google';
6 | import { PropsWithChildren } from 'react';
7 |
8 | import { AnalyticsWrapper } from 'components/Analytics';
9 | import ScrollToTop from 'components/ScrollToTop';
10 |
11 | import './globals.css';
12 |
13 | import Sidebar from 'components/Sidebar';
14 |
15 | const fontDisplay = Fredoka({
16 | variable: '--font-display',
17 | weight: '500',
18 | subsets: ['latin']
19 | });
20 |
21 | const fontSans = Fredoka({
22 | variable: '--font-sans',
23 | weight: ['400', '500', '600', '700'],
24 | subsets: ['latin']
25 | });
26 |
27 | export const metadata: Metadata = {
28 | title: {
29 | template: '%s – codedesign.dev',
30 | default: 'Untitled'
31 | }
32 | };
33 |
34 | export default function RootLayout({ children }: PropsWithChildren) {
35 | return (
36 |
43 |
44 |
45 |
46 |
49 | {/**/}
67 |
68 |
69 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/app/manual/page.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 |
3 | import Box, { BoxTitle } from 'components/Box';
4 |
5 | export default async function Page() {
6 | return (
7 |
8 | Manual
9 |
10 |
Motivation
11 |
12 | As a developer, it's important to continually challenge yourself
13 | and improve your skills. Code The Design was created with this goal in
14 | mind. We believe that the best way to learn and grow is by tackling
15 | real-life projects and challenges, rather than just following along
16 | with tutorials or practicing with contrived exercises.
17 |
18 |
19 | That's why we've created a platform that offers a wide
20 | variety of challenges, inspired by real-world designs and use cases.
21 | These challenges are meant to push you to think creatively,
22 | problem-solve, and develop your skills as a developer.
23 |
24 |
25 | We understand that everyone has different levels of experience and
26 | expertise, which is why we've included a subjective difficulty
27 | rating for each challenge. This allows you to choose challenges that
28 | are appropriate for your current skill level, and gradually work your
29 | way up as you improve.
30 |
31 |
32 | We also understand that the website is in a very early stage and far
33 | from being stable. Our focus is on getting the website visible as soon
34 | as possible, which is why we don't currently offer pre-exported
35 | assets and specific objectives for the challenges. However, we provide
36 | Figma design files for each challenge, which you can learn how the
37 | designs work and use to export any assets you need.
38 |
39 |
40 | We hope that Code The Design will serve as a valuable resource for you
41 | as you work to improve your skills and become a more confident and
42 | capable developer. Happy coding!
43 |
44 |
Technologies
45 |
46 | You have the freedom to choose the technologies that you feel
47 | comfortable using in order to complete the challenges on our website.
48 | We do not have a preferred tech stack, and we welcome developers from
49 | all backgrounds and skill levels to participate in our challenges.
50 | Whether you prefer to use front-end technologies like HTML, CSS, and
51 | JavaScript, or back-end technologies like PHP, Python, or Ruby, you
52 | are free to use whatever tools and frameworks you feel most
53 | comfortable with. Our goal is to provide you with real-life use case
54 | challenges that allow you to showcase your skills and abilities, and
55 | we believe that the best way to do this is by giving you the freedom
56 | to choose the technologies that work best for you.
57 |
58 |
Contribution
59 |
60 | We are glad that you are interested in contributing to our project. As
61 | a reminder, our project is open source and we welcome contributions
62 | from anyone who is interested in helping us build a better product.
63 |
64 |
65 | We believe that open source projects thrive when there is a diverse
66 | range of perspectives and skillsets involved. Whether you are a
67 | developer, designer, or simply have an idea that you think could
68 | improve the project, we encourage you to reach out and get involved.{' '}
69 |
70 |
71 | There are many ways you can contribute to our project. You can submit
72 | bug reports and feature requests, help us by testing new features and
73 | fixing issues, or even contribute code or design assets directly. No
74 | matter how you choose to get involved, your contribution will make a
75 | difference and help us to continue building a great product.
76 |
77 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { Arrow, Interfaces } from 'doodle-icons';
2 | import type { Metadata } from 'next';
3 | import Image from 'next/image';
4 | import Link from 'next/link';
5 | import sectionHeaderQuests from 'public/section-header-quests.png';
6 | import { getChallenges } from 'services/challenge-service';
7 |
8 | import Challenges from 'components/Challenges';
9 | import Designers from 'components/Designers';
10 | import { SectionTitle } from 'components/SectionTitle';
11 |
12 | export const metadata: Metadata = {
13 | title:
14 | 'Level up your coding skills with hands-on design challenges – codedesign.dev',
15 | description: 'Level up your coding skills with hands-on design challenges.',
16 | keywords: 'code the design, design with figma, design web, design challange',
17 | openGraph: {
18 | type: 'website',
19 | locale: 'en_US',
20 | url: 'https://codedesign.dev/',
21 | siteName: 'codedesign.dev',
22 | title: 'codedesign.dev',
23 | description: 'Level up your coding skills with hands-on design challenges.',
24 | images: ['https://codedesign.dev/code-design.png']
25 | }
26 | };
27 |
28 | export default async function Home() {
29 | const { challenges } = await getChallenges({
30 | limit: 2
31 | });
32 |
33 | return (
34 |
35 |
36 |
Challenges
37 |
41 | View all challenges
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Designers
51 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/app/submissions/page.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return Submissions
;
3 | }
4 |
--------------------------------------------------------------------------------
/app/test-1/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Link from 'next/link';
4 | import { useState } from 'react';
5 |
6 | export default function Page() {
7 | const [count, setCount] = useState(0);
8 |
9 | return (
10 |
11 | {count}
12 | setCount(count + 1)}>Increment
13 |
14 | Test 2
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/app/test-2/page.tsx:
--------------------------------------------------------------------------------
1 | import { getChallenges } from 'services/challenge-service';
2 |
3 | export default async function Page() {
4 | const challenges = await getChallenges();
5 |
6 | return Test 2 ;
7 | }
8 |
--------------------------------------------------------------------------------
/app/test/route.ts:
--------------------------------------------------------------------------------
1 | import { getSession } from 'lib/session';
2 |
3 | export async function GET() {
4 | const session = await getSession();
5 |
6 | const response = await fetch('https://api.github.com/user/emails', {
7 | headers: {
8 | Authorization: `Bearer ${session.token}`,
9 | },
10 | });
11 |
12 | const data = await response.json();
13 |
14 | return Response.json(data);
15 | }
16 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ['@commitlint/config-conventional'] };
2 |
--------------------------------------------------------------------------------
/components/Analytics.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Analytics } from '@vercel/analytics/react';
4 |
5 | export function AnalyticsWrapper() {
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/components/AuthNav.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { BoxTab, BoxTabs } from 'components/Box';
4 | import Link from 'next/link';
5 | import { usePathname } from 'next/navigation';
6 |
7 | const AuthNavs = () => {
8 | const pathname = usePathname();
9 |
10 | return (
11 |
12 |
13 | Profile
14 |
15 |
16 | Submissions
17 |
18 |
19 | );
20 | };
21 |
22 | export default AuthNavs;
23 |
--------------------------------------------------------------------------------
/components/Box.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from '@radix-ui/react-slot';
2 | import clsx from 'clsx';
3 |
4 | type BoxProps = React.HTMLAttributes;
5 |
6 | const Box = (props: BoxProps) => {
7 | return (
8 |
16 | );
17 | };
18 |
19 | type BoxTitleProps = React.HTMLAttributes;
20 |
21 | export const BoxTitle = ({ className, ...props }: BoxTitleProps) => {
22 | return (
23 |
30 | );
31 | };
32 |
33 | export const BoxDescription = ({ children }: { children: React.ReactNode }) => {
34 | return (
35 | {children}
36 | );
37 | };
38 |
39 | export const BoxTabs = ({ children }: { children: React.ReactNode }) => {
40 | return (
41 |
42 | {children}
43 |
44 | );
45 | };
46 |
47 | type BoxTabProps = React.HTMLAttributes & {
48 | asChild?: boolean;
49 | active?: boolean;
50 | };
51 |
52 | export const BoxTab = ({
53 | children,
54 | asChild = false,
55 | active,
56 | ...props
57 | }: BoxTabProps) => {
58 | const Comp = asChild ? Slot : 'button';
59 |
60 | return (
61 |
70 | {children}
71 |
72 | );
73 | };
74 |
75 | export default Box;
76 |
--------------------------------------------------------------------------------
/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from '@radix-ui/react-slot';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 | import { Interfaces } from 'doodle-icons';
4 | import { cn } from 'lib/utils';
5 | import * as React from 'react';
6 |
7 | const buttonVariants = cva(
8 | 'flex font-display items-center justify-center gap-4 uppercase',
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | 'border-transparent button-shadow button-text-shadow gradient-blue rounded-xl text-brand-light',
14 | secondary: 'border-black bg-orange-400',
15 | danger: 'border-black bg-red-500 text-brand'
16 | },
17 | size: {
18 | sm: 'border-2 px-4 py-2 text-lg',
19 | md: 'border-2 px-6 py-3 text-xl',
20 | default: 'border-2 px-8 py-4 text-xl'
21 | }
22 | },
23 | defaultVariants: {
24 | variant: 'default',
25 | size: 'default'
26 | }
27 | }
28 | );
29 |
30 | export interface ButtonProps
31 | extends React.ButtonHTMLAttributes,
32 | VariantProps {
33 | asChild?: boolean;
34 | progress?: boolean;
35 | }
36 |
37 | const Button = React.forwardRef(
38 | (
39 | {
40 | className,
41 | variant,
42 | size,
43 | asChild = false,
44 | progress,
45 | children,
46 | disabled,
47 | ...props
48 | },
49 | ref
50 | ) => {
51 | const Comp = asChild ? Slot : 'button';
52 | return (
53 |
59 | {progress ? (
60 |
64 | ) : (
65 | children
66 | )}
67 |
68 | );
69 | }
70 | );
71 | Button.displayName = 'Button';
72 |
73 | export { Button, buttonVariants };
74 |
--------------------------------------------------------------------------------
/components/CategoriesSelector.tsx:
--------------------------------------------------------------------------------
1 | import CategoriesSelectorClient from 'components/CategoriesSelectorClient';
2 | import { getCategories } from 'services/category-service';
3 |
4 | const CategoriesSelector = async () => {
5 | const categories = await getCategories();
6 |
7 | return ;
8 | };
9 |
10 | export default CategoriesSelector;
11 |
--------------------------------------------------------------------------------
/components/CategoriesSelectorClient.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useSearchParamsManipulator } from 'lib/hooks/useSearchParamsManipulator';
4 | import type { Category } from 'schemas/categories';
5 |
6 | import {
7 | Select,
8 | SelectContent,
9 | SelectItem,
10 | SelectTrigger
11 | } from 'components/Select';
12 |
13 | const CategoriesSelectorClient = ({
14 | categories
15 | }: {
16 | categories: Category[];
17 | }) => {
18 | const { setSearchParams, searchParams } = useSearchParamsManipulator();
19 |
20 | function handleSelect(category) {
21 | if (category === '-1') {
22 | category = undefined;
23 | }
24 |
25 | setSearchParams({
26 | category
27 | });
28 | }
29 |
30 | return (
31 |
35 |
36 |
37 | Category
38 | {categories.map(({ id, name }) => (
39 |
40 | {name}
41 |
42 | ))}
43 |
44 |
45 | );
46 | };
47 |
48 | export default CategoriesSelectorClient;
49 |
--------------------------------------------------------------------------------
/components/Challenge.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import { Arrow, Interfaces } from 'doodle-icons';
3 | import Image from 'next/image';
4 | import Link from 'next/link';
5 |
6 | export type ChallengeProps = {
7 | name: string;
8 | difficulty: string;
9 | category: string;
10 | accent: string;
11 | accent2: string;
12 | image: string;
13 | slug: string;
14 | designers: string[];
15 | };
16 |
17 | function Challenge({
18 | name,
19 | difficulty,
20 | category,
21 | accent,
22 | accent2,
23 | image,
24 | slug,
25 | designers
26 | }: ChallengeProps) {
27 | return (
28 |
29 |
41 |
46 | {name}
47 |
48 | Design by {designers[0]} {' '}
49 | {designers.length > 1 && `+ ${designers.length - 1} others`}
50 |
51 |
52 |
57 |
62 |
63 |
64 |
65 | );
66 | }
67 |
68 | type MetaProps = {
69 | icon: React.ElementType>;
70 | name: string;
71 | value: string;
72 | };
73 |
74 | function Meta({ icon: Icon, name, value }: MetaProps) {
75 | return (
76 |
77 |
78 |
79 | {name}
80 | {value}
81 |
82 |
83 | );
84 | }
85 |
86 | export default Challenge;
87 |
--------------------------------------------------------------------------------
/components/Challenges.tsx:
--------------------------------------------------------------------------------
1 | import { getChallenges, JoinedChallenge } from 'services/challenge-service';
2 |
3 | import { Button } from 'components/Button';
4 | import Challenge from 'components/Challenge';
5 |
6 | const Challenges = ({ challenges }: { challenges: JoinedChallenge[] }) => {
7 | return (
8 | <>
9 | {challenges.map(
10 | ({
11 | name,
12 | image,
13 | accent,
14 | accent2,
15 | slug,
16 | category,
17 | difficulty,
18 | designers
19 | }) => (
20 | name)}
30 | />
31 | )
32 | )}
33 | >
34 | );
35 | };
36 |
37 | export default Challenges;
38 |
--------------------------------------------------------------------------------
/components/Combobox.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import clsx from 'clsx';
4 | import { Command, useCommandState, type CommandRoot } from 'cmdk';
5 | import { inputStyle } from 'components/Form';
6 |
7 | type ComboboxProps = React.ComponentProps;
8 |
9 | export const Combobox = ({ children, ...props }: ComboboxProps) => {
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | };
16 |
17 | type ComboboxListProps = React.ComponentProps;
18 |
19 | export const ComboboxList = (props: ComboboxListProps) => {
20 | const search = useCommandState((state) => state.search);
21 |
22 | return (
23 |
31 | );
32 | };
33 |
34 | type ComboboxInputProps = React.ComponentProps;
35 |
36 | export const ComboboxInput = (props: ComboboxInputProps) => {
37 | return ;
38 | };
39 |
40 | type ComboboxItemProps = React.ComponentProps;
41 |
42 | export const ComboboxItem = (props: ComboboxItemProps) => {
43 | return (
44 |
48 | );
49 | };
50 |
51 | type ComboboxLoadingProps = React.ComponentProps;
52 |
53 | export const ComboboxLoading = (props: ComboboxLoadingProps) => {
54 | return (
55 |
59 | );
60 | };
61 |
62 | type ComboboxEmptyProps = React.ComponentProps;
63 |
64 | export const ComboboxEmpty = (props: ComboboxEmptyProps) => {
65 | return ;
66 | };
67 |
--------------------------------------------------------------------------------
/components/Designers.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import { getDesigners } from 'services/user-service';
3 |
4 | const Designers = async () => {
5 | const designers = await getDesigners();
6 |
7 | return (
8 |
9 | {designers.map(designer => (
10 |
14 |
19 |
{designer.name}
20 |
21 | ))}
22 |
23 | );
24 | };
25 |
26 | export default Designers;
27 |
--------------------------------------------------------------------------------
/components/DifficultiesSelector.tsx:
--------------------------------------------------------------------------------
1 | import DifficultiesSelectorClient from 'components/DifficultiesSelectorClient';
2 | import { getDifficulties } from 'services/difficulty-service';
3 |
4 | const DifficultiesSelector = async () => {
5 | const difficulties = await getDifficulties();
6 |
7 | return ;
8 | };
9 |
10 | export default DifficultiesSelector;
11 |
--------------------------------------------------------------------------------
/components/DifficultiesSelectorClient.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useSearchParamsManipulator } from 'lib/hooks/useSearchParamsManipulator';
4 | import type { Difficulty } from 'schemas/difficulties';
5 |
6 | import {
7 | Select,
8 | SelectContent,
9 | SelectItem,
10 | SelectTrigger
11 | } from 'components/Select';
12 |
13 | const DifficultiesSelectorClient = ({
14 | difficulties
15 | }: {
16 | difficulties: Difficulty[];
17 | }) => {
18 | const { setSearchParams, searchParams } = useSearchParamsManipulator();
19 |
20 | function handleSelect(difficulty) {
21 | if (difficulty === '-1') {
22 | difficulty = undefined;
23 | }
24 |
25 | setSearchParams({
26 | difficulty
27 | });
28 | }
29 |
30 | return (
31 |
35 |
36 |
37 | Difficulty
38 | {difficulties.map(({ id, name }) => (
39 |
40 | {name}
41 |
42 | ))}
43 |
44 |
45 | );
46 | };
47 |
48 | export default DifficultiesSelectorClient;
49 |
--------------------------------------------------------------------------------
/components/Dropdown.tsx:
--------------------------------------------------------------------------------
1 | import * as RadixDropdown from '@radix-ui/react-dropdown-menu';
2 | import clsx from 'clsx';
3 | import { ChevronDown } from 'lucide-react';
4 |
5 | const Dropdown = ({ children }: { children: React.ReactNode }) => {
6 | return {children} ;
7 | };
8 |
9 | const DropdownTrigger = ({ children }: { children: React.ReactNode }) => {
10 | return (
11 |
12 | {children}
13 |
14 |
15 | );
16 | };
17 |
18 | const DropdownContent = ({ children }: { children: React.ReactNode }) => {
19 | return (
20 |
21 |
24 | {children}
25 |
26 |
27 | );
28 | };
29 |
30 | const DropdownItem = ({
31 | children,
32 | ...props
33 | }: RadixDropdown.DropdownMenuItemProps) => {
34 | return (
35 |
41 | {children}
42 |
43 | );
44 | };
45 |
46 | export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem };
47 |
--------------------------------------------------------------------------------
/components/EmptyState.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 |
3 | const EmptyState = ({ children }) => {
4 | return {children}
;
5 | };
6 |
7 | const EmptyStateTitle = ({ children }) => {
8 | return {children} ;
9 | };
10 |
11 | const EmptyStateDescription = ({ children }) => {
12 | return {children}
;
13 | };
14 |
15 | const EmptyStateImage = ({ src, alt }) => {
16 | return ;
17 | };
18 |
19 | export { EmptyState, EmptyStateTitle, EmptyStateDescription, EmptyStateImage };
20 |
21 | export default EmptyState;
22 |
--------------------------------------------------------------------------------
/components/FigmaPreview.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import clsx from 'clsx';
4 | import { Interfaces } from 'doodle-icons';
5 | import { useState } from 'react';
6 |
7 | export type FigmaPreviewProps = {
8 | src: string;
9 | };
10 |
11 | export default function FigmaPreview({ src }: FigmaPreviewProps) {
12 | const [loading, setLoading] = useState(true);
13 | const id = src.split(/\//).pop();
14 |
15 | function loaded() {
16 | setLoading(false);
17 | }
18 |
19 | return (
20 |
27 | {loading && (
28 |
35 |
39 | Loading Figma Preview
40 |
41 | )}
42 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/components/FileUpload.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Files } from 'doodle-icons';
4 | import Image from 'next/image';
5 | import { useRef, useState, type ElementType, type SVGProps } from 'react';
6 |
7 | import { Button } from 'components/Button';
8 |
9 | type FileUploadProps = {
10 | name: string;
11 | icon?: ElementType>;
12 | defaultValue?: string;
13 | };
14 |
15 | const FileUpload = ({
16 | name,
17 | icon: Icon = Files.File,
18 | defaultValue
19 | }: FileUploadProps) => {
20 | const inputRef = useRef(null);
21 | const [, setFile] = useState(null);
22 | const [preview, setPreview] = useState(null);
23 | const [showInput, setShowInput] = useState(defaultValue ? false : true);
24 | const [oldFile, setOldFile] = useState(defaultValue);
25 |
26 | function handleClick() {
27 | setShowInput(true);
28 |
29 | setTimeout(() => {
30 | inputRef.current?.click();
31 | }, 0);
32 | }
33 |
34 | function handleChange(e) {
35 | const file = e.target.files[0];
36 |
37 | if (file) {
38 | setFile(file);
39 | setPreview(URL.createObjectURL(file));
40 | }
41 | }
42 |
43 | return (
44 |
45 | {showInput ? (
46 |
54 | ) : (
55 |
56 | )}
57 | {oldFile &&
}
58 |
59 |
60 |
61 | Choose File
62 |
63 |
64 | {preview && (
65 | // @ts-ignore
66 |
71 | )}
72 |
73 | {defaultValue && !preview && (
74 |
75 |
81 |
82 | )}
83 |
84 | );
85 | };
86 |
87 | export default FileUpload;
88 |
--------------------------------------------------------------------------------
/components/Form.tsx:
--------------------------------------------------------------------------------
1 | import { cva, VariantProps } from 'class-variance-authority';
2 | import { Asterisk } from 'lucide-react';
3 |
4 | export const Field = ({
5 | children,
6 | ...props
7 | }: React.HTMLAttributes) => {
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | };
14 |
15 | export const FieldAsterisk = () => {
16 | return (
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export const FieldHelp = (props: React.HTMLAttributes) => {
24 | return
;
25 | };
26 |
27 | type LabelProps = React.HTMLAttributes;
28 |
29 | export const Label = ({ children, ...props }: LabelProps) => {
30 | return (
31 |
32 | {children}
33 |
34 | );
35 | };
36 |
37 | export const inputStyle = cva(
38 | 'w-full px-4 py-2 border-4 border-black text-2xl focus:outline-none focus-visible:ring-0 text-black'
39 | );
40 |
41 | type InputProps = VariantProps &
42 | React.InputHTMLAttributes;
43 |
44 | export const Input = ({ className, ...props }: InputProps) => {
45 | return ;
46 | };
47 |
48 | type TextareaProps = VariantProps &
49 | React.TextareaHTMLAttributes;
50 |
51 | export const Textarea = ({ className, ...props }: TextareaProps) => {
52 | return ;
53 | };
54 |
55 | export const FieldMessage = ({ message }: { message?: string | string[] }) => {
56 | if (!message) return null;
57 |
58 | return {message}
;
59 | };
60 |
--------------------------------------------------------------------------------
/components/Header.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import clsx from 'clsx';
4 | import { Files, Interfaces, Logos } from 'doodle-icons';
5 | import Image from 'next/image';
6 | import Link from 'next/link';
7 | import { usePathname } from 'next/navigation';
8 | import logo from 'public/codedesign-logo.png';
9 | import { ElementType, ReactNode, SVGProps } from 'react';
10 |
11 | export type NavItemProps = {
12 | href: string;
13 | icon: ElementType>;
14 | children: ReactNode;
15 | active?: boolean;
16 | };
17 |
18 | function NavItem({ href, icon: Icon, children, active }: NavItemProps) {
19 | return (
20 |
28 |
29 | {children}
30 |
31 | );
32 | }
33 |
34 | function Header() {
35 | const pathname = usePathname();
36 |
37 | return (
38 |
44 |
45 |
49 |
50 |
51 |
52 |
60 |
65 | Challenges
66 |
67 |
72 | Manual
73 |
74 |
75 |
76 | );
77 | }
78 |
79 | export default Header;
80 |
--------------------------------------------------------------------------------
/components/LoadMore.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState } from 'react';
4 |
5 | import { Button } from 'components/Button';
6 |
7 | type LoadMoreProps = React.HTMLAttributes & {
8 | children: React.ReactNode;
9 | loadMoreParams: any;
10 | loadMoreAction: (offset, params) => any;
11 | limit: number;
12 | total: number;
13 | };
14 |
15 | const LoadMore = ({
16 | children,
17 | loadMoreAction,
18 | loadMoreParams,
19 | limit,
20 | total,
21 | ...props
22 | }: LoadMoreProps) => {
23 | const [moreNodes, setMoreNodes] = useState([]);
24 | const [offset, setOffset] = useState(0);
25 | const [loading, setLoading] = useState(false);
26 |
27 | async function handleClick() {
28 | setLoading(true);
29 |
30 | const nextOffset = offset + limit;
31 | const data = await loadMoreAction(nextOffset, loadMoreParams);
32 |
33 | setMoreNodes(prev => [...prev, data]);
34 | setOffset(nextOffset);
35 | setLoading(false);
36 | }
37 |
38 | const hasMore = offset + limit < total;
39 |
40 | return (
41 | <>
42 |
43 | {total === 1
44 | ? 'There is 1 challenge available'
45 | : `There are ${total} challenges available`}
46 |
47 |
48 |
49 | {children}
50 | {moreNodes}
51 |
52 | {hasMore ? (
53 |
58 | Load More
59 |
60 | ) : null}
61 | >
62 | );
63 | };
64 |
65 | export default LoadMore;
66 |
--------------------------------------------------------------------------------
/components/Meta.tsx:
--------------------------------------------------------------------------------
1 | import { ElementType, SVGProps } from 'react';
2 |
3 | export type MetaProps = {
4 | icon: ElementType>;
5 | name: string;
6 | value: string;
7 | };
8 |
9 | export default function Meta({ icon: Icon, name, value }: MetaProps) {
10 | return (
11 |
12 |
13 |
14 | {name}
15 | {value}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/components/ScrollToTop.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import clsx from 'clsx';
4 | import { Arrow } from 'doodle-icons';
5 | import { useEffect, useState } from 'react';
6 |
7 | export default function ScrollToTop() {
8 | const [isVisible, setIsVisible] = useState(false);
9 | const toggleVisibility = () => {
10 | if (window.pageYOffset > 300) {
11 | setIsVisible(true);
12 | } else {
13 | setIsVisible(false);
14 | }
15 | };
16 |
17 | const scrollToTop = () => {
18 | window.scrollTo({
19 | top: 0,
20 | behavior: 'smooth'
21 | });
22 | };
23 |
24 | useEffect(() => {
25 | window.addEventListener('scroll', toggleVisibility);
26 |
27 | return () => {
28 | window.removeEventListener('scroll', toggleVisibility);
29 | };
30 | }, []);
31 |
32 | return (
33 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/components/SectionTitle.tsx:
--------------------------------------------------------------------------------
1 | import { cva, VariantProps } from 'class-variance-authority';
2 |
3 | const sectionTitleVariants = cva(
4 | [
5 | 'text-2xl font-semibold relative',
6 | 'before:absolute before:-z-10 before:bg-blue-500',
7 | 'before:-left-2 before:-right-2 before:-top-1 before:-bottom-1',
8 | 'before:-rotate-2'
9 | ],
10 | {
11 | variants: {
12 | variant: {
13 | primary: 'before:bg-blue-500',
14 | rose: 'before:bg-rose-500'
15 | }
16 | },
17 | defaultVariants: {
18 | variant: 'primary'
19 | }
20 | }
21 | );
22 |
23 | type SectionTitleProps = React.HTMLAttributes &
24 | VariantProps;
25 |
26 | export function SectionTitle({
27 | children,
28 | variant,
29 | className
30 | }: SectionTitleProps) {
31 | return (
32 | {children}
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/components/Select.tsx:
--------------------------------------------------------------------------------
1 | import * as RadixSelect from '@radix-ui/react-select';
2 | import clsx from 'clsx';
3 | import { Check, ChevronDown } from 'lucide-react';
4 |
5 | const Select = ({ children, ...props }: RadixSelect.SelectProps) => {
6 | return {children} ;
7 | };
8 |
9 | type SelectTriggerProps = RadixSelect.SelectTriggerProps & {
10 | placeholder: string;
11 | };
12 |
13 | const SelectTrigger = ({ placeholder }: SelectTriggerProps) => {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | const SelectContent = ({ children }: { children: React.ReactNode }) => {
25 | return (
26 |
27 |
31 | {children}
32 |
33 |
34 | );
35 | };
36 |
37 | const SelectItem = ({
38 | children,
39 | disabled,
40 | ...props
41 | }: RadixSelect.SelectItemProps) => {
42 | return (
43 |
51 | {children}
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export { Select, SelectTrigger, SelectContent, SelectItem };
60 |
--------------------------------------------------------------------------------
/components/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Link from 'next/link';
3 | import logo from 'public/codedesign-logo.png';
4 |
5 | import { SidebarLittleNav, SidebarNavigation } from 'components/SidebarNav';
6 |
7 | export default function Sidebar() {
8 | return (
9 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/components/SidebarNav.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import clsx from 'clsx';
4 | import { Arrow } from 'doodle-icons';
5 | import type { Route } from 'next';
6 | import Link from 'next/link';
7 | // import useSound from 'use-sound';
8 | import { usePathname } from 'next/navigation';
9 |
10 | export function SidebarNavigation() {
11 | const pathname = usePathname();
12 |
13 | return (
14 |
15 |
16 | Home
17 |
18 |
22 | Challenges
23 |
24 | Submissions
25 | Maps
26 |
27 | Profile
28 |
29 |
30 | );
31 | }
32 |
33 | export function SidebarNav({ children }: { children: React.ReactNode }) {
34 | return {children} ;
35 | }
36 |
37 | type SidebarNavItemProps = {
38 | children: React.ReactNode;
39 | href: Route;
40 | active?: boolean;
41 | };
42 |
43 | export function SidebarNavItem({
44 | children,
45 | href,
46 | active
47 | }: SidebarNavItemProps) {
48 | // const [play, { stop }] = useSound('/nav-sfx-2.mp3');
49 |
50 | return (
51 | play()}
54 | // onMouseLeave={() => stop()}
55 | className={clsx(
56 | 'group flex items-center text-xl hover:text-brand',
57 | active && 'text-brand font-semibold'
58 | )}
59 | >
60 |
71 | {children}
72 |
73 | );
74 | }
75 |
76 | export function SidebarLittleNav({
77 | children,
78 | href,
79 | active
80 | }: SidebarNavItemProps) {
81 | // const [play, { stop }] = useSound('/nav-sfx-3.mp3');
82 |
83 | return (
84 | play()}
87 | // onMouseLeave={() => stop()}
88 | className={clsx('hover:text-brand', active && 'text-brand font-semibold')}
89 | >
90 | {children}
91 |
92 | );
93 | }
94 |
--------------------------------------------------------------------------------
/components/Steps.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 |
3 | type StepsProps = React.HTMLAttributes;
4 |
5 | export function Steps({ children }: StepsProps) {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export function Step({ children }: StepsProps) {
14 | return (
15 |
25 | {children}
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/components/Submission.tsx:
--------------------------------------------------------------------------------
1 | import SubmissionForm from 'components/SubmissionForm';
2 | import TechSelector from 'components/TechSelector';
3 |
4 | const Submission = () => {
5 | // return ;
6 | };
7 |
8 | export default Submission;
9 |
--------------------------------------------------------------------------------
/components/SubmissionModeCheckbox.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import clsx from 'clsx';
4 | import { Interfaces } from 'doodle-icons';
5 | import { useParams, useRouter } from 'next/navigation';
6 |
7 | const SubmissionModeCheckbox = () => {
8 | const router = useRouter();
9 | const { params = [] } = useParams();
10 | const [mode] = params;
11 |
12 | function handleChange(e) {
13 | const isChecked = e.target.checked;
14 |
15 | if (isChecked) {
16 | return router.push('/me/submissions/reviewer');
17 | }
18 |
19 | return router.push('/me/submissions');
20 | }
21 |
22 | return (
23 |
24 |
30 |
35 |
36 |
37 | As Reviewer
38 |
39 |
40 | );
41 | };
42 |
43 | export default SubmissionModeCheckbox;
44 |
--------------------------------------------------------------------------------
/components/TechSelector.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | Combobox,
5 | ComboboxInput,
6 | ComboboxItem,
7 | ComboboxList,
8 | ComboboxLoading,
9 | } from 'components/Combobox';
10 | import { Interfaces } from 'doodle-icons';
11 | import debounce from 'lodash.debounce';
12 | import { useCallback, useEffect, useState } from 'react';
13 | import { Tech } from 'schemas/techs';
14 |
15 | type TechSelectorProps = {
16 | loadFunc: (search: string) => Promise;
17 | defaultValue?: Tech[];
18 | };
19 |
20 | const TechSelector = ({ loadFunc, defaultValue }: TechSelectorProps) => {
21 | const [techs, setTechs] = useState([]);
22 | const [loading, setLoading] = useState(false);
23 | const [selected, setSelected] = useState(defaultValue || []);
24 | const [search, setSearch] = useState('');
25 | const [serializedValues, setSerializedValues] = useState('');
26 |
27 | useEffect(() => {
28 | setSerializedValues(selected.map((tech) => tech.id).join(','));
29 | }, [selected]);
30 |
31 | const debouncedLoadData = useCallback(
32 | debounce(async (search) => {
33 | if (search === '') {
34 | setTechs([]);
35 | setLoading(false);
36 | return;
37 | }
38 |
39 | const techs = await loadFunc(search);
40 |
41 | setTechs(techs);
42 | setLoading(false);
43 | }, 500),
44 | [loadFunc]
45 | );
46 |
47 | async function handleInput(input) {
48 | const value = input.target.value;
49 |
50 | setSearch(value);
51 | setLoading(true);
52 | debouncedLoadData(value);
53 | }
54 |
55 | async function handleSelect(value) {
56 | if (selected.find((tech) => tech.id === value.id)) {
57 | return;
58 | }
59 |
60 | setSelected((prev) => [...prev, value]);
61 | setTechs([]);
62 | setSearch('');
63 | debouncedLoadData.cancel();
64 | }
65 |
66 | async function handleRemove(id: number) {
67 | setSelected((prev) => prev.filter((tech) => tech.id !== id));
68 | }
69 |
70 | return (
71 | <>
72 |
73 |
78 | {loading && (
79 |
80 |
81 |
82 | )}
83 |
84 | {loading && Searching ... }
85 | {!loading && search.length > 0 && techs.length === 0 && (
86 | No results found
87 | )}
88 |
89 | {techs.map((tech) => (
90 | handleSelect(tech)}>
91 | {tech.name}
92 |
93 | ))}
94 |
95 |
96 |
97 | {selected.length > 0 && (
98 |
99 | {selected.map((tech) => (
100 |
103 | {tech.name}
104 | handleRemove(tech.id)}>
108 |
109 |
110 |
111 | ))}
112 |
113 | )}
114 |
115 |
116 | >
117 | );
118 | };
119 |
120 | export default TechSelector;
121 |
--------------------------------------------------------------------------------
/data/difficulties.ts:
--------------------------------------------------------------------------------
1 | export type Difficulty =
2 | | 'All'
3 | | 'Beginner'
4 | | 'Easy'
5 | | 'Intermediate'
6 | | 'Advanced'
7 | | 'Expert'
8 | | 'Master';
9 |
10 | const difficulties: Record<'name', Difficulty>[] = [
11 | {
12 | name: 'Beginner'
13 | },
14 | {
15 | name: 'Easy'
16 | },
17 | {
18 | name: 'Intermediate'
19 | },
20 | {
21 | name: 'Advanced'
22 | },
23 | {
24 | name: 'Expert'
25 | },
26 | {
27 | name: 'Master'
28 | }
29 | ];
30 |
31 | export default difficulties;
32 |
--------------------------------------------------------------------------------
/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 | import type { Config } from 'drizzle-kit';
3 |
4 | export default {
5 | schema: './schemas',
6 | out: './drizzle',
7 | driver: 'pg',
8 | dbCredentials: {
9 | connectionString: process.env.POSTGRES_URL || '',
10 | },
11 | } satisfies Config;
12 |
--------------------------------------------------------------------------------
/drizzle/0000_aromatic_mathemanic.sql:
--------------------------------------------------------------------------------
1 | DO $$ BEGIN
2 | CREATE TYPE "role" AS ENUM('user', 'admin');
3 | EXCEPTION
4 | WHEN duplicate_object THEN null;
5 | END $$;
6 | --> statement-breakpoint
7 | DO $$ BEGIN
8 | CREATE TYPE "status" AS ENUM('active', 'blocked');
9 | EXCEPTION
10 | WHEN duplicate_object THEN null;
11 | END $$;
12 | --> statement-breakpoint
13 | CREATE TABLE IF NOT EXISTS "badges" (
14 | "id" serial PRIMARY KEY NOT NULL,
15 | "name" varchar,
16 | "description" text,
17 | "image" text,
18 | "created_at" timestamp DEFAULT now(),
19 | "updated_at" timestamp,
20 | CONSTRAINT "badges_name_unique" UNIQUE("name")
21 | );
22 | --> statement-breakpoint
23 | CREATE TABLE IF NOT EXISTS "categories" (
24 | "id" serial PRIMARY KEY NOT NULL,
25 | "name" varchar,
26 | "slug" varchar,
27 | "created_at" timestamp DEFAULT now(),
28 | "updated_at" timestamp,
29 | CONSTRAINT "categories_name_unique" UNIQUE("name"),
30 | CONSTRAINT "categories_slug_unique" UNIQUE("slug")
31 | );
32 | --> statement-breakpoint
33 | CREATE TABLE IF NOT EXISTS "challenge_designers" (
34 | "id" serial PRIMARY KEY NOT NULL,
35 | "challenge_id" integer,
36 | "user_id" integer,
37 | CONSTRAINT "challenge_designers_challenge_id_user_id_unique" UNIQUE("challenge_id","user_id")
38 | );
39 | --> statement-breakpoint
40 | CREATE TABLE IF NOT EXISTS "challenges" (
41 | "id" serial PRIMARY KEY NOT NULL,
42 | "name" text,
43 | "slug" text,
44 | "description" text,
45 | "difficulty_id" integer,
46 | "category_id" integer,
47 | "image" text,
48 | "accent" text,
49 | "accent2" text,
50 | "figma" text,
51 | "created_at" timestamp DEFAULT now(),
52 | "updated_at" timestamp,
53 | CONSTRAINT "challenges_name_unique" UNIQUE("name"),
54 | CONSTRAINT "challenges_slug_unique" UNIQUE("slug")
55 | );
56 | --> statement-breakpoint
57 | CREATE TABLE IF NOT EXISTS "difficulties" (
58 | "id" serial PRIMARY KEY NOT NULL,
59 | "name" varchar,
60 | "created_at" timestamp DEFAULT now(),
61 | "updated_at" timestamp,
62 | CONSTRAINT "difficulties_name_unique" UNIQUE("name")
63 | );
64 | --> statement-breakpoint
65 | CREATE TABLE IF NOT EXISTS "user_badges" (
66 | "id" serial PRIMARY KEY NOT NULL,
67 | "user_id" integer,
68 | "badge_id" integer,
69 | "created_at" timestamp DEFAULT now(),
70 | "updated_at" timestamp,
71 | CONSTRAINT "user_badges_user_id_badge_id_unique" UNIQUE("user_id","badge_id")
72 | );
73 | --> statement-breakpoint
74 | CREATE TABLE IF NOT EXISTS "user_links" (
75 | "id" serial PRIMARY KEY NOT NULL,
76 | "user_id" integer,
77 | "name" varchar,
78 | "link" text,
79 | "created_at" timestamp DEFAULT now(),
80 | "updated_at" timestamp
81 | );
82 | --> statement-breakpoint
83 | CREATE TABLE IF NOT EXISTS "users" (
84 | "id" serial PRIMARY KEY NOT NULL,
85 | "username" varchar,
86 | "email" varchar,
87 | "name" varchar,
88 | "bio" text,
89 | "avatar" text,
90 | "github_id" bigint,
91 | "hireable" boolean,
92 | "role" "role" DEFAULT 'user',
93 | "refresh_token" text,
94 | "status" "status" DEFAULT 'active',
95 | "created_at" timestamp DEFAULT now(),
96 | "updated_at" timestamp,
97 | CONSTRAINT "users_username_unique" UNIQUE("username"),
98 | CONSTRAINT "users_email_unique" UNIQUE("email")
99 | );
100 | --> statement-breakpoint
101 | DO $$ BEGIN
102 | ALTER TABLE "challenge_designers" ADD CONSTRAINT "challenge_designers_challenge_id_challenges_id_fk" FOREIGN KEY ("challenge_id") REFERENCES "challenges"("id") ON DELETE no action ON UPDATE no action;
103 | EXCEPTION
104 | WHEN duplicate_object THEN null;
105 | END $$;
106 | --> statement-breakpoint
107 | DO $$ BEGIN
108 | ALTER TABLE "challenge_designers" ADD CONSTRAINT "challenge_designers_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
109 | EXCEPTION
110 | WHEN duplicate_object THEN null;
111 | END $$;
112 | --> statement-breakpoint
113 | DO $$ BEGIN
114 | ALTER TABLE "challenges" ADD CONSTRAINT "challenges_difficulty_id_difficulties_id_fk" FOREIGN KEY ("difficulty_id") REFERENCES "difficulties"("id") ON DELETE no action ON UPDATE no action;
115 | EXCEPTION
116 | WHEN duplicate_object THEN null;
117 | END $$;
118 | --> statement-breakpoint
119 | DO $$ BEGIN
120 | ALTER TABLE "challenges" ADD CONSTRAINT "challenges_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE no action ON UPDATE no action;
121 | EXCEPTION
122 | WHEN duplicate_object THEN null;
123 | END $$;
124 | --> statement-breakpoint
125 | DO $$ BEGIN
126 | ALTER TABLE "user_badges" ADD CONSTRAINT "user_badges_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
127 | EXCEPTION
128 | WHEN duplicate_object THEN null;
129 | END $$;
130 | --> statement-breakpoint
131 | DO $$ BEGIN
132 | ALTER TABLE "user_badges" ADD CONSTRAINT "user_badges_badge_id_badges_id_fk" FOREIGN KEY ("badge_id") REFERENCES "badges"("id") ON DELETE no action ON UPDATE no action;
133 | EXCEPTION
134 | WHEN duplicate_object THEN null;
135 | END $$;
136 | --> statement-breakpoint
137 | DO $$ BEGIN
138 | ALTER TABLE "user_links" ADD CONSTRAINT "user_links_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
139 | EXCEPTION
140 | WHEN duplicate_object THEN null;
141 | END $$;
142 |
--------------------------------------------------------------------------------
/drizzle/0001_icy_aqueduct.sql:
--------------------------------------------------------------------------------
1 | DO $$ BEGIN
2 | CREATE TYPE "phase" AS ENUM('published', 'submitted', 'rejected', 'archived');
3 | EXCEPTION
4 | WHEN duplicate_object THEN null;
5 | END $$;
6 | --> statement-breakpoint
7 | DO $$ BEGIN
8 | CREATE TYPE "visibility" AS ENUM('public', 'private', 'unlisted');
9 | EXCEPTION
10 | WHEN duplicate_object THEN null;
11 | END $$;
12 | --> statement-breakpoint
13 | CREATE TABLE IF NOT EXISTS "submission_techs" (
14 | "id" serial PRIMARY KEY NOT NULL,
15 | "submission_id" integer,
16 | "tech_id" integer
17 | );
18 | --> statement-breakpoint
19 | CREATE TABLE IF NOT EXISTS "submission_users" (
20 | "id" serial PRIMARY KEY NOT NULL,
21 | "submission_id" integer,
22 | "user_id" integer,
23 | "role" "role",
24 | "created_at" timestamp DEFAULT now(),
25 | "updated_at" timestamp
26 | );
27 | --> statement-breakpoint
28 | CREATE TABLE IF NOT EXISTS "submissions" (
29 | "id" serial PRIMARY KEY NOT NULL,
30 | "slug" varchar,
31 | "description" varchar,
32 | "repository" text,
33 | "demo" text,
34 | "visibility" "visibility" DEFAULT 'public',
35 | "phase" "phase" DEFAULT 'submitted',
36 | "created_at" timestamp DEFAULT now(),
37 | "updated_at" timestamp,
38 | CONSTRAINT "submissions_slug_unique" UNIQUE("slug")
39 | );
40 | --> statement-breakpoint
41 | CREATE TABLE IF NOT EXISTS "techs" (
42 | "id" serial PRIMARY KEY NOT NULL,
43 | "name" varchar,
44 | "slug" varchar,
45 | "description" text,
46 | "logo" text,
47 | CONSTRAINT "techs_name_unique" UNIQUE("name"),
48 | CONSTRAINT "techs_slug_unique" UNIQUE("slug")
49 | );
50 | --> statement-breakpoint
51 | DO $$ BEGIN
52 | ALTER TABLE "submission_techs" ADD CONSTRAINT "submission_techs_submission_id_submissions_id_fk" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE no action ON UPDATE no action;
53 | EXCEPTION
54 | WHEN duplicate_object THEN null;
55 | END $$;
56 | --> statement-breakpoint
57 | DO $$ BEGIN
58 | ALTER TABLE "submission_techs" ADD CONSTRAINT "submission_techs_tech_id_techs_id_fk" FOREIGN KEY ("tech_id") REFERENCES "techs"("id") ON DELETE no action ON UPDATE no action;
59 | EXCEPTION
60 | WHEN duplicate_object THEN null;
61 | END $$;
62 | --> statement-breakpoint
63 | DO $$ BEGIN
64 | ALTER TABLE "submission_users" ADD CONSTRAINT "submission_users_submission_id_submissions_id_fk" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE no action ON UPDATE no action;
65 | EXCEPTION
66 | WHEN duplicate_object THEN null;
67 | END $$;
68 | --> statement-breakpoint
69 | DO $$ BEGIN
70 | ALTER TABLE "submission_users" ADD CONSTRAINT "submission_users_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
71 | EXCEPTION
72 | WHEN duplicate_object THEN null;
73 | END $$;
74 |
--------------------------------------------------------------------------------
/drizzle/0002_black_otto_octavius.sql:
--------------------------------------------------------------------------------
1 | DO $$ BEGIN
2 | CREATE TYPE "classification" AS ENUM('language', 'framework', 'library', 'database', 'platform', 'service', 'tool', 'os', 'ide');
3 | EXCEPTION
4 | WHEN duplicate_object THEN null;
5 | END $$;
6 | --> statement-breakpoint
7 | DO $$ BEGIN
8 | CREATE TYPE "environment" AS ENUM('frontend', 'backend', 'fullstack', 'mobile', 'desktop');
9 | EXCEPTION
10 | WHEN duplicate_object THEN null;
11 | END $$;
12 | --> statement-breakpoint
13 | ALTER TABLE "techs" ADD COLUMN "classification" "classification";--> statement-breakpoint
14 | ALTER TABLE "techs" ADD COLUMN "environment" "environment";
--------------------------------------------------------------------------------
/drizzle/0003_happy_jack_power.sql:
--------------------------------------------------------------------------------
1 | ALTER TYPE "classification" ADD VALUE 'style';
--------------------------------------------------------------------------------
/drizzle/0004_steep_luckman.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "submissions" ADD COLUMN "challenge_id" integer;--> statement-breakpoint
2 | DO $$ BEGIN
3 | ALTER TABLE "submissions" ADD CONSTRAINT "submissions_challenge_id_challenges_id_fk" FOREIGN KEY ("challenge_id") REFERENCES "challenges"("id") ON DELETE no action ON UPDATE no action;
4 | EXCEPTION
5 | WHEN duplicate_object THEN null;
6 | END $$;
7 |
--------------------------------------------------------------------------------
/drizzle/0005_white_arachne.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "submissions" ADD COLUMN "note" text;
--------------------------------------------------------------------------------
/drizzle/0006_luxuriant_electro.sql:
--------------------------------------------------------------------------------
1 | DO $$ BEGIN
2 | CREATE TYPE "job" AS ENUM('owner', 'collaborator', 'reviewer');
3 | EXCEPTION
4 | WHEN duplicate_object THEN null;
5 | END $$;
6 | --> statement-breakpoint
7 | ALTER TABLE "submission_users" ADD COLUMN "job" "job";--> statement-breakpoint
8 | ALTER TABLE "submission_users" DROP COLUMN IF EXISTS "role";
--------------------------------------------------------------------------------
/drizzle/0007_curvy_blazing_skull.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "submissions" ALTER COLUMN "description" SET DATA TYPE text;
--------------------------------------------------------------------------------
/drizzle/0008_shiny_nuke.sql:
--------------------------------------------------------------------------------
1 | DO $$ BEGIN
2 | CREATE TYPE "badge_values" AS ENUM('designer', 'reviewer', 'contributor');
3 | EXCEPTION
4 | WHEN duplicate_object THEN null;
5 | END $$;
6 | --> statement-breakpoint
7 | ALTER TABLE "badges" ADD COLUMN "value" "badge_values";
--------------------------------------------------------------------------------
/drizzle/0009_keen_frightful_four.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "submissions" ADD COLUMN "image" text;
--------------------------------------------------------------------------------
/drizzle/0010_zippy_rick_jones.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "submission_techs" ADD CONSTRAINT "submission_techs_submission_id_tech_id_unique" UNIQUE("submission_id","tech_id");
--------------------------------------------------------------------------------
/drizzle/0011_complete_sabra.sql:
--------------------------------------------------------------------------------
1 | DO $$ BEGIN
2 | CREATE TYPE "action" AS ENUM('addition', 'deduction');
3 | EXCEPTION
4 | WHEN duplicate_object THEN null;
5 | END $$;
6 | --> statement-breakpoint
7 | CREATE TABLE IF NOT EXISTS "user_points" (
8 | "id" serial PRIMARY KEY NOT NULL,
9 | "user_id" integer,
10 | "value" integer,
11 | "action" "action",
12 | "created_at" timestamp DEFAULT now()
13 | );
14 | --> statement-breakpoint
15 | DO $$ BEGIN
16 | ALTER TABLE "user_points" ADD CONSTRAINT "user_points_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
17 | EXCEPTION
18 | WHEN duplicate_object THEN null;
19 | END $$;
20 |
--------------------------------------------------------------------------------
/drizzle/0012_sharp_senator_kelly.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "users" ADD COLUMN "points" bigint DEFAULT 0;
--------------------------------------------------------------------------------
/drizzle/0013_nifty_ender_wiggin.sql:
--------------------------------------------------------------------------------
1 | DO $$ BEGIN
2 | CREATE TYPE "point_type" AS ENUM('submission', 'review');
3 | EXCEPTION
4 | WHEN duplicate_object THEN null;
5 | END $$;
6 | --> statement-breakpoint
7 | ALTER TABLE "user_points" ADD COLUMN "type" "point_type";
--------------------------------------------------------------------------------
/drizzle/0014_illegal_machine_man.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE "submission_users" ADD CONSTRAINT "submission_users_submission_id_user_id_job_unique" UNIQUE("submission_id","user_id","job");
--------------------------------------------------------------------------------
/drizzle/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "5",
3 | "dialect": "pg",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "5",
8 | "when": 1707071528741,
9 | "tag": "0000_aromatic_mathemanic",
10 | "breakpoints": true
11 | },
12 | {
13 | "idx": 1,
14 | "version": "5",
15 | "when": 1707243377982,
16 | "tag": "0001_icy_aqueduct",
17 | "breakpoints": true
18 | },
19 | {
20 | "idx": 2,
21 | "version": "5",
22 | "when": 1707245034310,
23 | "tag": "0002_black_otto_octavius",
24 | "breakpoints": true
25 | },
26 | {
27 | "idx": 3,
28 | "version": "5",
29 | "when": 1707245379542,
30 | "tag": "0003_happy_jack_power",
31 | "breakpoints": true
32 | },
33 | {
34 | "idx": 4,
35 | "version": "5",
36 | "when": 1707308716217,
37 | "tag": "0004_steep_luckman",
38 | "breakpoints": true
39 | },
40 | {
41 | "idx": 5,
42 | "version": "5",
43 | "when": 1707404521022,
44 | "tag": "0005_white_arachne",
45 | "breakpoints": true
46 | },
47 | {
48 | "idx": 6,
49 | "version": "5",
50 | "when": 1707407076012,
51 | "tag": "0006_luxuriant_electro",
52 | "breakpoints": true
53 | },
54 | {
55 | "idx": 7,
56 | "version": "5",
57 | "when": 1707415137446,
58 | "tag": "0007_curvy_blazing_skull",
59 | "breakpoints": true
60 | },
61 | {
62 | "idx": 8,
63 | "version": "5",
64 | "when": 1707522172456,
65 | "tag": "0008_shiny_nuke",
66 | "breakpoints": true
67 | },
68 | {
69 | "idx": 9,
70 | "version": "5",
71 | "when": 1707590644558,
72 | "tag": "0009_keen_frightful_four",
73 | "breakpoints": true
74 | },
75 | {
76 | "idx": 10,
77 | "version": "5",
78 | "when": 1707908355288,
79 | "tag": "0010_zippy_rick_jones",
80 | "breakpoints": true
81 | },
82 | {
83 | "idx": 11,
84 | "version": "5",
85 | "when": 1707975530940,
86 | "tag": "0011_complete_sabra",
87 | "breakpoints": true
88 | },
89 | {
90 | "idx": 12,
91 | "version": "5",
92 | "when": 1707975614580,
93 | "tag": "0012_sharp_senator_kelly",
94 | "breakpoints": true
95 | },
96 | {
97 | "idx": 13,
98 | "version": "5",
99 | "when": 1707976487015,
100 | "tag": "0013_nifty_ender_wiggin",
101 | "breakpoints": true
102 | },
103 | {
104 | "idx": 14,
105 | "version": "5",
106 | "when": 1707983215943,
107 | "tag": "0014_illegal_machine_man",
108 | "breakpoints": true
109 | }
110 | ]
111 | }
112 |
--------------------------------------------------------------------------------
/lib/db.ts:
--------------------------------------------------------------------------------
1 | // import { sql } from '@vercel/postgres';
2 | // import { drizzle } from 'drizzle-orm/vercel-postgres';
3 |
4 | import { drizzle } from 'drizzle-orm/postgres-js';
5 | import postgres from 'postgres';
6 |
7 | declare global {
8 | var __db__: ReturnType;
9 | }
10 |
11 | let db: ReturnType;
12 | if (process.env.NODE_ENV === 'production') {
13 | // db = drizzle(sql);
14 | db = drizzle(postgres(process.env.SUPABASE_URL, { prepare: false }));
15 | } else {
16 | if (!globalThis.__db__) {
17 | globalThis.__db__ = drizzle(
18 | postgres(process.env.SUPABASE_URL, { prepare: false })
19 | );
20 | }
21 |
22 | db = globalThis.__db__;
23 | }
24 |
25 | export { db };
26 |
--------------------------------------------------------------------------------
/lib/hooks/usePageChanges.ts:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { usePathname, useSearchParams } from 'next/navigation';
4 | import { useEffect } from 'react';
5 |
6 | export function usePageChanges(callback) {
7 | const pathname = usePathname();
8 | const searchParams = useSearchParams();
9 |
10 | useEffect(() => {
11 | callback();
12 | }, [pathname, searchParams, callback]);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/hooks/useSearchParamsManipulator.ts:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { usePathname, useRouter, useSearchParams } from 'next/navigation';
4 |
5 | /*
6 | * Improved version of useSearchParams hook
7 | * since it doesn't allow to modify the search params.
8 | *
9 | */
10 |
11 | export function useSearchParamsManipulator(options?) {
12 | const searchParams = useSearchParams();
13 | const params = new URLSearchParams(searchParams);
14 | const pathname = usePathname();
15 | const router = useRouter();
16 |
17 | function push() {
18 | const search = params.toString();
19 | router.push(`${pathname}?${search}`, options);
20 | }
21 |
22 | function setSearchParams(newParams: Record) {
23 | for (const key in newParams) {
24 | if (newParams[key] === undefined) {
25 | params.delete(key);
26 | } else {
27 | params.set(key, newParams[key]);
28 | }
29 | }
30 |
31 | push();
32 | }
33 |
34 | function deleteSearchParams(keys: string[]) {
35 | for (const key of keys) {
36 | params.delete(key);
37 | }
38 |
39 | push();
40 | }
41 |
42 | function clearSearchParams() {
43 | for (const key of params.keys()) {
44 | params.delete(key);
45 | }
46 |
47 | push();
48 | }
49 |
50 | return {
51 | searchParams: params,
52 | setSearchParams,
53 | deleteSearchParams,
54 | clearSearchParams
55 | };
56 | }
57 |
--------------------------------------------------------------------------------
/lib/loader-image.ts:
--------------------------------------------------------------------------------
1 | export default function supabaseLoader({ src }) {
2 | // if src starts with http, return src
3 | if (src.startsWith('http') || src.startsWith('/_next/')) return src;
4 |
5 | let path = src;
6 |
7 | // get the '?s=' query string
8 | const queryString = path.split('?')[1] || '';
9 | const s = new URLSearchParams(queryString).get('s');
10 |
11 | if (s) {
12 | // add the s after the '/{firstFolder}/' in the src
13 | const firstFolder = path.split('/')[1];
14 | path = path.replace(`/${firstFolder}/`, `/${firstFolder}/${s}/`);
15 | }
16 |
17 | const url = `https://${process.env.NEXT_PUBLIC_SUPABASE_ID}.supabase.co/storage/v1/object/public/codedesign/${path}`;
18 |
19 | return url.replace(/\/+/g, '/');
20 | }
21 |
--------------------------------------------------------------------------------
/lib/migrate.ts:
--------------------------------------------------------------------------------
1 | // import { sql } from '@vercel/postgres';
2 | // import { drizzle } from 'drizzle-orm/vercel-postgres';
3 | // import { migrate } from 'drizzle-orm/vercel-postgres/migrator';
4 | import dotenv from 'dotenv';
5 | import { drizzle } from 'drizzle-orm/postgres-js';
6 | import { migrate } from 'drizzle-orm/postgres-js/migrator';
7 | import postgres from 'postgres';
8 |
9 | const path =
10 | process.env.NODE_ENV === 'production'
11 | ? '.env.production.local'
12 | : '.env.development.local';
13 |
14 | dotenv.config({
15 | path,
16 | });
17 |
18 | async function dbMigrate() {
19 | console.log('Migrating database...');
20 | // const db = drizzle(sql);
21 | const client = postgres(process.env.SUPABASE_URL, {
22 | prepare: false,
23 | onnotice: (notice) => {
24 | console.log(notice.message);
25 | },
26 | });
27 | const db = drizzle(client);
28 |
29 | try {
30 | await migrate(db, {
31 | migrationsFolder: './drizzle',
32 | });
33 | } catch (error) {
34 | console.error('Error migrating database:', error);
35 | }
36 |
37 | client.end();
38 |
39 | console.log('Database migrated');
40 | }
41 |
42 | dbMigrate();
43 |
--------------------------------------------------------------------------------
/lib/session.ts:
--------------------------------------------------------------------------------
1 | import { getIronSession, sealData, type SessionOptions } from 'iron-session';
2 | import { cookies } from 'next/headers';
3 | import type { User } from 'schemas/users';
4 |
5 | export type SessionData = {
6 | token: string;
7 | expires: number;
8 | userId: User['id'];
9 | };
10 |
11 | export const sessionOptions: SessionOptions = {
12 | password: process.env.SESSION_SECRET || '',
13 | cookieName: 'ctd',
14 | cookieOptions: {
15 | secure: process.env.NODE_ENV === 'production' ? true : false,
16 | },
17 | };
18 |
19 | export async function getSession() {
20 | return getIronSession(cookies(), sessionOptions);
21 | }
22 |
23 | export async function encryptData(data: unknown): Promise {
24 | return sealData(data, sessionOptions);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/storage.ts:
--------------------------------------------------------------------------------
1 | import { supabase } from 'lib/supabase';
2 |
3 | export const storage = supabase.storage;
4 |
--------------------------------------------------------------------------------
/lib/supabase.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from '@supabase/supabase-js';
2 |
3 | export const supabase = createClient(
4 | process.env.SUPABASE_PROJECT_URL,
5 | process.env.SUPABASE_API_KEY
6 | );
7 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from 'clsx';
2 | import { usePathname, useRouter, useSearchParams } from 'next/navigation';
3 | import { twMerge } from 'tailwind-merge';
4 | import {
5 | adjectives,
6 | animals,
7 | colors,
8 | uniqueNamesGenerator
9 | } from 'unique-names-generator';
10 |
11 | export function cn(...inputs: ClassValue[]) {
12 | return twMerge(clsx(inputs));
13 | }
14 |
15 | export function generateUniqueName() {
16 | const randomName = uniqueNamesGenerator({
17 | dictionaries: [adjectives, colors, animals],
18 | length: 3,
19 | separator: '-',
20 | style: 'lowerCase'
21 | });
22 |
23 | return randomName;
24 | }
25 |
26 | export function dateFormat(date: Date) {
27 | return new Intl.DateTimeFormat('en-US', {
28 | dateStyle: 'medium'
29 | }).format(date);
30 | }
31 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { authMiddleware } from 'middlewares/auth-middleware';
2 | import type { NextRequest } from 'next/server';
3 | import { NextResponse } from 'next/server';
4 |
5 | export async function middleware(request: NextRequest) {
6 | const url = new URL(request.nextUrl);
7 |
8 | const middlewares = [authMiddleware];
9 |
10 | for (const middleware of middlewares) {
11 | const response = await middleware(request);
12 |
13 | if (response) {
14 | return response;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/middlewares/auth-middleware.ts:
--------------------------------------------------------------------------------
1 | import { getSession } from 'lib/session';
2 | import { NextResponse, type NextRequest } from 'next/server';
3 |
4 | export async function authMiddleware(request: NextRequest) {
5 | const paths = ['/me'];
6 | const session = await getSession();
7 |
8 | if (paths.some((path) => request.nextUrl.pathname.startsWith(path))) {
9 | if (!session.userId) {
10 | return NextResponse.redirect(new URL('/auth/login', request.url));
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | protocol: 'https',
7 | hostname: 'avatars.githubusercontent.com'
8 | }
9 | ],
10 | loader: 'custom',
11 | loaderFile: './lib/loader-image.ts'
12 | }
13 | };
14 |
15 | module.exports = nextConfig;
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "code-the-design",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "export": "next export",
11 | "prepare": "husky install"
12 | },
13 | "dependencies": {
14 | "@auth/drizzle-adapter": "^0.5.0",
15 | "@radix-ui/react-dropdown-menu": "^2.0.2",
16 | "@radix-ui/react-select": "^2.0.0",
17 | "@radix-ui/react-slot": "^1.0.2",
18 | "@supabase/supabase-js": "^2.39.3",
19 | "@types/react-dom": "^18.2.18",
20 | "@vercel/analytics": "^0.1.6",
21 | "@vercel/blob": "^0.20.0",
22 | "@vercel/postgres": "^0.7.2",
23 | "autoprefixer": "^10.4.17",
24 | "class-variance-authority": "^0.7.0",
25 | "clsx": "^1.2.1",
26 | "cmdk": "^0.2.1",
27 | "doodle-icons": "^1.1.0",
28 | "dotenv": "^16.4.1",
29 | "drizzle-orm": "^0.29.3",
30 | "drizzle-zod": "^0.5.1",
31 | "eslint": "8.30.0",
32 | "eslint-config-next": "^14.2.5",
33 | "iron-session": "^8.0.1",
34 | "lodash.debounce": "^4.0.8",
35 | "lucide-react": "^0.105.0-alpha.4",
36 | "next": "^14.2.5",
37 | "pg": "^8.11.3",
38 | "postcss": "^8.4.33",
39 | "postgres": "^3.4.3",
40 | "react": "^18.3.1",
41 | "react-dom": "^18.3.1",
42 | "server-only": "^0.0.1",
43 | "tailwind-merge": "^2.2.1",
44 | "tailwindcss": "^3.4.1",
45 | "unique-names-generator": "^4.7.1",
46 | "use-sound": "^4.0.3",
47 | "zod": "^3.23.8"
48 | },
49 | "devDependencies": {
50 | "@commitlint/config-conventional": "^17.2.0",
51 | "@ianvs/prettier-plugin-sort-imports": "^3.7.1",
52 | "@types/node": "18.15.0",
53 | "@types/react": "^18.2.51",
54 | "drizzle-kit": "^0.20.14",
55 | "husky": "^8.0.3",
56 | "lint-staged": "^15.2.1",
57 | "prettier": "^2.8.0",
58 | "prettier-plugin-tailwindcss": "^0.2.1",
59 | "typescript": "^5.3.3"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/public/DumpingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/DumpingDoodle.png
--------------------------------------------------------------------------------
/public/alfian.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/alfian.jpeg
--------------------------------------------------------------------------------
/public/challenges/1000/adoptme.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/adoptme.webp
--------------------------------------------------------------------------------
/public/challenges/1000/agency.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/agency.webp
--------------------------------------------------------------------------------
/public/challenges/1000/agenone.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/agenone.webp
--------------------------------------------------------------------------------
/public/challenges/1000/al-nasr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/al-nasr.webp
--------------------------------------------------------------------------------
/public/challenges/1000/authed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/authed.webp
--------------------------------------------------------------------------------
/public/challenges/1000/bertumbuh.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/bertumbuh.webp
--------------------------------------------------------------------------------
/public/challenges/1000/bubble-bash.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/bubble-bash.webp
--------------------------------------------------------------------------------
/public/challenges/1000/chat-n-rechat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/chat-n-rechat.webp
--------------------------------------------------------------------------------
/public/challenges/1000/chatflow-landing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/chatflow-landing.webp
--------------------------------------------------------------------------------
/public/challenges/1000/chatflow.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/chatflow.webp
--------------------------------------------------------------------------------
/public/challenges/1000/clone-netflix.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/clone-netflix.webp
--------------------------------------------------------------------------------
/public/challenges/1000/collosal.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/collosal.webp
--------------------------------------------------------------------------------
/public/challenges/1000/comments.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/comments.webp
--------------------------------------------------------------------------------
/public/challenges/1000/construction.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/construction.webp
--------------------------------------------------------------------------------
/public/challenges/1000/destinize.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/destinize.webp
--------------------------------------------------------------------------------
/public/challenges/1000/digidaw.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/digidaw.webp
--------------------------------------------------------------------------------
/public/challenges/1000/dressly.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/dressly.webp
--------------------------------------------------------------------------------
/public/challenges/1000/edufree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/edufree.webp
--------------------------------------------------------------------------------
/public/challenges/1000/enlighten.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/enlighten.webp
--------------------------------------------------------------------------------
/public/challenges/1000/epictetus.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/epictetus.webp
--------------------------------------------------------------------------------
/public/challenges/1000/evenity.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/evenity.webp
--------------------------------------------------------------------------------
/public/challenges/1000/faceless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/faceless.webp
--------------------------------------------------------------------------------
/public/challenges/1000/grolin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/grolin.webp
--------------------------------------------------------------------------------
/public/challenges/1000/hangeulin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/hangeulin.webp
--------------------------------------------------------------------------------
/public/challenges/1000/holadok.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/holadok.webp
--------------------------------------------------------------------------------
/public/challenges/1000/hoster-support.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/hoster-support.webp
--------------------------------------------------------------------------------
/public/challenges/1000/jobless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/jobless.webp
--------------------------------------------------------------------------------
/public/challenges/1000/kourse.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/kourse.webp
--------------------------------------------------------------------------------
/public/challenges/1000/lidia.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/lidia.webp
--------------------------------------------------------------------------------
/public/challenges/1000/movies.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/movies.webp
--------------------------------------------------------------------------------
/public/challenges/1000/mstskp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/mstskp.webp
--------------------------------------------------------------------------------
/public/challenges/1000/musix-player.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/musix-player.webp
--------------------------------------------------------------------------------
/public/challenges/1000/nft.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/nft.webp
--------------------------------------------------------------------------------
/public/challenges/1000/nowted-app.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/nowted-app.webp
--------------------------------------------------------------------------------
/public/challenges/1000/nowted.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/nowted.webp
--------------------------------------------------------------------------------
/public/challenges/1000/omah.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/omah.webp
--------------------------------------------------------------------------------
/public/challenges/1000/online-learning.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/online-learning.webp
--------------------------------------------------------------------------------
/public/challenges/1000/priced.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/priced.webp
--------------------------------------------------------------------------------
/public/challenges/1000/pricy.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/pricy.webp
--------------------------------------------------------------------------------
/public/challenges/1000/profile-hover.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/profile-hover.webp
--------------------------------------------------------------------------------
/public/challenges/1000/suxz.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/suxz.webp
--------------------------------------------------------------------------------
/public/challenges/1000/swiftship.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/swiftship.webp
--------------------------------------------------------------------------------
/public/challenges/1000/tailor.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/tailor.webp
--------------------------------------------------------------------------------
/public/challenges/1000/team-collaboration.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/team-collaboration.webp
--------------------------------------------------------------------------------
/public/challenges/1000/testimoni.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/testimoni.webp
--------------------------------------------------------------------------------
/public/challenges/1000/the-malaka.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/the-malaka.webp
--------------------------------------------------------------------------------
/public/challenges/1000/the-starter.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/the-starter.webp
--------------------------------------------------------------------------------
/public/challenges/1000/the-sugihartos.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/the-sugihartos.webp
--------------------------------------------------------------------------------
/public/challenges/1000/the-zeitplan.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/the-zeitplan.webp
--------------------------------------------------------------------------------
/public/challenges/1000/twitter-embed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/twitter-embed.webp
--------------------------------------------------------------------------------
/public/challenges/1000/wumbo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/1000/wumbo.webp
--------------------------------------------------------------------------------
/public/challenges/5/adoptme.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/adoptme.webp
--------------------------------------------------------------------------------
/public/challenges/5/agency.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/agency.webp
--------------------------------------------------------------------------------
/public/challenges/5/agenone.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/agenone.webp
--------------------------------------------------------------------------------
/public/challenges/5/al-nasr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/al-nasr.webp
--------------------------------------------------------------------------------
/public/challenges/5/authed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/authed.webp
--------------------------------------------------------------------------------
/public/challenges/5/bertumbuh.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/bertumbuh.webp
--------------------------------------------------------------------------------
/public/challenges/5/bubble-bash.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/bubble-bash.webp
--------------------------------------------------------------------------------
/public/challenges/5/chat-n-rechat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/chat-n-rechat.webp
--------------------------------------------------------------------------------
/public/challenges/5/chatflow-landing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/chatflow-landing.webp
--------------------------------------------------------------------------------
/public/challenges/5/chatflow.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/chatflow.webp
--------------------------------------------------------------------------------
/public/challenges/5/clone-netflix.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/clone-netflix.webp
--------------------------------------------------------------------------------
/public/challenges/5/collosal.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/collosal.webp
--------------------------------------------------------------------------------
/public/challenges/5/comments.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/comments.webp
--------------------------------------------------------------------------------
/public/challenges/5/construction.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/construction.webp
--------------------------------------------------------------------------------
/public/challenges/5/destinize.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/destinize.webp
--------------------------------------------------------------------------------
/public/challenges/5/digidaw.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/digidaw.webp
--------------------------------------------------------------------------------
/public/challenges/5/dressly.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/dressly.webp
--------------------------------------------------------------------------------
/public/challenges/5/edufree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/edufree.webp
--------------------------------------------------------------------------------
/public/challenges/5/enlighten.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/enlighten.webp
--------------------------------------------------------------------------------
/public/challenges/5/epictetus.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/epictetus.webp
--------------------------------------------------------------------------------
/public/challenges/5/evenity.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/evenity.webp
--------------------------------------------------------------------------------
/public/challenges/5/faceless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/faceless.webp
--------------------------------------------------------------------------------
/public/challenges/5/grolin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/grolin.webp
--------------------------------------------------------------------------------
/public/challenges/5/hangeulin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/hangeulin.webp
--------------------------------------------------------------------------------
/public/challenges/5/holadok.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/holadok.webp
--------------------------------------------------------------------------------
/public/challenges/5/hoster-support.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/hoster-support.webp
--------------------------------------------------------------------------------
/public/challenges/5/jobless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/jobless.webp
--------------------------------------------------------------------------------
/public/challenges/5/kourse.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/kourse.webp
--------------------------------------------------------------------------------
/public/challenges/5/lidia.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/lidia.webp
--------------------------------------------------------------------------------
/public/challenges/5/movies.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/movies.webp
--------------------------------------------------------------------------------
/public/challenges/5/mstskp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/mstskp.webp
--------------------------------------------------------------------------------
/public/challenges/5/musix-player.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/musix-player.webp
--------------------------------------------------------------------------------
/public/challenges/5/nft.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/nft.webp
--------------------------------------------------------------------------------
/public/challenges/5/nowted-app.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/nowted-app.webp
--------------------------------------------------------------------------------
/public/challenges/5/nowted.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/nowted.webp
--------------------------------------------------------------------------------
/public/challenges/5/omah.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/omah.webp
--------------------------------------------------------------------------------
/public/challenges/5/online-learning.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/online-learning.webp
--------------------------------------------------------------------------------
/public/challenges/5/priced.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/priced.webp
--------------------------------------------------------------------------------
/public/challenges/5/pricy.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/pricy.webp
--------------------------------------------------------------------------------
/public/challenges/5/profile-hover.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/profile-hover.webp
--------------------------------------------------------------------------------
/public/challenges/5/suxz.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/suxz.webp
--------------------------------------------------------------------------------
/public/challenges/5/swiftship.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/swiftship.webp
--------------------------------------------------------------------------------
/public/challenges/5/tailor.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/tailor.webp
--------------------------------------------------------------------------------
/public/challenges/5/team-collaboration.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/team-collaboration.webp
--------------------------------------------------------------------------------
/public/challenges/5/testimoni.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/testimoni.webp
--------------------------------------------------------------------------------
/public/challenges/5/the-malaka.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/the-malaka.webp
--------------------------------------------------------------------------------
/public/challenges/5/the-starter.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/the-starter.webp
--------------------------------------------------------------------------------
/public/challenges/5/the-sugihartos.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/the-sugihartos.webp
--------------------------------------------------------------------------------
/public/challenges/5/the-zeitplan.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/the-zeitplan.webp
--------------------------------------------------------------------------------
/public/challenges/5/twitter-embed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/twitter-embed.webp
--------------------------------------------------------------------------------
/public/challenges/5/wumbo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/5/wumbo.webp
--------------------------------------------------------------------------------
/public/challenges/adoptme.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/adoptme.webp
--------------------------------------------------------------------------------
/public/challenges/agency.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/agency.webp
--------------------------------------------------------------------------------
/public/challenges/agenone.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/agenone.webp
--------------------------------------------------------------------------------
/public/challenges/al-nasr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/al-nasr.webp
--------------------------------------------------------------------------------
/public/challenges/authed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/authed.webp
--------------------------------------------------------------------------------
/public/challenges/bertumbuh.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/bertumbuh.webp
--------------------------------------------------------------------------------
/public/challenges/bubble-bash.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/bubble-bash.webp
--------------------------------------------------------------------------------
/public/challenges/chat-n-rechat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/chat-n-rechat.webp
--------------------------------------------------------------------------------
/public/challenges/chatflow-landing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/chatflow-landing.webp
--------------------------------------------------------------------------------
/public/challenges/chatflow.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/chatflow.webp
--------------------------------------------------------------------------------
/public/challenges/clone-netflix.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/clone-netflix.webp
--------------------------------------------------------------------------------
/public/challenges/collosal.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/collosal.webp
--------------------------------------------------------------------------------
/public/challenges/comments.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/comments.webp
--------------------------------------------------------------------------------
/public/challenges/construction.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/construction.webp
--------------------------------------------------------------------------------
/public/challenges/destinize.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/destinize.webp
--------------------------------------------------------------------------------
/public/challenges/digidaw.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/digidaw.webp
--------------------------------------------------------------------------------
/public/challenges/dressly.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/dressly.webp
--------------------------------------------------------------------------------
/public/challenges/edufree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/edufree.webp
--------------------------------------------------------------------------------
/public/challenges/enlighten.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/enlighten.webp
--------------------------------------------------------------------------------
/public/challenges/epictetus.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/epictetus.webp
--------------------------------------------------------------------------------
/public/challenges/evenity.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/evenity.webp
--------------------------------------------------------------------------------
/public/challenges/faceless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/faceless.webp
--------------------------------------------------------------------------------
/public/challenges/grolin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/grolin.webp
--------------------------------------------------------------------------------
/public/challenges/hangeulin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/hangeulin.webp
--------------------------------------------------------------------------------
/public/challenges/holadok.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/holadok.webp
--------------------------------------------------------------------------------
/public/challenges/hoster-support.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/hoster-support.webp
--------------------------------------------------------------------------------
/public/challenges/jobless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/jobless.webp
--------------------------------------------------------------------------------
/public/challenges/kourse.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/kourse.webp
--------------------------------------------------------------------------------
/public/challenges/lidia.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/lidia.webp
--------------------------------------------------------------------------------
/public/challenges/movies.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/movies.webp
--------------------------------------------------------------------------------
/public/challenges/mstskp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/mstskp.webp
--------------------------------------------------------------------------------
/public/challenges/musix-player.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/musix-player.webp
--------------------------------------------------------------------------------
/public/challenges/nft.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/nft.webp
--------------------------------------------------------------------------------
/public/challenges/nowted-app.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/nowted-app.webp
--------------------------------------------------------------------------------
/public/challenges/nowted.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/nowted.webp
--------------------------------------------------------------------------------
/public/challenges/omah.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/omah.webp
--------------------------------------------------------------------------------
/public/challenges/online-learning.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/online-learning.webp
--------------------------------------------------------------------------------
/public/challenges/priced.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/priced.webp
--------------------------------------------------------------------------------
/public/challenges/pricy.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/pricy.webp
--------------------------------------------------------------------------------
/public/challenges/profile-hover.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/profile-hover.webp
--------------------------------------------------------------------------------
/public/challenges/suxz.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/suxz.webp
--------------------------------------------------------------------------------
/public/challenges/swiftship.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/swiftship.webp
--------------------------------------------------------------------------------
/public/challenges/tailor.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/tailor.webp
--------------------------------------------------------------------------------
/public/challenges/team-collaboration.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/team-collaboration.webp
--------------------------------------------------------------------------------
/public/challenges/testimoni.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/testimoni.webp
--------------------------------------------------------------------------------
/public/challenges/the-malaka.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/the-malaka.webp
--------------------------------------------------------------------------------
/public/challenges/the-starter.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/the-starter.webp
--------------------------------------------------------------------------------
/public/challenges/the-sugihartos.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/the-sugihartos.webp
--------------------------------------------------------------------------------
/public/challenges/the-zeitplan.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/the-zeitplan.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/adoptme.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/adoptme.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/agency.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/agency.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/agenone.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/agenone.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/al-nasr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/al-nasr.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/authed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/authed.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/bertumbuh.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/bertumbuh.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/bubble-bash.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/bubble-bash.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/chat-n-rechat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/chat-n-rechat.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/chatflow-landing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/chatflow-landing.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/chatflow.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/chatflow.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/clone-netflix.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/clone-netflix.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/collosal.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/collosal.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/comments.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/comments.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/construction.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/construction.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/destinize.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/destinize.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/digidaw.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/digidaw.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/dressly.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/dressly.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/edufree.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/edufree.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/enlighten.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/enlighten.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/epictetus.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/epictetus.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/evenity.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/evenity.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/faceless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/faceless.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/grolin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/grolin.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/hangeulin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/hangeulin.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/holadok.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/holadok.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/hoster-support.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/hoster-support.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/jobless.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/jobless.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/kourse.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/kourse.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/lidia.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/lidia.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/movies.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/movies.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/mstskp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/mstskp.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/musix-player.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/musix-player.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/nft.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/nft.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/nowted-app.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/nowted-app.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/nowted.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/nowted.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/omah.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/omah.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/online-learning.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/online-learning.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/priced.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/priced.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/pricy.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/pricy.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/profile-hover.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/profile-hover.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/suxz.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/suxz.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/swiftship.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/swiftship.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/tailor.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/tailor.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/team-collaboration.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/team-collaboration.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/testimoni.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/testimoni.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/the-malaka.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/the-malaka.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/the-starter.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/the-starter.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/the-sugihartos.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/the-sugihartos.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/the-zeitplan.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/the-zeitplan.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/twitter-embed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/twitter-embed.webp
--------------------------------------------------------------------------------
/public/challenges/thumbs/wumbo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/thumbs/wumbo.webp
--------------------------------------------------------------------------------
/public/challenges/twitter-embed.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/twitter-embed.webp
--------------------------------------------------------------------------------
/public/challenges/wumbo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/challenges/wumbo.webp
--------------------------------------------------------------------------------
/public/code-design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/code-design.png
--------------------------------------------------------------------------------
/public/code-the-design.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/codedesign-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/codedesign-bg.png
--------------------------------------------------------------------------------
/public/codedesign-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/codedesign-logo.png
--------------------------------------------------------------------------------
/public/lidia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/lidia.png
--------------------------------------------------------------------------------
/public/nav-sfx-2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/nav-sfx-2.mp3
--------------------------------------------------------------------------------
/public/nav-sfx-3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/nav-sfx-3.mp3
--------------------------------------------------------------------------------
/public/nav-sfx.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/nav-sfx.mp3
--------------------------------------------------------------------------------
/public/section-header-quests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/section-header-quests.png
--------------------------------------------------------------------------------
/public/users/afin.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/afin.jpeg
--------------------------------------------------------------------------------
/public/users/alfian.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/alfian.jpeg
--------------------------------------------------------------------------------
/public/users/arie.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/arie.jpeg
--------------------------------------------------------------------------------
/public/users/brainstew.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/brainstew.jpeg
--------------------------------------------------------------------------------
/public/users/dimas.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/dimas.jpeg
--------------------------------------------------------------------------------
/public/users/dzaki.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/dzaki.jpeg
--------------------------------------------------------------------------------
/public/users/fauzan.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/fauzan.jpeg
--------------------------------------------------------------------------------
/public/users/glenn.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/glenn.jpeg
--------------------------------------------------------------------------------
/public/users/irham.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/irham.jpeg
--------------------------------------------------------------------------------
/public/users/irvan.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/irvan.jpeg
--------------------------------------------------------------------------------
/public/users/mahyu.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/mahyu.jpeg
--------------------------------------------------------------------------------
/public/users/nauval.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/nauval.jpg
--------------------------------------------------------------------------------
/public/users/rafiq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/rafiq.png
--------------------------------------------------------------------------------
/public/users/syauqi.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/syauqi.jpeg
--------------------------------------------------------------------------------
/public/users/taufik.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/taufik.jpeg
--------------------------------------------------------------------------------
/public/users/yohana.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nauvalazhar/code-design/7017111bfa383ea4cf0e4bd6072fb5fc9239102e/public/users/yohana.jpeg
--------------------------------------------------------------------------------
/schemas/badges.ts:
--------------------------------------------------------------------------------
1 | import {
2 | pgEnum,
3 | pgTable,
4 | serial,
5 | text,
6 | timestamp,
7 | varchar,
8 | } from 'drizzle-orm/pg-core';
9 |
10 | export const badgeValues = pgEnum('badge_values', [
11 | 'designer',
12 | 'reviewer',
13 | 'contributor',
14 | ]);
15 |
16 | export const badges = pgTable('badges', {
17 | id: serial('id').primaryKey(),
18 | name: varchar('name').unique(),
19 | value: badgeValues('value'),
20 | description: text('description'),
21 | image: text('image'),
22 | createdAt: timestamp('created_at').defaultNow(),
23 | updatedAt: timestamp('updated_at'),
24 | });
25 |
26 | export type Badge = typeof badges.$inferSelect;
27 | export type NewBadge = typeof badges.$inferInsert;
28 |
--------------------------------------------------------------------------------
/schemas/categories.ts:
--------------------------------------------------------------------------------
1 | import { pgTable, serial, timestamp, varchar } from 'drizzle-orm/pg-core';
2 |
3 | export const categories = pgTable('categories', {
4 | id: serial('id').primaryKey(),
5 | name: varchar('name').unique(),
6 | slug: varchar('slug').unique(),
7 | createdAt: timestamp('created_at').defaultNow(),
8 | updatedAt: timestamp('updated_at'),
9 | });
10 |
11 | export type Category = typeof categories.$inferSelect;
12 | export type NewCategory = typeof categories.$inferInsert;
13 |
--------------------------------------------------------------------------------
/schemas/challenge_designers.tsx:
--------------------------------------------------------------------------------
1 | import { challenges } from './challenges';
2 | import { users } from './users';
3 | import { integer, pgTable, serial, unique } from 'drizzle-orm/pg-core';
4 |
5 | export const challengeDesigners = pgTable(
6 | 'challenge_designers',
7 | {
8 | id: serial('id').primaryKey(),
9 | challengeId: integer('challenge_id').references(() => challenges.id),
10 | userId: integer('user_id').references(() => users.id),
11 | },
12 | (table) => ({
13 | challengeUser: unique().on(table.challengeId, table.userId),
14 | })
15 | );
16 |
17 | export type ChallengeDesigner = typeof challengeDesigners.$inferSelect;
18 | export type NewChallengeDesigner = typeof challengeDesigners.$inferInsert;
19 |
--------------------------------------------------------------------------------
/schemas/challenges.ts:
--------------------------------------------------------------------------------
1 | import { categories } from './categories';
2 | import { difficulties } from './difficulties';
3 | import { integer, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
4 |
5 | export const challenges = pgTable('challenges', {
6 | id: serial('id').primaryKey(),
7 | name: text('name').unique(),
8 | slug: text('slug').unique(),
9 | description: text('description'),
10 | difficultyId: integer('difficulty_id').references(() => difficulties.id),
11 | categoryId: integer('category_id').references(() => categories.id),
12 | image: text('image'),
13 | accent: text('accent'),
14 | accent2: text('accent2'),
15 | figma: text('figma'),
16 | createdAt: timestamp('created_at').defaultNow(),
17 | updatedAt: timestamp('updated_at'),
18 | });
19 |
20 | export type Challenge = typeof challenges.$inferSelect;
21 | export type NewChallenge = typeof challenges.$inferInsert;
22 |
--------------------------------------------------------------------------------
/schemas/difficulties.ts:
--------------------------------------------------------------------------------
1 | import { pgTable, serial, timestamp, varchar } from 'drizzle-orm/pg-core';
2 |
3 | export const difficulties = pgTable('difficulties', {
4 | id: serial('id').primaryKey(),
5 | name: varchar('name').unique(),
6 | createdAt: timestamp('created_at').defaultNow(),
7 | updatedAt: timestamp('updated_at'),
8 | });
9 |
10 | export type Difficulty = typeof difficulties.$inferSelect;
11 | export type NewDifficulty = typeof difficulties.$inferInsert;
12 |
--------------------------------------------------------------------------------
/schemas/submission_techs.ts:
--------------------------------------------------------------------------------
1 | import { integer, pgTable, serial, unique } from 'drizzle-orm/pg-core';
2 | import { submissions } from 'schemas/submissions';
3 | import { techs } from 'schemas/techs';
4 | import z from 'zod';
5 |
6 | export const submissionTechs = pgTable(
7 | 'submission_techs',
8 | {
9 | // the internet convinced me to add an id column for the sake of consistency and performance
10 | id: serial('id').primaryKey(),
11 | submissionId: integer('submission_id').references(() => submissions.id),
12 | techId: integer('tech_id').references(() => techs.id),
13 | },
14 | (table) => ({
15 | submissionTech: unique().on(table.submissionId, table.techId),
16 | })
17 | );
18 |
19 | export type SubmissionTech = typeof submissionTechs.$inferSelect;
20 | export type NewSubmissionTech = typeof submissionTechs.$inferInsert;
21 | export const techIdsSchema = z.object({
22 | techs: z
23 | .string()
24 | .min(1, {
25 | message: 'At least one tech is required',
26 | })
27 | .transform((val) => val.split(',').map(Number))
28 | .pipe(z.number().array()),
29 | });
30 |
31 | export type TechIds = z.infer;
32 |
--------------------------------------------------------------------------------
/schemas/submission_users.ts:
--------------------------------------------------------------------------------
1 | import {
2 | integer,
3 | pgEnum,
4 | pgTable,
5 | serial,
6 | timestamp,
7 | unique
8 | } from 'drizzle-orm/pg-core';
9 | import { submissions } from 'schemas/submissions';
10 | import { users } from 'schemas/users';
11 |
12 | // each submission might have multiple collaborators
13 | // for example, a team of developers might submit a project together
14 | // in such a case, the one who submits the project is the owner
15 | // and the others are collaborators
16 | export const jobEnum = pgEnum('job', ['owner', 'collaborator', 'reviewer']);
17 |
18 | export const submissionUsers = pgTable(
19 | 'submission_users',
20 | {
21 | id: serial('id').primaryKey(),
22 | submissionId: integer('submission_id').references(() => submissions.id),
23 | userId: integer('user_id').references(() => users.id),
24 | job: jobEnum('job'),
25 | createdAt: timestamp('created_at').defaultNow(),
26 | updatedAt: timestamp('updated_at')
27 | },
28 | table => ({
29 | submissionUserJob: unique().on(table.submissionId, table.userId, table.job)
30 | })
31 | );
32 |
33 | export type SubmissionUser = typeof submissionUsers.$inferSelect;
34 | export type NewSubmissionUser = typeof submissionUsers.$inferInsert;
35 |
--------------------------------------------------------------------------------
/schemas/submissions.ts:
--------------------------------------------------------------------------------
1 | import {
2 | integer,
3 | pgEnum,
4 | pgTable,
5 | serial,
6 | text,
7 | timestamp,
8 | varchar,
9 | } from 'drizzle-orm/pg-core';
10 | import { createInsertSchema } from 'drizzle-zod';
11 | import { challenges } from 'schemas/challenges';
12 | import z from 'zod';
13 |
14 | export const phaseEnums = [
15 | 'published',
16 | 'submitted',
17 | 'rejected',
18 | 'archived',
19 | ] as const;
20 |
21 | export const phaseEnum = pgEnum('phase', phaseEnums);
22 |
23 | export const visibilityEnum = pgEnum('visibility', [
24 | 'public',
25 | 'private',
26 | 'unlisted',
27 | ]);
28 |
29 | export const submissions = pgTable('submissions', {
30 | id: serial('id').primaryKey(),
31 | challengeId: integer('challenge_id').references(() => challenges.id),
32 | slug: varchar('slug').unique(),
33 | description: text('description'),
34 | repository: text('repository'),
35 | demo: text('demo'),
36 | image: text('image'),
37 | visibility: visibilityEnum('visibility').default('public'),
38 | phase: phaseEnum('phase').default('submitted'),
39 | note: text('note'),
40 | createdAt: timestamp('created_at').defaultNow(),
41 | updatedAt: timestamp('updated_at'),
42 | });
43 |
44 | export type Submission = typeof submissions.$inferSelect;
45 | export type NewSubmission = typeof submissions.$inferInsert;
46 |
47 | const MAX_IMAGE_SIZE = 1024 * 1024;
48 | const ACCEPTED_IMAGE_TYPES = [
49 | 'image/jpeg',
50 | 'image/jpg',
51 | 'image/png',
52 | 'image/webp',
53 | ];
54 |
55 | export const insertSubmissionSchema = createInsertSchema(submissions, {
56 | demo: (schema) => schema.demo.url(),
57 | repository: (schema) => schema.repository.url(),
58 | phase: z.enum(phaseEnums),
59 | image: z
60 | .instanceof(Blob)
61 | .optional()
62 | .refine(
63 | (value: Blob) => {
64 | return value.size > 0;
65 | },
66 | {
67 | message: 'Image is required',
68 | }
69 | )
70 | .refine((value) => value.size <= MAX_IMAGE_SIZE, {
71 | message: 'Image size must be less than 1MB',
72 | })
73 | .refine((value) => ACCEPTED_IMAGE_TYPES.includes(value.type), {
74 | message: 'Image type must be jpeg, jpg, png, or webp',
75 | })
76 | .or(z.string()),
77 | });
78 |
--------------------------------------------------------------------------------
/schemas/techs.ts:
--------------------------------------------------------------------------------
1 | import { pgEnum, pgTable, serial, text, varchar } from 'drizzle-orm/pg-core';
2 |
3 | export const classificationEnum = pgEnum('classification', [
4 | 'language', // JavaScript, Python, Ruby, etc.
5 | 'framework', // Next, Django, Ruby on Rails, etc.
6 | 'style', // Vanilla, Tailwind, Bootstrap, etc.
7 | 'library', // React, Redux, etc.
8 | 'database', // PostgreSQL, MySQL, MongoDB, etc.
9 | 'platform', // Node.js, .NET, etc.
10 | 'service', // AWS, Azure, etc.
11 | 'tool', // Webpack, Babel, etc.
12 | 'os', // Windows, macOS, Linux, etc.
13 | 'ide', // VSCode, Sublime, etc.
14 | ]);
15 |
16 | export const environmentEnum = pgEnum('environment', [
17 | 'frontend',
18 | 'backend',
19 | 'fullstack',
20 | 'mobile',
21 | 'desktop',
22 | ]);
23 |
24 | export const techs = pgTable('techs', {
25 | id: serial('id').primaryKey(),
26 | name: varchar('name').unique(),
27 | slug: varchar('slug').unique(),
28 | description: text('description'),
29 | classification: classificationEnum('classification'),
30 | environment: environmentEnum('environment'),
31 | logo: text('logo'),
32 | });
33 |
34 | export type Tech = typeof techs.$inferSelect;
35 | export type NewTech = typeof techs.$inferInsert;
36 |
--------------------------------------------------------------------------------
/schemas/user_badges.ts:
--------------------------------------------------------------------------------
1 | import { badges } from './badges';
2 | import { users } from './users';
3 | import {
4 | integer,
5 | pgTable,
6 | serial,
7 | timestamp,
8 | unique,
9 | } from 'drizzle-orm/pg-core';
10 |
11 | export const userBadges = pgTable(
12 | 'user_badges',
13 | {
14 | id: serial('id').primaryKey(),
15 | userId: integer('user_id').references(() => users.id),
16 | badgeId: integer('badge_id').references(() => badges.id),
17 | createdAt: timestamp('created_at').defaultNow(),
18 | updatedAt: timestamp('updated_at'),
19 | },
20 | (table) => ({
21 | userBadge: unique().on(table.userId, table.badgeId),
22 | })
23 | );
24 |
25 | export type UserBadge = typeof userBadges.$inferSelect;
26 | export type NewUserBadge = typeof userBadges.$inferInsert;
27 |
--------------------------------------------------------------------------------
/schemas/user_links.ts:
--------------------------------------------------------------------------------
1 | import { users } from './users';
2 | import {
3 | integer,
4 | pgTable,
5 | serial,
6 | text,
7 | timestamp,
8 | varchar,
9 | } from 'drizzle-orm/pg-core';
10 |
11 | export const userLinks = pgTable('user_links', {
12 | id: serial('id').primaryKey(),
13 | userId: integer('user_id').references(() => users.id),
14 | name: varchar('name'),
15 | link: text('link'),
16 | createdAt: timestamp('created_at').defaultNow(),
17 | updatedAt: timestamp('updated_at'),
18 | });
19 |
20 | export type UserLink = typeof userLinks.$inferSelect;
21 | export type NewUserLink = typeof userLinks.$inferInsert;
22 |
--------------------------------------------------------------------------------
/schemas/user_points.ts:
--------------------------------------------------------------------------------
1 | import {
2 | integer,
3 | pgEnum,
4 | pgTable,
5 | serial,
6 | timestamp
7 | } from 'drizzle-orm/pg-core';
8 | import { users } from 'schemas/users';
9 |
10 | export const actionEnums = ['addition', 'deduction'] as const;
11 | export const actionEnum = pgEnum('action', actionEnums);
12 | export const pointTypes = ['submission', 'review'] as const;
13 | export const pointType = pgEnum('point_type', pointTypes);
14 |
15 | export const userPoints = pgTable('user_points', {
16 | id: serial('id').primaryKey(),
17 | userId: integer('user_id').references(() => users.id),
18 | value: integer('value'),
19 | action: actionEnum('action'),
20 | type: pointType('type'),
21 | createdAt: timestamp('created_at').defaultNow()
22 | });
23 |
24 | export type UserPoint = typeof userPoints.$inferSelect;
25 | export type NewUserPoint = typeof userPoints.$inferInsert;
26 | export type PointAction = (typeof actionEnums)[number];
27 | export type PointType = (typeof pointTypes)[number];
28 |
--------------------------------------------------------------------------------
/schemas/users.ts:
--------------------------------------------------------------------------------
1 | import {
2 | bigint,
3 | boolean,
4 | pgEnum,
5 | pgTable,
6 | serial,
7 | text,
8 | timestamp,
9 | varchar
10 | } from 'drizzle-orm/pg-core';
11 |
12 | export const roleEnum = pgEnum('role', ['user', 'admin']);
13 | export const statusEnum = pgEnum('status', ['active', 'blocked']);
14 |
15 | export const users = pgTable('users', {
16 | id: serial('id').primaryKey(),
17 | username: varchar('username').unique(),
18 | email: varchar('email').unique(),
19 | name: varchar('name'),
20 | bio: text('bio'),
21 | avatar: text('avatar'),
22 | githubId: bigint('github_id', { mode: 'bigint' }),
23 | hireable: boolean('hireable'),
24 | role: roleEnum('role').default('user'),
25 | refreshToken: text('refresh_token'),
26 | status: statusEnum('status').default('active'),
27 | points: bigint('points', { mode: 'number' }).default(0),
28 | createdAt: timestamp('created_at').defaultNow(),
29 | updatedAt: timestamp('updated_at')
30 | });
31 |
32 | export type User = typeof users.$inferSelect;
33 | export type NewUser = typeof users.$inferInsert;
34 |
--------------------------------------------------------------------------------
/seeds/tech-seed.ts:
--------------------------------------------------------------------------------
1 | import { sql } from 'drizzle-orm';
2 | import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
3 | import { techs } from 'schemas/techs';
4 | import { techsData } from 'seeds/data/tech-data';
5 |
6 | export async function techSeed(db: PostgresJsDatabase) {
7 | try {
8 | await db.insert(techs).values(techsData).onConflictDoNothing();
9 | } catch (error) {
10 | console.log(error);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/services/auth-service.ts:
--------------------------------------------------------------------------------
1 | import { getSession } from 'lib/session';
2 | import { cache } from 'react';
3 | import { getUserById } from 'services/user-service';
4 |
5 | export async function getAuthId() {
6 | const session = await getSession();
7 |
8 | return session.userId;
9 | }
10 |
11 | export async function isAuth() {
12 | const session = await getSession();
13 |
14 | return Boolean(session.userId);
15 | }
16 |
17 | export const getAuthUser = cache(async () => {
18 | const id = await getAuthId();
19 |
20 | const user = await getUserById(id);
21 |
22 | return user;
23 | });
24 |
25 | export const isUserReviewer = cache(async () => {
26 | const user = await getAuthUser();
27 |
28 | return user.badges.some((badge) => badge.badge.value === 'reviewer');
29 | });
30 |
--------------------------------------------------------------------------------
/services/category-service.ts:
--------------------------------------------------------------------------------
1 | import { db } from 'lib/db';
2 | import { type Category, categories } from 'schemas/categories';
3 |
4 | export async function getCategories(): Promise {
5 | return db.select().from(categories);
6 | }
7 |
--------------------------------------------------------------------------------
/services/difficulty-service.ts:
--------------------------------------------------------------------------------
1 | import { db } from 'lib/db';
2 | import { type Difficulty, difficulties } from 'schemas/difficulties';
3 |
4 | export async function getDifficulties(): Promise {
5 | return db.select().from(difficulties);
6 | }
7 |
--------------------------------------------------------------------------------
/services/point_service.tsx:
--------------------------------------------------------------------------------
1 | import { eq } from 'drizzle-orm';
2 | import { db } from 'lib/db';
3 | import {
4 | UserPoint,
5 | userPoints,
6 | type PointAction,
7 | type PointType
8 | } from 'schemas/user_points';
9 | import { users } from 'schemas/users';
10 |
11 | import 'server-only';
12 |
13 | type Points = Record;
14 |
15 | const points: Points = {
16 | submission: 50,
17 | review: 10
18 | };
19 |
20 | export async function pointsAddition(userId: number, pointType: PointType) {
21 | return pointsAction(userId, pointType, 'addition');
22 | }
23 |
24 | export async function pointsDeduction(userId: number, pointType: PointType) {
25 | return pointsAction(userId, pointType, 'deduction');
26 | }
27 |
28 | export async function pointsAction(
29 | userId: number,
30 | type: PointType,
31 | action: PointAction
32 | ) {
33 | try {
34 | await db.transaction(async trx => {
35 | const value = points[type];
36 | await trx.insert(userPoints).values({
37 | value,
38 | userId,
39 | action,
40 | type
41 | });
42 |
43 | const user = await trx.select().from(users).where(eq(users.id, userId));
44 |
45 | await trx
46 | .update(users)
47 | .set({
48 | points: user[0].points + value
49 | })
50 | .where(eq(users.id, userId));
51 | });
52 | } catch (error) {
53 | console.error(error);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/services/tech-service.ts:
--------------------------------------------------------------------------------
1 | import 'server-only';
2 | import { SQL, ilike, sql } from 'drizzle-orm';
3 | import { db } from 'lib/db';
4 | import { Tech, techs } from 'schemas/techs';
5 |
6 | export type GetTechsParams = {
7 | limit?: number;
8 | offset?: number;
9 | where?:
10 | | {
11 | search?: string;
12 | }
13 | | undefined;
14 | };
15 |
16 | export async function getTechs({
17 | limit = 10,
18 | offset = 0,
19 | where,
20 | }: GetTechsParams = {}): Promise {
21 | const query: SQL[] = [];
22 |
23 | query.push(sql`SELECT ${techs.id}, ${techs.name} FROM ${techs}`);
24 |
25 | if (where) {
26 | const whereChunks: SQL[] = [];
27 |
28 | if (where.search) {
29 | whereChunks.push(ilike(techs.name, `%${where.search}%`));
30 | }
31 |
32 | if (whereChunks.length > 0) {
33 | const whereClause: SQL[] = [];
34 | whereClause.push(sql`WHERE`);
35 |
36 | for (let i = 0; i < whereChunks.length; i++) {
37 | if (i > 0) {
38 | whereClause.push(sql`AND`);
39 | }
40 | whereClause.push(whereChunks[i]);
41 | }
42 |
43 | query.push(sql.join(whereClause, sql.raw(' ')));
44 | }
45 | }
46 |
47 | query.push(sql`LIMIT ${limit}`);
48 |
49 | if (offset > 0) {
50 | query.push(sql`OFFSET ${offset}`);
51 | }
52 |
53 | const finalQuery = sql.join(query, sql.raw(' '));
54 |
55 | return db.execute(finalQuery);
56 | }
57 |
--------------------------------------------------------------------------------
/services/user-service.ts:
--------------------------------------------------------------------------------
1 | import { eq, sql } from 'drizzle-orm';
2 | import { alias } from 'drizzle-orm/pg-core';
3 | import { db } from 'lib/db';
4 | import { type Badge, badges } from 'schemas/badges';
5 | import { type UserBadge, userBadges } from 'schemas/user_badges';
6 | import { NewUser, User, users } from 'schemas/users';
7 |
8 | type JoinedUserById = User & {
9 | badges: {
10 | badge: Badge;
11 | userBadge: UserBadge;
12 | }[];
13 | };
14 |
15 | export async function getUserById(id: User['id']): Promise {
16 | const userBadgesAlias = alias(userBadges, 'userBadge');
17 | const badgesAlias = alias(badges, 'badge');
18 |
19 | const [userData] = await db.select().from(users).where(eq(users.id, id));
20 | const userBadgesData = await db
21 | .select()
22 | .from(userBadgesAlias)
23 | .innerJoin(badgesAlias, eq(userBadgesAlias.badgeId, badgesAlias.id))
24 | .where(eq(userBadgesAlias.userId, id));
25 |
26 | return {
27 | ...userData,
28 | badges: userBadgesData,
29 | };
30 | }
31 |
32 | export async function isUserBadge(badge: Badge['value']) {
33 | const userBadge = await db
34 | .select()
35 | .from(userBadges)
36 | .innerJoin(badges, eq(userBadges.badgeId, badges.id))
37 | .where(eq(badges.value, badge));
38 |
39 | return Boolean(userBadge.length);
40 | }
41 |
42 | export async function getUserByEmail(email: string): Promise {
43 | return db.select().from(users).where(eq(users.email, email));
44 | }
45 |
46 | export async function getUser(id: User['id']): Promise {
47 | return db.select().from(users).where(eq(users.id, id));
48 | }
49 |
50 | export async function createUser(user: NewUser): Promise {
51 | return db.insert(users).values(user).returning();
52 | }
53 |
54 | export async function getUserByGithubId(
55 | githubId: User['githubId']
56 | ): Promise {
57 | return db.select().from(users).where(eq(users.githubId, githubId));
58 | }
59 |
60 | export async function getUserByUsername(
61 | username: User['username']
62 | ): Promise {
63 | return db.select().from(users).where(eq(users.username, username));
64 | }
65 |
66 | export async function updateUserById(
67 | user: NewUser,
68 | id: User['id']
69 | ): Promise {
70 | return db.update(users).set(user).where(eq(users.id, id)).returning();
71 | }
72 |
73 | export async function getOrRegisterUser(user: NewUser): Promise {
74 | // if user has already been registered, return the user
75 | const registeredUser = await getUserByGithubId(user.githubId);
76 |
77 | if (registeredUser.length > 0) {
78 | return registeredUser;
79 | }
80 |
81 | // if user exists, but has not been registered, update the user
82 | const unregisteredUser = await getUserByUsername(user.username);
83 |
84 | if (unregisteredUser.length > 0) {
85 | return updateUserById(user, unregisteredUser[0].id);
86 | }
87 |
88 | // if user does not exist, create the user
89 | return createUser(user);
90 | }
91 |
92 | export async function getDesigners(): Promise {
93 | const query = sql`
94 | SELECT
95 | ${users.id},
96 | ${users.username},
97 | ${users.name},
98 | ${users.avatar}
99 | FROM ${users}
100 | INNER JOIN ${userBadges} ON ${users.id} = ${userBadges.userId}
101 | WHERE ${userBadges.badgeId} = 1
102 | `;
103 |
104 | return db.execute(query);
105 | }
106 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./{app,components}/**/*.{js,ts,tsx,jsx}'],
4 | theme: {
5 | extend: {
6 | fontFamily: {
7 | sans: 'var(--font-sans)',
8 | display: 'var(--font-display)'
9 | },
10 | colors: {
11 | brand: {
12 | DEFAULT: '#FFD466',
13 | light: '#FFF8E4',
14 | dark: '#4E4634'
15 | },
16 | body: {
17 | DEFAULT: '#fff8e4'
18 | }
19 | },
20 | boxShadow: {
21 | 'solid-sm': '8px 8px 0 #000',
22 | solid: '15px 15px 0 #000'
23 | }
24 | }
25 | },
26 | plugins: []
27 | };
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "incremental": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve",
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ],
26 | "baseUrl": ".",
27 | "paths": {
28 | "auth": [
29 | "./auth.ts"
30 | ]
31 | }
32 | },
33 | "include": [
34 | "next-env.d.ts",
35 | ".next/types/**/*.ts",
36 | "**/*.ts",
37 | "**/*.tsx",
38 | "./types/css.d.ts"
39 | ],
40 | "exclude": [
41 | "node_modules"
42 | ]
43 | }
--------------------------------------------------------------------------------
/types/css.d.ts:
--------------------------------------------------------------------------------
1 | import 'react';
2 |
3 | declare module 'react' {
4 | interface CSSProperties {
5 | [key: `--${string}`]: string | number;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------