├── .gitattributes
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
└── vite.svg
├── src
├── @types
│ └── styled.d.ts
├── App.tsx
├── assets
│ ├── avatar.svg
│ ├── logo.svg
│ ├── react.svg
│ └── tech-bg.svg
├── components
│ └── Header
│ │ ├── index.tsx
│ │ └── styles.ts
├── layouts
│ └── DefaultLayout
│ │ ├── index.tsx
│ │ └── styles.ts
├── lib
│ ├── Router.tsx
│ └── axios.ts
├── main.tsx
├── pages
│ ├── Home
│ │ ├── PersonInfo
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── PostCard
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── index.tsx
│ │ └── styles.ts
│ └── PostDetail
│ │ ├── index.tsx
│ │ └── styles.ts
├── styles
│ ├── global.ts
│ └── themes
│ │ └── default.ts
├── utils
│ ├── formatDate.ts
│ └── formatText.ts
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 Github Blog 🚀
2 |
3 | That is the challenge for Ignite Course from Rocketseat
4 |
5 | We built a Simple Github Blog, to train our fundamentals about React and to test how to use Markdown on WebApp
6 |
7 | It is totally responsive, so try to access in your phone too
8 |
9 | ### 👉 Link to Access: https://github-blog-hazel.vercel.app/
10 |
11 | ## ▶ How to start project
12 |
13 | First you need to install and the dependencies and start the project
14 | ```shell
15 | npm run install
16 | npm run start
17 | ```
18 |
19 | ## ⚙ Config Section
20 |
21 | ### 🛠 Tools:
22 | - React - TypeScript
23 | - Styled Components
24 | - Font Awesome
25 | - React Markdown
26 | - Axios
27 | - Date FNS
28 | - Github API
29 |
30 |
31 |
32 | ### ✔ You can:
33 | - See all Post from My Repo
34 | - See the details from The Post that you clicked
35 | - See the body from a Markdown Type
36 |
37 |
38 | ## 📸 Screenshot Section
39 | ### 💻 Desktop Mode
40 |
41 | ## Initial Page
42 | 
43 |
44 | ## Initial Page - List Section
45 | 
46 |
47 | ## Filter by Text
48 | 
49 |
50 | ## Detail Page
51 | 
52 |
53 |
54 | ### 💻 Mobile Mode
55 |
56 | ## Initial Page
57 | 
58 |
59 | ## initial Page - List Section
60 | 
61 |
62 | ## Detail Page
63 | 
64 |
65 |
66 |
67 | 👉 Visit my linkedin: https://www.linkedin.com/in/pedrovdf/
68 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Github Blog
10 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "github-blog",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "axios": "^0.27.2",
13 | "date-fns": "^2.29.1",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-markdown": "^8.0.3",
17 | "react-router-dom": "^6.3.0",
18 | "react-syntax-highlighter": "^15.5.0",
19 | "remark-gfm": "^3.0.1",
20 | "styled-components": "^5.3.5"
21 | },
22 | "devDependencies": {
23 | "@types/react": "^18.0.17",
24 | "@types/react-dom": "^18.0.6",
25 | "@types/react-syntax-highlighter": "^15.5.4",
26 | "@types/styled-components": "^5.1.26",
27 | "@vitejs/plugin-react": "^2.0.1",
28 | "typescript": "^4.6.4",
29 | "vite": "^3.0.6"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/@types/styled.d.ts:
--------------------------------------------------------------------------------
1 | import 'styled-components'
2 | import { defaultTheme } from '../styles/themes/default'
3 |
4 | type ThemeType = typeof defaultTheme
5 |
6 | declare module 'styled-components' {
7 | export interface DefaultTheme extends ThemeType { }
8 | }
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from "styled-components";
2 | import { GlobalStyle } from "./styles/global";
3 | import { defaultTheme } from "./styles/themes/default";
4 | import { BrowserRouter } from "react-router-dom";
5 | import { Router } from "./lib/Router";
6 |
7 | function App() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/src/assets/avatar.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/tech-bg.svg:
--------------------------------------------------------------------------------
1 |
128 |
--------------------------------------------------------------------------------
/src/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { HeaderContainer } from "./styles";
3 |
4 | import techBg from "../../assets/tech-bg.svg";
5 |
6 | export function Header() {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/Header/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HeaderContainer = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: center;
8 | max-width: 100vw;
9 | img {
10 | width: 100%
11 |
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/src/layouts/DefaultLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router-dom";
2 | import { Header } from "../../components/Header";
3 | import { LayoutContainer } from "./styles";
4 |
5 | export function DefaultLayout() {
6 | return (
7 |
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/layouts/DefaultLayout/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const LayoutContainer = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | `;
7 |
--------------------------------------------------------------------------------
/src/lib/Router.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route, Routes } from "react-router-dom";
3 | import { DefaultLayout } from "../layouts/DefaultLayout";
4 | import { Home } from "../pages/Home";
5 | import { PostDetail } from "../pages/PostDetail";
6 |
7 | export function Router() {
8 | return (
9 |
10 | }>
11 | }>
12 | }>
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/lib/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const api = axios.create({
4 | baseURL: 'https://api.github.com/'
5 | })
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 |
5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
6 |
7 |
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/src/pages/Home/PersonInfo/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { PersonInfoContainer } from "./styles";
3 |
4 | import avatar from "../../../assets/avatar.svg";
5 | import { api } from "../../../lib/axios";
6 |
7 | interface IUserInfo {
8 | name: string;
9 | followers: number;
10 | githubUsername: string;
11 | company: string;
12 | url: string;
13 | imgUrl: string;
14 | description: string;
15 | }
16 |
17 | export function PersonInfo() {
18 | const [userInfo, setUserInfo] = useState();
19 |
20 | async function fetchUsers() {
21 | const response = await api.get("users/devpedrodiass");
22 | const { name, followers, login, company, html_url, avatar_url, bio } =
23 | response.data;
24 | const newUserObj = {
25 | name,
26 | followers,
27 | githubUsername: login,
28 | company,
29 | url: html_url,
30 | imgUrl: avatar_url,
31 | description: bio,
32 | };
33 | setUserInfo(newUserObj);
34 | }
35 |
36 | useEffect(() => {
37 | fetchUsers();
38 | }, []);
39 |
40 | return (
41 |
42 |
43 |
44 |
50 |
51 | {userInfo?.description}
52 |
53 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/src/pages/Home/PersonInfo/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PersonInfoContainer = styled.div`
4 | max-width: 864px;
5 | width: 100%;
6 | height: 212px;
7 | display: flex;
8 | background: ${props => props.theme['base-profile']};
9 | box-shadow: 0px 2px 28px rgba(0, 0, 0, 0.2);
10 | border-radius: 10px;
11 | padding: 2rem;
12 | gap: 2rem;
13 | img {
14 | border-radius: 10px;
15 | }
16 | div {
17 | width: 100%;
18 | height: 100%;
19 | display: flex;
20 | flex-direction: column;
21 | gap: 0.5rem;
22 | header {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | h1 {
27 | font-weight: 700;
28 | font-size: 1.5rem;
29 | line-height: 130%;
30 | }
31 | a {
32 | font-weight: 700;
33 | font-size: 0.75rem;
34 | line-height: 160%;
35 | text-transform: uppercase;
36 | text-decoration: none;
37 | display: flex;
38 | gap: 0.5rem;
39 | align-items: center;
40 | color: ${props => props.theme.blue};
41 | transition: border 0.2s;
42 | border-bottom: 2px solid transparent;
43 | &:hover {
44 | border-bottom: 2px solid ${props => props.theme.blue};
45 | }
46 | }
47 | }
48 | main {
49 | p {
50 | margin-top: 0.5rem;
51 | word-wrap: break-word;
52 | }
53 | }
54 | footer {
55 | display: flex;
56 | height: 100%;
57 | align-items: flex-end;
58 | gap: 1.5rem;
59 | span {
60 | display: flex;
61 | align-items: center;
62 | gap: 0.5rem;
63 | color: ${props => props.theme['base-subtitle']};
64 | i {
65 | color: ${props => props.theme['base-label']};
66 | }
67 | }
68 |
69 | }
70 |
71 | }
72 | @media (max-width:680px) {
73 | display: flex;
74 | flex-direction: column;
75 | height: auto;
76 | align-items: center;
77 | justify-content: center;
78 | }
79 | @media (max-width: 450px) {
80 | div {
81 | header {
82 | flex-direction: column;
83 | gap: 0.8rem;
84 | }
85 | main {
86 | p {
87 | text-align: center;
88 | }
89 | }
90 | footer {
91 | display: flex;
92 | flex-direction: column;
93 | align-items: center ;
94 | }
95 | }
96 | }
97 | `;
98 |
--------------------------------------------------------------------------------
/src/pages/Home/PostCard/index.tsx:
--------------------------------------------------------------------------------
1 | import { formatDistanceToNow } from "date-fns";
2 | import { enUS } from "date-fns/locale";
3 | import React from "react";
4 | import { IPost } from "..";
5 | import { formatText } from "../../../utils/formatText";
6 | import { PostCardContainer } from "./styles";
7 |
8 | interface IPostCard {
9 | post: IPost;
10 | }
11 |
12 | export function PostCard({ post }: IPostCard) {
13 | const { created_at, body, title, number } = post;
14 | const formattedDate = formatDistanceToNow(new Date(created_at), {
15 | locale: enUS,
16 | addSuffix: true,
17 | });
18 | return (
19 |
20 |
21 | {title}
22 | {formattedDate}
23 |
24 |
25 | {formatText(body, 80)}
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/src/pages/Home/PostCard/styles.ts:
--------------------------------------------------------------------------------
1 | import { NavLink } from 'react-router-dom';
2 | import styled from 'styled-components';
3 |
4 | export const PostCardContainer = styled(NavLink)`
5 | width: 100%;
6 | text-decoration: none;
7 | display: flex;
8 | flex-direction: column;
9 | gap: 1.25rem;
10 | padding: 2rem;
11 |
12 | background: ${props => props.theme['base-post']};
13 | border-radius: 10px;
14 | border: 2px solid transparent;
15 |
16 | height: 260px;
17 | overflow: hidden;
18 |
19 | transition: border 0.2s;
20 |
21 | cursor: pointer;
22 | header {
23 | display: flex;
24 | justify-content: space-between;
25 | gap: 1rem;
26 | h1 {
27 | font-weight: 700;
28 | font-size: 1.125rem;
29 | line-height: 160%;
30 | color: ${props => props.theme['base-title']};
31 | text-align: justify;
32 | }
33 |
34 | span {
35 | font-size: 0.875rem;
36 | line-height: 160%;
37 | color: ${props => props.theme['base-span']};
38 |
39 | }
40 | }
41 |
42 | main {
43 | height: 112px;
44 | overflow: hidden;
45 | p {
46 | height: 100%;
47 | text-align: justify;
48 | color: ${props => props.theme['base-text']};
49 | }
50 | }
51 |
52 | &:hover {
53 | border: 2px solid ${props => props.theme['base-label']};
54 | }
55 | `;
56 |
--------------------------------------------------------------------------------
/src/pages/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { api } from "../../lib/axios";
3 | import { PersonInfo } from "./PersonInfo";
4 | import { PostCard } from "./PostCard";
5 | import {
6 | HomeContainer,
7 | HomeContent,
8 | ListSection,
9 | SearchSection,
10 | } from "./styles";
11 |
12 | export interface IPost {
13 | title: string;
14 | body: string;
15 | created_at: string;
16 | number: string;
17 | }
18 |
19 | export function Home() {
20 | const [posts, setPosts] = useState([] as IPost[]);
21 | const [postsCounter, setPostsCounter] = useState(0);
22 |
23 | async function fetchPosts(query = "") {
24 | const response = await api.get(
25 | `search/issues?q=${
26 | query ? query : ""
27 | }%20repo:${"devpedrodiass"}/Github-blog-issues`
28 | );
29 | setPosts(response.data.items);
30 | setPostsCounter(response.data.total_count);
31 | }
32 |
33 | useEffect(() => {
34 | fetchPosts();
35 | }, []);
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 | Posts
44 | {postsCounter} posts
45 |
46 | fetchPosts(e.target.value)}
49 | placeholder="Search a Post"
50 | />
51 |
52 |
53 | {posts &&
54 | posts.map((post) => (
55 |
59 | ))}
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/pages/Home/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HomeContainer = styled.div`
4 | width: 100%;
5 | margin-top: -5.5rem;
6 |
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 | justify-content: center;
11 | gap: 4.5rem;
12 | padding: 1rem 2rem;
13 | `;
14 |
15 | export const HomeContent = styled.div`
16 | max-width: 864px;
17 | width: 100%;
18 | display: flex;
19 | flex-direction: column;
20 | gap: 3rem;
21 | `;
22 |
23 | export const SearchSection = styled.section`
24 | width: 100%;
25 | div {
26 | display: flex ;
27 | justify-content: space-between;
28 | span {
29 | font-weight: 700;
30 | font-size: 1.125rem;
31 | line-height: 160%;
32 | color: ${props => props.theme['base-subtitle']};
33 | }
34 | small {
35 | font-style: normal;
36 | font-weight: 400;
37 | font-size: 14px;
38 | line-height: 160%;
39 | color: ${props => props.theme['base-span']};
40 | }
41 | }
42 | input {
43 | margin-top: 0.75rem;
44 | background: ${props => props.theme['base-input']};
45 | border: 1px solid ${props => props.theme['base-border']};
46 | border-radius: 6px;
47 | padding: 0.75rem 1rem;
48 | width: 100%;
49 | color: ${props => props.theme['base-text']};
50 | &::placeholder {
51 | color: ${props => props.theme['base-label']};
52 | }
53 | }
54 | `
55 |
56 | export const ListSection = styled.div`
57 | display: grid;
58 | grid-template-columns: repeat(2,1fr);
59 | gap: 2rem;
60 |
61 | @media (max-width: 950px) {
62 | grid-template-columns: 1fr;
63 | }
64 | `
--------------------------------------------------------------------------------
/src/pages/PostDetail/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
3 |
4 | import ReactMarkdown from "react-markdown";
5 | import { useParams } from "react-router-dom";
6 | import remarkGfm from "remark-gfm";
7 | import { api } from "../../lib/axios";
8 | import { dark } from "react-syntax-highlighter/dist/esm/styles/prism";
9 | import {
10 | NavButton,
11 | PostDetailCard,
12 | PostDetailContainer,
13 | PostDetailContent,
14 | } from "./styles";
15 | import { formatDistanceToNow } from "date-fns";
16 | import { enUS } from "date-fns/locale";
17 |
18 | interface IPostDetail {
19 | title: string;
20 | comments: number;
21 | createdAt: string;
22 | githubUsername: string;
23 | url: string;
24 | body: string;
25 | }
26 |
27 | export function PostDetail() {
28 | const [post, setPost] = useState({} as IPostDetail);
29 | const { id } = useParams();
30 |
31 | async function fetchPost() {
32 | const response = await api.get(
33 | `/repos/devpedrodiass/Github-blog-issues/issues/${id}`
34 | );
35 | const { title, comments, created_at, user, html_url, body } = response.data;
36 | const newPostObj = {
37 | title,
38 | githubUsername: user.login,
39 | comments,
40 | createdAt: formatDistanceToNow(new Date(created_at), {
41 | locale: enUS,
42 | addSuffix: true,
43 | }),
44 | url: html_url,
45 | body,
46 | };
47 | setPost(newPostObj);
48 | }
49 |
50 | useEffect(() => {
51 | fetchPost();
52 | }, []);
53 |
54 | return (
55 |
56 |
57 |
67 |
68 |
{post.title}
69 |
70 |
84 |
85 |
86 |
87 | {post.body}
88 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/src/pages/PostDetail/styles.ts:
--------------------------------------------------------------------------------
1 | import { NavLink } from 'react-router-dom';
2 | import styled from 'styled-components';
3 |
4 | export const PostDetailContainer = styled.div`
5 | margin-top: -5.5rem;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | justify-content: center;
10 | padding: 0 1rem;
11 | `;
12 |
13 | export const PostDetailCard = styled.div`
14 | max-width: 864px;
15 | width: 100%;
16 | height: auto;
17 | background: ${props => props.theme['base-profile']};
18 | padding: 2rem;
19 | box-shadow: 0px 2px 28px rgba(0, 0, 0, 0.2);
20 | border-radius: 10px;
21 | display: flex;
22 | flex-direction: column;
23 |
24 | header {
25 | display: flex;
26 | align-items: center;
27 | justify-content: space-between;
28 | a {
29 | text-decoration: none;
30 | background: transparent;
31 | color: ${props => props.theme.blue};
32 | transition: border 0.2s;
33 | border-bottom: 2px solid transparent;
34 | display: flex;
35 | align-items: center;
36 | gap: 0.5rem;
37 | text-transform: uppercase;
38 |
39 | font-weight: 700;
40 | font-size: 0.75rem;
41 | line-height: 160%;
42 |
43 | &:hover {
44 | border-bottom: 2px solid ${props => props.theme.blue};
45 | }
46 | }
47 | }
48 | div {
49 | margin-top: 1.5rem;
50 | }
51 | footer {
52 | margin-top: 0.5rem;
53 | display: flex;
54 | align-items: center;
55 | gap: 1.5rem;
56 | span {
57 | display: flex;
58 | align-items: center;
59 | gap: 0.5rem;
60 | color: ${props => props.theme['base-subtitle']};
61 | i {
62 | color: ${props => props.theme['base-label']};
63 | }
64 | }
65 | }
66 |
67 | @media (max-width:500px) {
68 | div {
69 | h1 {
70 | text-align: center;
71 | }
72 | }
73 | footer {
74 | flex-direction: column;
75 | }
76 | }
77 | `
78 |
79 | export const NavButton = styled(NavLink)`
80 | text-decoration: none;
81 | background: transparent;
82 | color: ${props => props.theme.blue};
83 | transition: border 0.2s;
84 | border-bottom: 2px solid transparent;
85 | display: flex;
86 | align-items: center;
87 | gap: 0.5rem;
88 | text-transform: uppercase;
89 |
90 | font-weight: 700;
91 | font-size: 0.75rem;
92 | line-height: 160%;
93 |
94 | &:hover {
95 | border-bottom: 2px solid ${props => props.theme.blue};
96 | }
97 | `
98 | export const PostDetailContent = styled.main`
99 | max-width: 864px;
100 | width: 100%;
101 | padding: 2.5rem;
102 | white-space: pre-wrap;
103 | overflow: hidden;
104 | div {
105 | overflow-x: auto;
106 | width: 100%;
107 | height: 100%;
108 | /* width */
109 | ::-webkit-scrollbar {
110 | width: 10px;
111 | height: 8px;
112 | }
113 |
114 | /* Track */
115 | ::-webkit-scrollbar-track {
116 | background: ${props => props.theme['base-profile']};
117 | }
118 |
119 | /* Handle */
120 | ::-webkit-scrollbar-thumb {
121 | background: ${props => props.theme.blue};
122 | }
123 | }
124 |
125 |
126 |
127 | `
--------------------------------------------------------------------------------
/src/styles/global.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 |
3 | export const GlobalStyle = createGlobalStyle`
4 | * {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | }
9 |
10 | :focus {
11 | outline: 0;
12 | box-shadow: 0 0 0 2px ${props => props.theme['blue']};
13 | }
14 |
15 | body {
16 | background: ${props => props.theme['base-background']};
17 | color: ${props => props.theme['base-text']};
18 | -webkit-font-smoothing: antialiased;
19 | }
20 |
21 | body, input, textarea, button {
22 | font: 400 1rem Nunito, 'sans-serif';
23 | }
24 |
25 | /* width */
26 | ::-webkit-scrollbar {
27 | width: 10px;
28 | height: 8px;
29 | }
30 |
31 | /* Track */
32 | ::-webkit-scrollbar-track {
33 | background: ${props => props.theme['base-profile']};
34 | }
35 |
36 | /* Handle */
37 | ::-webkit-scrollbar-thumb {
38 | background: ${props => props.theme.blue};
39 | }
40 | `
--------------------------------------------------------------------------------
/src/styles/themes/default.ts:
--------------------------------------------------------------------------------
1 | export const defaultTheme = {
2 | white: '#fff',
3 | blue: '#3294F8',
4 | 'base-title': '#E7EDF4',
5 | 'base-subtitle': '#C4D4E3',
6 | 'base-text': '#AFC2D4',
7 | 'base-span': '#7B96B2',
8 | 'base-label': '#3A536B',
9 | 'base-border': '#1C2F41',
10 | 'base-post': '#112131',
11 | 'base-profile': '#0B1B2B',
12 | 'base-background': '#071422',
13 | 'base-input': '#040F1A',
14 | } as const
--------------------------------------------------------------------------------
/src/utils/formatDate.ts:
--------------------------------------------------------------------------------
1 | export const formatDate = new Intl.DateTimeFormat('en-US')
--------------------------------------------------------------------------------
/src/utils/formatText.ts:
--------------------------------------------------------------------------------
1 | export function formatText(text: string, limitLength = 50) {
2 | const textArr = text.split(" ")
3 | const newText = textArr.map((string, index) => {
4 | if (index < limitLength) return string
5 | }).filter(string => string !== undefined
6 | )
7 | return `${newText.toString().replaceAll(",", " ")}...`
8 | }
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------