Skip to content

Commit

Permalink
Upgraded the whole project to use the latest version of NextJS
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinXPN committed Jan 18, 2025
1 parent 272902b commit c3a8041
Show file tree
Hide file tree
Showing 22 changed files with 4,108 additions and 4,542 deletions.
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
15 changes: 2 additions & 13 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,17 @@ module.exports = withSentryConfig({

return config;
},
},

// Injected content via Sentry wizard below
{
}, {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/

// Suppresses source map uploading logs during build
silent: true,
org: process.env.NEXT_PUBLIC_SENTRY_ORG,
project: process.env.NEXT_PUBLIC_SENTRY_PROJECT,
},
{
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/

// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,

// Transpiles SDK to be compatible with IE11 (increases bundle size)
transpileClientSDK: true,

// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
tunnelRoute: '/monitoring',

Expand Down
41 changes: 21 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,32 @@
"emulate": "(yarn --cwd functions build -- --watch) | (firebase emulators:start --import=./test_data --export-on-exit)"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hookform/resolvers": "^3.3.2",
"@mui/icons-material": "^5.14.18",
"@mui/material": "^5.14.18",
"@sentry/nextjs": "^7.81.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@hookform/resolvers": "^3.10.0",
"@mui/icons-material": "^6.4.0",
"@mui/material": "^6.4.0",
"@mui/material-nextjs": "^6.3.1",
"@sentry/nextjs": "^8.50.0",
"@svgr/webpack": "^8.1.0",
"firebase": "^10.6.0",
"firebase-admin": "^11.11.0",
"firebase": "^11.2.0",
"firebase-admin": "^13.0.2",
"firebaseui": "^6.1.0",
"models": "file:functions/models",
"next": "^14.0.3",
"next-firebase-auth-edge": "^0.10.2",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.48.2",
"next": "^15.1.5",
"next-firebase-auth-edge": "^1.8.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"use-async-effect": "^2.2.7",
"zod": "^3.22.4"
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "^20.9.3",
"@types/react": "^18.2.38",
"@types/react-dom": "^18.2.16",
"eslint": "^8.54.0",
"eslint-config-next": "^14.0.3",
"typescript": "^5.3.2"
"@types/node": "^22.10.7",
"@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3",
"eslint": "^9.18.0",
"eslint-config-next": "^15.1.5",
"typescript": "^5.7.3"
}
}
23 changes: 19 additions & 4 deletions sentry.client.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,32 @@ import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
debug: false,
enabled: process.env.NODE_ENV !== 'development',

ignoreErrors: [
'ResizeObserver loop limit exceeded', // Chrome ResizeObserver
'ResizeObserver loop completed with undelivered notifications', // Firefox ResizeObserver
'ResizeObserver loop limit exceeded', // Safari ResizeObserver
'auth/too-many-requests', // Firebase rate limiting
'Failed to get document because the client is offline', // Firebase offline
'FirebaseError: Missing or insufficient permissions.', // Firebase permissions
'INTERNAL ASSERTION FAILED: Pending promise was never set', // Firebase/auth assertion
'TypeError: Failed to fetch', // Firebase installations
'Loading CSS chunk', // Next.js chunk loading
'TypeError: Load failed', // Next.js chunk loading
"Unexpected token '<'", // HTML parsing errors
"expected expression, got '<'", // HTML parsing errors
'Minified React error #', // React errors
],

tracesSampleRate: 0.01,
replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 0.005,

integrations: [
new Sentry.BrowserTracing(),
new Sentry.Replay({
// Additional Replay configuration goes in here, for example:
Sentry.browserTracingIntegration(),
Sentry.replayIntegration({
maskAllText: false,
blockAllMedia: true,
}),
],
});
12 changes: 0 additions & 12 deletions sentry.edge.config.ts

This file was deleted.

11 changes: 0 additions & 11 deletions sentry.server.config.ts

This file was deleted.

15 changes: 5 additions & 10 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import {ReactNode} from "react";
import {Metadata} from "next";
import {cookies} from 'next/headers';
import Script from "next/script";
import CssBaseline from "@mui/material/CssBaseline";

