diff --git a/src/pages/thank-you.module.css b/src/pages/thank-you.module.css new file mode 100644 index 0000000..e3c24b6 --- /dev/null +++ b/src/pages/thank-you.module.css @@ -0,0 +1,398 @@ +/* + * Thank-you page styles + * Mirrors the Webflow `.link-in-bio` layout. + * Colors reuse the same tokens already defined in src/components/landing/styles/shared.module.css + * so the look matches the rest of the site. + */ + +.page { + --color-black: #040114; + --color-gray-1: #070417; + --color-gray-2: #1b1a25; + --color-gray-3: #242328; + --color-gray-4: #e1e0e9; + --color-white: #ffffff; + --color-primary-rp: #d35f5f; + --color-darker-primary: #bd5454; + --color-border-doc: #444950; + + background-color: var(--color-black); + color: var(--color-gray-4); + font-size: 18px; + line-height: 1.5em; + min-height: 100vh; + padding: 50px 20px; + display: flex; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; +} + +/* Halo radial dégradé derrière la card (linear 180° + blur, + * comme Webflow .utility-page-content-decoration). */ +.decoration { + position: absolute; + inset: 0; + margin: auto; + width: 500px; + height: 50%; + background-image: linear-gradient(180deg, var(--color-primary-rp) 40%, #a561a3); + filter: blur(150px); + opacity: 0.5; + pointer-events: none; + z-index: 0; +} + +.card { + z-index: 22; + border: 1px solid var(--color-gray-3); + border-radius: 20px; + background-color: var(--color-gray-1); + text-align: center; + flex-direction: column; + width: 100%; + max-width: 650px; + padding: 60px; + display: flex; + position: relative; +} + +.logoWrap { + justify-content: center; + align-items: center; + margin-bottom: 30px; + margin-left: auto; + margin-right: auto; + text-decoration: none; + display: flex; +} + +.logo { + height: 70px; + width: auto; +} + +.title { + color: var(--color-gray-4); + font-size: 18px; + line-height: 1.5em; + margin: 0 0 32px; +} + +.description { + color: var(--color-gray-4); + margin: 0 0 10px; +} + +.buttonRow { + grid-column-gap: 15px; + grid-row-gap: 15px; + flex-direction: column; + padding-top: 20px; + padding-bottom: 0; + display: flex; +} + +.btnPrimary { + display: inline-block; + border: 1px solid var(--color-darker-primary); + border-radius: 50px; + background-color: var(--color-darker-primary); + color: #fff; + text-align: center; + padding: 20px 28px 18px; + font-size: 18px; + font-weight: 500; + line-height: 1.1em; + text-decoration: none; + transition: border-color 0.3s, background-color 0.3s, transform 0.3s, color 0.3s; +} + +.btnPrimary:hover { + text-decoration: none; + color: #fff; + border-color: var(--color-darker-primary); + background-color: var(--color-darker-primary); + transform: translate3d(0, -3px, 0.01px); +} + +.btnSecondary { + display: inline-block; + border: 1px solid var(--color-darker-primary); + border-radius: 50px; + background-color: transparent; + color: var(--color-white); + text-align: center; + padding: 20px 28px 18px; + font-size: 18px; + line-height: 1.1em; + text-decoration: none; + transition: border-color 0.3s, transform 0.3s, background-color 0.3s, color 0.3s; +} + +.btnSecondary:hover { + text-decoration: none; + color: #fff; + border-color: var(--color-darker-primary); + background-color: var(--color-darker-primary); + transform: translate3d(0, -3px, 0.01px); +} + +/* Server card */ + +.centerServer { + margin: 0 auto 30px; +} + +.clientServer { + grid-column-gap: 30px; + grid-row-gap: 30px; + border: 1px solid var(--color-border-doc); + border-radius: 0.8rem; + background-color: var(--color-gray-3); + width: auto; + min-width: 275px; + padding: 30px; + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: flex-start; + transition: border-color 0.3s; +} + +.clientServer:hover { + border-color: var(--color-primary-rp); + text-decoration: none; +} + +.avatarInfo { + grid-column-gap: 15px; + grid-row-gap: 15px; + display: flex; + align-items: center; + font-size: 14px; + line-height: 1.5em; + text-align: left; +} + +.avatar { + object-fit: cover; + border-radius: 50%; + flex: none; + width: 50px; + height: 50px; +} + +.serverInfos { + grid-column-gap: 6px; + grid-row-gap: 6px; + flex-flow: column; + justify-content: center; + align-items: flex-start; + display: flex; +} + +.serverNameRow { + justify-content: flex-start; + align-items: center; + display: flex; +} + +.serverName { + color: var(--color-white); + margin: 0; + font-size: 18px; + font-weight: 600; +} + +.serverBadge { + margin-left: 10px; +} + +.serverMembers { + color: var(--color-gray-4); + font-size: 14px; +} + +/* Permission warning */ + +.permissionWarning { + padding: 15px; + border-radius: 8px; + text-align: center; + max-width: 600px; + margin: 20px auto; + padding-bottom: 1px; + color: var(--color-white); +} + +.success { + background: #003100; +} + +.adminWarning { + background: #193c47; +} + +.missingPermissions { + background: #4d3800; +} + +.permissionList { + list-style-type: none; + padding: 0; + margin-top: 10px; +} + +.permissionList li { + margin: 5px 0; +} + +.permissionList code { + background-color: rgba(255, 255, 255, 0.08); + padding: 2px 8px; + border-radius: 4px; + font-size: 0.95em; +} + +/* Footer (langue + réseaux sociaux) */ + +.footerThank { + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 16px; + grid-row-gap: 16px; + justify-content: center; + align-items: center; + margin-top: 8px; +} + +.langDropdown { + position: relative; + display: inline-flex; + justify-content: center; + align-items: center; +} + +.langTrigger { + color: #fff; + display: inline-flex; + align-items: center; + padding: 1.15px 12px; + cursor: pointer; + user-select: none; +} + +.langTrigger::after { + content: ''; + display: inline-block; + position: relative; + top: 3px; + margin-left: 0.3em; + transform: translateY(-50%); + border-style: solid; + border-width: 0.4em 0.4em 0; + border-color: currentColor transparent transparent; +} + +.langTrigger:hover { + color: var(--color-primary-rp); +} + +.iconLanguage { + display: inline-block; + margin-right: 5px; + transform: translateY(4px); +} + +.currentLanguage { + font-family: var(--ifm-font-family-base, inherit); +} + +.langDropdownList { + position: absolute; + inset: auto auto 30px auto; + display: none; + flex-direction: column; + min-width: 10rem; + padding: 0.5rem; + border-radius: 10px; + background-color: var(--color-gray-2); + text-align: left; + z-index: 30; +} + +.langDropdown:hover .langDropdownList, +.langDropdown:focus-within .langDropdownList { + display: flex; +} + +.langDropdownItem { + color: var(--color-gray-4); + margin-top: 0.2rem; + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + text-decoration: none; + border-radius: 5px; +} + +.langDropdownItem:hover { + background-color: var(--color-gray-3); + color: var(--color-gray-4); + text-decoration: none; +} + +.langDropdownItemCurrent { + background-color: var(--color-gray-3); + color: var(--color-primary-rp); +} + +.socialWrap { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + grid-column-gap: 20px; + grid-row-gap: 20px; + margin: 0; +} + +.socialLink { + display: block; + color: var(--color-white); + transition: transform 0.3s ease-in-out; +} + +.socialLink:hover { + transform: translateY(-3px); +} + +.socialIcon { + width: 18px; + height: 18px; + display: block; +} + +@media screen and (max-width: 767px) { + .card { + padding: 40px 24px; + } + .title { + font-size: 18px; + margin-bottom: 40px; + } + .btnPrimary, + .btnSecondary { + padding: 20px 26px 16px; + font-size: 16px; + } + .footerThank { + grid-template-columns: 1fr; + grid-row-gap: 20px; + } + .decoration { + width: 100%; + max-width: 400px; + } +} diff --git a/src/pages/thank-you.tsx b/src/pages/thank-you.tsx new file mode 100644 index 0000000..6aa12f0 --- /dev/null +++ b/src/pages/thank-you.tsx @@ -0,0 +1,398 @@ +import React, {type ReactNode, useEffect, useState} from 'react'; +import Head from '@docusaurus/Head'; +import Link from '@docusaurus/Link'; +import styles from './thank-you.module.css'; + +type ServerBadge = 'partner' | 'verified' | null; + +type ServerInfo = { + name: string; + members: string; + inviteUrl: string; + iconUrl: string; + badge: ServerBadge; +}; + +type PermissionWarning = { + tone: 'admin' | 'missing'; + message: string; + missing: string[]; +}; + +const DEFAULT_ICON_URL = + 'https://cdn.prod.website-files.com/677fbd67c3c9318f7fb56659/67c33922eb3265808c183c50_411d8a698dd15ddf.webp'; + +const BADGE_SRC: Record, string> = { + partner: '/img/landing/serverBadgePartner.svg', + verified: '/img/landing/serverBadgeVerified.svg', +}; + +const REQUIRED_PERMISSIONS = 1117660769534n; +const ADMIN_PERMISSION = 8n; + +// Order matches the Webflow source so the missing-permissions list reads the same. +const PERMISSION_MESSAGES: Array<[string, bigint]> = [ + ['Administrateur', ADMIN_PERMISSION], + ['Gérer le serveur', 32n], + ['Gérer les rôles', 268435456n], + ['Gérer les salons', 16n], + ['Expulser des membres', 2n], + ['Bannir des membres', 4n], + ['Gérer les pseudos', 134217728n], + ['Gérer les webhooks', 536870912n], + ['Voir les logs du serveur', 524288n], + ['Voir les salons', 1024n], + ['Modérer les membres', 1099511627776n], + ['Envoyer des messages', 2048n], + ['Gérer les messages', 8192n], + ['Gérer les fils', 17179869184n], + ['Intégrer des liens', 16384n], + ['Joindre des fichiers', 32768n], + ['Voir les anciens messages', 65536n], + ['Ajouter des réactions', 64n], + ['Utiliser des émojis externes', 262144n], + ['Rendre les membres muets', 4194304n], + ['Mettre en sourdine des membres', 8388608n], + ['Déplacer des membres', 16777216n], +]; + +async function fetchServerInfo(guildId: string): Promise { + try { + const widgetResponse = await fetch( + `https://discord.com/api/guilds/${guildId}/widget.json`, + ); + if (!widgetResponse.ok) { + throw new Error('Widget désactivé.'); + } + const widgetData = await widgetResponse.json(); + + const name: string = widgetData.name || 'Serveur inconnu'; + const members: string = + typeof widgetData.presence_count === 'number' + ? `${widgetData.presence_count} membres en ligne` + : 'Nombre de membres inconnu'; + const inviteUrl: string = widgetData.instant_invite || '#'; + + let iconUrl = DEFAULT_ICON_URL; + let badge: ServerBadge = null; + + const inviteCode: string | null = widgetData.instant_invite + ? widgetData.instant_invite.split('/').pop() || null + : null; + + if (inviteCode) { + const inviteResponse = await fetch( + `https://discord.com/api/invites/${inviteCode}?with_counts=true&with_expiration=true`, + ); + if (inviteResponse.ok) { + const inviteData = await inviteResponse.json(); + const server = inviteData.guild; + if (server?.icon) { + iconUrl = `https://cdn.discordapp.com/icons/${server.id}/${server.icon}.png`; + } + if (Array.isArray(server?.features)) { + if (server.features.includes('PARTNERED')) { + badge = 'partner'; + } else if (server.features.includes('VERIFIED')) { + badge = 'verified'; + } + } + } + } + + return {name, members, inviteUrl, iconUrl, badge}; + } catch (error) { + // Discord widget may be disabled or network may be unreachable. + // eslint-disable-next-line no-console + console.error(error); + return null; + } +} + +function computePermissionWarning( + permissionsParam: string, +): PermissionWarning | null { + let currentPermissions: bigint; + try { + currentPermissions = BigInt(permissionsParam); + } catch { + return null; + } + + const hasAdminPermission = + (currentPermissions & ADMIN_PERMISSION) === ADMIN_PERMISSION; + const missingPermissions = PERMISSION_MESSAGES.filter( + ([, value]) => + (REQUIRED_PERMISSIONS & value) === value && + (currentPermissions & value) !== value, + ).map(([name]) => name); + + if (hasAdminPermission || missingPermissions.length === 0) { + return null; + } + + if ( + missingPermissions.length === 1 && + missingPermissions[0] === 'Administrateur' + ) { + return { + tone: 'admin', + message: + '⚠️ Toutes les permissions spécifiques sont accordées, mais sans la permission Administrateur, le bot pourrait ne pas accéder à tous les salons.', + missing: [], + }; + } + + return { + tone: 'missing', + message: + "⚠️ Afin d'assurer le bon fonctionnement du bot, nous vous recommandons d'ajouter les permissions suivantes :", + missing: missingPermissions, + }; +} + +export default function ThankYou(): ReactNode { + const [serverInfo, setServerInfo] = useState(null); + const [permissionWarning, setPermissionWarning] = + useState(null); + + useEffect(() => { + if (typeof window === 'undefined') return; + + const urlParams = new URLSearchParams(window.location.search); + + const guildId = urlParams.get('guild_id'); + let cancelled = false; + if (guildId) { + fetchServerInfo(guildId).then((info) => { + if (!cancelled && info) { + setServerInfo(info); + } + }); + } + + const permissionsParam = urlParams.get('permissions'); + if (permissionsParam) { + setPermissionWarning(computePermissionWarning(permissionsParam)); + } + + // Redirection auto vers l'invite Discord après 60s, comme la page Webflow. + const redirectTimer = window.setTimeout(() => { + window.location.href = 'https://discord.com/invite/HfMYDHbgqc'; + }, 60000); + + return () => { + cancelled = true; + window.clearTimeout(redirectTimer); + }; + }, []); + + return ( + <> + + Merci | RaidProtect + + +
+
+ + RaidProtect title logo + + +

Merci d'avoir invité RaidProtect !

+ + {serverInfo && ( + + )} + + {permissionWarning && ( +
+ {permissionWarning.message} + {permissionWarning.missing.length > 0 && ( +
    + {permissionWarning.missing.map((perm) => ( +
  • + {perm} +
  • + ))} +
+ )} +
+ )} + +

+ Pour bien débuter, nous vous recommandons de consulter notre + documentation et de rejoindre notre serveur. +

+ +
+ + Rejoindre notre serveur Discord + + + Consulter la documentation + +
+ +
+
+
+ + Français +
+ +
+ + +
+
+ +
+ + ); +}