persian-auth
v0.1.1
Published
Persian (Farsi) authentication React components: phone + OTP, username/password, email, and GitHub OAuth.
Downloads
12
Maintainers
Readme
persian-auth
Persian (Farsi) authentication React components — without React Query.
Phone + OTP, username/password, email, and GitHub OAuth. RTL-ready, fully typed, framework-agnostic.
Highlights
- Drop-in Persian login UI for phone + OTP, username/password, email/password, and GitHub OAuth.
- Ships ESM + CJS with full type definitions.
- Works in any React 18/19 app — Next.js, Vite, Remix, CRA, etc.
- No React Query, no global cache, no extra setup — just render a component.
- All Persian copy is built in. Validation powered by
zod. - Pure callbacks: success and error are surfaced as typed handlers.
Why no React Query?
persian-auth is designed for authentication flows, not general server-state caching.
Most auth screens only need a small set of async actions:
- request an OTP
- verify an OTP
- submit username/password or email/password credentials
- start an OAuth redirect
- receive a typed success or error result
This library owns those auth-specific loading and error states for you. You don't need to wrap login screens in useMutation, configure a QueryClient, or pull in a server-state cache just to build a login page.
If you already use React Query in your app, that's fine —
persian-authdoesn't fight it. It just doesn't require it.
Auth Actions, in one sentence
An auth action is a focused async operation used by an authentication UI — request OTP, verify OTP, submit credentials, start OAuth, log out.
Unlike a data-fetching library, persian-auth intentionally does not manage query keys, cache invalidation, stale times, pagination, or background refetching. It only handles the state needed for authentication flows.
Table of contents
- Install
- Quick start
- Wiring up a real backend
- GitHub OAuth
- Public API
- Repository layout
- Scripts
- Publishing
- License
Install
npm install persian-auth zod
# or
pnpm add persian-auth zod
# or
yarn add persian-auth zodreact and react-dom are peer dependencies (>= 18).
Quick start
import {
PersianLoginLibrary,
GithubLoginButton,
type AuthSuccessData,
} from "persian-auth";
import "persian-auth/styles.css"; // optional default styling
export function LoginPage() {
const onAuthSuccess = (data: AuthSuccessData) => {
console.log("logged in:", data);
};
const onAuthError = (err: string) => console.error(err);
return (
<>
<PersianLoginLibrary
type="phone"
onAuthSuccess={onAuthSuccess}
onAuthError={onAuthError}
/>
<PersianLoginLibrary
type="email"
onAuthSuccess={onAuthSuccess}
onAuthError={onAuthError}
/>
<PersianLoginLibrary
type="username"
mode="signup"
onAuthSuccess={onAuthSuccess}
onAuthError={onAuthError}
/>
<GithubLoginButton
clientId={process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID!}
redirectUri="https://example.com/login"
onAuthSuccess={onAuthSuccess}
onError={onAuthError}
/>
</>
);
}Wrap your app in an RTL container so layout renders correctly:
<html lang="fa" dir="rtl">
…
</html>Wiring up a real backend
By default the form simulates API calls with an 800 ms delay. Pass your own async functions to talk to a real backend — no useMutation needed, the library tracks loading and errors for you:
<PersianLoginForm
type="phone"
onAuthSuccess={onAuthSuccess}
onError={onAuthError}
requestOtp={async (phoneNumber) => {
await fetch("/api/otp/request", {
method: "POST",
body: JSON.stringify({ phoneNumber }),
});
}}
verifyOtp={async (phoneNumber, code) => {
const res = await fetch("/api/otp/verify", {
method: "POST",
body: JSON.stringify({ phoneNumber, code }),
});
if (!res.ok) throw new Error("کد تأیید اشتباه است");
}}
submitCredentials={async ({ username, email, password }, mode) => {
const res = await fetch(`/api/auth/${mode}`, {
method: "POST",
body: JSON.stringify({ username, email, password }),
});
if (!res.ok) throw new Error("خطا در احراز هویت");
}}
/>Anything thrown becomes a typed error in onError. Anything resolved triggers onAuthSuccess with a typed payload.
GitHub OAuth
GithubLoginButton performs the redirect half of the standard authorization-code flow:
- On click, it redirects the browser to
https://github.com/login/oauth/authorizewithclient_id,redirect_uri,scope, and a CSRFstate(persisted insessionStorage). - GitHub redirects back to your
redirectUriwith?code=…&state=…. - The button, when rendered on that return page, reads the
code, validatesstate, and callsonAuthSuccess({ type: "github", provider: "github", githubCode }). - Your server must exchange the code for a token — this requires the client secret and cannot be done in the browser.
Typical server-side exchange:
// Next.js Route Handler example: app/api/github/callback/route.ts
const res = await fetch("https://github.com/login/oauth/access_token", {
method: "POST",
headers: { Accept: "application/json", "Content-Type": "application/json" },
body: JSON.stringify({
client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code,
redirect_uri: process.env.GITHUB_REDIRECT_URI,
}),
});Public API
| Export | Kind | Purpose |
| --------------------------------------------- | --------- | ---------------------------------------------------------------------------------- |
| PersianLoginLibrary | component | High-level wrapper with onAuthSuccess / onAuthError. |
| PersianLoginForm | component | Lower-level form. Accepts custom requestOtp / verifyOtp / submitCredentials. |
| GithubLoginButton | component | OAuth kick-off + automatic callback handler. |
| usePersianLoginForm | hook | Headless form state and handlers for custom UI. |
| LoginType, FormMode, AuthSuccessData, … | types | See src/types.ts. |
Repository layout
src/ # library source (shipped)
index.ts # public API
components/ # PersianLoginForm, PersianLoginLibrary, GithubLoginButton
hooks/ # usePersianLoginForm
types.ts # shared public types
styles.css # optional default styling (opt-in)
app/ # Next.js demo / showcase (not shipped)
dist/ # build output (git-ignored)Scripts
npm run dev # start the Next.js demo at http://localhost:3000
npm run build # production build of the demo app
npm run typecheck # tsc --noEmit across the whole repo
npm run build:lib # bundle src/ into dist/ (ESM + CJS + .d.ts) via tsupLicense
MIT
