├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── components
├── feed
│ ├── posts.tsx
│ └── skeleton.tsx
├── layout
│ ├── container.tsx
│ ├── meta.tsx
│ ├── navbar.tsx
│ └── sidebar.tsx
└── listing.tsx
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
├── _app.tsx
├── _document.tsx
└── index.tsx
├── public
├── assets
│ └── images
│ │ ├── bell.svg
│ │ ├── comment.svg
│ │ ├── like.svg
│ │ ├── logo.svg
│ │ ├── notification.svg
│ │ ├── settings.svg
│ │ └── sidebar
│ │ ├── home.svg
│ │ ├── listing.svg
│ │ ├── podcast.svg
│ │ ├── reading.svg
│ │ ├── tag.svg
│ │ └── video.svg
├── devto.png
└── favicon.png
├── tsconfig.json
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals"]
3 | }
4 |
--------------------------------------------------------------------------------
/.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 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | MIT License
3 |
4 | Copyright (c) 2021 Muhammad Ahmad
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Dev.to Clone
7 |
8 |
9 | Built with Typescript, React & Next.js
10 |
11 |
12 |
13 | ## Preview
14 |
15 | 
16 |
17 | ## Overview
18 |
19 | This application is built with the following technologies:
20 |
21 | - [Typescript](https://www.typescriptlang.org/)
22 | - [Nextjs](https://nextjs.org/)
23 | - [Chakra UI](https://chakra-ui.com)
24 | - [SWR](https://swr.vercel.app/)
25 | - [Dev.to api](https://docs.forem.com/api/)
26 |
27 |
28 | ### Installation
29 |
30 | 1. Clone the repo
31 | ```sh
32 | git clone https://github.com/MA-Ahmad/dev.to-clone
33 | ```
34 | 2. Install NPM packages
35 | ```sh
36 | yarn install
37 | ```
38 | 3. Start the application
39 | ```sh
40 | yarn dev
41 | ```
42 | The above command will start the application on [http://localhost:3000/](http://localhost:3000).
43 |
44 | ## View and copy code of your favourite components
45 | [TemplatesKart website](https://templateskart.com/projects/devto-clone)
46 |
47 | ## License
48 |
49 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
50 |
--------------------------------------------------------------------------------
/components/feed/posts.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Heading,
4 | Spacer,
5 | Button,
6 | VStack,
7 | HStack,
8 | Grid,
9 | Text,
10 | Link,
11 | Image,
12 | } from '@chakra-ui/react'
13 | import { css } from '@emotion/react'
14 | import styled from '@emotion/styled'
15 | import useSWR from 'swr'
16 | import { useState } from 'react'
17 | import SkeletonCards from './skeleton'
18 |
19 | const HeaderBtn = styled(Button)`
20 | position: relative;
21 | padding: 0.4rem 0.5rem;
22 | font-weight: normal;
23 |
24 | &:hover {
25 | color: #3b49df;
26 |
27 | &::after {
28 | width: 100%;
29 | }
30 | }
31 |
32 | &:focus {
33 | box-shadow: none;
34 | }
35 |
36 | ${(props) =>
37 | props.isCurrent &&
38 | css`
39 | font-weight: 500;
40 |
41 | &::after {
42 | transition: width 0.2s ease;
43 | position: absolute;
44 | bottom: 0;
45 | margin: auto;
46 | content: '';
47 | height: 3px;
48 | width: 70%;
49 | border-radius: 4px;
50 | background-color: #3b49df;
51 | }
52 | `}
53 | `
54 |
55 | const Header = ({ isActive, setIsActive }) => {
56 | return (
57 |
58 |
59 | Posts
60 |
61 | {timeperiods.map((item, idx) => {
62 | if (isActive === item) {
63 | return (
64 | setIsActive(item)}
68 | >
69 | {item}
70 |
71 | )
72 | }
73 | return (
74 | setIsActive(item)}>
75 | {item}
76 |
77 | )
78 | })}
79 |
80 |
81 | )
82 | }
83 |
84 | function Card({
85 | title,
86 | username,
87 | userProfile,
88 | publishedDate,
89 | tagList,
90 | headerImage,
91 | postLink,
92 | readingTime,
93 | reactionCount,
94 | commentCount,
95 | }) {
96 | return (
97 |
105 | {headerImage ? : ''}
106 |
111 | {/* */}
112 |
113 |
114 |
120 |
125 |
126 | {username}
127 |
128 |
129 | {publishedDate}
130 |
131 |
132 |
133 |
134 |
139 |
140 | {username}
141 |
142 |
143 | {publishedDate}
144 |
145 |
146 |
147 |
152 | {title}
153 |
154 |
155 |
156 | {tagList.map((tag, idx) => (
157 |
158 | #{tag}
159 |
160 | ))}
161 |
162 |
163 | }
165 | ml={-2}
166 | bg="transparent"
167 | padding="6px 8px"
168 | height="auto"
169 | fontWeight="normal"
170 | fontSize="14px"
171 | lineHeight="1.2"
172 | borderRadius="4px"
173 | _hover={{ bg: '#f6f6f6' }}
174 | >
175 | {reactionCount}
176 |
177 | reactions
178 |
179 |
180 | }
182 | bg="transparent"
183 | padding="6px 8px"
184 | height="auto"
185 | fontWeight="normal"
186 | fontSize="14px"
187 | lineHeight="1.2"
188 | borderRadius="4px"
189 | _hover={{ bg: '#f6f6f6' }}
190 | >
191 | {commentCount}
192 |
193 | comments
194 |
195 |
196 |
197 | {readingTime} min read
198 |
208 |
209 |
210 |
211 |
212 | )
213 | }
214 |
215 | const fetcher = (url) => fetch(url).then((res) => res.json())
216 |
217 | const timeperiods = ['Week', 'Month', 'Year', 'Latest']
218 | function returnFetchUrl(isActive) {
219 | if (isActive === 'Week') {
220 | return ''
221 | }
222 | return isActive.toLowerCase()
223 | }
224 |
225 | const Posts = () => {
226 | const [isActive, setIsActive] = useState(timeperiods[3])
227 | const { data, error } = useSWR(
228 | `https://dev.to/stories/feed/${returnFetchUrl(isActive)}?page=1`,
229 | fetcher
230 | )
231 |
232 | if (error) return failed to load
233 | if (!data)
234 | return (
235 |
236 |
237 |
238 |
239 | )
240 |
241 | return (
242 |
243 |
244 | {data.map((post, idx) => (
245 |
258 | ))}
259 |
260 | )
261 | }
262 |
263 | export default Posts
264 |
--------------------------------------------------------------------------------
/components/feed/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Box, HStack, Skeleton, Stack } from "@chakra-ui/react";
2 |
3 | const SkeletonCards = () => {
4 | return (
5 |
6 | {[1, 2, 3, 4, 5, 6, 7, 8].map(id => {
7 | return (
8 |
18 | {id === 1 ? (
19 |
20 | ) : (
21 | ""
22 | )}
23 |
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
54 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | })}
69 |
70 | );
71 | };
72 |
73 | export default SkeletonCards;
74 |
--------------------------------------------------------------------------------
/components/layout/container.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@chakra-ui/layout";
2 | import { ReactNode } from "react";
3 |
4 | type ContainerProps = {
5 | children: ReactNode;
6 | };
7 |
8 | const Container = ({ children }: ContainerProps) => {
9 | return (
10 |
11 | {children}
12 |
13 | );
14 | };
15 |
16 | export default Container;
17 |
--------------------------------------------------------------------------------
/components/layout/meta.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 |
3 | const Meta = ({ title, keywords, description }) => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 | {title}
11 |
12 | );
13 | };
14 |
15 | Meta.defaultProps = {
16 | title: "DEV Community 👨💻👨💻",
17 | keywords: "Nextjs, Charkra-UI, React",
18 | description: "Dev.to clone build by Muhammad Ahmad"
19 | };
20 |
21 | export default Meta;
22 |
--------------------------------------------------------------------------------
/components/layout/navbar.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Avatar,
4 | Button,
5 | HStack,
6 | VStack,
7 | Image,
8 | Input,
9 | Spacer,
10 | Menu,
11 | MenuButton,
12 | MenuList,
13 | MenuItem,
14 | Text,
15 | Link,
16 | MenuDivider
17 | } from "@chakra-ui/react";
18 | import Container from "./container";
19 | import { ReactNode } from "react";
20 |
21 | type IconButtonProps = {
22 | children: ReactNode;
23 | };
24 |
25 | const IconButton = ({ children }: IconButtonProps) => {
26 | return (
27 |
37 | );
38 | };
39 |
40 | const Navbar = () => {
41 | return (
42 |
52 |
53 |
54 |
55 |
62 |
63 |
64 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
125 |
126 |
127 |
128 |
129 | );
130 | };
131 |
132 | export default Navbar;
133 |
--------------------------------------------------------------------------------
/components/layout/sidebar.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Flex,
5 | Heading,
6 | Image,
7 | Text,
8 | Spacer
9 | } from "@chakra-ui/react";
10 | import { ReactNode } from "react";
11 |
12 | type LinkButtonProps = {
13 | children: ReactNode;
14 | };
15 |
16 | const LinkButton = ({ children }: LinkButtonProps) => {
17 | return (
18 |
28 | );
29 | };
30 |
31 | const Links = () => {
32 | return (
33 |
34 |
35 |
36 | Home
37 |
38 |
39 |
40 | Reading List
41 |
42 |
43 |
44 | Listings
45 |
46 |
47 |
48 | Podcasts
49 |
50 |
51 |
52 | Videos
53 |
54 |
55 |
56 | Tags
57 |
58 |
59 |
60 | More...
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | const TagList = ({ children }) => {
68 | return (
69 |
70 | {children &&
71 | children.map((item, idx) => #{item})}
72 |
73 | );
74 | };
75 |
76 | const Tags = () => {
77 | return (
78 |
79 |
80 |
81 | My Tags
82 |
83 |
84 |
85 |
86 |
87 |
88 | {[
89 | "Nextjs",
90 | "react",
91 | "javascript",
92 | "ruby",
93 | "ruby on rails",
94 | "beginners",
95 | "typescript"
96 | ]}
97 |
98 |
99 |
100 | );
101 | };
102 |
103 | const Sidebar = props => {
104 | return (
105 |
106 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default Sidebar;
113 |
--------------------------------------------------------------------------------
/components/listing.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Flex,
4 | Link,
5 | Heading,
6 | Text,
7 | Spacer,
8 | VStack,
9 | Skeleton
10 | } from "@chakra-ui/react";
11 | import useSWR from "swr";
12 | import { useState } from "react";
13 |
14 | function ListHeading() {
15 | return (
16 |
17 |
18 | Listings
19 |
20 |
21 | See all
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | function HashTagHeading() {
29 | return (
30 |
31 | #news
32 |
33 | );
34 | }
35 |
36 | const fetcher = url => fetch(url).then(res => res.json());
37 |
38 | const List = () => {
39 | const { data, error } = useSWR("https://dev.to/api/listings", fetcher);
40 |
41 | if (error) return failed to load;
42 | if (!data)
43 | return (
44 |
45 |
46 | {[1, 2, 3, 4, 5].map(id => {
47 | return (
48 |
49 |
50 |
51 | );
52 | })}
53 |
54 | );
55 |
56 | return (
57 |
58 |
59 | {data.slice(0, 7).map((list, index) => (
60 |
61 | ))}
62 |
63 | );
64 | };
65 |
66 | const ListBox = ({ title, category, slug }) => {
67 | return (
68 |
73 |
74 |
75 | {title}
76 |
77 | {category}
78 |
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | const News = () => {
86 | return (
87 |
88 |
89 | {[
90 | {
91 | title: "Javascript developer (~ 2 YOE) looking for remote work",
92 | category: "events",
93 | slug: ""
94 | },
95 | {
96 | title:
97 | "Want to know how a JavaScript framework works under the hood?",
98 | category: "education",
99 | slug: ""
100 | },
101 | { title: "Pair Programming with Jhey", category: "events", slug: "" }
102 | ].map((news, index) => (
103 |
104 | ))}
105 |
106 | );
107 | };
108 |
109 | const Listing = props => {
110 | return (
111 |
112 |
113 |
114 |
115 | );
116 | };
117 |
118 | export default Listing;
119 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devto-clone",
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 | },
11 | "dependencies": {
12 | "@chakra-ui/react": "^1.6.4",
13 | "@emotion/react": "^11",
14 | "@emotion/styled": "^11",
15 | "framer-motion": "^4",
16 | "next": "11.0.1",
17 | "react": "17.0.2",
18 | "react-dom": "17.0.2",
19 | "swr": "^0.5.6"
20 | },
21 | "devDependencies": {
22 | "@types/react": "17.0.11",
23 | "eslint": "7.29.0",
24 | "eslint-config-next": "11.0.1",
25 | "typescript": "4.3.4"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { ChakraProvider } from "@chakra-ui/react";
2 | import { AppProps } from "next/app";
3 | import Head from "next/head";
4 |
5 | const MyApp = ({ Component, pageProps }: AppProps) => {
6 | return (
7 |
8 |
9 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default MyApp;
20 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from "next/document";
2 |
3 | class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default MyDocument;
22 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Fragment } from "react";
2 | import Navbar from "../components/layout/navbar";
3 | import { Box, Grid } from "@chakra-ui/react";
4 | import Sidebar from "../components/layout/sidebar";
5 | import Container from "../components/layout/container";
6 | import Listing from "../components/listing";
7 | import Posts from "../components/feed/posts";
8 | import Meta from "../components/layout/meta";
9 |
10 | export default function Index() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/public/assets/images/bell.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/assets/images/comment.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/images/like.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/assets/images/notification.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/assets/images/settings.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/assets/images/sidebar/home.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/public/assets/images/sidebar/listing.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/assets/images/sidebar/podcast.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/public/assets/images/sidebar/reading.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/public/assets/images/sidebar/tag.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/assets/images/sidebar/video.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/devto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MA-Ahmad/dev.to-clone/9811951978cc8131028b7111bf540e8f95364f4f/public/devto.png
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MA-Ahmad/dev.to-clone/9811951978cc8131028b7111bf540e8f95364f4f/public/favicon.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": false,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve"
16 | },
17 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
18 | "exclude": ["node_modules"]
19 | }
20 |
--------------------------------------------------------------------------------