Autenticação
Supabase Auth
- supabase.auth.signUp
- supabase.auth.signInWithPassword
- supabase.auth.signInWithOAuth
- supabase.auth.signOut
- supabase.auth.onAuthStateChange
Banco de Dados
Coluna uid
na tabela investments
:
create table
public.investments (
name character varying null,
created_at timestamp with time zone not null default now(),
value bigint null,
origin character varying null,
category character varying null,
interest character varying null,
id uuid not null default gen_random_uuid (),
uid uuid null,
constraint investments_pkey primary key (id),
constraint investments_uid_fkey foreign key (uid) references auth.users (id)
) tablespace pg_default;
create table
public.investments (
name character varying null,
created_at timestamp with time zone not null default now(),
value bigint null,
origin character varying null,
category character varying null,
interest character varying null,
id uuid not null default gen_random_uuid (),
uid uuid null,
constraint investments_pkey primary key (id),
constraint investments_uid_fkey foreign key (uid) references auth.users (id)
) tablespace pg_default;
Permitir Acesso Anônimo
Row Level Security (Authentication > Configuration > Policies) - authenticated users:
-- Turn on security
alter table "investments"
enable row level security;
-- Allow anonymous access
CREATE POLICY "Allow anonymous access"
ON investments
FOR SELECT
TO anon, authenticated
USING (true);
-- Turn on security
alter table "investments"
enable row level security;
-- Allow anonymous access
CREATE POLICY "Allow anonymous access"
ON investments
FOR SELECT
TO anon, authenticated
USING (true);
Google Provider
InvestApp (Auth)
Arquivos
invest-app
├── README.md
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── next.svg
│ └── vercel.svg
├── src
│ ├── app
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── investments
│ │ │ └── [slug]
│ │ │ └── page.jsx
│ │ ├── layout.jsx
│ │ ├── page.jsx
│ │ ├── signin
│ │ │ └── page.jsx
│ │ └── signup
│ │ └── page.jsx
│ ├── components
│ │ ├── InvestmentCard.jsx
│ │ ├── InvestmentForm.jsx
│ │ ├── Modal.jsx
│ │ ├── NavBar.jsx
│ │ └── ProtectedPage.jsx
│ ├── contexts
│ │ ├── InvestmentContext.jsx
│ │ └── UserAuthContext.jsx
│ ├── lib
│ │ └── format.js
│ └── services
│ ├── storage.js
│ └── supabase.js
└── tailwind.config.js
Arquivos
invest-app
├── README.md
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── next.svg
│ └── vercel.svg
├── src
│ ├── app
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── investments
│ │ │ └── [slug]
│ │ │ └── page.jsx
│ │ ├── layout.jsx
│ │ ├── page.jsx
│ │ ├── signin
│ │ │ └── page.jsx
│ │ └── signup
│ │ └── page.jsx
│ ├── components
│ │ ├── InvestmentCard.jsx
│ │ ├── InvestmentForm.jsx
│ │ ├── Modal.jsx
│ │ ├── NavBar.jsx
│ │ └── ProtectedPage.jsx
│ ├── contexts
│ │ ├── InvestmentContext.jsx
│ │ └── UserAuthContext.jsx
│ ├── lib
│ │ └── format.js
│ └── services
│ ├── storage.js
│ └── supabase.js
└── tailwind.config.js
/codes/react/supabase-auth/invest-app/src/services/supabase.js
import { createClient } from '@supabase/supabase-js';
const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_KEY;
const API_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
export const supabase = createClient(API_URL, API_KEY);
/codes/react/supabase-auth/invest-app/src/services/supabase.js
import { createClient } from '@supabase/supabase-js';
const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_KEY;
const API_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
export const supabase = createClient(API_URL, API_KEY);
/codes/react/supabase-auth/invest-app/src/contexts/UserAuthContext.jsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { supabase } from '@/services/supabase';
const UserAuthContext = createContext({});
export function UserAuthContextProvider({ children }) {
const [user, setUser] = useState({});
const [loading, setLoading] = useState(true);
function logIn(email, password) {
return supabase.auth.signInWithPassword({ email, password });
}
function signUp(email, password) {
return supabase.auth.signUp({ email, password });
}
function logOut() {
setUser(null);
return supabase.auth.signOut();
}
async function googleSignIn() {
return await supabase.auth.signInWithOAuth({
provider: 'google',
});
}
useEffect(() => {
const {
data: { subscription: authListener },
} = supabase.auth.onAuthStateChange((event, session) => {
console.log('session', event, session);
if (session) {
setUser(session?.user);
} else {
setUser(null);
}
setLoading(false);
});
return () => {
authListener?.unsubscribe();
};
}, []);
return (
<UserAuthContext.Provider
value={{ user, logIn, signUp, logOut, googleSignIn }}
>
{loading ? <div>Loading...</div> : children}
</UserAuthContext.Provider>
);
}
export function useUserAuth() {
return useContext(UserAuthContext);
}
/codes/react/supabase-auth/invest-app/src/contexts/UserAuthContext.jsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { supabase } from '@/services/supabase';
const UserAuthContext = createContext({});
export function UserAuthContextProvider({ children }) {
const [user, setUser] = useState({});
const [loading, setLoading] = useState(true);
function logIn(email, password) {
return supabase.auth.signInWithPassword({ email, password });
}
function signUp(email, password) {
return supabase.auth.signUp({ email, password });
}
function logOut() {
setUser(null);
return supabase.auth.signOut();
}
async function googleSignIn() {
return await supabase.auth.signInWithOAuth({
provider: 'google',
});
}
useEffect(() => {
const {
data: { subscription: authListener },
} = supabase.auth.onAuthStateChange((event, session) => {
console.log('session', event, session);
if (session) {
setUser(session?.user);
} else {
setUser(null);
}
setLoading(false);
});
return () => {
authListener?.unsubscribe();
};
}, []);
return (
<UserAuthContext.Provider
value={{ user, logIn, signUp, logOut, googleSignIn }}
>
{loading ? <div>Loading...</div> : children}
</UserAuthContext.Provider>
);
}
export function useUserAuth() {
return useContext(UserAuthContext);
}
/codes/react/supabase-auth/invest-app/src/components/ProtectedPage.jsx
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useUserAuth } from '@/contexts/UserAuthContext';
export default function ProtectedPage({ children }) {
const { user } = useUserAuth();
const router = useRouter();
if (user == null) {
router.push('/signin');
}
useEffect(() => {
if (user == null) {
router.push('/signin');
}
}, [user]);
return <>{children}</>;
}
/codes/react/supabase-auth/invest-app/src/components/ProtectedPage.jsx
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import { useUserAuth } from '@/contexts/UserAuthContext';
export default function ProtectedPage({ children }) {
const { user } = useUserAuth();
const router = useRouter();
if (user == null) {
router.push('/signin');
}
useEffect(() => {
if (user == null) {
router.push('/signin');
}
}, [user]);
return <>{children}</>;
}
/codes/react/supabase-auth/invest-app/src/components/NavBar.jsx
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { useUserAuth } from '@/contexts/UserAuthContext';
const initialMenu = [
{ title: 'Entrar', path: '/signin' },
{ title: 'Cadastrar', path: '/signup' },
];
const userMenu = [
{ title: 'Investimentos', path: '/' },
// { title: 'Extrato', path: '/report' },
];
export default function NavBar() {
const [menu, setMenu] = useState(initialMenu);
const pathname = usePathname();
const router = useRouter();
const { logOut, user } = useUserAuth();
const handleLogout = async () => {
try {
router.push('/signin');
await logOut();
} catch (error) {
console.log(error.message);
}
};
useEffect(() => {
if (user) {
setMenu(userMenu);
} else {
setMenu(initialMenu);
}
}, [user]);
return (
<nav className="bg-white border-gray-200 dark:bg-gray-900">
<div className="max-w-screen-lg flex flex-wrap items-center justify-between mx-auto p-4">
<Link
href="/"
className="text-2xl font-semibold whitespace-nowrap dark:text-white"
>
InvestApp
</Link>
<button
data-collapse-toggle="navbar-default"
type="button"
className="inline-flex items-center p-2 ml-3 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="navbar-default"
aria-expanded="false"
>
<span className="sr-only">Abrir menu</span>
<svg
className="w-6 h-6"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clipRule="evenodd"
></path>
</svg>
</button>
<div className="hidden w-full md:block md:w-auto" id="navbar-default">
<ul className="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
{menu.map((item, index) => (
<li key={index}>
{pathname === item.path ? (
<Link
href={item.path}
className="block py-2 pl-3 pr-4 text-white bg-gray-600 rounded md:bg-transparent md:text-gray-800 md:hover:text-gray-500 md:p-0 dark:text-white md:dark:text-gray-500"
aria-current="page"
>
{item.title}
</Link>
) : (
<Link
href={item.path}
className="block py-2 pl-3 pr-4 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-gray-700 md:p-0 dark:text-white md:dark:hover:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
>
{item.title}
</Link>
)}
</li>
))}
{user && (
<li>
<button
type="button"
className="block py-2 pl-3 pr-4 text-gray-500 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-gray-700 md:p-0 dark:text-white md:dark:hover:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
onClick={handleLogout}
>
Sair
</button>
</li>
)}
</ul>
</div>
</div>
</nav>
);
}
/codes/react/supabase-auth/invest-app/src/components/NavBar.jsx
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { useUserAuth } from '@/contexts/UserAuthContext';
const initialMenu = [
{ title: 'Entrar', path: '/signin' },
{ title: 'Cadastrar', path: '/signup' },
];
const userMenu = [
{ title: 'Investimentos', path: '/' },
// { title: 'Extrato', path: '/report' },
];
export default function NavBar() {
const [menu, setMenu] = useState(initialMenu);
const pathname = usePathname();
const router = useRouter();
const { logOut, user } = useUserAuth();
const handleLogout = async () => {
try {
router.push('/signin');
await logOut();
} catch (error) {
console.log(error.message);
}
};
useEffect(() => {
if (user) {
setMenu(userMenu);
} else {
setMenu(initialMenu);
}
}, [user]);
return (
<nav className="bg-white border-gray-200 dark:bg-gray-900">
<div className="max-w-screen-lg flex flex-wrap items-center justify-between mx-auto p-4">
<Link
href="/"
className="text-2xl font-semibold whitespace-nowrap dark:text-white"
>
InvestApp
</Link>
<button
data-collapse-toggle="navbar-default"
type="button"
className="inline-flex items-center p-2 ml-3 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="navbar-default"
aria-expanded="false"
>
<span className="sr-only">Abrir menu</span>
<svg
className="w-6 h-6"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clipRule="evenodd"
></path>
</svg>
</button>
<div className="hidden w-full md:block md:w-auto" id="navbar-default">
<ul className="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
{menu.map((item, index) => (
<li key={index}>
{pathname === item.path ? (
<Link
href={item.path}
className="block py-2 pl-3 pr-4 text-white bg-gray-600 rounded md:bg-transparent md:text-gray-800 md:hover:text-gray-500 md:p-0 dark:text-white md:dark:text-gray-500"
aria-current="page"
>
{item.title}
</Link>
) : (
<Link
href={item.path}
className="block py-2 pl-3 pr-4 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-gray-700 md:p-0 dark:text-white md:dark:hover:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
>
{item.title}
</Link>
)}
</li>
))}
{user && (
<li>
<button
type="button"
className="block py-2 pl-3 pr-4 text-gray-500 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-gray-700 md:p-0 dark:text-white md:dark:hover:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
onClick={handleLogout}
>
Sair
</button>
</li>
)}
</ul>
</div>
</div>
</nav>
);
}
/codes/react/supabase-auth/invest-app/src/app/signup/page.jsx
'use client';
import { useUserAuth } from '@/contexts/UserAuthContext';
import { useState } from 'react';
export default function Signup() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [message, setMessage] = useState('');
const { signUp } = useUserAuth();
const handleSubmit = async (event) => {
event.preventDefault();
setMessage('');
if (password !== confirmPassword) {
setMessage('As senhas não conferem.');
}
try {
if (!message) {
await signUp(email, password);
setMessage('Acess sua caixa de email para confirmar o cadastro.');
}
} catch (err) {
setMessage('Erro no cadastro, tente novamente.');
}
};
return (
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm h-full px-6 py-12 lg:px-8">
<h1 className="mb-8 text-3xl text-center">Cadastro</h1>
{message && (
<div class="bg-gray-300 text-gray-700 p-4 my-4" role="alert">
<p>{message}</p>
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
htmlFor="email"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Email
</label>
<input
id="email"
type="email"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Senha
</label>
<input
id="password"
type="password"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Confirmar Senha
</label>
<input
id="confirmPassword"
type="password"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<button
type="submit"
className="flex w-full justify-center rounded-md bg-gray-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cadastrar
</button>
</form>
</div>
);
}
/codes/react/supabase-auth/invest-app/src/app/signup/page.jsx
'use client';
import { useUserAuth } from '@/contexts/UserAuthContext';
import { useState } from 'react';
export default function Signup() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [message, setMessage] = useState('');
const { signUp } = useUserAuth();
const handleSubmit = async (event) => {
event.preventDefault();
setMessage('');
if (password !== confirmPassword) {
setMessage('As senhas não conferem.');
}
try {
if (!message) {
await signUp(email, password);
setMessage('Acess sua caixa de email para confirmar o cadastro.');
}
} catch (err) {
setMessage('Erro no cadastro, tente novamente.');
}
};
return (
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm h-full px-6 py-12 lg:px-8">
<h1 className="mb-8 text-3xl text-center">Cadastro</h1>
{message && (
<div class="bg-gray-300 text-gray-700 p-4 my-4" role="alert">
<p>{message}</p>
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
htmlFor="email"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Email
</label>
<input
id="email"
type="email"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Senha
</label>
<input
id="password"
type="password"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Confirmar Senha
</label>
<input
id="confirmPassword"
type="password"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<button
type="submit"
className="flex w-full justify-center rounded-md bg-gray-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Cadastrar
</button>
</form>
</div>
);
}
/codes/react/supabase-auth/invest-app/src/app/signin/page.jsx
'use client';
import { useUserAuth } from '@/contexts/UserAuthContext';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const { logIn, googleSignIn } = useUserAuth();
const router = useRouter();
const handleLogin = async (event) => {
event.preventDefault();
setError('');
try {
await logIn(email, password);
router.push('/');
} catch (err) {
setError('Erro no login, tente novamente.');
}
};
const handleGoogleSignIn = async (event) => {
event.preventDefault();
try {
await googleSignIn();
router.push('/');
} catch (error) {
setError('Erro no login, tente novamente.');
}
};
return (
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm h-full px-6 py-12 lg:px-8">
<h1 className="mb-8 text-3xl text-center">Entrar</h1>
{error && (
<div className="bg-orange-100 text-orange-700 p-4 my-4" role="alert">
<p>{error}</p>
</div>
)}
<form onSubmit={handleLogin}>
<div className="mb-4">
<label
htmlFor="email"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Email
</label>
<input
id="email"
type="email"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Senha
</label>
<input
id="password"
type="password"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button
type="submit"
className="flex w-full justify-center rounded-md bg-gray-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Entrar
</button>
<div className="my-4 flex items-center before:mt-0.5 before:flex-1 before:border-t before:border-neutral-300 after:mt-0.5 after:flex-1 after:border-t after:border-neutral-300">
<p className="mx-4 mb-0 text-center font-semibold dark:text-neutral-200">
ou
</p>
</div>
<button
type="button"
className="text-white w-full mt-4 bg-[#4285F4] hover:bg-[#4285F4]/90 focus:ring-4 focus:outline-none focus:ring-[#4285F4]/50 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center justify-between dark:focus:ring-[#4285F4]/55 mr-2 mb-2"
onClick={handleGoogleSignIn}
>
<svg
className="mr-2 -ml-1 w-4 h-4"
aria-hidden="true"
focusable="false"
data-prefix="fab"
data-icon="google"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 488 512"
>
<path
fill="currentColor"
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
></path>
</svg>
Entrar com Google<div></div>
</button>
</form>
</div>
);
}
/codes/react/supabase-auth/invest-app/src/app/signin/page.jsx
'use client';
import { useUserAuth } from '@/contexts/UserAuthContext';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const { logIn, googleSignIn } = useUserAuth();
const router = useRouter();
const handleLogin = async (event) => {
event.preventDefault();
setError('');
try {
await logIn(email, password);
router.push('/');
} catch (err) {
setError('Erro no login, tente novamente.');
}
};
const handleGoogleSignIn = async (event) => {
event.preventDefault();
try {
await googleSignIn();
router.push('/');
} catch (error) {
setError('Erro no login, tente novamente.');
}
};
return (
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm h-full px-6 py-12 lg:px-8">
<h1 className="mb-8 text-3xl text-center">Entrar</h1>
{error && (
<div className="bg-orange-100 text-orange-700 p-4 my-4" role="alert">
<p>{error}</p>
</div>
)}
<form onSubmit={handleLogin}>
<div className="mb-4">
<label
htmlFor="email"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Email
</label>
<input
id="email"
type="email"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-gray-900 mb-2"
>
Senha
</label>
<input
id="password"
type="password"
className="block p-2 w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button
type="submit"
className="flex w-full justify-center rounded-md bg-gray-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Entrar
</button>
<div className="my-4 flex items-center before:mt-0.5 before:flex-1 before:border-t before:border-neutral-300 after:mt-0.5 after:flex-1 after:border-t after:border-neutral-300">
<p className="mx-4 mb-0 text-center font-semibold dark:text-neutral-200">
ou
</p>
</div>
<button
type="button"
className="text-white w-full mt-4 bg-[#4285F4] hover:bg-[#4285F4]/90 focus:ring-4 focus:outline-none focus:ring-[#4285F4]/50 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center justify-between dark:focus:ring-[#4285F4]/55 mr-2 mb-2"
onClick={handleGoogleSignIn}
>
<svg
className="mr-2 -ml-1 w-4 h-4"
aria-hidden="true"
focusable="false"
data-prefix="fab"
data-icon="google"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 488 512"
>
<path
fill="currentColor"
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
></path>
</svg>
Entrar com Google<div></div>
</button>
</form>
</div>
);
}