├── .gitignore ├── README.md ├── images ├── Web │ ├── tag1.png │ └── tag2.png └── desktop-preview.jpg ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── robots.txt ├── src ├── App.ts ├── api │ └── api.ts ├── assets │ ├── icon-down.svg │ ├── icon-facebook.svg │ ├── icon-instagram.svg │ ├── icon-twitter.svg │ ├── icon-up.svg │ └── icon-youtube.svg ├── components │ ├── FollowersCard │ │ ├── index.ts │ │ └── styles.scss │ ├── Header │ │ ├── index.ts │ │ └── styles.scss │ ├── OverviewCard │ │ ├── index.ts │ │ └── styles.scss │ └── ToggleButton │ │ ├── index.ts │ │ └── styles.scss ├── contexts │ └── themeContext.ts ├── hooks │ ├── useLocalStorage.ts │ └── useTheme.ts ├── index.ts ├── pages │ └── Dashboard │ │ ├── index.ts │ │ └── styles.scss ├── routes │ └── index.ts └── styles │ ├── app.scss │ └── global.scss └── tsconfig.json /.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Social Media Dashboard App 2 | 3 | ![Preview](images/desktop-preview.jpg) 4 | 5 | Welcome to the Social Media Dashboard App! This application allows you to manage and monitor your social media accounts from a single centralized dashboard. Whether you're a social media manager, influencer, or someone who wants to stay on top of their social media presence, this app provides a convenient way to streamline your activities. 6 | 7 | ## Features 8 | 9 | 1. **Account Integration**: Connect your social media accounts, such as Facebook, Twitter, Instagram, LinkedIn, and more, to the app. 10 | 2. **Unified Dashboard**: Access all your connected accounts from a single dashboard, eliminating the need to switch between multiple apps or tabs. 11 | 3. **Real-time Analytics**: Get real-time insights into your social media performance, including followers, likes, comments, and engagement metrics. 12 | 4. **Scheduling and Publishing**: Schedule and publish posts across multiple social media platforms at once, saving time and effort. 13 | 5. **Social Listening**: Monitor mentions, hashtags, and keywords relevant to your brand or industry to stay updated on trends and engage with your audience effectively. 14 | 6. **Inbox Management**: Receive and respond to direct messages, comments, and notifications from different social media platforms within the app. 15 | 7. **Content Curation**: Discover and curate content from various sources, such as articles, images, and videos, to share with your audience. 16 | 8. **Collaboration and Team Management**: Collaborate with team members and assign roles and permissions for efficient social media management. 17 | 9. **Customizable Reports**: Generate customized reports with key performance indicators (KPIs) and export them for analysis or sharing with stakeholders. 18 | 19 | ## Installation 20 | 21 | To install and run the Social Media Dashboard App, follow these steps: 22 | 23 | 1. Clone the repository to your local machine: 24 | 25 | ``` 26 | git clone https://github.com/MithuLix/Social-Media-Dashboard.git 27 | ``` 28 | 29 | 2. Navigate to the project directory: 30 | 31 | ``` 32 | cd social-media-dashboard 33 | ``` 34 | 35 | 3. Install the dependencies using a package manager like npm or yarn: 36 | 37 | ``` 38 | npm install 39 | ``` 40 | 41 | 4. Configure the necessary API keys and credentials for each social media platform in the app's configuration file. Refer to the documentation for each platform to obtain the required credentials. 42 | 43 | 5. Build and start the app: 44 | 45 | ``` 46 | npm start 47 | ``` 48 | 49 | 6. Open your web browser and visit `http://localhost:3000` to access the Social Media Dashboard App. 50 | 51 | ## Usage 52 | 53 | Once you have installed and launched the app, follow these steps to get started: 54 | 55 | 1. Create an account or log in to your existing account. 56 | 57 | 2. Connect your desired social media accounts using the provided integration options. 58 | 59 | 3. Explore the dashboard to view real-time analytics, scheduled posts, and incoming messages. 60 | 61 | 4. Use the scheduling and publishing features to plan and publish content across your social media accounts. 62 | 63 | 5. Monitor your brand or industry-related keywords and engage with your audience using the social listening features. 64 | 65 | 6. Customize the app's settings and notifications according to your preferences. 66 | 67 | 7. Generate and export reports to track your social media performance and share insights with others. 68 | 69 | ## Technologies 70 |

71 | React 72 | TypeScript 73 | Sass 74 |

