diff --git a/.env b/.env index 96f78c0..5559cc2 100644 --- a/.env +++ b/.env @@ -3,16 +3,18 @@ POSTGRES_PORT=5432 POSTGRES_USER=local_user POSTGRES_DB=local_db POSTGRES_PASSWORD=local_password +POSTGRES_CA="-----BEGIN CERTIFICATE-----\nMIIDhjCCAwygAwIBAgISBNzos6EbVpPFn8nem8wBwErWMAoGCCqGSM49BAMDMDIx\nCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF\nNjAeFw0yNDEwMDcxNjAyMThaFw0yNTAxMDUxNjAyMTdaMBUxEzARBgNVBAMTCmFs\ncGVuZWQuaW8wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT3CdKEpCiMAENO9z4R\n3uQPiq/Ww5YKyYEYXdYUsIxNbMm6eeDUHZhC/Dbd+BFwtdUN+0cdZJxGJ7EkN/qu\nPgslo4ICHTCCAhkwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMB\nBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTjwnHYRiaBlBqSK4c1\nsRi6xUvs9jAfBgNVHSMEGDAWgBSTJ0aYA6lRaI6Y1sRCSNsjv1iU0jBVBggrBgEF\nBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6Ly9lNi5vLmxlbmNyLm9yZzAiBggr\nBgEFBQcwAoYWaHR0cDovL2U2LmkubGVuY3Iub3JnLzAlBgNVHREEHjAcggphbHBl\nbmVkLmlvgg53d3cuYWxwZW5lZC5pbzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQUG\nCisGAQQB1nkCBAIEgfYEgfMA8QB2AM8RVu7VLnyv84db2Wkum+kacWdKsBfsrAHS\nW3fOzDsIAAABkmftKG4AAAQDAEcwRQIhALewnaUKAvEbs2GE31Yp7k6eBt6pW+2/\nVRgHotXHj68RAiAvnZ6I5YntLrj7Hez7IVgN3dUoVpKr0SyrqIpL29O1NwB3AH1Z\nHhLheCp7HGFnfF79+NCHXBSgTpWeuQMv2Q6MLnm4AAABkmftKDAAAAQDAEgwRgIh\nAJDKFTWpnWYebI+5wxVDeu843KGym7QvX8DXyN6SAIJiAiEA2Ro751daHDfhAKxS\nzT+LWSPWRpSMhCnqHeUqvzUjmG4wCgYIKoZIzj0EAwMDaAAwZQIxAMk8OvpQsgGQ\nM1Cjgp47y+2fI3dBVIyUed9tSYSsreWGSJJdtBlIJr59ZqYscBZ7RAIwFUmWNTJG\n6P+GueQv+BOvR10/DdGo7MvqyqbJoBgFVnpsJ7mRuFgZ3S7X2pdg/10r\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\nWhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\nRW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G\nh/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV\n6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw\ngfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\nATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj\nv1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB\nAQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g\nBAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu\nY3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc\nMxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL\npMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp\neDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH\npOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7\ns8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu\nh4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv\nYlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8\nZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0\nLyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+\nEwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY\nIg46v9mFmBvyH04=\n-----END CERTIFICATE-----" DATABASE_URL=postgresql://local_user:local_password@localhost:5432/local_db -NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=v5+DnKYpcVpu6/sjEP2AVub1Stoql2qz/RAiwp5udLk SOCKET_PORT=4000 HOODPAY_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjIzMjI1IiwiZXhwIjoyMDQzNTQwODY2fQ.ZSzD7Vj2tgOfseg_6PcDhF_PkmGag6slEXUFwz4llpE HOODPAY_BUSINESS_ID=21578 + +NEXTAUTH_URL=https://alpened.io +NEXT_PUBLIC_SHOUTBOX_SOCKET=https://alpened.io NEXT_PUBLIC_APP_URL=https://alpened.io -NEXT_PUBLIC_SHOUTBOX_SOCKET=http://localhost:4000 \ No newline at end of file diff --git a/.env.development b/.env.development index 7a92ad5..e07e22a 100644 --- a/.env.development +++ b/.env.development @@ -13,6 +13,6 @@ SOCKET_PORT=4000 HOODPAY_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjIzMDM4IiwiZXhwIjoyMDQyOTgxMzU4fQ.ElWanjvJZ_NLr5gvk0XwKGBLMGnU32NG_J-qjtbJhfA HOODPAY_BUSINESS_ID=21424 -NEXT_PUBLIC_APP_URL=http://localhost:3000 -NEXT_PUBLIC_SHOUTBOX_SOCKET=http://localhost:4000 \ No newline at end of file +NEXT_PUBLIC_SHOUTBOX_SOCKET=http://localhost:4000 +NEXT_PUBLIC_APP_URL=http://localhost:3000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index fc230c7..46b0443 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env*.local # vercel @@ -36,4 +37,4 @@ yarn-error.log* next-env.d.ts # Socket server -socket-server/node_modules \ No newline at end of file +socket-server/node_modules diff --git a/app/(dashboard)/page.tsx b/app/(dashboard)/page.tsx index ca48091..e460c07 100644 --- a/app/(dashboard)/page.tsx +++ b/app/(dashboard)/page.tsx @@ -3,20 +3,49 @@ import React from "react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; -import { CircularProgress } from "@mui/material"; -import ForumSummary from "@/components/ForumSummary"; -import StickyTopics from "@/components/AnnouncementsTopics"; -import RecentTopics from "@/components/RecentTopics"; -import AnimatedDashboard from "@/components/AnimatedDashboard"; -import MountainBackground from "@/components/MountainBackground"; -import Shoutbox from "@/components/Shoutbox"; - -export default function ForumDashboard() { +import CircularProgress from "@mui/material/CircularProgress"; +import dynamic from "next/dynamic"; + +const DynamicShoutbox = dynamic(() => import("@/components/Shoutbox"), { + loading: () =>

