fractostate 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ import { useFlow } from "fractostate";
2
+ import { AuthFlow } from "../store/auth";
3
+ import { CartFlow, UIFlow } from "../store/flows";
4
+ import { Link } from "react-router-dom";
5
+
6
+ export default function Layout({ children }: { children: React.ReactNode }) {
7
+ const [auth, { ops: authOps }] = useFlow(AuthFlow);
8
+ const [cart] = useFlow(CartFlow);
9
+ const [, { ops: uiOps }] = useFlow(UIFlow);
10
+
11
+ const cartCount = cart.items.reduce((acc, item) => acc + item.quantity, 0);
12
+
13
+ return (
14
+ <div className="min-h-screen bg-[#050505] selection:bg-brand selection:text-white pb-20">
15
+ {/* Header Global */}
16
+ <nav className="fixed top-0 left-0 right-0 h-16 glass z-50 flex items-center justify-between px-6">
17
+ <Link to="/" className="flex items-center gap-2">
18
+ <div className="w-8 h-8 bg-brand rounded flex items-center justify-center font-bold text-lg select-none">
19
+ F
20
+ </div>
21
+ <span className="font-bold text-xl tracking-tight hidden sm:inline">
22
+ FractoShop
23
+ </span>
24
+ </Link>
25
+
26
+ {auth.isAuthenticated && (
27
+ <div className="flex items-center gap-4">
28
+ {/* Cart Button */}
29
+ <button
30
+ onClick={() => uiOps.self.cartOpen.set(true)}
31
+ className="relative p-2 hover:bg-white/5 rounded-full transition-colors cursor-pointer mr-2"
32
+ >
33
+ <span className="text-2xl">🛒</span>
34
+ {cartCount > 0 && (
35
+ <span className="absolute -top-1 -right-1 bg-brand text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full ring-2 ring-black">
36
+ {cartCount}
37
+ </span>
38
+ )}
39
+ </button>
40
+
41
+ <Link
42
+ to="/profile"
43
+ className="flex items-center gap-3 hover:bg-white/5 p-1.5 pr-4 rounded-full transition-colors"
44
+ >
45
+ <img
46
+ src={auth.user?.avatar}
47
+ className="w-8 h-8 rounded-full border border-white/10"
48
+ alt="Avatar"
49
+ />
50
+ <span className="text-sm font-medium text-white/80 hidden md:inline">
51
+ {auth.user?.username}
52
+ </span>
53
+ </Link>
54
+ <button
55
+ onClick={() => authOps.self.isAuthenticated.set(false)}
56
+ className="text-xs text-red-400 hover:text-red-300 font-bold tracking-wider px-3 py-1.5 border border-red-500/20 rounded-lg hover:bg-red-500/10 transition-all"
57
+ >
58
+ LOGOUT
59
+ </button>
60
+ </div>
61
+ )}
62
+ </nav>
63
+
64
+ {/* Main Content Injection */}
65
+ {children}
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,19 @@
1
+ import { useFlow } from "fractostate";
2
+ import { AuthFlow } from "../store/auth";
3
+ import { Navigate, useLocation } from "react-router-dom";
4
+
5
+ export default function ProtectedRoute({
6
+ children,
7
+ }: {
8
+ children: React.ReactNode;
9
+ }) {
10
+ const [auth] = useFlow(AuthFlow);
11
+ const location = useLocation();
12
+
13
+
14
+ if (!auth.isAuthenticated) {
15
+ return <Navigate to="/login" state={{ from: location }} replace />;
16
+ }
17
+
18
+ return <>{children}</>;
19
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,86 @@
1
+ import { useFlow } from "fractostate";
2
+ import { AuthFlow } from "../store/auth";
3
+ import { useNavigate } from "react-router-dom";
4
+
5
+ export default function LoginPage() {
6
+ const [auth, { ops: authOps }] = useFlow(AuthFlow);
7
+ const navigate = useNavigate();
8
+
9
+ const handleLogin = async (e: React.FormEvent) => {
10
+ e.preventDefault();
11
+
12
+ // Simulation API
13
+ authOps.self.isLoading.set(true);
14
+
15
+ // Fake Server Delay
16
+ await new Promise((r) => setTimeout(r, 1000));
17
+
18
+ // Update State (Native-like mutation via Proxy)
19
+ authOps.self.merge({
20
+ isAuthenticated: true,
21
+ user: {
22
+ username: "FractoDev",
23
+ avatar: "https://api.dicebear.com/7.x/bottts/svg?seed=Fracto",
24
+ role: "admin",
25
+ },
26
+ token: "fra_abc123_secure_token",
27
+ isLoading: false,
28
+ });
29
+
30
+ navigate("/");
31
+ };
32
+
33
+ return (
34
+ <div className="min-h-screen flex items-center justify-center bg-[#050505] p-4">
35
+ <div className="glass p-8 rounded-2xl w-full max-w-md border border-white/5 space-y-6">
36
+ <div className="text-center">
37
+ <div className="w-12 h-12 bg-brand rounded-lg mx-auto mb-4 flex items-center justify-center font-bold text-xl">
38
+ F
39
+ </div>
40
+ <h1 className="text-2xl font-bold">Welcome Back</h1>
41
+ <p className="text-white/40 text-sm">
42
+ Sign in to access the FractoState Demo
43
+ </p>
44
+ </div>
45
+
46
+ <form onSubmit={handleLogin} className="space-y-4">
47
+ <div className="space-y-2">
48
+ <label className="text-xs font-bold text-white/60 ml-1">
49
+ USERNAME
50
+ </label>
51
+ <input
52
+ type="text"
53
+ defaultValue="demo_user"
54
+ className="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-3 outline-none focus:border-brand/50 transition-colors text-sm font-medium"
55
+ />
56
+ </div>
57
+ <div className="space-y-2">
58
+ <label className="text-xs font-bold text-white/60 ml-1">
59
+ PASSWORD
60
+ </label>
61
+ <input
62
+ type="password"
63
+ defaultValue="password"
64
+ className="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-3 outline-none focus:border-brand/50 transition-colors text-sm font-medium"
65
+ />
66
+ </div>
67
+
68
+ <button
69
+ disabled={auth.isLoading}
70
+ className="w-full btn-primary py-3 font-bold mt-4 flex items-center justify-center gap-2"
71
+ >
72
+ {auth.isLoading ? (
73
+ <span className="animate-spin text-lg">⏳</span>
74
+ ) : (
75
+ "Sign In"
76
+ )}
77
+ </button>
78
+ </form>
79
+
80
+ <p className="text-center text-xs text-white/20">
81
+ Powered by FractoState Secure Vault
82
+ </p>
83
+ </div>
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1,48 @@
1
+ import { useFlow } from "fractostate";
2
+ import { AuthFlow } from "../store/auth";
3
+
4
+ export default function ProfilePage() {
5
+ const [auth] = useFlow(AuthFlow);
6
+
7
+ return (
8
+ <div className="pt-32 px-6 max-w-4xl mx-auto">
9
+ <div className="glass p-8 rounded-2xl border border-white/5 flex items-start gap-8">
10
+ <img
11
+ src={auth.user?.avatar}
12
+ className="w-32 h-32 rounded-2xl border-2 border-brand shadow-lg shadow-brand/20"
13
+ alt="Profile"
14
+ />
15
+ <div className="space-y-4 flex-1">
16
+ <div>
17
+ <h1 className="text-3xl font-bold">{auth.user?.username}</h1>
18
+ <span className="bg-brand/20 text-brand text-[10px] font-bold px-2 py-1 rounded uppercase tracking-widest border border-brand/20">
19
+ {auth.user?.role}
20
+ </span>
21
+ </div>
22
+
23
+ <div className="grid grid-cols-2 gap-4 pt-4">
24
+ <div className="bg-white/5 p-4 rounded-xl">
25
+ <span className="text-xs text-white/40 font-bold block mb-1">
26
+ SESSION TOKEN
27
+ </span>
28
+ <code className="text-xs font-mono text-brand truncate block">
29
+ {auth.token}
30
+ </code>
31
+ </div>
32
+ <div className="bg-white/5 p-4 rounded-xl">
33
+ <span className="text-xs text-white/40 font-bold block mb-1">
34
+ AUTH STATUS
35
+ </span>
36
+ <div className="flex items-center gap-2">
37
+ <span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
38
+ <span className="text-xs font-bold text-white/80">
39
+ Active & Secure
40
+ </span>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,54 @@
1
+ import { useFlow } from "fractostate";
2
+ import { ShopFlow } from "../store/flows";
3
+ import ProductList from "../components/ProductList";
4
+ import CartDrawer from "../components/CartDrawer";
5
+ import Notifications from "../components/Notifications";
6
+
7
+ export default function ShopPage() {
8
+ const [shop, { ops: shopOps }] = useFlow(ShopFlow);
9
+
10
+ return (
11
+ <>
12
+ <CartDrawer />
13
+ <Notifications />
14
+
15
+ <main className="max-w-7xl mx-auto">
16
+ <header className="px-6 pt-32 pb-4 text-center">
17
+ <h1 className="text-4xl md:text-6xl font-black mb-4 bg-gradient-to-r from-white to-white/40 bg-clip-text text-transparent italic tracking-tighter">
18
+ THE FUTURE OF STATE.
19
+ </h1>
20
+ <p className="text-white/40 font-medium max-w-xl mx-auto">
21
+ This entire application runs on{" "}
22
+ <span className="text-brand font-bold uppercase tracking-widest text-xs px-1">
23
+ FractoState
24
+ </span>
25
+ . Zero Prop-Drilling. Zero Context Providers. Ultra-fast surgical
26
+ updates.
27
+ </p>
28
+ </header>
29
+
30
+ <section className="mt-8">
31
+ <div className="px-6 flex items-center justify-between mb-8">
32
+ <h2 className="text-2xl font-bold flex items-center gap-2">
33
+ <span className="w-8 h-[2px] bg-brand inline-block" /> Latest
34
+ Drops
35
+ </h2>
36
+ <div className="flex gap-2">
37
+ {["All", "Electronics", "Apparel", "Accessories"].map((cat) => (
38
+ <button
39
+ key={cat}
40
+ onClick={() => shopOps.self.category.set(cat)}
41
+ className={`text-xs font-bold px-4 py-1.5 rounded-full transition-all cursor-pointer ${shop.category === cat ? "bg-brand text-white shadow-lg shadow-brand/20" : "bg-white/5 hover:bg-white/10 text-white/60"}`}
42
+ >
43
+ {cat}
44
+ </button>
45
+ ))}
46
+ </div>
47
+ </div>
48
+
49
+ <ProductList />
50
+ </section>
51
+ </main>
52
+ </>
53
+ );
54
+ }
@@ -0,0 +1,39 @@
1
+ import { defineFlow } from "fractostate";
2
+
3
+ export interface User {
4
+ username: string;
5
+ avatar: string;
6
+ role: "admin" | "user";
7
+ }
8
+
9
+ export interface AuthState {
10
+ isAuthenticated: boolean;
11
+ user: User | null;
12
+ token: string | null;
13
+ isLoading: boolean;
14
+ }
15
+
16
+ // Simulons une persistance basique en lisant le localStorage au démarrage (si on le voulait vraiment)
17
+ // Pour l'instant, on part de zéro pour la démo.
18
+
19
+ export const AuthFlow = defineFlow<AuthState>(
20
+ "auth",
21
+ {
22
+ isAuthenticated: false,
23
+ user: null,
24
+ token: null,
25
+ isLoading: false,
26
+ },
27
+ {
28
+ // Middleware pour logger les actions d'auth (ex: sécu)
29
+ middleware: [
30
+ (state) => {
31
+ console.log(
32
+ "[Auth Audit]:",
33
+ state.isAuthenticated ? "User Logged In" : "User Guest",
34
+ );
35
+ return state;
36
+ },
37
+ ],
38
+ },
39
+ );
@@ -0,0 +1,74 @@
1
+ import { defineFlow } from "fractostate";
2
+
3
+ export interface Product {
4
+ id: number;
5
+ name: string;
6
+ price: number;
7
+ image: string;
8
+ category: string;
9
+ }
10
+
11
+ export interface CartItem extends Product {
12
+ quantity: number;
13
+ }
14
+
15
+ // --- Cart Flow ---
16
+ export const CartFlow = defineFlow<{ items: CartItem[] }>("cart", {
17
+ items: [],
18
+ });
19
+
20
+ // --- User Flow ---
21
+ export const UserFlow = defineFlow<{
22
+ name: string;
23
+ isPremium: boolean;
24
+ avatar: string;
25
+ }>("user", {
26
+ name: "Marc Ansene",
27
+ isPremium: true,
28
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Marc",
29
+ });
30
+
31
+ // --- Shop Flow ---
32
+ export const ShopProducts = [
33
+ {
34
+ id: 1,
35
+ name: "Fractal Watch",
36
+ price: 299,
37
+ category: "Accessories",
38
+ image: "⌚",
39
+ },
40
+ { id: 2, name: "Neon Hoodie", price: 89, category: "Apparel", image: "🧥" },
41
+ {
42
+ id: 3,
43
+ name: "Quantum Buds",
44
+ price: 159,
45
+ category: "Electronics",
46
+ image: "🎧",
47
+ },
48
+ {
49
+ id: 4,
50
+ name: "Void Keyboard",
51
+ price: 199,
52
+ category: "Electronics",
53
+ image: "⌨️",
54
+ },
55
+ ];
56
+
57
+ export const ShopFlow = defineFlow<{
58
+ loading: boolean;
59
+ category: string;
60
+ search: string;
61
+ }>("shop", {
62
+ loading: false,
63
+ category: "All",
64
+ search: "",
65
+ });
66
+
67
+ // --- UI / Notification Flow ---
68
+ export const UIFlow = defineFlow<{
69
+ cartOpen: boolean;
70
+ notifications: { id: number; text: string; type: "success" | "error" }[];
71
+ }>("ui", {
72
+ cartOpen: false,
73
+ notifications: [],
74
+ });
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+ "paths": {
11
+ "fractostate": ["../src"]
12
+ },
13
+
14
+ /* Bundler mode */
15
+ "moduleResolution": "bundler",
16
+ "allowImportingTsExtensions": true,
17
+ "verbatimModuleSyntax": true,
18
+ "moduleDetection": "force",
19
+ "noEmit": true,
20
+ "jsx": "react-jsx",
21
+
22
+ /* Linting */
23
+ "strict": true,
24
+ "noUnusedLocals": true,
25
+ "noUnusedParameters": true,
26
+ "erasableSyntaxOnly": true,
27
+ "noFallthroughCasesInSwitch": true,
28
+ "noUncheckedSideEffectImports": true
29
+ },
30
+ "include": ["src", "postcss.config.mjs"]
31
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true,
24
+ "paths": {
25
+ "fractostate": ["../src"]
26
+ }
27
+ },
28
+ "include": ["vite.config.ts"]
29
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ import path from "path";
5
+
6
+ // https://vite.dev/config/
7
+ export default defineConfig({
8
+ plugins: [react()],
9
+ resolve: {
10
+ alias: {
11
+ fractostate: path.resolve(__dirname, "../src"),
12
+ react: path.resolve(__dirname, "./node_modules/react"),
13
+ "react-dom": path.resolve(__dirname, "./node_modules/react-dom"),
14
+ },
15
+ },
16
+ });