75 | 76 | 77 | ## Contributing 78 | 79 | We welcome contributions to the Social Media Dashboard App! If you have any suggestions, bug reports, or feature requests, please open an issue on the project repository. 80 | 81 | If you would like to contribute code to the project, follow these steps: 82 | 83 | 1. Fork the repository on GitHub. 84 | 85 | 2. Create a new branch for your feature or bug fix: 86 | 87 | ``` 88 | git checkout -b feature/your-feature-name 89 | ``` 90 | 91 | 3. Make the necessary changes and commit them: 92 | 93 | ``` 94 | git commit -m "Add your commit message" 95 | ``` 96 | 97 | 4. Push your branch to your forked repository: 98 | 99 | ``` 100 | git push origin feature/your-feature-name 101 | ``` 102 | 103 | 104 | 105 | 106 | MIT License 107 | 108 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 109 | 110 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 111 | 112 | THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 113 | -------------------------------------------------------------------------------- /images/Web/tag1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mithulix/Social-Media-Dashboard/e8a54e30c0eb3202f07b47abf0257f1ba7cc75a5/images/Web/tag1.png -------------------------------------------------------------------------------- /images/Web/tag2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mithulix/Social-Media-Dashboard/e8a54e30c0eb3202f07b47abf0257f1ba7cc75a5/images/Web/tag2.png -------------------------------------------------------------------------------- /images/desktop-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mithulix/Social-Media-Dashboard/e8a54e30c0eb3202f07b47abf0257f1ba7cc75a5/images/desktop-preview.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "social-media-dashboard", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^1.4.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-scripts": "^2.1.3", 13 | "saas": "^1.0.0", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "devDependencies": { 41 | "typescript": "^5.0.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mithulix/Social-Media-Dashboard/e8a54e30c0eb3202f07b47abf0257f1ba7cc75a5/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.ts: -------------------------------------------------------------------------------- 1 | // Import the useTheme hook from the hooks folder 2 | import useTheme from './hooks/useTheme'; 3 | // Import the Router component from the routes folder 4 | import { Router } from './routes'; 5 | 6 | // Import the SCSS stylesheets for the app 7 | import './styles/app.scss'; 8 | import './styles/global.scss'; 9 | 10 | // Define the App component 11 | function App() { 12 | // Call the useTheme hook to retrieve the current theme 13 | const { theme } = useTheme(); 14 | 15 | // Return the app's JSX 16 | return ( 17 |
18 |
19 | 20 |
21 | ); 22 | } 23 | 24 | // Export the App component as the default export of this module 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/api/api.ts: -------------------------------------------------------------------------------- 1 | // Importing axios package to make HTTP requests 2 | import axios from "axios"; 3 | 4 | // Defining the base URL for the JSON server API 5 | const urlServer = "https://my-json-server.typicode.com/Cunegundess/serverTeste"; 6 | 7 | // Defining the dataFake type that represents the structure of the data returned from the "/dataFake" endpoint of the server 8 | export type dataFake = { 9 | readonly id: string; // a required string field that contains the unique identifier of the data 10 | nickname: string; // a string field that contains the nickname of the data 11 | followers: number; // a number field that contains the number of followers of the data 12 | followersOverview: number; // a number field that contains the overview of followers of the data 13 | readonly type: string; // a required string field that contains the type of the data 14 | status: boolean; // a boolean field that indicates the status of the data 15 | }; 16 | 17 | // Defining the fakeOverviewData type that represents the structure of the data returned from the "/fakeOverview" endpoint of the server 18 | export type fakeOverviewData = { 19 | readonly id: string; // a required string field that contains the unique identifier of the data 20 | title: string; // a string field that contains the title of the data 21 | total: number; // a number field that contains the total of the data 22 | percent: number; // a number field that contains the percentage of the data 23 | readonly type: string; // a required string field that contains the type of the data 24 | status: boolean; // a boolean field that indicates the status of the data 25 | }; 26 | 27 | // Defining the fetchData function that makes an HTTP GET request to the provided URL and returns the response data 28 | export async function fetchData(url: string): Promise { 29 | try { 30 | const response = await axios.get(url); // Making an HTTP GET request using axios package 31 | return response.data; // Returning the response data 32 | } catch (error) { 33 | console.error(error); // Logging the error to the console 34 | return []; // Returning an empty array in case of error 35 | } 36 | } 37 | 38 | // Defining the getDataFake function that returns an array of dataFake objects obtained from the server 39 | export async function getDataFake(): Promise { 40 | const dataUrl = `${urlServer}/dataFake`; // Constructing the URL for the "/dataFake" endpoint 41 | return await fetchData(dataUrl); // Making an HTTP GET request to the "/dataFake" endpoint and returning the response data as an array of dataFake objects 42 | } 43 | 44 | // Defining the getFakeOverviewData function that returns an array of fakeOverviewData objects obtained from the server 45 | export async function getFakeOverviewData(): Promise { 46 | const overviewUrl = `${urlServer}/fakeOverview`; // Constructing the URL for the "/fakeOverview" endpoint 47 | return await fetchData(overviewUrl); // Making an HTTP GET request to the "/fakeOverview" endpoint and returning the response data as an array of fakeOverviewData objects 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/icon-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon-facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon-instagram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon-youtube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/FollowersCard/index.ts: -------------------------------------------------------------------------------- 1 | // Import the necessary SVG icons and the useState hook from React 2 | import { dataFake } from "../../api/api"; 3 | import { ReactComponent as DownSvg } from "../../assets/icon-down.svg"; 4 | import { ReactComponent as UpSvg } from "../../assets/icon-up.svg"; 5 | import { ReactComponent as FacebookSvg } from "../../assets/icon-facebook.svg"; 6 | import { ReactComponent as InstagramSvg } from "../../assets/icon-instagram.svg"; 7 | import { ReactComponent as TwitterSvg } from "../../assets/icon-twitter.svg"; 8 | import { ReactComponent as YoutubeSvg } from "../../assets/icon-youtube.svg"; 9 | import { useState } from 'react' 10 | 11 | // Import the styles for this component 12 | import "./styles.scss"; 13 | 14 | // Define the shape of the props that this component accepts 15 | type Props = { 16 | borderTheme?: string; 17 | data: dataFake; 18 | }; 19 | 20 | // This function takes a string representing a social media platform 21 | // and returns the corresponding SVG icon for that platform 22 | function iconSvg(type: string) { 23 | switch (type) { 24 | case "instagram": 25 | return ; 26 | case "facebook": 27 | return ; 28 | case "youtube": 29 | return ; 30 | default: 31 | return ; 32 | } 33 | } 34 | 35 | // This function takes a fake data object and returns a new object with 36 | // randomly generated numbers for the followers count, followers overview, 37 | // and status (whether the count has gone up or down) 38 | function calculateNumbers(numbers: dataFake) { 39 | const followers = Math.floor(Math.random() * 100000) + 1000; 40 | const followersOverview = Math.floor(Math.random() * 1000) + 1000; 41 | const status = Math.random() < 0.5; 42 | 43 | return { 44 | followers, 45 | followersOverview, 46 | status 47 | }; 48 | } 49 | 50 | // This is the main component that renders the followers card 51 | export function FollowersCard({ borderTheme, data }: Props) { 52 | // Initialize the state with a null value 53 | const [latestNumbers] = useState(null); 54 | 55 | // If no data is provided, return null 56 | if (!data) { 57 | return null; 58 | } 59 | 60 | // Calculate the random numbers to be displayed on the card 61 | const dashboardNumbers = calculateNumbers(latestNumbers || data); 62 | 63 | return ( 64 | // Render the followers card using the provided data and generated numbers 65 |
66 |
67 |
68 |
69 | {/* Render the SVG icon for the social media platform */} 70 | {iconSvg(data.type)} 71 | {/* Render the nickname of the platform */} 72 | {data.nickname} 73 |
74 | 75 |
76 | {/* Render the followers count */} 77 | 78 | {dashboardNumbers.followers} 79 | 80 | {/* Render "subscribers" if the platform is YouTube, "followers" otherwise */} 81 | {data.type === "youtube" ? "subscribers" : "followers"} 82 |
83 | 84 | 96 |
97 |
98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /src/components/FollowersCard/styles.scss: -------------------------------------------------------------------------------- 1 | #followers-card-container { 2 | width: 100%; 3 | height: 100%; 4 | 5 | background: var(--card); 6 | border-radius: 0.25rem; 7 | 8 | cursor: pointer; 9 | transition: background-color 0.26s ease-in; 10 | 11 | hr { 12 | width: 100%; 13 | height: 4px; 14 | border: transparent; 15 | border-radius: 0.25rem 0.25rem 0px 0px; 16 | } 17 | 18 | .center { 19 | display: flex; 20 | } 21 | 22 | #followers-card-content { 23 | width: 100%; 24 | height: 100%; 25 | 26 | flex-direction: column; 27 | justify-content: space-evenly; 28 | align-items: center; 29 | 30 | #header-card-followers { 31 | align-items: center; 32 | justify-content: center; 33 | 34 | gap: 0.5rem; 35 | 36 | span { 37 | font-size: 0.85rem; 38 | color: var(--accent-text); 39 | font-weight: bold; 40 | } 41 | } 42 | 43 | #center-card-followers { 44 | flex-direction: column; 45 | align-items: center; 46 | 47 | strong { 48 | color: var(--text); 49 | font-size: 3rem; 50 | } 51 | 52 | span { 53 | color: var(--accent-text); 54 | text-transform: uppercase; 55 | font-size: 0.8rem; 56 | letter-spacing: 0.2rem; 57 | } 58 | } 59 | 60 | #footer-card-followers { 61 | display: flex; 62 | align-items: center; 63 | gap: 0.3rem; 64 | 65 | span { 66 | font-weight: bold; 67 | font-size: 0.85rem; 68 | } 69 | } 70 | 71 | .green-status { 72 | span { 73 | color: var(--lime-green); 74 | } 75 | } 76 | 77 | .red-status { 78 | span { 79 | color: var(--bright-red); 80 | } 81 | } 82 | } 83 | 84 | &:hover { 85 | background-color: var(--card-accent); 86 | } 87 | } 88 | 89 | .facebook { 90 | background: var(--facebook); 91 | } 92 | 93 | .instagram { 94 | background: linear-gradient(to right, hsl(37, 97%, 70%), hsl(329, 70%, 58%)) border-box; 95 | } 96 | 97 | .twitter { 98 | background: var(--twitter); 99 | } 100 | 101 | .youtube { 102 | background: var(--youTube); 103 | } 104 | -------------------------------------------------------------------------------- /src/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | // Import ToggleButton component, useState hook and dataFake type from respective files 2 | import { ToggleButton } from "../ToggleButton"; 3 | import { useState } from "react"; 4 | import { dataFake } from "../../api/api"; 5 | // Import styles for this component 6 | import "./styles.scss"; 7 | 8 | // Define Props interface for Header component 9 | type Props = { 10 | data: dataFake; 11 | } 12 | 13 | // Define function to calculate followers number 14 | function calculateNumbers(numbers: dataFake) { 15 | const followers = Math.floor(Math.random() * 1000000) + 100000; 16 | return { 17 | followers 18 | }; 19 | } 20 | 21 | // Define Header component with Props as parameter 22 | export function Header({data}: Props) { 23 | // Declare state for latestNumbers with default value as null 24 | const [latestNumbers ] = useState(null); 25 | 26 | // If data is null, return null 27 | if (!data) { 28 | return null; 29 | } 30 | 31 | // Calculate dashboard numbers based on latestNumbers or data 32 | const dashboardNumbers = calculateNumbers(latestNumbers || data); 33 | 34 | // Render Header component 35 | return ( 36 |
37 | {/* Render title and total followers */} 38 |
39 |

