Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions examples/nextjs-ssr/app/authenticated-app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

"use client";

import { multiFactor, sendEmailVerification, signOut } from "firebase/auth";
import { useRouter } from "next/navigation";
import { useUser } from "@/lib/firebase/hooks";
import { auth } from "@/lib/firebase/clientApp";
import { type User } from "firebase/auth";

export function AuthenticatedApp({ initialUser }: { initialUser: User | null }) {
const user = useUser(initialUser);
const router = useRouter();

if (!user) {
return null;
}

const mfa = multiFactor(user);

return (
<div className="max-w-sm mx-auto pt-36 space-y-6 pb-36">
<div className="border border-neutral-200 dark:border-neutral-800 rounded-md p-4 space-y-4">
<h1 className="text-md font-medium">Welcome, {user.displayName || user.email || user.phoneNumber}</h1>
{user.email ? (
<>
{user.emailVerified ? (
<div className="text-green-500">Email verified</div>
) : (
<button
className="bg-red-500 text-white px-3 py-1.5 rounded text-sm"
onClick={async () => {
try {
await sendEmailVerification(user);
alert("Email verification sent, please check your email");
} catch (error) {
console.error(error);
alert("Error sending email verification, check console");
}
}}
>
Verify Email &rarr;
</button>
)}
</>
) : null}

<hr className="opacity-30" />
<h2 className="text-sm font-medium">Multi-factor Authentication</h2>
{mfa.enrolledFactors.map((factor) => {
return (
<div key={factor.factorId}>
{factor.factorId} - {factor.displayName}
</div>
);
})}
<button
className="bg-blue-500 text-white px-3 py-1.5 rounded text-sm"
onClick={() => {
router.push("/screens/mfa-enrollment-screen");
}}
>
Add MFA Factor &rarr;
</button>
<hr className="opacity-30" />
<button
className="bg-blue-500 text-white px-3 py-1.5 rounded text-sm"
onClick={async () => await signOut(auth)}
>
Sign Out &rarr;
</button>
</div>
</div>
);
}
18 changes: 18 additions & 0 deletions examples/nextjs-ssr/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,25 @@
*/

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@import "@invertase/firebaseui-styles/tailwind";

/* Prevent flash by hiding content until theme is loaded */
html:not(.theme-loaded) body {
visibility: hidden;
}

html.theme-loaded body {
visibility: visible;
}

.fui-provider__button[data-provider="oidc.line"][data-themed="true"] {
--line-primary: #07B53B;
--color-primary: var(--line-primary);
--color-primary-hover: --alpha(var(--line-primary) / 85%);
--color-primary-surface: #FFFFFF;
--color-border: var(--line-primary);
}

/* @import "@invertase/firebaseui-styles/themes/dark.css"; */
/* @import "@invertase/firebaseui-styles/themes/brutalist.css"; */
52 changes: 32 additions & 20 deletions examples/nextjs-ssr/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,13 @@
* limitations under the License.
*/

import { getCurrentUser } from "@/lib/firebase/serverApp";
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";

import { Header } from "@/lib/components/header";
import Script from "next/script";
import { FirebaseUIProviderHoc } from "@/lib/firebase/ui";
import { ThemeToggle } from "@/lib/components/theme-toggle";
import { PirateToggle } from "@/lib/components/pirate-toggle";
import "./globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "Create Next App (SSR)",
description: "Generated by create next app with SSR",
Expand All @@ -42,13 +31,36 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const { currentUser } = await getCurrentUser();

