diff --git a/examples/react/.gitignore b/examples/react/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/react/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/react/README.md b/examples/react/README.md new file mode 100644 index 0000000..7059a96 --- /dev/null +++ b/examples/react/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/examples/react/eslint.config.js b/examples/react/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/examples/react/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/examples/react/index.html b/examples/react/index.html new file mode 100644 index 0000000..183149b --- /dev/null +++ b/examples/react/index.html @@ -0,0 +1,14 @@ + + + + + + + Vite + React + + +
+ + + + diff --git a/examples/react/lib/components/header.tsx b/examples/react/lib/components/header.tsx new file mode 100644 index 0000000..b50f2e2 --- /dev/null +++ b/examples/react/lib/components/header.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { NavLink } from "react-router"; +import { useUser } from "../firebase/hooks"; +import { signOut, type User } from "firebase/auth"; +import { auth } from "../firebase/clientApp"; + +export function Header() { + const user = useUser(); + + async function onSignOut() { + await signOut(auth); + router.push("/sign-in"); + } + + return ( +
+
+
+ FirebaseUI +
+
+
    + {user ?
  • :
  • Sign In
  • } +
+
+
+
+ ); +} \ No newline at end of file diff --git a/examples/react/lib/firebase/clientApp.ts b/examples/react/lib/firebase/clientApp.ts new file mode 100644 index 0000000..72c3c55 --- /dev/null +++ b/examples/react/lib/firebase/clientApp.ts @@ -0,0 +1,34 @@ +"use client"; + +import { initializeApp, getApps } from "firebase/app"; +import { firebaseConfig } from "./config"; +import { connectAuthEmulator, getAuth } from "firebase/auth"; +import { autoAnonymousLogin, initializeUI } from "@firebase-ui/core"; +import { customLanguage, english } from "@firebase-ui/translations"; + +export const firebaseApp = + getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]; + +export const auth = getAuth(firebaseApp); + +export const ui = initializeUI({ + app: firebaseApp, + behaviors: [autoAnonymousLogin()], + translations: [ + customLanguage(english.locale, { + labels: { + signIn: "Sign In", + }, + prompts: { + signInToAccount: "Sign in to your account", + }, + errors: { + invalidEmail: "Please enter a valid email address", + }, + }), + ], +}); + +if (import.meta.env.MODE === "development") { + connectAuthEmulator(auth, "http://localhost:9099"); +} diff --git a/examples/react/lib/firebase/config.ts b/examples/react/lib/firebase/config.ts new file mode 100644 index 0000000..fee2fca --- /dev/null +++ b/examples/react/lib/firebase/config.ts @@ -0,0 +1,11 @@ +export const firebaseConfig = { + apiKey: "AIzaSyAotbJXqnZxg9aAsULFn8MLwp_twtMUl2k", + authDomain: "ff-test-74aeb.firebaseapp.com", + databaseURL: + "https://ff-test-74aeb-default-rtdb.asia-southeast1.firebasedatabase.app", + projectId: "ff-test-74aeb", + storageBucket: "ff-test-74aeb.appspot.com", + messagingSenderId: "950537677105", + appId: "1:950537677105:web:da72ccc1718279f3cde810", + measurementId: "G-B5Y2YD83TJ", +}; diff --git a/examples/react/lib/firebase/hooks.ts b/examples/react/lib/firebase/hooks.ts new file mode 100644 index 0000000..56bc7ce --- /dev/null +++ b/examples/react/lib/firebase/hooks.ts @@ -0,0 +1,17 @@ +import { useState } from "react"; + +import { onAuthStateChanged } from "firebase/auth"; +import { User } from "firebase/auth"; +import { useEffect } from "react"; +import { auth } from "./clientApp"; + +export function useUser(initalUser?: User | null) { + const [user, setUser] = useState(initalUser ?? null); + + useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, setUser); + return () => unsubscribe(); + }, []); + + return user; +} \ No newline at end of file diff --git a/examples/react/lib/firebase/ui.tsx b/examples/react/lib/firebase/ui.tsx new file mode 100644 index 0000000..e8a97cb --- /dev/null +++ b/examples/react/lib/firebase/ui.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { ui } from "./clientApp"; +import { ConfigProvider } from "@firebase-ui/react"; + +export function FirebaseUIProvider({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/examples/react/package.json b/examples/react/package.json new file mode 100644 index 0000000..4f1ee4f --- /dev/null +++ b/examples/react/package.json @@ -0,0 +1,35 @@ +{ + "name": "react", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@firebase-ui/core": "workspace:*", + "@firebase-ui/react": "workspace:*", + "@firebase-ui/styles": "workspace:*", + "@firebase-ui/translations": "workspace:*", + "firebase": "^11.6.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.5.1" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.4", + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.1", + "tailwindcss": "^4.1.4" + } +} diff --git a/examples/react/public/vite.svg b/examples/react/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/react/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/react/src/App.css b/examples/react/src/App.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/react/src/App.jsx b/examples/react/src/App.jsx new file mode 100644 index 0000000..d1ec242 --- /dev/null +++ b/examples/react/src/App.jsx @@ -0,0 +1,110 @@ +import { NavLink } from "react-router"; +import { useUser } from "../lib/firebase/hooks"; + +function App() { + const user = useUser(); + + return ( +
+

Firebase UI Demo

+
+ {user &&
Welcome: {user.email || user.phoneNumber}
} +
+
+

Auth Screens

+
    +
  • + + Sign In Auth Screen + +
  • +
  • + + Sign In Auth Screen with Handlers + +
  • +
  • + + Sign In Auth Screen with OAuth + +
  • +
  • + + Email Link Auth Screen + +
  • +
  • + + Email Link Auth Screen with OAuth + +
  • +
  • + + Phone Auth Screen + +
  • +
  • + + Phone Auth Screen with OAuth + +
  • +
  • + + Sign Up Auth Screen + +
  • +
  • + + Sign Up Auth Screen with OAuth + +
  • +
  • + + OAuth Screen + +
  • +
  • + + Password Reset Screen + +
  • +
+
+
+ ); +} + +export default App; diff --git a/examples/react/src/assets/react.svg b/examples/react/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/examples/react/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/react/src/index.css b/examples/react/src/index.css new file mode 100644 index 0000000..2931ddf --- /dev/null +++ b/examples/react/src/index.css @@ -0,0 +1,5 @@ +@import "tailwindcss"; +@import "@firebase-ui/styles/src/base.css"; + +/* @import "@firebase-ui/styles/src/themes/dark.css"; */ +/* @import "@firebase-ui/styles/src/themes/brutalist.css"; */ \ No newline at end of file diff --git a/examples/react/src/main.jsx b/examples/react/src/main.jsx new file mode 100644 index 0000000..abbda05 --- /dev/null +++ b/examples/react/src/main.jsx @@ -0,0 +1,85 @@ +import { BrowserRouter, RouterProvider, Routes, Route } from "react-router"; + +import React from "react"; +import ReactDOM from "react-dom/client"; + +import App from "./App"; +import { Header } from "../lib/components/header"; +import { FirebaseUIProvider } from "../lib/firebase/ui"; + +/** Sign In */ +import SignInAuthScreenPage from "./screens/sign-in-auth-screen"; +import SignInAuthScreenWithHandlersPage from "./screens/sign-in-auth-screen-w-handlers"; +import SignInAuthScreenWithOAuthPage from "./screens/sign-in-auth-screen-w-oauth"; + +/** Email */ +import EmailLinkAuthScreenPage from "./screens/email-link-auth-screen"; +import EmailLinkAuthScreenWithOAuthPage from "./screens/email-link-auth-screen-w-oauth"; + +/** Phone Auth */ +import PhoneAuthScreenPage from "./screens/phone-auth-screen"; +import PhoneAuthScreenWithOAuthPage from "./screens/phone-auth-screen-w-oauth"; + +/** Sign up */ +import SignUpAuthScreenPage from "./screens/sign-in-auth-screen-w-oauth"; +import SignUpAuthScreenWithOAuthPage from "./screens/sign-in-auth-screen"; + +/** oAuth */ +import OAuthScreenPage from "./screens/oauth-screen"; + +/** Password Reset */ +import PasswordResetScreenPage from "./screens/password-reset-screen"; + +const root = document.getElementById("root"); + +ReactDOM.createRoot(root).render( + +
+ + + } /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + } /> + } + /> + + + +); diff --git a/examples/react/src/screens/email-link-auth-screen-w-oauth.tsx b/examples/react/src/screens/email-link-auth-screen-w-oauth.tsx new file mode 100644 index 0000000..074231f --- /dev/null +++ b/examples/react/src/screens/email-link-auth-screen-w-oauth.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { EmailLinkAuthScreen, GoogleSignInButton } from "@firebase-ui/react"; + +export default function EmailLinkAuthScreenWithOAuthPage() { + return ( + + + + ); +} diff --git a/examples/react/src/screens/email-link-auth-screen.tsx b/examples/react/src/screens/email-link-auth-screen.tsx new file mode 100644 index 0000000..f7c3045 --- /dev/null +++ b/examples/react/src/screens/email-link-auth-screen.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { EmailLinkAuthScreen } from "@firebase-ui/react"; + +export default function EmailLinkAuthScreenPage() { + return ; +} diff --git a/examples/react/src/screens/oauth-screen.tsx b/examples/react/src/screens/oauth-screen.tsx new file mode 100644 index 0000000..f0fb139 --- /dev/null +++ b/examples/react/src/screens/oauth-screen.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { GoogleSignInButton, OAuthScreen } from "@firebase-ui/react"; + +export default function OAuthScreenPage() { + return ( + + + + ); +} diff --git a/examples/react/src/screens/password-reset-screen.tsx b/examples/react/src/screens/password-reset-screen.tsx new file mode 100644 index 0000000..9b029a2 --- /dev/null +++ b/examples/react/src/screens/password-reset-screen.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { PasswordResetScreen } from "@firebase-ui/react"; + +export default function PasswordResetScreenPage() { + return ( + {}} + /> + ); +} diff --git a/examples/react/src/screens/phone-auth-screen-w-oauth.tsx b/examples/react/src/screens/phone-auth-screen-w-oauth.tsx new file mode 100644 index 0000000..97e883a --- /dev/null +++ b/examples/react/src/screens/phone-auth-screen-w-oauth.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { GoogleSignInButton, PhoneAuthScreen } from "@firebase-ui/react"; + +export default function PhoneAuthScreenWithOAuthPage() { + return ( + + + + ); +} diff --git a/examples/react/src/screens/phone-auth-screen.tsx b/examples/react/src/screens/phone-auth-screen.tsx new file mode 100644 index 0000000..d63d16c --- /dev/null +++ b/examples/react/src/screens/phone-auth-screen.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { PhoneAuthScreen } from "@firebase-ui/react"; + +export default function PhoneAuthScreenPage() { + return ; +} diff --git a/examples/react/src/screens/sign-in-auth-screen-w-handlers.tsx b/examples/react/src/screens/sign-in-auth-screen-w-handlers.tsx new file mode 100644 index 0000000..fdc8c86 --- /dev/null +++ b/examples/react/src/screens/sign-in-auth-screen-w-handlers.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { SignInAuthScreen } from "@firebase-ui/react"; + +export default function SignInAuthScreenWithHandlersPage() { + return ( + {}} + onRegisterClick={() => {}} + /> + ); +} diff --git a/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx b/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx new file mode 100644 index 0000000..944850f --- /dev/null +++ b/examples/react/src/screens/sign-in-auth-screen-w-oauth.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { GoogleSignInButton, SignInAuthScreen } from "@firebase-ui/react"; +import { useNavigate } from "react-router"; + +export default function SignInAuthScreenWithOAuthPage() { + let navigate = useNavigate(); + + return ( + navigate("/password-reset-screen")} + onRegisterClick={() => navigate("/sign-up-auth-screen")} + > + + + ); +} diff --git a/examples/react/src/screens/sign-in-auth-screen.tsx b/examples/react/src/screens/sign-in-auth-screen.tsx new file mode 100644 index 0000000..3311477 --- /dev/null +++ b/examples/react/src/screens/sign-in-auth-screen.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { SignInAuthScreen } from "@firebase-ui/react"; + +export default function SignInAuthScreenPage() { + return ; +} diff --git a/examples/react/src/screens/sign-up-auth-screen-w-oauth.tsx b/examples/react/src/screens/sign-up-auth-screen-w-oauth.tsx new file mode 100644 index 0000000..bf57c48 --- /dev/null +++ b/examples/react/src/screens/sign-up-auth-screen-w-oauth.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { GoogleSignInButton, SignUpAuthScreen } from "@firebase-ui/react"; + +export default function SignUpAuthScreenWithOAuthPage() { + return ( + + + + ); +} diff --git a/examples/react/src/screens/sign-up-auth-screen.tsx b/examples/react/src/screens/sign-up-auth-screen.tsx new file mode 100644 index 0000000..8fb529c --- /dev/null +++ b/examples/react/src/screens/sign-up-auth-screen.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { SignUpAuthScreen } from "@firebase-ui/react"; + +export default function SignUpAuthScreenPage() { + return ; +} diff --git a/examples/react/vite.config.js b/examples/react/vite.config.js new file mode 100644 index 0000000..8a24cd2 --- /dev/null +++ b/examples/react/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [tailwindcss(), react()], +});