Skip to content
Blog

Custom Login UI

When your app needs its own branded login experience, you can build custom forms and wire them into the SDK through tribe.login() and tribe.register(). You get complete control over the interface while Tribe handles session management, token storage, and security checks like breached password detection under the hood.

import { Tribe } from "@tribecloud/sdk";
import { useState } from "react";
const tribe = new Tribe({ siteId: "YOUR_SITE_ID" });
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
try {
const { user } = await tribe.login(email, password);
console.log("Logged in:", user);
} catch (err) {
setError(err.message);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit">Sign In</button>
</form>
);
}
const { user } = await tribe.register(email, password);

Registration behaves just like login from your code’s perspective. The session and user tokens are stored automatically, and the user is signed in the moment their account is created.

Before rendering a login form, it’s worth checking whether the user already has a valid session. This avoids showing a login page to someone who’s already signed in:

const session = await tribe.getSession();
if (session) {
// User is already logged in
console.log("Welcome back:", session.user);
} else {
// Show login form
}
await tribe.logout();
// Session token and user token are cleared from localStorage

A common pattern in React apps is centralizing auth state in a context provider. This makes the current user and auth actions available anywhere in your component tree without prop drilling:

import { Tribe } from "@tribecloud/sdk";
import { createContext, useContext, useEffect, useState } from "react";
const tribe = new Tribe({ siteId: "YOUR_SITE_ID" });
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
tribe.getSession().then((session) => {
setUser(session?.user ?? null);
setLoading(false);
});
}, []);
const login = async (email, password) => {
const { user } = await tribe.login(email, password);
setUser(user);
};
const register = async (email, password) => {
const { user } = await tribe.register(email, password);
setUser(user);
};
const logout = async () => {
await tribe.logout();
setUser(null);
};
return (
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
};

After a user registers, Tribe sends them a verification email automatically. You just need a page that handles the token when they click the link:

// On your /verify-email?token=xxx page
const token = new URLSearchParams(window.location.search).get("token");
await tribe.verifyEmail(token);

If the user needs the email resent, you can trigger that programmatically:

await tribe.resendVerification();
// Optionally override the verify URL:
await tribe.resendVerification({ verifyEmailUrl: "https://myapp.com/verify-email" });

To kick off a password reset, send the user a reset email. Tribe delivers it; you just provide the address:

await tribe.forgotPassword(email);
// Optionally override the reset URL:
await tribe.forgotPassword(email, { resetPasswordUrl: "https://myapp.com/reset-password" });

Then on your reset page, pull the token from the URL and set the new password:

// On your /reset-password?token=xxx page
const token = new URLSearchParams(window.location.search).get("token");
await tribe.resetPassword(token, newPassword);

Sites can operate in either "public" or "internal" access mode. Querying the auth config lets you adapt your UI accordingly, for example hiding the registration form on internal sites:

const config = await tribe.getAuthConfig();
if (config.accessMode === "internal") {
// Only organization members can register/login
// Consider showing "Contact your admin" instead of a register form
}