Loading Shoutbox...

, +}); + +const DynamicForumSummary = dynamic(() => import("@/components/ForumSummary"), { + loading: () =>

Loading Forum Summary...

, +}); + +const DynamicStickyTopics = dynamic( + () => import("@/components/AnnouncementsTopics"), + { + loading: () =>

Loading Sticky Topics...

, + }, +); + +const DynamicRecentTopics = dynamic(() => import("@/components/RecentTopics"), { + loading: () =>

Loading Recent Topics...

, +}); + +const MemoizedMountainBackground = React.memo( + dynamic(() => import("@/components/MountainBackground")), +); +const DynamicAnimatedDashboard = dynamic( + () => import("@/components/AnimatedDashboard"), +); + +interface ForumDashboardProps { + // Add any props if needed +} + +const ForumDashboard: React.FC = () => { const { data: session, status } = useSession(); const router = useRouter(); if (status === "loading") { - return ; + return ( +
+ +
+ ); } if (status === "unauthenticated") { @@ -26,25 +55,27 @@ export default function ForumDashboard() { return (
- +
- +

Welcome, {session?.user?.name}

- - + +
- - + +
-
+
); -} +}; + +export default ForumDashboard; diff --git a/components/ErrorBoundary.tsx b/components/ErrorBoundary.tsx new file mode 100644 index 0000000..4d1a006 --- /dev/null +++ b/components/ErrorBoundary.tsx @@ -0,0 +1,35 @@ +// components/ErrorBoundary.tsx +import React, { Component, ErrorInfo, ReactNode } from "react"; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; +} + +class ErrorBoundary extends Component { + public state: State = { + hasError: false, + }; + + // eslint-disable-next-line no-unused-vars + public static getDerivedStateFromError(_: Error): State { + return { hasError: true }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error("Uncaught error:", error, errorInfo); + } + + public render() { + if (this.state.hasError) { + return

Sorry.. there was an error

; + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx index 5000f0e..1a6981b 100644 --- a/components/LoginForm.tsx +++ b/components/LoginForm.tsx @@ -1,61 +1,96 @@ "use client"; -import React, { useState } from "react"; +import React, { useState, useCallback, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Alert, Button, Snackbar, IconButton } from "@mui/material"; import { Visibility, VisibilityOff } from "@mui/icons-material"; -import { signIn } from "next-auth/react"; +import { signIn, useSession } from "next-auth/react"; import Link from "next/link"; -import MountainBackground from "./MountainBackground"; +import dynamic from "next/dynamic"; + +const DynamicMountainBackground = dynamic( + () => import("./MountainBackground"), + { + ssr: false, + }, +); export default function LoginForm() { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); + const [formData, setFormData] = useState({ username: "", password: "" }); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isSuccess, setIsSuccess] = useState(false); const [showPassword, setShowPassword] = useState(false); const router = useRouter(); + // eslint-disable-next-line no-unused-vars + const { data: session, status } = useSession(); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsLoading(true); - setError(null); - - if (!username.trim() || !password.trim()) { - setError("Username and password are required"); - setIsLoading(false); - return; + useEffect(() => { + if (status === "authenticated") { + window.location.href = "/"; } + }, [status, router]); + + const handleInputChange = useCallback( + (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }, + [], + ); - try { - const response = (await signIn("credentials", { - username: username.trim(), - password: password.trim(), - redirect: false, - })) as unknown as { - ok: boolean; - json: () => Promise<{ message: string }>; - }; + const handleSubmit = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + setError(null); - if (response.ok) { - setIsSuccess(true); - router.push("/"); - } else { - const data = await response.json(); - setError(data.message || "Login failed"); + const { username, password } = formData; + + if (!username.trim() || !password.trim()) { + setError("Username and password are required"); + setIsLoading(false); + return; } - } catch (err) { - console.error("Login error:", err); - setError("An error occurred. Please try again."); - } finally { - setIsLoading(false); - } - }; + + try { + const result = await signIn("credentials", { + username: username.trim(), + password: password.trim(), + redirect: false, + }); + + if (result?.error) { + setError("Login failed"); + } else { + setIsSuccess(true); + // The useEffect hook will handle redirection + } + } catch (err) { + console.error("Login error:", err); + setError("An error occurred. Please try again."); + } finally { + setIsLoading(false); + } + }, + [formData], + ); + + const togglePasswordVisibility = useCallback(() => { + setShowPassword((prev) => !prev); + }, []); + + if (status === "loading") { + return
Loading...
; + } + + if (status === "authenticated") { + return
Redirecting...
; + } return (
- +

Login

@@ -67,9 +102,10 @@ export default function LoginForm() { )} setUsername(e.target.value)} + value={formData.username} + onChange={handleInputChange} className="w-full px-3 py-2 mb-4 border rounded" autoFocus /> @@ -77,13 +113,14 @@ export default function LoginForm() {
setPassword(e.target.value)} + value={formData.password} + onChange={handleInputChange} className="w-full px-3 py-2 border rounded" /> setShowPassword(!showPassword)} + onClick={togglePasswordVisibility} className="ml-2" style={{ color: "inherit" }} > @@ -103,8 +140,8 @@ export default function LoginForm() {

Don't have an account?{" "} - - Register + + Register

diff --git a/components/Navbar.tsx b/components/Navbar.tsx index adf2638..3589804 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useSession, signOut } from "next-auth/react"; import Image from "next/image"; @@ -12,9 +12,9 @@ const Navbar: React.FC = () => { const dropdownRef = useRef(null); - const handleDropdownClick = (e: React.MouseEvent) => { + const handleDropdownClick = useCallback((e: React.MouseEvent) => { e.stopPropagation(); - }; + }, []); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -50,39 +50,35 @@ const Navbar: React.FC = () => { fetchAvatarUrl(); }, [session]); - const toggleDropdown = () => { - setIsDropdownOpen(!isDropdownOpen); - }; + const toggleDropdown = useCallback(() => { + setIsDropdownOpen((prev) => !prev); + }, []); + + const navLinks = [ + { href: "/", label: "Home" }, + { href: "/upgrade", label: "Upgrade" }, + { href: "/search", label: "Search" }, + { href: "/games", label: "Games" }, + { href: "/market", label: "Market" }, + { href: "/mm", label: "MiddleMan" }, + { href: "/affiliate", label: "Affiliate" }, + { href: "/help", label: "Help" }, + ]; return (