diff --git a/.env.example b/.env.example index 0424964..3f013e0 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # Copy to ".env" at the repository root (Docker Compose and local tooling load it from here). # Django also loads this file via backend/setrsoft/settings.py (repo root). -#Docker +#Docker (for production) DOCKER_NETWORK_NAME=default # --- PORTS & IP --- diff --git a/README.md b/README.md index 998225a..4bf9c2e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ SetRsoft is an open source community driven software. Test it live on the website http://www.setrsoft.com/ +For better understanding the purpose of this project you can have a look at [one of those videos](https://www.youtube.com/results?search_query=routesetting+in+climbing+gym). + Feel free to create new Github Issues for new features or signal bugs. diff --git a/frontend/index.html b/frontend/index.html index c935043..75faafa 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,7 +8,15 @@ - setrsoft + SetRsoft – Éditeur 3D de blocs d'escalade bouldering + + + + + + + +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 46bda8d..60f3ae2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "posthog-js": "^1.364.7", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-helmet-async": "^3.0.0", "react-i18next": "^16.6.0", "react-router-dom": "^7.13.1", "three": "^0.177.0", @@ -3654,6 +3655,15 @@ "node": ">=0.8.19" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3715,7 +3725,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4101,6 +4110,18 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4461,6 +4482,26 @@ "react": "^19.2.4" } }, + "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==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz", + "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==", + "license": "Apache-2.0", + "dependencies": { + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-i18next": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.6.0.tgz", @@ -4637,6 +4678,12 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 84d04c8..ae470e5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "posthog-js": "^1.364.7", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-helmet-async": "^2.0.1", "react-i18next": "^16.6.0", "react-router-dom": "^7.13.1", "three": "^0.177.0", diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..1ce4aee --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,6 @@ +User-agent: * +Allow: / +Disallow: /editor/ +Disallow: /gym/ + +Sitemap: https://setrsoft.com/sitemap.xml diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml new file mode 100644 index 0000000..3ff939e --- /dev/null +++ b/frontend/public/sitemap.xml @@ -0,0 +1,18 @@ + + + + https://setrsoft.com/ + weekly + 1.0 + + + https://setrsoft.com/partners + monthly + 0.6 + + + https://setrsoft.com/contact + yearly + 0.5 + + diff --git a/frontend/src/features/editor/EditorApp.tsx b/frontend/src/features/editor/EditorApp.tsx index 56eb0be..3e98e88 100644 --- a/frontend/src/features/editor/EditorApp.tsx +++ b/frontend/src/features/editor/EditorApp.tsx @@ -70,7 +70,7 @@ function EditorApp() { posthog.capture('editor session opened', { wall_id: wallId, session_id: session_data.id }); } } - }, [session_data?.id, wallModels.length, handleLoad]); + }, [session_data?.id, wallId, wallModels.length, handleLoad]); useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { @@ -152,7 +152,7 @@ function EditorApp() { ))} - + diff --git a/frontend/src/features/showcase/ContactPage.tsx b/frontend/src/features/showcase/ContactPage.tsx index 65921f9..6586e7d 100644 --- a/frontend/src/features/showcase/ContactPage.tsx +++ b/frontend/src/features/showcase/ContactPage.tsx @@ -1,4 +1,5 @@ import { useTranslation, Trans } from 'react-i18next'; +import { Helmet } from 'react-helmet-async'; const EMAIL = 'contact@setrsoft.com'; @@ -6,14 +7,20 @@ export function ContactPage() { const { t } = useTranslation(); return ( -
-

{t('contact.title')}

-

- }} - /> -

-
+ <> + + Contact – SetRsoft + + +
+

{t('contact.title')}

+

+ }} + /> +

+
+ ); } diff --git a/frontend/src/features/showcase/HomePage.tsx b/frontend/src/features/showcase/HomePage.tsx index 261ce7c..29e6073 100644 --- a/frontend/src/features/showcase/HomePage.tsx +++ b/frontend/src/features/showcase/HomePage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; +import { Helmet } from 'react-helmet-async'; import { ROUTES } from '@/core/config'; import { posthog } from '@/shared/analytics/posthog'; import demoVideo from '@/assets/demo-CISe8V-c.mp4'; @@ -38,6 +39,21 @@ export function HomePage() { }, []); return ( + <> + + SetRsoft – Éditeur 3D Bouldering en ligne + + +
{/* Hero Section */} @@ -60,7 +76,7 @@ export function HomePage() {
+ ); } diff --git a/frontend/src/features/showcase/PartnersPage.tsx b/frontend/src/features/showcase/PartnersPage.tsx index cb0e443..d69260f 100644 --- a/frontend/src/features/showcase/PartnersPage.tsx +++ b/frontend/src/features/showcase/PartnersPage.tsx @@ -1,4 +1,5 @@ import { useTranslation, Trans } from 'react-i18next'; +import { Helmet } from 'react-helmet-async'; const EMAIL = 'contact@setrsoft.com'; @@ -6,14 +7,20 @@ export function PartnersPage() { const { t } = useTranslation(); return ( -
-

{t('partners.title')}

-

- }} - /> -

-
+ <> + + Partenaires – SetRsoft + + +
+

{t('partners.title')}

+

+ }} + /> +

+
+ ); } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f179768..65d1d1f 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,6 +1,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { HelmetProvider } from 'react-helmet-async'; import { AuthProvider } from '@/shared/auth'; import { App } from '@/app/App'; import './index.css'; @@ -11,10 +12,12 @@ const queryClient = new QueryClient(); createRoot(document.getElementById('root')!).render( - - - - - + + + + + + + );