Add example project
This commit is contained in:
parent
20b12f3c60
commit
db9e65e9d7
18 changed files with 4650 additions and 0 deletions
13
example/frontend/index.html
Normal file
13
example/frontend/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>TAuth SDK Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
21
example/frontend/package.json
Normal file
21
example/frontend/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@tauth-example/frontend",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1 --port 5173",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview --host 127.0.0.1 --port 5173"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"vite": "^7.3.0",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
139
example/frontend/src/main.tsx
Normal file
139
example/frontend/src/main.tsx
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { StrictMode, useEffect, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./styles.css";
|
||||
|
||||
type User = {
|
||||
status: string;
|
||||
username: string;
|
||||
public_key: string;
|
||||
user_id: number;
|
||||
iota_id: number;
|
||||
sub_level: number;
|
||||
sub_end: number;
|
||||
display: string;
|
||||
status_message: string;
|
||||
about: string;
|
||||
};
|
||||
|
||||
type MeResponse = {
|
||||
logged_in: boolean;
|
||||
session_id?: number;
|
||||
user?: User;
|
||||
app_data?: unknown;
|
||||
};
|
||||
|
||||
function App() {
|
||||
const [me, setMe] = useState<MeResponse | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
async function refresh() {
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch("/api/me", { credentials: "include" });
|
||||
if (!response.ok) throw new Error(await response.text());
|
||||
setMe(await response.json());
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
|
||||
async function saveExampleData() {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch("/api/app-data", {
|
||||
method: "PUT",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
savedFrom: "vite-react-example",
|
||||
savedAt: new Date().toISOString(),
|
||||
}),
|
||||
});
|
||||
if (!response.ok) throw new Error(await response.text());
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="shell">
|
||||
<section className="hero">
|
||||
<p className="eyebrow">TAuth SDK Example</p>
|
||||
<h1>Minimal TAuth login with a Rust backend.</h1>
|
||||
<p className="lede">
|
||||
This Vite app calls a tiny backend that uses the local Rust TAuth SDK,
|
||||
stores the callback session in JSON, and shows the logged-in user.
|
||||
</p>
|
||||
|
||||
<div className="actions">
|
||||
<a className="primary" href="/api/login">
|
||||
Login with TAuth
|
||||
</a>
|
||||
<button type="button" onClick={() => void refresh()}>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{error ? <pre className="error">{error}</pre> : null}
|
||||
|
||||
<section className="card">
|
||||
<div className="cardHeader">
|
||||
<h2>Session</h2>
|
||||
<span className={me?.logged_in ? "pill good" : "pill"}>
|
||||
{me?.logged_in ? "Logged in" : "Anonymous"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{!me ? (
|
||||
<p>Loading...</p>
|
||||
) : me.logged_in && me.user ? (
|
||||
<div className="grid">
|
||||
<Info label="Display" value={me.user.display || me.user.username} />
|
||||
<Info label="Username" value={me.user.username} />
|
||||
<Info label="User ID" value={String(me.user.user_id)} />
|
||||
<Info label="Session ID" value={String(me.session_id)} />
|
||||
<Info label="Status" value={me.user.status_message || me.user.status} />
|
||||
<Info label="Subscription" value={String(me.user.sub_level)} />
|
||||
</div>
|
||||
) : (
|
||||
<p>No TAuth session yet. Click the login button to start the flow.</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="card">
|
||||
<div className="cardHeader">
|
||||
<h2>App Data</h2>
|
||||
<button disabled={!me?.logged_in || saving} onClick={() => void saveExampleData()}>
|
||||
{saving ? "Saving..." : "Save Example Data"}
|
||||
</button>
|
||||
</div>
|
||||
<pre className="json">{JSON.stringify(me?.app_data ?? null, null, 2)}</pre>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function Info({ label, value }: { label: string; value?: string }) {
|
||||
return (
|
||||
<div className="info">
|
||||
<span>{label}</span>
|
||||
<strong>{value || "-"}</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
167
example/frontend/src/styles.css
Normal file
167
example/frontend/src/styles.css
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
:root {
|
||||
color-scheme: dark;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
background: #07070a;
|
||||
color: #f4f4f5;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(95, 91, 255, 0.35), transparent 34rem),
|
||||
radial-gradient(circle at bottom right, rgba(24, 197, 141, 0.18), transparent 28rem),
|
||||
#07070a;
|
||||
}
|
||||
|
||||
button,
|
||||
a.primary {
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
border-radius: 999px;
|
||||
padding: 0.8rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #f4f4f5;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
a.primary {
|
||||
border: 0;
|
||||
background: linear-gradient(135deg, #8b5cf6, #14b8a6);
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.shell {
|
||||
width: min(980px, calc(100% - 2rem));
|
||||
margin: 0 auto;
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
color: #5eead4;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 760px;
|
||||
margin: 0;
|
||||
font-size: clamp(2.4rem, 7vw, 5.8rem);
|
||||
line-height: 0.95;
|
||||
letter-spacing: -0.06em;
|
||||
}
|
||||
|
||||
.lede {
|
||||
max-width: 700px;
|
||||
color: #c4c4cc;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.actions,
|
||||
.cardHeader {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cardHeader {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-top: 1rem;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 28px;
|
||||
background: rgba(10, 10, 14, 0.72);
|
||||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.32);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 1rem;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.info span {
|
||||
display: block;
|
||||
color: #a1a1aa;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.info strong {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.pill {
|
||||
padding: 0.35rem 0.7rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #d4d4d8;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pill.good {
|
||||
background: rgba(20, 184, 166, 0.18);
|
||||
color: #5eead4;
|
||||
}
|
||||
|
||||
.json,
|
||||
.error {
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
border-radius: 18px;
|
||||
background: rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 1rem 0;
|
||||
color: #fecaca;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.shell {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
1
example/frontend/src/vite-env.d.ts
vendored
Normal file
1
example/frontend/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
20
example/frontend/tsconfig.json
Normal file
20
example/frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
12
example/frontend/vite.config.ts
Normal file
12
example/frontend/vite.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": "http://127.0.0.1:8787",
|
||||
"/tauth": "http://127.0.0.1:8787",
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue