diff --git a/package-lock.json b/package-lock.json index d47892b..da7863e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@types/axios": "^0.14.4", "@types/dotenv": "^8.2.3", + "@types/react-helmet": "^6.1.11", "@types/react-icons": "^3.0.0", "@types/react-router-dom": "^5.3.3", "axios": "^1.7.7", @@ -17,6 +18,7 @@ "dotenv": "^16.4.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", "react-icons": "^5.3.0", "react-markdown": "^9.0.1", "react-router-dom": "^6.28.0", @@ -2104,6 +2106,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-helmet": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz", + "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-icons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz", @@ -10253,7 +10263,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10962,6 +10971,16 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/property-information": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", @@ -11058,6 +11077,25 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, "node_modules/react-icons": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", @@ -11066,6 +11104,11 @@ "react": "*" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react-markdown": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", @@ -11130,6 +11173,14 @@ "react-dom": ">=16.8" } }, + "node_modules/react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index db6e0e9..583c0cb 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { "@types/axios": "^0.14.4", "@types/dotenv": "^8.2.3", + "@types/react-helmet": "^6.1.11", "@types/react-icons": "^3.0.0", "@types/react-router-dom": "^5.3.3", "axios": "^1.7.7", @@ -24,6 +25,7 @@ "dotenv": "^16.4.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", "react-icons": "^5.3.0", "react-markdown": "^9.0.1", "react-router-dom": "^6.28.0", diff --git a/public/og-dl.png b/public/og-dl.png new file mode 100644 index 0000000..fc8fa79 Binary files /dev/null and b/public/og-dl.png differ diff --git a/public/ring-resize.svg b/public/ring-resize.svg new file mode 100644 index 0000000..951875e --- /dev/null +++ b/public/ring-resize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/search.gif b/public/search.gif new file mode 100644 index 0000000..b251925 Binary files /dev/null and b/public/search.gif differ diff --git a/src/App.tsx b/src/App.tsx index 92b0fbb..ca5108d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,25 +1,17 @@ -import { ThemeProvider } from './providers/ThemeProvider'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; -import AppContainer from './hoc/AppContainer'; import Home from './views/Home'; import Results from './views/Results'; -import { HistoryProvider } from './providers/HistoryProvider'; -import HistoryModal from './components/HistoryModal'; +import Layout from './hoc/Layout'; const App = () => { return ( - - - - - } /> - } /> - - - - - + + + } /> + } /> + + ); }; diff --git a/src/constants/meta.ts b/src/constants/meta.ts new file mode 100644 index 0000000..057e518 --- /dev/null +++ b/src/constants/meta.ts @@ -0,0 +1,4 @@ +export const BASE_URL = 'https://domain-lookup.nikola-nenovski.info'; +export const DESCRIPTION = + 'A single-page application for fetching DNS, SSL, WHOIS data, and other useful information for a domain, such as active CDN and WordPress instances.'; +export const TYPE = 'website'; diff --git a/src/hoc/Layout.tsx b/src/hoc/Layout.tsx new file mode 100644 index 0000000..5bc0082 --- /dev/null +++ b/src/hoc/Layout.tsx @@ -0,0 +1,18 @@ +import React, { ReactNode } from 'react'; +import AppContainer from './AppContainer'; +import { HistoryProvider } from '../providers/HistoryProvider'; +import { ThemeProvider } from '../providers/ThemeProvider'; +import HistoryModal from '../components/HistoryModal'; + +const Layout: React.FC<{ children: ReactNode }> = ({ children }) => { + return ( + + + {children} + + + + ); +}; + +export default Layout; diff --git a/src/hoc/Meta.tsx b/src/hoc/Meta.tsx new file mode 100644 index 0000000..4f28a30 --- /dev/null +++ b/src/hoc/Meta.tsx @@ -0,0 +1,53 @@ +import { Helmet } from 'react-helmet'; +import { useLocation, useSearchParams } from 'react-router-dom'; +import { BASE_URL, DESCRIPTION, TYPE } from '../constants/meta'; +import { useEffect, useState } from 'react'; + +const spinnerFrames = ['◐', '◓', '◑', '◒']; + +const Meta = ({ loading }: { loading?: boolean }) => { + const [searchParams, _setSearchParams] = useSearchParams(); + const { pathname, search } = useLocation(); + const [currentSpinnerFrame, setCurrentSpinnerFrame] = useState( + () => spinnerFrames[0] + ); + + const domain = searchParams.get('domain'); + + const title = `${loading ? currentSpinnerFrame + ' - ' : ''} ${ + domain ? `${domain} - ` : '' + }DomainLookup`; + const currentUrl = BASE_URL + pathname + search; + + useEffect(() => { + if (!loading) return; + const spinnerInterval = setInterval(() => { + setCurrentSpinnerFrame(prev => { + const nextIndex = + (spinnerFrames.indexOf(prev) + 1) % spinnerFrames.length; + + console.log(nextIndex); + return spinnerFrames[nextIndex]; + }); + }, 200); + + return () => clearInterval(spinnerInterval); + }, [loading]); + + return ( + + + + + {title} + + + + + + + + ); +}; + +export default Meta; diff --git a/src/views/Home.tsx b/src/views/Home.tsx index 0dc2777..d59f557 100644 --- a/src/views/Home.tsx +++ b/src/views/Home.tsx @@ -4,6 +4,7 @@ import SearchForm from '../components/SearchForm'; import Logo from '../components/Logo'; import { useSearchParams } from 'react-router-dom'; import { useEffect } from 'react'; +import Meta from '../hoc/Meta'; const Home = () => { const [_searchParams, setSearchParams] = useSearchParams(); @@ -14,6 +15,7 @@ const Home = () => { return ( <> + diff --git a/src/views/Results.tsx b/src/views/Results.tsx index 5de513c..e227216 100644 --- a/src/views/Results.tsx +++ b/src/views/Results.tsx @@ -17,6 +17,7 @@ import { H1 } from '../hoc/H1'; import Markers from '../components/Markers'; import { isWordpressInstalled } from '../services/wpCheck.service'; import { isCdnActive } from '../services/cdnCheck.service'; +import Meta from '../hoc/Meta'; interface WhoIsData { result?: Record; @@ -42,6 +43,7 @@ const Results = () => { ); }, [sslData, searchParams]); + const [isLoading, setIsLoading] = useState(false); const [isWhoIsLoading, setIsWhoisLoading] = useState(false); const [isDnsLoading, setIsDnsLoading] = useState(false); const [isSslLoading, setIsSslLoading] = useState(false); @@ -51,7 +53,7 @@ const Results = () => { useEffect(() => { const domain = searchParams.get('domain'); if (!domain) return; - + setIsLoading(true); setIsWhoisLoading(true); setIsSslLoading(true); setIsDnsLoading(true); @@ -89,6 +91,7 @@ const Results = () => { ]; await Promise.all(tasks); + setIsLoading(false); }; fetchData(); @@ -99,6 +102,7 @@ const Results = () => { return ( <> +