Social Media Dashboard

40 | Total Followers: {dashboardNumbers.followers} 41 |
42 | 43 | {/* Render toggle button and theme color */} 44 |
45 | Dark Theme 46 | 47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Header/styles.scss: -------------------------------------------------------------------------------- 1 | #container-header { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: flex-start; 5 | 6 | h1 { 7 | color: var(--text); 8 | } 9 | 10 | span { 11 | color: var(--accent-text); 12 | font-weight: bold; 13 | } 14 | 15 | #theme-color { 16 | color: var(--toggle); 17 | } 18 | 19 | #toggle-container { 20 | display: flex; 21 | align-items: center; 22 | gap: 0.8rem; 23 | 24 | padding-top: 0.4rem; 25 | } 26 | 27 | @media (max-width: 375px) { 28 | flex-direction: column; 29 | 30 | h1 { 31 | font-size: 1.55rem; 32 | } 33 | 34 | #toggle-container { 35 | // background: red; 36 | width: 100%; 37 | justify-content: space-between; 38 | 39 | border-top: 1px solid var(--accent-text); 40 | 41 | margin-top: 1.6rem; 42 | padding-top: 1.2rem; 43 | align-items: flex-start; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/OverviewCard/index.ts: -------------------------------------------------------------------------------- 1 | import { ReactComponent as DownSvg } from "../../assets/icon-down.svg"; 2 | import { ReactComponent as UpSvg } from "../../assets/icon-up.svg"; 3 | import { ReactComponent as FacebookSvg } from "../../assets/icon-facebook.svg"; 4 | import { ReactComponent as InstagramSvg } from "../../assets/icon-instagram.svg"; 5 | import { ReactComponent as TwitterSvg } from "../../assets/icon-twitter.svg"; 6 | import { ReactComponent as YoutubeSvg } from "../../assets/icon-youtube.svg"; 7 | import { fakeOverviewData } from "../../api/api"; 8 | import { useState } from "react"; 9 | 10 | import "./styles.scss"; 11 | 12 | // Define the type for the props passed to this component 13 | type Props = { 14 | data: fakeOverviewData; 15 | }; 16 | 17 | // Helper function to calculate random numbers for the component 18 | function calculateNumbers(numbers: fakeOverviewData) { 19 | const total = Math.floor(Math.random() * 10000); // Generate a random total number 20 | const status = Math.random() < 0.5; // Generate a random status (true or false) 21 | const percent = Math.floor(Math.random() * 1000); // Generate a random percentage 22 | return { 23 | total, 24 | status, 25 | percent 26 | }; 27 | } 28 | 29 | // Define the OverviewCard component 30 | export function OverviewCard({ data }: Props) { 31 | // Set the initial state of latestNumbers to null using the useState hook 32 | const [latestNumbers] = useState(null); 33 | 34 | // If there's no data passed to the component, return null 35 | if (!data) { 36 | return null; 37 | } 38 | 39 | // Calculate the overview numbers based on the latestNumbers or the data passed to the component 40 | const overviewNumbers = calculateNumbers(latestNumbers || data); 41 | 42 | // Helper function to render the appropriate SVG icon based on the type of social media 43 | function iconSvg(type: string) { 44 | switch (type) { 45 | case "instagram": 46 | return ; 47 | case "facebook": 48 | return ; 49 | case "youtube": 50 | return ; 51 | default: 52 | return ; 53 | } 54 | } 55 | 56 | // Render the component 57 | return ( 58 |
59 |
60 | {data.title} 61 | {overviewNumbers.total} 62 |
63 |
64 | {/* Render the appropriate SVG icon */} 65 | {iconSvg(data.type)} 66 |
71 | {/* Render the appropriate SVG icon based on the status */} 72 | {overviewNumbers.status ? : } 73 | {overviewNumbers.percent} 74 | % 75 |
76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/components/OverviewCard/styles.scss: -------------------------------------------------------------------------------- 1 | #overview-card-container { 2 | width: 100%; 3 | height: 100%; 4 | 5 | background: var(--card); 6 | border-radius: 0.25rem; 7 | 8 | cursor: pointer; 9 | transition: background-color 0.26s ease-in; 10 | 11 | &:hover { 12 | background-color: var(--card-accent); 13 | } 14 | 15 | display: flex; 16 | justify-content: space-around; 17 | align-items: center; 18 | 19 | .align-content { 20 | height: 100%; 21 | 22 | display: flex; 23 | justify-content: space-evenly; 24 | flex-direction: column; 25 | } 26 | 27 | #left-content { 28 | align-items: flex-start; 29 | 30 | span { 31 | color: var(--accent-text); 32 | font-weight: bold; 33 | } 34 | 35 | strong { 36 | color: var(--text); 37 | font-size: 1.5rem; 38 | } 39 | } 40 | 41 | #right-content { 42 | align-items: flex-end; 43 | 44 | #percent-overview { 45 | display: flex; 46 | align-items: center; 47 | 48 | #margin-left { 49 | margin-left: 0.2rem; 50 | } 51 | 52 | span { 53 | font-weight: bold; 54 | } 55 | } 56 | 57 | .green-status { 58 | span { 59 | color: var(--lime-green); 60 | } 61 | } 62 | 63 | .red-status { 64 | span { 65 | color: var(--bright-red); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/ToggleButton/index.ts: -------------------------------------------------------------------------------- 1 | import useTheme from "../../hooks/useTheme"; // importing the custom hook that provides the current theme and theme toggle function 2 | import './styles.scss'; // importing the component's styles 3 | 4 | // Defining the component function 5 | export function ToggleButton() { 6 | const { theme, themeToggle } = useTheme(); // calling the useTheme hook to get the current theme and theme toggle function 7 | 8 | // Returning the JSX for the toggle button 9 | return ( 10 |
11 | { // Using a ternary operator to render different JSX based on the current theme 12 | theme === "dark-mode" ? // if the current theme is "dark-mode" 13 | // render an input element with the "checked" attribute set to true 14 | : 15 | // otherwise, render an input element with the "checked" attribute set to false 16 | } 17 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/ToggleButton/styles.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | /** sunny side **/ 3 | --blue-background: #c2e9f6; 4 | --blue-border: #72cce3; 5 | --blue-color: #96dcee; 6 | --yellow-background: #fffaa8; 7 | --yellow-border: #f5eb71; 8 | /** dark side **/ 9 | --indigo-background: #221b41; 10 | --indigo-border: #47396d; 11 | --indigo-color: #6b7abb; 12 | --gray-border: rgb(177, 177, 177); 13 | --gray-dots: #ebe5e6; 14 | } 15 | 16 | @keyframes reverse { 17 | 0% { 18 | left: 25px; 19 | width: 20px; 20 | } 21 | 60% { 22 | left: 3px; 23 | width: 40px; 24 | } 25 | 100% { 26 | left: 3px; 27 | } 28 | } 29 | 30 | @keyframes switch { 31 | 0% { 32 | left: 3px; 33 | } 34 | 60% { 35 | left: 3px; 36 | width: 40px; 37 | } 38 | 100% { 39 | left: 25px; 40 | width: 20px; 41 | } 42 | } 43 | 44 | .toggle-content { 45 | .toggle-checkbox { 46 | display: none; 47 | } 48 | 49 | /* background */ 50 | .toggle-label { 51 | width: 3rem; 52 | height: 1.5rem; 53 | background: var(--toggle); 54 | border-radius: 0.75rem; 55 | display: flex; 56 | position: relative; 57 | transition: all 350ms ease-in; 58 | } 59 | 60 | .toggle-checkbox:checked + .toggle-label { 61 | background: var(--toggle-gradient); 62 | } 63 | 64 | .toggle-checkbox:checked + .toggle-label:before { 65 | animation-name: reverse; 66 | animation-duration: 350ms; 67 | animation-fill-mode: forwards; 68 | transition: all 360ms ease-in; 69 | background: var(--card-accent); 70 | } 71 | 72 | .toggle-label:before { 73 | animation-name: switch; 74 | animation-duration: 350ms; 75 | animation-fill-mode: forwards; 76 | content: ""; 77 | width: 1.2rem; 78 | height: 1.2rem; 79 | top: 0.13rem; 80 | position: absolute; 81 | border-radius: 1.2rem; 82 | background: var(--card); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/contexts/themeContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, ReactNode } from 'react'; // Import necessary dependencies 2 | import { useLocalStorage } from '../hooks/useLocalStorage'; // Import custom hook for handling local storage 3 | 4 | type ThemeContextData = { 5 | theme: string; 6 | themeToggle: () => void; 7 | } 8 | 9 | // Create a new context with the default value being an empty object 10 | export const ThemeContext = createContext({} as ThemeContextData); 11 | 12 | type ThemeContextProviderProps = { 13 | children: ReactNode; 14 | } 15 | 16 | // Create a new component to provide the theme context 17 | export function ThemeContextProvider({ children }: ThemeContextProviderProps) { 18 | // Use the custom hook to get and set the theme in local storage 19 | const [theme, setTheme] = useLocalStorage('theme', 'dark-mode'); 20 | 21 | // Create a function to toggle the theme 22 | function themeToggle() { 23 | if (theme === 'dark-mode') { 24 | setTheme('light-mode'); 25 | } else { 26 | setTheme('dark-mode'); 27 | } 28 | } 29 | 30 | // Render the context provider and pass in the current theme and the toggle function as context value 31 | return ( 32 | 35 | {children} 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/hooks/useLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | // Custom hook that allows you to store data in local storage 4 | export function useLocalStorage(key: string, initialValue: string) { 5 | 6 | // Use the useState hook to define a state variable that holds the current value of the item 7 | const [storedValue, setStoredValue] = useState(() => { 8 | try { 9 | // Get the value from local storage using the specified key 10 | const item = window.localStorage.getItem(key); 11 | // If the item exists, parse it from JSON and return it. Otherwise, return the initial value passed to the hook 12 | return item ? JSON.parse(item) : initialValue; 13 | } catch (error) { 14 | // If there's an error, log it and return the initial value 15 | console.log(error); 16 | return initialValue; 17 | } 18 | }); 19 | 20 | // Define a function to update the value stored in local storage 21 | const setValue = (value: any) => { 22 | try { 23 | // If the new value is a function, call it with the current value of the item to get the new value 24 | const valueToStore = 25 | value instanceof Function ? value(storedValue) : value; 26 | // Update the state variable with the new value 27 | setStoredValue(valueToStore); 28 | // Store the new value in local storage using the specified key 29 | window.localStorage.setItem(key, JSON.stringify(valueToStore)); 30 | } catch (error) { 31 | // If there's an error, log it 32 | console.log(error); 33 | } 34 | }; 35 | 36 | // Return an array containing the current value of the item and the function to update it 37 | return [storedValue, setValue]; 38 | } 39 | -------------------------------------------------------------------------------- /src/hooks/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { ThemeContext } from '../contexts/themeContext'; 3 | 4 | // This custom hook provides access to the theme and themeToggle function 5 | const useTheme = () => { 6 | // Use the useContext hook to get the current ThemeContext 7 | const context = useContext(ThemeContext); 8 | 9 | // If the context is undefined (i.e. it hasn't been provided by a parent component), 10 | // throw an error 11 | if (!context) { 12 | throw new Error('Nenhum contexto encontrado'); 13 | } 14 | 15 | // Otherwise, return the context object (which includes the theme and themeToggle function) 16 | return context; 17 | } 18 | 19 | // Export the custom hook as the default export of this module 20 | export default useTheme; 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | import { ThemeContextProvider } from './contexts/themeContext'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | -------------------------------------------------------------------------------- /src/pages/Dashboard/index.ts: -------------------------------------------------------------------------------- 1 | import { FollowersCard } from "../../components/FollowersCard"; 2 | import { Header } from "../../components/Header"; 3 | import { OverviewCard } from "../../components/OverviewCard"; 4 | import { useEffect, useState } from "react"; 5 | import { getDataFake, getFakeOverviewData } from "../../api/api"; 6 | import { dataFake, fakeOverviewData } from "../../api/api"; 7 | import "./styles.scss"; 8 | 9 | export function Dashboard() { 10 | const [data, setData] = useState([]); // State for followers data 11 | const [dataOverview, setDataOverview] = useState([]); // State for overview data 12 | 13 | useEffect(() => { 14 | async function fetchData() { 15 | const fetchedData = await getDataFake(); // Fetch followers data 16 | const fetchedDataOverview = await getFakeOverviewData(); // Fetch overview data 17 | setData(fetchedData); 18 | setDataOverview(fetchedDataOverview); 19 | } 20 | fetchData(); 21 | 22 | // Set up an interval to refetch data every 5 seconds 23 | const interval = setInterval(() => { 24 | fetchData(); 25 | }, 5000); 26 | 27 | // Clean up the interval when the component unmounts 28 | return () => clearInterval(interval); 29 | }, []); 30 | 31 | return ( 32 |
33 | {/* Render the header component, passing the first follower data as props */} 34 |
35 | 36 |
37 | {data.map((item) => ( 38 | // Render the FollowersCard component for each follower data, passing it as props along with the follower's border theme and ID 39 | ))} 40 |
41 | 42 |

Overview - Today

43 | 44 |
45 | {dataOverview.map((item) => ( 46 | // Render the OverviewCard component for each overview data, passing it as props along with the ID 47 | ))} 48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/pages/Dashboard/styles.scss: -------------------------------------------------------------------------------- 1 | #dashboard-container { 2 | width: 80%; 3 | position: absolute; 4 | margin: auto; 5 | top: 0; 6 | right: 0; 7 | left: 0; 8 | 9 | padding-top: 2rem; 10 | 11 | #followers-container { 12 | width: 100%; 13 | height: 13.5rem; 14 | 15 | margin-top: 3rem; 16 | 17 | display: grid; 18 | grid-template-columns: 1fr 1fr 1fr 1fr; 19 | grid-column-gap: 1.875rem; 20 | } 21 | 22 | #overview { 23 | color: var(--text); 24 | margin-top: 2rem; 25 | 26 | margin-bottom: 1rem; 27 | } 28 | 29 | #overview-container { 30 | width: 100%; 31 | height: 17rem; 32 | 33 | display: grid; 34 | grid-template-columns: 1fr 1fr 1fr 1fr; 35 | grid-template-rows: 1fr 1fr; 36 | grid-column-gap: 1.875rem; 37 | grid-row-gap: 1.5rem; 38 | } 39 | 40 | @media (max-width: 375px) { 41 | width: 90%; 42 | 43 | #followers-container { 44 | height: 58.5rem; 45 | 46 | grid-template-columns: 1fr; 47 | grid-template-rows: 1fr 1fr 1fr 1fr; 48 | 49 | grid-row-gap: 1.5rem; 50 | 51 | margin-top: 2.875rem; 52 | } 53 | 54 | #overview-container { 55 | height: 69.5rem; 56 | 57 | grid-template-columns: 1fr; 58 | grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; 59 | 60 | grid-row-gap: 1.5rem; 61 | } 62 | } 63 | 64 | @media (min-width: 376px) and (max-width: 860px) { 65 | width: 90%; 66 | 67 | #followers-container { 68 | height: 30rem; 69 | 70 | grid-template-columns: 1fr 1fr; 71 | grid-template-rows: 1fr 1fr; 72 | 73 | grid-row-gap: 1.5rem; 74 | 75 | margin-top: 2.875rem; 76 | } 77 | 78 | #overview-container { 79 | height: 30rem; 80 | 81 | grid-template-columns: 1fr 1fr; 82 | grid-template-rows: 1fr 1fr 1fr 1fr; 83 | 84 | grid-row-gap: 1.5rem; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Routes } from 'react-router-dom'; 2 | import { Dashboard } from '../pages/Dashboard'; 3 | 4 | export function Router() { 5 | return ( 6 | 7 | {/* Declare all your routes inside a `Routes` component */} 8 | 9 | {/* Each `Route` component defines a mapping between a URL and a component */} 10 | {/* Here, we are mapping the root URL to the `Dashboard` component */} 11 | } /> 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | #container-primary { 2 | width: 100vw; 3 | height: 50rem; 4 | 5 | background: var(--bg); 6 | 7 | #header-top { 8 | width: 100%; 9 | height: 15rem; 10 | position: relative; 11 | background: var(--bg-accent); 12 | 13 | border-bottom-left-radius: 1rem; 14 | border-bottom-right-radius: 1rem; 15 | } 16 | 17 | @media (max-width: 375px) { 18 | height: 149.1875rem; 19 | } 20 | 21 | @media (min-width: 376px) and (max-width: 860px) { 22 | height: 80rem; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | --lime-green: hsl(163, 72%, 41%); 9 | --bright-red: hsl(356, 69%, 56%); 10 | 11 | --facebook: hsl(208, 92%, 53%); 12 | --twitter: hsl(203, 89%, 53%); 13 | // --instagram: linear-gradient(hsl(37, 97%, 70%), hsl(329, 70%, 58%)) 1; 14 | --youTube: hsl(348, 97%, 39%); 15 | 16 | --toggle-gradient: linear-gradient(hsl(210, 78%, 56%), hsl(146, 68%, 55%)); 17 | 18 | --toggle: hsl(230, 22%, 74%); 19 | 20 | // Estilização do mode Dark 21 | 22 | .dark-mode { 23 | --bg: hsl(230, 17%, 14%); 24 | --bg-accent: hsl(232, 19%, 15%); 25 | 26 | --card: hsl(228, 28%, 20%); 27 | --card-accent: hsl(228, 26%, 27%); 28 | 29 | --text: hsl(0, 0%, 100%); 30 | --accent-text: hsl(228, 34%, 66%); 31 | } 32 | 33 | // Estilização do Modo Light 34 | .light-mode { 35 | --bg: hsl(0, 0%, 100%); 36 | --bg-accent: hsl(225, 100%, 98%); 37 | 38 | --card: hsl(227, 47%, 96%); 39 | --card-accent: hsl(232, 33%, 91%); 40 | 41 | --text: hsl(230, 17%, 14%); 42 | --accent-text: hsl(228, 12%, 44%); 43 | } 44 | } 45 | h1, 46 | h2, 47 | span, 48 | strong { 49 | font-family: Inter, sans-serif; 50 | } 51 | 52 | body { 53 | overflow-x: hidden; 54 | } 55 | 56 | * { 57 | scrollbar-width: thin; 58 | scrollbar-color: black yellow; 59 | } 60 | 61 | /* Works on Chrome, Edge, and Safari */ 62 | *::-webkit-scrollbar { 63 | width: 0.8rem; 64 | } 65 | 66 | *::-webkit-scrollbar-track { 67 | background: transparent; 68 | } 69 | 70 | *::-webkit-scrollbar-thumb { 71 | background-color: rgb(192, 192, 192); 72 | border-radius: 20px; 73 | border: 3px solid transparent; 74 | } 75 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": false, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "strictNullChecks": true, 23 | "noImplicitAny": false, 24 | "strictFunctionTypes": false, 25 | }, 26 | "include": [ 27 | "src" 28 | ] 29 | } 30 | --------------------------------------------------------------------------------