├── .gitignore ├── LICENSE ├── README.md ├── functions └── update.js ├── package-lock.json ├── package.json ├── public ├── _redirects ├── apple-touch-icon.png ├── arrowdown.svg ├── contact-mail.svg ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── google-2.svg ├── google-21.svg ├── icon.svg ├── icon1.svg ├── icon2.svg ├── icon3.svg ├── iconoutlinearrowright.svg ├── iconoutlinearrowright1.svg ├── iconoutlinearrowright5.svg ├── iconoutlinearrowright6.svg ├── iconoutlineplay.svg ├── icons.svg ├── illustration.svg ├── image-1@2x.png ├── index.html ├── left-icon.svg ├── lines.svg ├── logo.svg ├── logo192.png ├── logo512.png ├── logofbsimple-2.svg ├── logogithub-1.svg ├── logoinstagram-1.svg ├── logotwitter-2.svg ├── manifest.json ├── morevertical.svg ├── og.png ├── robots.txt ├── send@2x.png ├── vector.svg └── vector1.svg └── src ├── App.js ├── components ├── EmailsTable.js ├── Features.js ├── Footer.js ├── Hero.js ├── Input.js ├── PopUp.js ├── ProtectedDashboard.js ├── Sidebar.js └── Spinner.js ├── index.js ├── pages ├── Home.js ├── Overview.js ├── PageNotFound.js ├── Profile.js ├── SignIn.js └── SignUp.js └── styles ├── components ├── Input.module.css ├── Popup.module.css └── Spinner.module.css ├── global.css └── pages ├── Dashboard.module.css ├── Home.module.css ├── PageNotFound.module.css ├── Profile.module.css ├── SignIn.module.css └── SignUp.module.css /.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* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Aashish Panthi 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mailsbe 2 | 3 | **Suppose**, you have applied for a job through an email. You have sent the email two days ago but you haven't gotten any response. And you don't know if the employer read your email or not. Well no need to worry now because I have a solution for that, **introducing Mailsbe**. 4 | 5 | ## Introduction 6 | 7 | **Mailsbe** is an MIT Licensed open source project and a platform with the help of which you can find out if the Email sent by you has **been read or not**. It is a simple platform that has been designed by keeping the **user's privacy **in mind. We don't ask for full email read/write access. Instead, we provide a simple way to find the status if your email **respecting your privacy**. 8 | 9 | ## Tech stack 10 | 11 | It doesn't involve a very much complicated tech stack. Instead, it uses only a few technologies like React and Nhost. 12 | 13 | - **React** -> For frontend part 14 | - **Nhost** -> For user authentication, database, hosting and serverless function 15 | 16 | > If you don't know about Nhost then simply put it as a firebase alternative. Even officially, Nhost is called an open-source Firebase alternative with GraphQL. 17 | 18 | ## Process 19 | 20 | I am not going to explain all of the technical jargon here, instead, I will take you through the working process of the project. 21 | 22 | First of all, you need to create an account. Then only you can access your dashboard. Inside of your dashboard, you will see the list of emails you have sent and complete detail about them. If you haven't sent any emails then you can send one by clicking on compose button on the top left side of the page. 23 | 24 | No, you can't send emails from here. Instead, you will be provided an image (1x1 transparent pixel) which you can copy and paste to the email client from where you are sending the email. Then fill out some more information about the email (it will make it easy to find the email in the future) and click save. 25 | 26 | After you sent your email, you can now access the status of the email on the dashboard. The status is either _seen_ or _unseen_. The status will be updated when the receiver opens up the email you sent. And the serverless function helps to do that by accepting the receiver's request and updating it on the database. 27 | 28 | > This detailed guide will help you: https://blog.aashish-panthi.com.np/make-an-email-tracker-using-nhost-serverless-functions 29 | -------------------------------------------------------------------------------- /functions/update.js: -------------------------------------------------------------------------------- 1 | import { NhostClient } from "@nhost/nhost-js"; 2 | 3 | const accessToken = process.env.NHOST_ADMIN_SECRET; 4 | const backendUrl = process.env.NHOST_BACKEND_URL; 5 | 6 | const nhost = new NhostClient({ 7 | backendUrl: backendUrl, 8 | }); 9 | 10 | nhost.graphql.setAccessToken(accessToken); 11 | 12 | export default async (req, res) => { 13 | // get the data from the request 14 | const imgText = req.query.text; 15 | console.log("imgText", imgText); 16 | 17 | if (!imgText) { 18 | return res.status(500).json({ error: "No image token provided" }); 19 | } 20 | 21 | // make a get query to get email id using imgText 22 | const GET_EMAIL_ID = ` 23 | query getId($text: String!) { 24 | emails(where: {img_text: {_eq: $text}}) { 25 | id 26 | seen 27 | } 28 | }`; 29 | 30 | // update query with the email id 31 | const UPDATE_QUERY = ` 32 | mutation UpdateEmail($id: Int!, $date: timestamptz!) { 33 | update_emails(where: {id: {_eq: $id}}, _set: {seen: true, seen_at: $date}) { 34 | affected_rows 35 | } 36 | }`; 37 | 38 | try { 39 | const { data, error } = await nhost.graphql.request(GET_EMAIL_ID, { 40 | text: imgText, 41 | }); 42 | 43 | if (error) { 44 | return res.status(500).json({ error: error.message }); 45 | } 46 | 47 | if (!data) { 48 | return res.status(500).json({ error: "No email found" }); 49 | } 50 | 51 | // extract the email id from the response 52 | const emailId = data.emails[0].id; 53 | const seen = data.emails[0].seen; 54 | 55 | if (seen) { 56 | return res.status(500).json({ error: "Kaam hogaya vai" }); 57 | } 58 | 59 | //update the seen column in emails table 60 | const { data: updatedData, error: updateError } = 61 | await nhost.graphql.request(UPDATE_QUERY, { 62 | id: emailId, 63 | date: new Date(), 64 | }); 65 | 66 | if (updateError) { 67 | return res.status(500).json({ error: error.message }); 68 | } 69 | 70 | res.status(404).send({ error: "Bye bye" }); 71 | } catch (error) { 72 | console.log(error); 73 | res.status(500).json({ error }); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mailsbe", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.7.0", 7 | "@emotion/react": "^11.10.4", 8 | "@emotion/styled": "^11.10.4", 9 | "@headlessui/react": "^1.7.3", 10 | "@heroicons/react": "^1.0.6", 11 | "@mui/icons-material": "^5.10.9", 12 | "@mui/lab": "^5.0.0-alpha.103", 13 | "@mui/material": "^5.10.9", 14 | "@mui/styled-engine": "^5.10.8", 15 | "@nhost/react": "^0.13.0", 16 | "@nhost/react-apollo": "^4.7.6", 17 | "@testing-library/jest-dom": "^5.16.5", 18 | "@testing-library/react": "^13.4.0", 19 | "@testing-library/user-event": "^13.5.0", 20 | "@types/jest": "^27.0.3", 21 | "@types/node": "^16.11.14", 22 | "@types/react": "^18.0.17", 23 | "@types/react-dom": "^18.0.6", 24 | "classnames": "^2.3.2", 25 | "express": "^4.18.2", 26 | "graphql": "^16.6.0", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-helmet": "^6.1.0", 30 | "react-hot-toast": "^2.4.0", 31 | "react-router-dom": "^6.4.2", 32 | "react-scripts": "^5.0.1", 33 | "web-vitals": "^2.1.4" 34 | }, 35 | "scripts": { 36 | "start": "react-scripts start", 37 | "build": "react-scripts build", 38 | "test": "react-scripts test", 39 | "eject": "react-scripts eject" 40 | }, 41 | "eslintConfig": { 42 | "extends": [ 43 | "react-app", 44 | "react-app/jest" 45 | ] 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/arrowdown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/contact-mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/favicon.ico -------------------------------------------------------------------------------- /public/google-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/google-21.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icon1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icon2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icon3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/iconoutlinearrowright.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/iconoutlinearrowright1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/iconoutlinearrowright5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/iconoutlinearrowright6.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/iconoutlineplay.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/image-1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/image-1@2x.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Mailsbe, your email partner 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /public/left-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/lines.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/logo512.png -------------------------------------------------------------------------------- /public/logofbsimple-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logogithub-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logoinstagram-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logotwitter-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mailsbe", 3 | "name": "Email status", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/morevertical.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/og.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/send@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aashishpanthi/mailsbe/9ea788a17fa916381ea4206cb22c0f127348b001/public/send@2x.png -------------------------------------------------------------------------------- /public/vector.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vector1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 2 | import { Toaster } from "react-hot-toast"; 3 | import { NhostClient, NhostReactProvider } from "@nhost/react"; 4 | import { NhostApolloProvider } from "@nhost/react-apollo"; 5 | 6 | import SignUp from "./pages/SignUp"; 7 | import SignIn from "./pages/SignIn"; 8 | import Home from "./pages/Home"; 9 | import Profile from "./pages/Profile"; 10 | import PageNotFound from "./pages/PageNotFound"; 11 | import ProtectedDashboard from "./components/ProtectedDashboard"; 12 | import Overview from "./pages/Overview"; 13 | 14 | const App = () => { 15 | const nhost = new NhostClient({ 16 | subdomain: process.env.REACT_APP_NHOST_SUBDOMAIN, 17 | region: process.env.REACT_APP_NHOST_REGION, 18 | }); 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | } /> 26 | } /> 27 | } /> 28 | 29 | }> 30 | } /> 31 | } /> 32 | 33 | } /> 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | }; 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /src/components/EmailsTable.js: -------------------------------------------------------------------------------- 1 | import { Delete } from "@mui/icons-material"; 2 | import { IconButton } from "@mui/material"; 3 | import { gql, useLazyQuery, useMutation } from "@apollo/client"; 4 | import { useUserData } from "@nhost/react"; 5 | import { CircularProgress } from "@mui/material"; 6 | import toast from "react-hot-toast"; 7 | import { useEffect, useState } from "react"; 8 | 9 | const GET_EMAILS = gql` 10 | query getEmails($user: String) { 11 | emails(order_by: { created_at: desc }, where: { user: { _eq: $user } }) { 12 | created_at 13 | description 14 | email 15 | id 16 | img_text 17 | seen 18 | seen_at 19 | } 20 | } 21 | `; 22 | 23 | const DELETE_EMAIL = gql` 24 | mutation deleteEmail($id: Int) { 25 | delete_emails(where: { id: { _eq: $id } }) { 26 | affected_rows 27 | } 28 | } 29 | `; 30 | 31 | const EmailsTable = ({ styles }) => { 32 | const user = useUserData(); 33 | const [emails, setEmails] = useState([]); 34 | 35 | const [getEmails, { loading, error, data }] = useLazyQuery(GET_EMAILS, { 36 | variables: { user: user.id }, 37 | }); 38 | 39 | const [deleteTodo, { loading: deleting, error: deleteError }] = 40 | useMutation(DELETE_EMAIL); 41 | 42 | const fetchEmails = async () => { 43 | try { 44 | await getEmails({ 45 | variables: { user: user.id }, 46 | }); 47 | 48 | // toast.success("Emails fetched successfully"); 49 | setEmails(data.emails); 50 | } catch (err) { 51 | console.log(err); 52 | } 53 | }; 54 | 55 | useEffect(() => { 56 | fetchEmails(); 57 | }, [data, user]); 58 | 59 | const deleteEmail = async (id) => { 60 | const confirmation = window.confirm( 61 | "Are you sure you want to delete this?" 62 | ); 63 | if (!confirmation) { 64 | return; 65 | } 66 | 67 | try { 68 | await deleteTodo({ 69 | variables: { 70 | id: id, 71 | }, 72 | }); 73 | 74 | toast.success("Email deleted successfully"); 75 | window.location.reload(); 76 | } catch (err) { 77 | toast.error("Unable to delete email"); 78 | } 79 | }; 80 | 81 | if (loading) { 82 | return ( 83 |
84 | 85 |
86 | ); 87 | } 88 | 89 | if (emails.length === 0) { 90 | return
No emails found
; 91 | } 92 | 93 | return ( 94 |
95 |
96 |
97 |
98 |
Email
99 |
100 |
101 | {emails.map((email) => ( 102 |
103 |
{email.email}
104 |
105 | ))} 106 |
107 |
108 |
109 |
110 |
Status
111 |
112 |
113 | {emails.map(({ seen, id }) => ( 114 |
115 |
116 |
117 |
118 | {seen ? `Seen` : `Unseen`} 119 |
120 |
121 |
122 |
123 | ))} 124 |
125 |
126 |
127 |
128 |
Description
129 |
130 |
131 | {emails.map(({ description, id }) => ( 132 |
133 |
{description}
134 |
135 | ))} 136 |
137 |
138 |
139 |
140 |
Date sent
141 |
142 |
143 | 144 | {emails.map(({ created_at, id }) => ( 145 |
146 |
147 | {new Date(created_at).toLocaleString()} 148 |
149 |
150 | ))} 151 |
152 |
153 |
154 |
155 |
Date seen
156 |
157 |
158 | {emails.map(({ seen_at, id, seen }) => ( 159 |
160 |
161 | {seen ? new Date(seen_at).toLocaleString() : "Not seen"} 162 |
163 |
164 | ))} 165 |
166 |
167 |
168 | {emails.map(({ id }) => ( 169 |
170 | deleteEmail(id)}> 171 | 172 | 173 |
174 | ))} 175 |
176 |
177 | ); 178 | }; 179 | 180 | export default EmailsTable; 181 | -------------------------------------------------------------------------------- /src/components/Features.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Features({ styles }) { 4 | return ( 5 |
6 |
7 | Use email like a geek 8 |
9 | Explore the unexplored potential of email 10 |
11 |
12 |
13 |
14 | 15 | Send 16 |
17 | Compose a new email with pixel tracking enabled. 18 |
19 |
20 |
21 | 22 | Status 23 |
24 | Know if the user read the email or not. 25 |
26 |
27 |
28 | 29 | History 30 |
31 | Find out exactly when the receiver read your mail. 32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | 39 | export default Features; 40 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Footer({ styles }) { 4 | return ( 5 | 109 | ); 110 | } 111 | 112 | export default Footer; 113 | -------------------------------------------------------------------------------- /src/components/Hero.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | function Hero({ styles }) { 5 | const handleScroll = () => { 6 | const videoContainer = document.getElementById("video-container"); 7 | videoContainer.scrollIntoView({ behavior: "smooth" }); 8 | }; 9 | 10 | return ( 11 |
12 | 34 |
35 |
36 | Smart email status finder, made for Everyone 37 |
38 |
39 | Know if your email has been read 40 |
41 |
42 | 43 |
44 | Get started now 45 |
46 | 51 |
52 |
53 | 54 | 70 |
71 |
72 | Create your account. For free of cost 73 |
74 |
75 |
76 | ); 77 | } 78 | 79 | export default Hero; 80 | -------------------------------------------------------------------------------- /src/components/Input.js: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/Input.module.css'; 2 | 3 | const Input = ({ type = 'text', label = '', ...props }) => { 4 | return ( 5 |
6 | {label ? : null} 7 | 8 |
9 | ); 10 | }; 11 | 12 | export default Input; 13 | -------------------------------------------------------------------------------- /src/components/PopUp.js: -------------------------------------------------------------------------------- 1 | import { 2 | TextField, 3 | Typography, 4 | IconButton, 5 | FormHelperText, 6 | FormControl, 7 | } from "@mui/material"; 8 | import LoadingButton from "@mui/lab/LoadingButton"; 9 | import SaveIcon from "@mui/icons-material/Save"; 10 | import HighlightOffIcon from "@mui/icons-material/HighlightOff"; 11 | import toast from "react-hot-toast"; 12 | import { useUserData } from "@nhost/react"; 13 | 14 | import styles from "../styles/components/Popup.module.css"; 15 | import { useState, useEffect, useRef } from "react"; 16 | import { gql, useMutation } from "@apollo/client"; 17 | 18 | const ADD_EMAIL = gql` 19 | mutation addEmail( 20 | $email: String 21 | $description: String 22 | $img_text: String 23 | $user: String 24 | ) { 25 | insert_emails( 26 | objects: { 27 | description: $description 28 | email: $email 29 | img_text: $img_text 30 | user: $user 31 | } 32 | ) { 33 | affected_rows 34 | } 35 | } 36 | `; 37 | 38 | const PopUp = ({ setPopUp }) => { 39 | //get the user data 40 | const user = useUserData(); 41 | 42 | const [email, setEmail] = useState(""); 43 | const [description, setDescription] = useState(""); 44 | const [name, setName] = useState(user.displayName); 45 | const [imgText, setImgText] = useState(""); 46 | 47 | const [addEmail, { data, loading, error }] = useMutation(ADD_EMAIL); 48 | 49 | const ref = useRef(); 50 | 51 | const handleSubmit = async (e) => { 52 | e.preventDefault(); 53 | 54 | try { 55 | await addEmail({ 56 | variables: { 57 | email: email, 58 | description: description, 59 | img_text: imgText.split("=")[1], 60 | user: user.id, 61 | }, 62 | }); 63 | toast.success("Email added successfully"); 64 | setPopUp(false); 65 | window.location.reload(); 66 | } catch (err) { 67 | toast.error("Unable to add email"); 68 | } 69 | }; 70 | 71 | useEffect(() => { 72 | const time = new Date().getTime(); 73 | setImgText( 74 | `https://tkjfsvqlulofoefmacvj.nhost.run/v1/functions/update?text=${time}` 75 | ); 76 | }, []); 77 | 78 | return ( 79 |
80 |
81 |
82 | 83 | Enter new email details 84 | 85 | 86 | setPopUp(false)}> 87 | 88 | 89 |
90 |
91 | 92 | setEmail(e.target.value)} 105 | /> 106 | setDescription(e.target.value)} 118 | /> 119 | 120 | setName(e.target.value)} 130 | /> 131 | 132 |
133 |
134 | {name && name.substring(0, 1)} 135 | 141 | {name && name.substring(1, name.length)} 142 |
143 | 144 | Copy this text and paste it in the email.{" "} 145 | Imp: Don't erase it after pasting. 146 | 147 |
148 | 149 | {error && ( 150 | {`Error occured! ${error.message}`} 151 | )} 152 | 153 | } 158 | size="large" 159 | fullWidth 160 | type="submit" 161 | loading={loading} 162 | > 163 | Save 164 | 165 |
166 |
167 |
168 |
169 | ); 170 | }; 171 | 172 | export default PopUp; 173 | -------------------------------------------------------------------------------- /src/components/ProtectedDashboard.js: -------------------------------------------------------------------------------- 1 | import Sidebar from "../components/Sidebar"; 2 | import { Outlet, Navigate, useLocation } from "react-router-dom"; 3 | import styles from "../styles/pages/Dashboard.module.css"; 4 | import { useAuthenticationStatus, useUserData } from "@nhost/react"; 5 | import Spinner from "./Spinner"; 6 | import { useState } from "react"; 7 | import PopUp from "./PopUp"; 8 | 9 | function ProtectedDashboard() { 10 | const user = useUserData(); 11 | const { isAuthenticated, isLoading } = useAuthenticationStatus(); 12 | const location = useLocation(); 13 | 14 | const [popUp, setPopUp] = useState(false); 15 | 16 | if (isLoading) { 17 | return ( 18 |
19 | 20 |
21 | ); 22 | } 23 | 24 | if (!isAuthenticated) { 25 | return ; 26 | } 27 | 28 | return ( 29 |
30 | 31 | 32 | {popUp && } 33 |
34 | ); 35 | } 36 | 37 | export default ProtectedDashboard; 38 | -------------------------------------------------------------------------------- /src/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import { Link, useNavigate } from "react-router-dom"; 2 | import { useCallback } from "react"; 3 | import { useSignOut } from "@nhost/react"; 4 | 5 | const Sidebar = ({ styles, user, setPopUp }) => { 6 | const navigate = useNavigate(); 7 | const { signOut } = useSignOut(); 8 | 9 | const onLogOutButtonClick = useCallback(() => { 10 | signOut(); 11 | navigate("/"); 12 | }, [navigate]); 13 | 14 | const name = user?.metadata?.name ? user?.metadata?.name : user.displayName; 15 | const email = user.email; 16 | 17 | const image = user.avatarUrl.includes("gravatar.com") 18 | ? user.avatarUrl 19 | : `https://img.icons8.com/external-linector-lineal-linector/344/external-avatar-man-avatar-linector-lineal-linector-6.png`; 20 | 21 | return ( 22 |
23 | 24 | 29 |
MAILSBE
30 | 31 | 39 |
40 |
Menu
41 |
42 | 43 | 44 |
Overview
45 | 46 |
47 |
48 | 49 | 50 |
Settings
51 | 52 |
53 |
54 |
Profile
55 |
56 | {name.substring(0, 61 |
62 |
{name}
63 |
{email}
64 |
65 |
66 |
67 | 77 |
78 | ); 79 | }; 80 | 81 | export default Sidebar; 82 | -------------------------------------------------------------------------------- /src/components/Spinner.js: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/Spinner.module.css'; 2 | 3 | import classNames from 'classnames'; 4 | 5 | const Spinner = ({ size = '' }) => ( 6 | 11 | ); 12 | 13 | export default Spinner; 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./styles/global.css"; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /src/pages/Home.js: -------------------------------------------------------------------------------- 1 | import styles from "../styles/pages/Home.module.css"; 2 | import Hero from "../components/Hero"; 3 | import Features from "../components/Features"; 4 | import Footer from "../components/Footer"; 5 | import { useAuthenticationStatus } from "@nhost/react"; 6 | import Spinner from "../components/Spinner"; 7 | import { Navigate, useLocation } from "react-router-dom"; 8 | import { Helmet } from "react-helmet"; 9 | 10 | const Home = () => { 11 | const { isAuthenticated, isLoading } = useAuthenticationStatus(); 12 | const location = useLocation(); 13 | 14 | if (isLoading) { 15 | return ( 16 |
17 | 18 |
19 | ); 20 | } 21 | 22 | if (isAuthenticated) { 23 | return ; 24 | } 25 | 26 | return ( 27 | <> 28 | 29 | Mailsbe - Track your emails 30 | 31 | 32 |
33 | 34 |