return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<Header currentUser={currentUser} />
<FirebaseUIProviderHoc>{children}</FirebaseUIProviderHoc>
<html lang="en" suppressHydrationWarning>
<body className="dark:bg-neutral-900 dark:text-white" suppressHydrationWarning>
<Script
id="dark-mode-init"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
!(function() {
try {
var theme = localStorage.getItem('theme');
var isDark = theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (isDark) {
document.documentElement.classList.add('dark');
}
// Mark theme as loaded - this removes the visibility:hidden
document.documentElement.classList.add('theme-loaded');
} catch (e) {
// If anything fails, still show content
document.documentElement.classList.add('theme-loaded');
}
})();
`,
}}
/>
<FirebaseUIProviderHoc>
<ThemeToggle />
<PirateToggle />
{children}
</FirebaseUIProviderHoc>
</body>
</html>
);
Expand Down
74 changes: 7 additions & 67 deletions examples/nextjs-ssr/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,75 +15,15 @@
*/

import { getCurrentUser } from "@/lib/firebase/serverApp";
import Link from "next/link";
import { UnauthenticatedApp } from "./unauthenticated-app";
import { AuthenticatedApp } from "./authenticated-app";

export default async function Home() {
const { currentUser } = await getCurrentUser();

return (
<div className="p-8 ">
<h1 className="text-3xl font-bold mb-6">Firebase UI Demo (SSR)</h1>
<div className="mb-6">{currentUser && <div>Welcome: {currentUser.email || currentUser.phoneNumber}</div>}</div>
<div>
<h2 className="text-2xl font-bold mb-4">Auth Screens</h2>
<ul className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<li>
<Link href="/screens/sign-in-auth-screen" className="text-blue-500 hover:underline">
Sign In Auth Screen
</Link>
</li>
<li>
<Link href="/screens/sign-in-auth-screen-w-handlers" className="text-blue-500 hover:underline">
Sign In Auth Screen with Handlers
</Link>
</li>
<li>
<Link href="/screens/sign-in-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Sign In Auth Screen with OAuth
</Link>
</li>
<li>
<Link href="/screens/email-link-auth-screen" className="text-blue-500 hover:underline">
Email Link Auth Screen
</Link>
</li>
<li>
<Link href="/screens/email-link-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Email Link Auth Screen with OAuth
</Link>
</li>
<li>
<Link href="/screens/phone-auth-screen" className="text-blue-500 hover:underline">
Phone Auth Screen
</Link>
</li>
<li>
<Link href="/screens/phone-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Phone Auth Screen with OAuth
</Link>
</li>
<li>
<Link href="/screens/sign-up-auth-screen" className="text-blue-500 hover:underline">
Sign Up Auth Screen
</Link>
</li>
<li>
<Link href="/screens/sign-up-auth-screen-w-oauth" className="text-blue-500 hover:underline">
Sign Up Auth Screen with OAuth
</Link>
</li>
<li>
<Link href="/screens/oauth-screen" className="text-blue-500 hover:underline">
OAuth Screen
</Link>
</li>
<li>
<Link href="/screens/password-reset-screen" className="text-blue-500 hover:underline">
Password Reset Screen
</Link>
</li>
</ul>
</div>
</div>
);
if (currentUser) {
return <AuthenticatedApp initialUser={currentUser} />;
}

return <UnauthenticatedApp />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,59 @@

"use client";

import { EmailLinkAuthScreen, GoogleSignInButton } from "@invertase/firebaseui-react";
import {
AppleSignInButton,
EmailLinkAuthScreen,
FacebookSignInButton,
GitHubSignInButton,
GoogleSignInButton,
MicrosoftSignInButton,
TwitterSignInButton,
OAuthButton,
} from "@invertase/firebaseui-react";
import { OAuthProvider } from "firebase/auth";
import { useRouter } from "next/navigation";

export default function EmailLinkAuthScreenWithOAuthPage() {
const router = useRouter();

return (
<EmailLinkAuthScreen>
<EmailLinkAuthScreen
onEmailSent={() => {
alert("Email has been sent - please check your email");
}}
onSignIn={(credential) => {
console.log(credential);
router.push("/");
}}
>
<GoogleSignInButton />
<FacebookSignInButton />
<AppleSignInButton />
<GitHubSignInButton />
<MicrosoftSignInButton />
<TwitterSignInButton />
<LineSignInButton />
</EmailLinkAuthScreen>
);
}

function LineSignInButton() {
const provider = new OAuthProvider("oidc.line");

return (
<OAuthButton provider={provider}>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 48 48">
<path
fill="#00c300"
d="M12.5 42h23a6.5 6.5 0 0 0 6.5-6.5v-23A6.5 6.5 0 0 0 35.5 6h-23A6.5 6.5 0 0 0 6 12.5v23a6.5 6.5 0 0 0 6.5 6.5"
/>
<path
fill="#fff"
d="M37.113 22.417c0-5.865-5.88-10.637-13.107-10.637s-13.108 4.772-13.108 10.637c0 5.258 4.663 9.662 10.962 10.495.427.092 1.008.282 1.155.646.132.331.086.85.042 1.185 0 0-.153.925-.187 1.122-.057.331-.263 1.296 1.135.707s7.548-4.445 10.298-7.611h-.001c1.901-2.082 2.811-4.197 2.811-6.544m-18.238 3.49h-2.604a.69.69 0 0 1-.687-.688V20.01a.688.688 0 0 1 1.374 0v4.521h1.917a.688.688 0 0 1 0 1.376m2.693-.688a.688.688 0 1 1-1.374 0V20.01a.688.688 0 0 1 1.374 0zm6.27 0a.684.684 0 0 1-.688.688.69.69 0 0 1-.549-.275l-2.669-3.635v3.222a.689.689 0 0 1-1.376 0V20.01a.687.687 0 0 1 1.237-.412l2.67 3.635V20.01a.688.688 0 0 1 1.375 0zm4.214-3.292a.689.689 0 0 1 0 1.375h-1.917v1.23h1.917a.688.688 0 0 1 0 1.375h-2.604a.69.69 0 0 1-.687-.688v-5.208c0-.379.308-.687.687-.687h2.604a.688.688 0 1 1 0 1.374h-1.917v1.23h1.917z"
/>
</svg>
<span>Sign in with Line</span>
</OAuthButton>
);
}
31 changes: 31 additions & 0 deletions examples/nextjs-ssr/app/screens/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Link from "next/link";

export default function ScreensLayout({ children }: { children: React.ReactNode }) {
return (
<div className="p-8">
<Link
href="/"
className="border border-gray-300 dark:border-gray-700 border-rounded px-4 py-2 rounded-md text-sm inline-block"
>
&larr; Back to overview
</Link>
<div className="pt-12">{children}</div>
</div>
);
}
Loading