import EmotionRootStyleRegistry from '@/theme/EmotionRootStyleRegistry';
import ThemeProvider from "@/theme/ThemeProvider";
import ErrorBoundary from "@/components/ErrorBoundary";
import AppLayout from "@/components/home/AppLayout";
Expand Down Expand Up @@ -37,8 +35,8 @@ export async function generateMetadata(): Promise<Metadata> {
export default async function RootLayout({children}: {
children: ReactNode,
}) {
const defaultUser = await getUser(cookies());
console.log('layout default user:', defaultUser?.id);
const defaultUser = await getUser(await cookies());
console.log(`layout default user: ${defaultUser?.id} with ${defaultUser?.signInProvider} provider`);

return <>
<html lang="en">
Expand All @@ -53,18 +51,15 @@ export default async function RootLayout({children}: {
</head>

<body>
<EmotionRootStyleRegistry>
<ThemeProvider>
<ErrorBoundary>
<AuthProvider defaultUser={defaultUser}>
<CssBaseline/>
<AppLayout>
{children}
</AppLayout>
<AppLayout>
{children}
</AppLayout>
</AuthProvider>
</ErrorBoundary>
</ThemeProvider>
</EmotionRootStyleRegistry>
</body>
</html>
</>
Expand Down
9 changes: 6 additions & 3 deletions src/app/users/[userId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import UserPage from '@/components/user/UserPage';
import {getUser} from "@/server/users";

export default async function Page({params: {userId}}: {
params: { userId: string}
}) {
const Page = async ({params}: {
params: Promise<{ userId: string }>,
}) => {
const {userId} = await params;
const user = await getUser(userId);
console.log('Server user:', user);
return <UserPage initialUser={user} />
}

export default Page;
2 changes: 1 addition & 1 deletion src/auth/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createContext} from "react";
import {ParsedToken} from "@firebase/auth";
import type {ParsedToken} from "@firebase/auth";

export type AuthUser = {
id: string | null;
Expand Down
39 changes: 21 additions & 18 deletions src/auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import {ReactNode, useCallback, useEffect, useState} from "react";
import {ReactNode, useCallback, useState} from "react";
import {AuthContext, AuthUser} from "./AuthContext";
import {EmailAuthProvider, getAuth, onIdTokenChanged, User as FirebaseUser} from "firebase/auth";
import type {User as FirebaseUser} from "firebase/auth";
import {updateUserInfo} from "@/services/users";
import {getAnalytics, setUserId} from "firebase/analytics";
import {useRouter} from "next/navigation";
Expand All @@ -14,21 +14,25 @@ function AuthProvider({defaultUser, children}: { defaultUser: AuthUser | null, c
const [user, setUser] = useState<AuthUser | null>(defaultUser);

const handleIdTokenChanged = useCallback(async (firebaseUser: FirebaseUser | null) => {
const providerData = firebaseUser?.providerData && firebaseUser.providerData[0];
console.log('token changed:', firebaseUser);
console.log('providerData:', providerData);
if (firebaseUser && firebaseUser.uid === user?.id && firebaseUser.emailVerified === user?.emailVerified)
return;
return setUser(user => {
if (!user) return null;
// Set sign in provider as `next-firebase-auth-edge` doesn't load it from cookies
return {...user, signInProvider: providerData?.providerId || null};
});

// No user => log out the current one or do nothing if there is no current user
if (!firebaseUser) {
return setUser(currentUser => {
if (!currentUser)
return null;
console.log('logging out...');
fetch('/api/logout', {method: 'GET'})
.then(() => console.log('logged out'))
.then(() => router.refresh());
console.log('No firebase user... Logging out...');
if (!user)
return null;
});
console.log('logging out...');
setUser(null);
await fetch('/api/logout', {method: 'GET'});
return router.refresh();
}

console.log(`Setting the user... ${user?.id} -> ${firebaseUser.uid}`);
Expand All @@ -38,14 +42,13 @@ function AuthProvider({defaultUser, children}: { defaultUser: AuthUser | null, c
console.log(`login: ${login.status} ${login.statusText} ${await login.text()}`);
if (login.status !== 200) {
console.error('Failed to log in... Logging out instead');
const {getAuth} = await import('firebase/auth');
await getAuth().signOut();
await fetch('/api/logout', {method: 'GET'});
return router.refresh();
}

// Update the current user
const providerData = firebaseUser.providerData && firebaseUser.providerData[0];
console.log('providerData:', providerData);
setUser({
id: firebaseUser.uid,
displayName: firebaseUser.displayName || providerData?.displayName || firebaseUser.email || null,
Expand All @@ -59,13 +62,14 @@ function AuthProvider({defaultUser, children}: { defaultUser: AuthUser | null, c

console.log('Updating the page...');
router.refresh();
}, [router, user?.emailVerified, user?.id]);
}, [router, user?.emailVerified, user?.id, setUser]);


useEffect(() => {
useAsyncEffect(async () => {
const {getAuth, onIdTokenChanged} = await import('firebase/auth');
const auth = getAuth();
return onIdTokenChanged(auth, handleIdTokenChanged);
}, [handleIdTokenChanged]);
}, unsubscribe => unsubscribe && unsubscribe(), [handleIdTokenChanged]);


useAsyncEffect(async () => {
Expand All @@ -76,8 +80,7 @@ function AuthProvider({defaultUser, children}: { defaultUser: AuthUser | null, c
}, [user?.id, user?.displayName, user?.photoURL]);

const isVerified = Boolean(
user && user.signInProvider &&
(user.emailVerified || (user.signInProvider !== EmailAuthProvider.PROVIDER_ID && user.signInProvider !== 'custom'))
user && user.signInProvider && (user.emailVerified || user.signInProvider !== 'password')
);
// console.log('isVerified:', isVerified, user?.emailVerified, user?.signInProvider);

Expand Down
35 changes: 20 additions & 15 deletions src/components/user/FirebaseAuth.tsx → src/auth/FirebaseAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import {useEffect, useRef, useState} from 'react';
import CircularProgress from "@mui/material/CircularProgress";
import useAsyncEffect from "use-async-effect";
import {EmailAuthProvider, onAuthStateChanged, sendEmailVerification} from 'firebase/auth';
import {EmailAuthProvider, getAuth, onAuthStateChanged, sendEmailVerification} from 'firebase/auth';
import 'firebaseui/dist/firebaseui.css';
import {auth} from "firebaseui";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import Divider from "@mui/material/Divider";
import {app} from "@/firebase";
import type {auth} from "firebaseui";

interface Props {

const FirebaseAuth = ({uiConfig, className, uiCallback}: {
// The Firebase UI Web UI Config object.
// See: https://github.com/firebase/firebaseui-web#configuration
uiConfig: auth.Config;
uiConfig: auth.Config,
// Callback that will be passed the FirebaseUi instance before it is
// started. This allows access to certain configuration options such as
// disableAutoSignIn().
uiCallback?(ui: auth.AuthUI): void;
uiCallback?(ui: auth.AuthUI): void,
// The Firebase App auth instance to use.
firebaseAuth: any; // As firebaseui-web
className?: string;
}


const FirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) => {
className?: string,
}) => {
const [firebaseui, setFirebaseui] = useState<typeof import('firebaseui') | null>(null);
const [userSignedIn, setUserSignedIn] = useState(false);
const [isVerified, setIsVerified] = useState(true);
Expand All @@ -39,6 +37,7 @@ const FirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) =>
useEffect(() => {
if (firebaseui === null )
return;
const firebaseAuth = getAuth(app);

// Get or Create a firebaseUI instance.
const firebaseUiWidget = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseAuth);
Expand All @@ -49,11 +48,17 @@ const FirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) =>
const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, user => {
if (!user && userSignedIn)
firebaseUiWidget.reset();
if( user && user.providerData?.[0]?.providerId === EmailAuthProvider.PROVIDER_ID && !user.emailVerified ) {

const signInProvider = user?.providerData?.[0]?.providerId;
const isVerified = Boolean(
user && signInProvider && (user.emailVerified || signInProvider !== EmailAuthProvider.PROVIDER_ID)
);

if( user && !isVerified ) {
sendEmailVerification(user).then(() => console.log('Email verification sent!'));
firebaseUiWidget.reset();
}
setIsVerified(!!user?.emailVerified);
setIsVerified(isVerified);
setUserSignedIn(!!user);
});

Expand All @@ -64,8 +69,8 @@ const FirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) =>
// Render the firebaseUi Widget.
// @ts-ignore
firebaseUiWidget.start(elementRef.current, {...uiConfig, callbacks: {uiShown() {
setLoading(false);
}}});
setLoading(false);
}}});

return () => {
unregisterAuthObserver();
Expand Down
Loading

0 comments on commit c3a8041

Please sign in to comment.