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 (
<>
+