create-fluxstack 1.22.0 → 1.22.1
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.
- package/app/client/src/components/AppLayout.tsx +290 -290
- package/app/client/src/components/BackButton.tsx +16 -16
- package/app/client/src/components/DemoPage.tsx +135 -135
- package/app/client/src/framework/ClientOnly.tsx +14 -14
- package/app/client/src/framework/LivePage.tsx +55 -55
- package/app/client/src/framework/RscHomePage.tsx +125 -125
- package/app/client/src/framework/RscLink.tsx +31 -31
- package/app/client/src/framework/RscNav.tsx +42 -42
- package/app/client/src/framework/RscRoot.tsx +54 -54
- package/app/client/src/framework/csrf.ts +22 -22
- package/app/client/src/framework/entry.browser.tsx +51 -51
- package/app/client/src/framework/entry.rsc.tsx +41 -41
- package/app/client/src/framework/entry.ssr.tsx +12 -12
- package/app/client/src/framework/navigation.ts +29 -29
- package/app/client/src/framework/params.tsx +22 -22
- package/app/client/src/framework/routes.ts +143 -143
- package/app/client/src/framework/vite-rsc.d.ts +21 -21
- package/app/client/src/live/AuthDemo.tsx +270 -270
- package/app/client/src/live/CounterDemo.tsx +152 -152
- package/app/client/src/live/FormDemo.tsx +140 -140
- package/app/client/src/live/PingPongDemo.tsx +180 -180
- package/app/client/src/live/RoomChatDemo.tsx +397 -397
- package/app/client/src/pages/auth.tsx +13 -13
- package/app/client/src/pages/blog/[slug].tsx +23 -23
- package/app/client/src/pages/counter.tsx +15 -15
- package/app/client/src/pages/form.tsx +13 -13
- package/app/client/src/pages/index.tsx +9 -9
- package/app/client/src/pages/ping-pong.tsx +13 -13
- package/app/client/src/pages/room-chat.tsx +13 -13
- package/app/client/src/pages/shared-counter.tsx +13 -13
- package/app/client/src/pages/sobre.tsx +19 -19
- package/app/server/live/rooms/index.ts +14 -14
- package/core/plugins/built-in/rsc/index.ts +156 -156
- package/core/plugins/built-in/ssr/bun-asset-loader.ts +46 -46
- package/core/plugins/built-in/ssr/index.ts +143 -143
- package/core/plugins/built-in/ssr/registry.ts +38 -38
- package/core/utils/version.ts +1 -1
- package/package.json +1 -1
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
// HomePage como SERVER COMPONENT (RSC) — a página bonita original, portada.
|
|
2
|
-
// Sem estado/interatividade → roda 100% no server, 0 JS no client.
|
|
3
|
-
// Diferenças vs HomePage.tsx original:
|
|
4
|
-
// - <Link> (react-router) → <a> (navegação RSC usa anchor + fetch do payload)
|
|
5
|
-
// - apiStatus vem como prop do server (sem fetch client no shell)
|
|
6
|
-
import FluxStack from '@client/src/assets/fluxstack.svg'
|
|
7
|
-
import {
|
|
8
|
-
FaArrowRight,
|
|
9
|
-
FaBolt,
|
|
10
|
-
FaCodeBranch,
|
|
11
|
-
FaLayerGroup,
|
|
12
|
-
FaServer,
|
|
13
|
-
FaShieldAlt,
|
|
14
|
-
FaTerminal,
|
|
15
|
-
} from 'react-icons/fa'
|
|
16
|
-
|
|
17
|
-
export function RscHomePage() {
|
|
18
|
-
const features = [
|
|
19
|
-
{ icon: FaBolt, title: 'Runtime rapido', copy: 'Bun no centro do fluxo de dev e build, com feedback curto para projetos full stack.' },
|
|
20
|
-
{ icon: FaShieldAlt, title: 'Type-safe', copy: 'Contratos compartilhados entre Elysia, Eden Treaty e React sem duplicar modelos.' },
|
|
21
|
-
{ icon: FaLayerGroup, title: 'Live Components', copy: 'Estado no servidor com UI reativa, salas em tempo real e componentes declarativos.' },
|
|
22
|
-
]
|
|
23
|
-
const stackItems = ['Bun', 'Elysia', 'React', 'TypeScript']
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className="relative min-h-[calc(100vh-72px)] overflow-hidden px-4 py-10 sm:px-6 lg:px-8">
|
|
27
|
-
<div className="absolute inset-0 app-grid-bg opacity-70" />
|
|
28
|
-
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/25 to-transparent" />
|
|
29
|
-
|
|
30
|
-
<div className="relative z-10 mx-auto flex w-full max-w-7xl flex-col gap-10 lg:min-h-[calc(100vh-144px)] lg:justify-center">
|
|
31
|
-
<section className="grid items-center gap-8 lg:grid-cols-[1.04fr_0.96fr]">
|
|
32
|
-
<div className="max-w-3xl">
|
|
33
|
-
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-gray-300 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]">
|
|
34
|
-
<span className="h-1.5 w-1.5 rounded-full bg-emerald-300" />
|
|
35
|
-
<span className="rounded-full border border-emerald-400/25 bg-emerald-400/10 text-emerald-200 px-2 py-0.5">Server Rendered (RSC)</span>
|
|
36
|
-
<span className="hidden sm:inline">Full-stack starter for production apps</span>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<div className="mb-5 flex items-center gap-4">
|
|
40
|
-
<div className="relative flex h-16 w-16 items-center justify-center rounded-lg border border-white/10 bg-white/[0.04] shadow-2xl shadow-black/30">
|
|
41
|
-
<img src={FluxStack} alt="FluxStack" className="h-11 w-11 glow-theme" />
|
|
42
|
-
</div>
|
|
43
|
-
<div className="hidden h-px flex-1 bg-gradient-to-r from-white/20 to-transparent sm:block" />
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<h1 className="max-w-4xl text-left text-5xl font-semibold tracking-tight text-white sm:text-6xl lg:text-7xl">
|
|
47
|
-
FluxStack
|
|
48
|
-
<span className="block bg-theme-gradient bg-clip-text text-transparent">TypeScript full stack.</span>
|
|
49
|
-
</h1>
|
|
50
|
-
|
|
51
|
-
<p className="mt-5 max-w-2xl text-left text-base leading-8 text-gray-400 sm:text-lg">
|
|
52
|
-
Um framework direto ao ponto para construir APIs Elysia, interfaces React,
|
|
53
|
-
componentes em tempo real e contratos type-safe em uma unica base.
|
|
54
|
-
</p>
|
|
55
|
-
|
|
56
|
-
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
|
|
57
|
-
<a href="/counter" className="inline-flex h-11 items-center justify-center gap-2 rounded-lg bg-white px-5 text-sm font-semibold text-black transition hover:bg-gray-200">
|
|
58
|
-
Ver demos
|
|
59
|
-
<FaArrowRight className="h-3.5 w-3.5" />
|
|
60
|
-
</a>
|
|
61
|
-
<a href="/swagger" target="_blank" rel="noopener noreferrer" className="inline-flex h-11 items-center justify-center gap-2 rounded-lg border border-white/10 bg-white/[0.03] px-5 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.06]">
|
|
62
|
-
<FaServer className="h-3.5 w-3.5 text-theme" />
|
|
63
|
-
API docs
|
|
64
|
-
</a>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div className="mt-8 flex flex-wrap gap-2">
|
|
68
|
-
{stackItems.map((item) => (
|
|
69
|
-
<span key={item} className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-xs font-medium text-gray-300">{item}</span>
|
|
70
|
-
))}
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<div className="relative">
|
|
75
|
-
<div className="rounded-lg border border-white/10 bg-[#050508]/80 shadow-2xl shadow-black/40 backdrop-blur">
|
|
76
|
-
<div className="flex items-center justify-between border-b border-white/10 px-4 py-3">
|
|
77
|
-
<div className="flex items-center gap-2">
|
|
78
|
-
<span className="h-2.5 w-2.5 rounded-full bg-red-400/80" />
|
|
79
|
-
<span className="h-2.5 w-2.5 rounded-full bg-amber-300/80" />
|
|
80
|
-
<span className="h-2.5 w-2.5 rounded-full bg-emerald-300/80" />
|
|
81
|
-
</div>
|
|
82
|
-
<span className="font-mono text-xs text-gray-500">fluxstack.config.ts</span>
|
|
83
|
-
</div>
|
|
84
|
-
<div className="space-y-5 p-5 sm:p-6">
|
|
85
|
-
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
86
|
-
<div className="mb-3 flex items-center gap-2 text-sm font-semibold text-white">
|
|
87
|
-
<FaTerminal className="text-theme" />
|
|
88
|
-
Ship from one command
|
|
89
|
-
</div>
|
|
90
|
-
<pre className="overflow-x-auto rounded-md bg-black/50 p-4 text-left text-xs leading-6 text-gray-300">
|
|
91
|
-
<code>{`bunx create-fluxstack my-app\ncd my-app\nbun dev`}</code>
|
|
92
|
-
</pre>
|
|
93
|
-
</div>
|
|
94
|
-
<div className="grid gap-3 sm:grid-cols-2">
|
|
95
|
-
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
96
|
-
<FaCodeBranch className="mb-4 text-theme-secondary" />
|
|
97
|
-
<p className="text-sm font-semibold text-white">Typed routes</p>
|
|
98
|
-
<p className="mt-1 text-xs leading-5 text-gray-500">Client e server compartilham o contrato.</p>
|
|
99
|
-
</div>
|
|
100
|
-
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
101
|
-
<FaLayerGroup className="mb-4 text-theme" />
|
|
102
|
-
<p className="text-sm font-semibold text-white">Live UI</p>
|
|
103
|
-
<p className="mt-1 text-xs leading-5 text-gray-500">WebSocket, rooms e estado remoto.</p>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
</section>
|
|
110
|
-
|
|
111
|
-
<section className="grid gap-3 sm:grid-cols-3">
|
|
112
|
-
{features.map(({ icon: Icon, title, copy }) => (
|
|
113
|
-
<article key={title} className="group rounded-lg border border-white/10 bg-white/[0.025] p-5 text-left transition hover:border-white/20 hover:bg-white/[0.045]">
|
|
114
|
-
<div className="mb-5 flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-black/30 text-theme transition group-hover:border-theme-active">
|
|
115
|
-
<Icon className="h-4 w-4" />
|
|
116
|
-
</div>
|
|
117
|
-
<h2 className="text-sm font-semibold text-white">{title}</h2>
|
|
118
|
-
<p className="mt-2 text-sm leading-6 text-gray-500">{copy}</p>
|
|
119
|
-
</article>
|
|
120
|
-
))}
|
|
121
|
-
</section>
|
|
122
|
-
</div>
|
|
123
|
-
</div>
|
|
124
|
-
)
|
|
125
|
-
}
|
|
1
|
+
// HomePage como SERVER COMPONENT (RSC) — a página bonita original, portada.
|
|
2
|
+
// Sem estado/interatividade → roda 100% no server, 0 JS no client.
|
|
3
|
+
// Diferenças vs HomePage.tsx original:
|
|
4
|
+
// - <Link> (react-router) → <a> (navegação RSC usa anchor + fetch do payload)
|
|
5
|
+
// - apiStatus vem como prop do server (sem fetch client no shell)
|
|
6
|
+
import FluxStack from '@client/src/assets/fluxstack.svg'
|
|
7
|
+
import {
|
|
8
|
+
FaArrowRight,
|
|
9
|
+
FaBolt,
|
|
10
|
+
FaCodeBranch,
|
|
11
|
+
FaLayerGroup,
|
|
12
|
+
FaServer,
|
|
13
|
+
FaShieldAlt,
|
|
14
|
+
FaTerminal,
|
|
15
|
+
} from 'react-icons/fa'
|
|
16
|
+
|
|
17
|
+
export function RscHomePage() {
|
|
18
|
+
const features = [
|
|
19
|
+
{ icon: FaBolt, title: 'Runtime rapido', copy: 'Bun no centro do fluxo de dev e build, com feedback curto para projetos full stack.' },
|
|
20
|
+
{ icon: FaShieldAlt, title: 'Type-safe', copy: 'Contratos compartilhados entre Elysia, Eden Treaty e React sem duplicar modelos.' },
|
|
21
|
+
{ icon: FaLayerGroup, title: 'Live Components', copy: 'Estado no servidor com UI reativa, salas em tempo real e componentes declarativos.' },
|
|
22
|
+
]
|
|
23
|
+
const stackItems = ['Bun', 'Elysia', 'React', 'TypeScript']
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="relative min-h-[calc(100vh-72px)] overflow-hidden px-4 py-10 sm:px-6 lg:px-8">
|
|
27
|
+
<div className="absolute inset-0 app-grid-bg opacity-70" />
|
|
28
|
+
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/25 to-transparent" />
|
|
29
|
+
|
|
30
|
+
<div className="relative z-10 mx-auto flex w-full max-w-7xl flex-col gap-10 lg:min-h-[calc(100vh-144px)] lg:justify-center">
|
|
31
|
+
<section className="grid items-center gap-8 lg:grid-cols-[1.04fr_0.96fr]">
|
|
32
|
+
<div className="max-w-3xl">
|
|
33
|
+
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-gray-300 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]">
|
|
34
|
+
<span className="h-1.5 w-1.5 rounded-full bg-emerald-300" />
|
|
35
|
+
<span className="rounded-full border border-emerald-400/25 bg-emerald-400/10 text-emerald-200 px-2 py-0.5">Server Rendered (RSC)</span>
|
|
36
|
+
<span className="hidden sm:inline">Full-stack starter for production apps</span>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div className="mb-5 flex items-center gap-4">
|
|
40
|
+
<div className="relative flex h-16 w-16 items-center justify-center rounded-lg border border-white/10 bg-white/[0.04] shadow-2xl shadow-black/30">
|
|
41
|
+
<img src={FluxStack} alt="FluxStack" className="h-11 w-11 glow-theme" />
|
|
42
|
+
</div>
|
|
43
|
+
<div className="hidden h-px flex-1 bg-gradient-to-r from-white/20 to-transparent sm:block" />
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<h1 className="max-w-4xl text-left text-5xl font-semibold tracking-tight text-white sm:text-6xl lg:text-7xl">
|
|
47
|
+
FluxStack
|
|
48
|
+
<span className="block bg-theme-gradient bg-clip-text text-transparent">TypeScript full stack.</span>
|
|
49
|
+
</h1>
|
|
50
|
+
|
|
51
|
+
<p className="mt-5 max-w-2xl text-left text-base leading-8 text-gray-400 sm:text-lg">
|
|
52
|
+
Um framework direto ao ponto para construir APIs Elysia, interfaces React,
|
|
53
|
+
componentes em tempo real e contratos type-safe em uma unica base.
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
|
|
57
|
+
<a href="/counter" className="inline-flex h-11 items-center justify-center gap-2 rounded-lg bg-white px-5 text-sm font-semibold text-black transition hover:bg-gray-200">
|
|
58
|
+
Ver demos
|
|
59
|
+
<FaArrowRight className="h-3.5 w-3.5" />
|
|
60
|
+
</a>
|
|
61
|
+
<a href="/swagger" target="_blank" rel="noopener noreferrer" className="inline-flex h-11 items-center justify-center gap-2 rounded-lg border border-white/10 bg-white/[0.03] px-5 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.06]">
|
|
62
|
+
<FaServer className="h-3.5 w-3.5 text-theme" />
|
|
63
|
+
API docs
|
|
64
|
+
</a>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div className="mt-8 flex flex-wrap gap-2">
|
|
68
|
+
{stackItems.map((item) => (
|
|
69
|
+
<span key={item} className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-xs font-medium text-gray-300">{item}</span>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div className="relative">
|
|
75
|
+
<div className="rounded-lg border border-white/10 bg-[#050508]/80 shadow-2xl shadow-black/40 backdrop-blur">
|
|
76
|
+
<div className="flex items-center justify-between border-b border-white/10 px-4 py-3">
|
|
77
|
+
<div className="flex items-center gap-2">
|
|
78
|
+
<span className="h-2.5 w-2.5 rounded-full bg-red-400/80" />
|
|
79
|
+
<span className="h-2.5 w-2.5 rounded-full bg-amber-300/80" />
|
|
80
|
+
<span className="h-2.5 w-2.5 rounded-full bg-emerald-300/80" />
|
|
81
|
+
</div>
|
|
82
|
+
<span className="font-mono text-xs text-gray-500">fluxstack.config.ts</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="space-y-5 p-5 sm:p-6">
|
|
85
|
+
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
86
|
+
<div className="mb-3 flex items-center gap-2 text-sm font-semibold text-white">
|
|
87
|
+
<FaTerminal className="text-theme" />
|
|
88
|
+
Ship from one command
|
|
89
|
+
</div>
|
|
90
|
+
<pre className="overflow-x-auto rounded-md bg-black/50 p-4 text-left text-xs leading-6 text-gray-300">
|
|
91
|
+
<code>{`bunx create-fluxstack my-app\ncd my-app\nbun dev`}</code>
|
|
92
|
+
</pre>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
95
|
+
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
96
|
+
<FaCodeBranch className="mb-4 text-theme-secondary" />
|
|
97
|
+
<p className="text-sm font-semibold text-white">Typed routes</p>
|
|
98
|
+
<p className="mt-1 text-xs leading-5 text-gray-500">Client e server compartilham o contrato.</p>
|
|
99
|
+
</div>
|
|
100
|
+
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
101
|
+
<FaLayerGroup className="mb-4 text-theme" />
|
|
102
|
+
<p className="text-sm font-semibold text-white">Live UI</p>
|
|
103
|
+
<p className="mt-1 text-xs leading-5 text-gray-500">WebSocket, rooms e estado remoto.</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<section className="grid gap-3 sm:grid-cols-3">
|
|
112
|
+
{features.map(({ icon: Icon, title, copy }) => (
|
|
113
|
+
<article key={title} className="group rounded-lg border border-white/10 bg-white/[0.025] p-5 text-left transition hover:border-white/20 hover:bg-white/[0.045]">
|
|
114
|
+
<div className="mb-5 flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-black/30 text-theme transition group-hover:border-theme-active">
|
|
115
|
+
<Icon className="h-4 w-4" />
|
|
116
|
+
</div>
|
|
117
|
+
<h2 className="text-sm font-semibold text-white">{title}</h2>
|
|
118
|
+
<p className="mt-2 text-sm leading-6 text-gray-500">{copy}</p>
|
|
119
|
+
</article>
|
|
120
|
+
))}
|
|
121
|
+
</section>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
// Link client-side para navegação RSC sem reload. Intercepta o clique e usa
|
|
3
|
-
// o navigate do RootClient (fetch do .rsc + transition). Cai pra <a> normal
|
|
4
|
-
// em ctrl/cmd-click, target _blank, ou links externos.
|
|
5
|
-
import type { ReactNode, MouseEvent } from 'react'
|
|
6
|
-
import { navigate } from './navigation'
|
|
7
|
-
|
|
8
|
-
export function RscLink({
|
|
9
|
-
href,
|
|
10
|
-
className,
|
|
11
|
-
children,
|
|
12
|
-
external,
|
|
13
|
-
}: {
|
|
14
|
-
href: string
|
|
15
|
-
className?: string
|
|
16
|
-
children: ReactNode
|
|
17
|
-
external?: boolean
|
|
18
|
-
}) {
|
|
19
|
-
function onClick(e: MouseEvent<HTMLAnchorElement>) {
|
|
20
|
-
if (external) return // deixa o browser tratar (ex: /swagger, target _blank)
|
|
21
|
-
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return
|
|
22
|
-
e.preventDefault()
|
|
23
|
-
navigate(href)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<a href={href} className={className} onClick={onClick} {...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}>
|
|
28
|
-
{children}
|
|
29
|
-
</a>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
// Link client-side para navegação RSC sem reload. Intercepta o clique e usa
|
|
3
|
+
// o navigate do RootClient (fetch do .rsc + transition). Cai pra <a> normal
|
|
4
|
+
// em ctrl/cmd-click, target _blank, ou links externos.
|
|
5
|
+
import type { ReactNode, MouseEvent } from 'react'
|
|
6
|
+
import { navigate } from './navigation'
|
|
7
|
+
|
|
8
|
+
export function RscLink({
|
|
9
|
+
href,
|
|
10
|
+
className,
|
|
11
|
+
children,
|
|
12
|
+
external,
|
|
13
|
+
}: {
|
|
14
|
+
href: string
|
|
15
|
+
className?: string
|
|
16
|
+
children: ReactNode
|
|
17
|
+
external?: boolean
|
|
18
|
+
}) {
|
|
19
|
+
function onClick(e: MouseEvent<HTMLAnchorElement>) {
|
|
20
|
+
if (external) return // deixa o browser tratar (ex: /swagger, target _blank)
|
|
21
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return
|
|
22
|
+
e.preventDefault()
|
|
23
|
+
navigate(href)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<a href={href} className={className} onClick={onClick} {...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}>
|
|
28
|
+
{children}
|
|
29
|
+
</a>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
// Navbar SERVER COMPONENT — gerada automaticamente a partir das rotas descobertas
|
|
2
|
-
// (file-based). Adicionar uma página em pages/ a faz aparecer aqui sozinha
|
|
3
|
-
// (a menos que a página exporte `nav = false`). Links via RscLink (SPA sem reload).
|
|
4
|
-
import FluxStackLogo from '@client/src/assets/fluxstack.svg'
|
|
5
|
-
import { RscLink } from './RscLink'
|
|
6
|
-
import { routes } from './routes'
|
|
7
|
-
|
|
8
|
-
export function RscNav({ active }: { active: string }) {
|
|
9
|
-
const navItems = routes.filter((r) => r.inNav)
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<header className="sticky top-0 z-50 border-b border-white/10 bg-black/60 backdrop-blur">
|
|
13
|
-
<div className="mx-auto flex max-w-7xl items-center gap-6 px-4 py-3">
|
|
14
|
-
<RscLink href="/" className="flex items-center gap-2">
|
|
15
|
-
<img src={FluxStackLogo} alt="FluxStack" className="h-6 w-6" />
|
|
16
|
-
<span className="font-semibold text-white">FluxStack</span>
|
|
17
|
-
</RscLink>
|
|
18
|
-
<nav className="flex flex-wrap items-center gap-1">
|
|
19
|
-
{navItems.map((item) => {
|
|
20
|
-
const isActive = item.path === active
|
|
21
|
-
return (
|
|
22
|
-
<RscLink
|
|
23
|
-
key={item.path}
|
|
24
|
-
href={item.path}
|
|
25
|
-
className={`rounded-lg px-3 py-1.5 text-sm transition ${
|
|
26
|
-
isActive
|
|
27
|
-
? 'bg-white/10 text-white'
|
|
28
|
-
: 'text-gray-400 hover:bg-white/[0.05] hover:text-white'
|
|
29
|
-
}`}
|
|
30
|
-
>
|
|
31
|
-
{item.navLabel}
|
|
32
|
-
</RscLink>
|
|
33
|
-
)
|
|
34
|
-
})}
|
|
35
|
-
</nav>
|
|
36
|
-
<RscLink href="/swagger" external className="ml-auto text-sm text-gray-400 hover:text-white">
|
|
37
|
-
API docs
|
|
38
|
-
</RscLink>
|
|
39
|
-
</div>
|
|
40
|
-
</header>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
1
|
+
// Navbar SERVER COMPONENT — gerada automaticamente a partir das rotas descobertas
|
|
2
|
+
// (file-based). Adicionar uma página em pages/ a faz aparecer aqui sozinha
|
|
3
|
+
// (a menos que a página exporte `nav = false`). Links via RscLink (SPA sem reload).
|
|
4
|
+
import FluxStackLogo from '@client/src/assets/fluxstack.svg'
|
|
5
|
+
import { RscLink } from './RscLink'
|
|
6
|
+
import { routes } from './routes'
|
|
7
|
+
|
|
8
|
+
export function RscNav({ active }: { active: string }) {
|
|
9
|
+
const navItems = routes.filter((r) => r.inNav)
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<header className="sticky top-0 z-50 border-b border-white/10 bg-black/60 backdrop-blur">
|
|
13
|
+
<div className="mx-auto flex max-w-7xl items-center gap-6 px-4 py-3">
|
|
14
|
+
<RscLink href="/" className="flex items-center gap-2">
|
|
15
|
+
<img src={FluxStackLogo} alt="FluxStack" className="h-6 w-6" />
|
|
16
|
+
<span className="font-semibold text-white">FluxStack</span>
|
|
17
|
+
</RscLink>
|
|
18
|
+
<nav className="flex flex-wrap items-center gap-1">
|
|
19
|
+
{navItems.map((item) => {
|
|
20
|
+
const isActive = item.path === active
|
|
21
|
+
return (
|
|
22
|
+
<RscLink
|
|
23
|
+
key={item.path}
|
|
24
|
+
href={item.path}
|
|
25
|
+
className={`rounded-lg px-3 py-1.5 text-sm transition ${
|
|
26
|
+
isActive
|
|
27
|
+
? 'bg-white/10 text-white'
|
|
28
|
+
: 'text-gray-400 hover:bg-white/[0.05] hover:text-white'
|
|
29
|
+
}`}
|
|
30
|
+
>
|
|
31
|
+
{item.navLabel}
|
|
32
|
+
</RscLink>
|
|
33
|
+
)
|
|
34
|
+
})}
|
|
35
|
+
</nav>
|
|
36
|
+
<RscLink href="/swagger" external className="ml-auto text-sm text-gray-400 hover:text-white">
|
|
37
|
+
API docs
|
|
38
|
+
</RscLink>
|
|
39
|
+
</div>
|
|
40
|
+
</header>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
// SERVER COMPONENTS do site FluxStack via RSC — agora com roteamento FILE-BASED.
|
|
2
|
-
//
|
|
3
|
-
// O dev NÃO edita este arquivo para adicionar páginas: basta criar um arquivo em
|
|
4
|
-
// app/client/src/pages/ (ver framework/routes.ts). Aqui só montamos a casca
|
|
5
|
-
// (navbar + a página que casa com o pathname) a partir do mapa descoberto.
|
|
6
|
-
import '../index.css' // Tailwind + temas
|
|
7
|
-
import { RscNav } from './RscNav'
|
|
8
|
-
import { routes, matchRoute } from './routes'
|
|
9
|
-
|
|
10
|
-
function NotFound({ pathname }: { pathname: string }) {
|
|
11
|
-
return (
|
|
12
|
-
<div className="flex min-h-[calc(100vh-57px)] flex-col items-center justify-center gap-4 text-center">
|
|
13
|
-
<h1 className="text-6xl font-black text-white">404</h1>
|
|
14
|
-
<p className="text-gray-400">A rota <code className="text-theme">{pathname}</code> não existe.</p>
|
|
15
|
-
<a href="/" className="rounded-lg bg-white px-5 py-2 text-sm font-semibold text-black">Voltar ao início</a>
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Conteúdo da rota (nav + página). É o que as navegações client buscam (.rsc). */
|
|
21
|
-
export function RscPage({ pathname = '/' }: { pathname?: string }) {
|
|
22
|
-
const match = matchRoute(pathname)
|
|
23
|
-
return (
|
|
24
|
-
<>
|
|
25
|
-
<RscNav active={pathname} />
|
|
26
|
-
{match
|
|
27
|
-
? <match.route.Component params={match.params} />
|
|
28
|
-
: <NotFound pathname={pathname} />}
|
|
29
|
-
</>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Documento completo. Controle client (navegação + keep-alive WS) vive no
|
|
34
|
-
* entry.browser; o body renderiza o RscPage direto.
|
|
35
|
-
* csrfToken: injetado pelo server p/ o client não precisar fazer fetch('/api/__csrf'). */
|
|
36
|
-
export function RscDocument({ pathname = '/', csrfToken }: { pathname?: string; csrfToken?: string }) {
|
|
37
|
-
return (
|
|
38
|
-
<html lang="pt-BR">
|
|
39
|
-
<head>
|
|
40
|
-
<meta charSet="utf-8" />
|
|
41
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
42
|
-
{csrfToken && <meta name="csrf-token" content={csrfToken} />}
|
|
43
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
44
|
-
<title>FluxStack</title>
|
|
45
|
-
</head>
|
|
46
|
-
<body>
|
|
47
|
-
<RscPage pathname={pathname} />
|
|
48
|
-
</body>
|
|
49
|
-
</html>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// re-export para conveniência
|
|
54
|
-
export { routes }
|
|
1
|
+
// SERVER COMPONENTS do site FluxStack via RSC — agora com roteamento FILE-BASED.
|
|
2
|
+
//
|
|
3
|
+
// O dev NÃO edita este arquivo para adicionar páginas: basta criar um arquivo em
|
|
4
|
+
// app/client/src/pages/ (ver framework/routes.ts). Aqui só montamos a casca
|
|
5
|
+
// (navbar + a página que casa com o pathname) a partir do mapa descoberto.
|
|
6
|
+
import '../index.css' // Tailwind + temas
|
|
7
|
+
import { RscNav } from './RscNav'
|
|
8
|
+
import { routes, matchRoute } from './routes'
|
|
9
|
+
|
|
10
|
+
function NotFound({ pathname }: { pathname: string }) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex min-h-[calc(100vh-57px)] flex-col items-center justify-center gap-4 text-center">
|
|
13
|
+
<h1 className="text-6xl font-black text-white">404</h1>
|
|
14
|
+
<p className="text-gray-400">A rota <code className="text-theme">{pathname}</code> não existe.</p>
|
|
15
|
+
<a href="/" className="rounded-lg bg-white px-5 py-2 text-sm font-semibold text-black">Voltar ao início</a>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Conteúdo da rota (nav + página). É o que as navegações client buscam (.rsc). */
|
|
21
|
+
export function RscPage({ pathname = '/' }: { pathname?: string }) {
|
|
22
|
+
const match = matchRoute(pathname)
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
<RscNav active={pathname} />
|
|
26
|
+
{match
|
|
27
|
+
? <match.route.Component params={match.params} />
|
|
28
|
+
: <NotFound pathname={pathname} />}
|
|
29
|
+
</>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Documento completo. Controle client (navegação + keep-alive WS) vive no
|
|
34
|
+
* entry.browser; o body renderiza o RscPage direto.
|
|
35
|
+
* csrfToken: injetado pelo server p/ o client não precisar fazer fetch('/api/__csrf'). */
|
|
36
|
+
export function RscDocument({ pathname = '/', csrfToken }: { pathname?: string; csrfToken?: string }) {
|
|
37
|
+
return (
|
|
38
|
+
<html lang="pt-BR">
|
|
39
|
+
<head>
|
|
40
|
+
<meta charSet="utf-8" />
|
|
41
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
42
|
+
{csrfToken && <meta name="csrf-token" content={csrfToken} />}
|
|
43
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
44
|
+
<title>FluxStack</title>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<RscPage pathname={pathname} />
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// re-export para conveniência
|
|
54
|
+
export { routes }
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
// CSRF via SSR — gera o token no server e o entrega já no HTML.
|
|
2
|
-
//
|
|
3
|
-
// Em vez do client fazer fetch('/api/__csrf') após carregar (round-trip extra),
|
|
4
|
-
// o render SSR gera o token, injeta <meta name="csrf-token"> no <head> e seta o
|
|
5
|
-
// cookie XSRF-TOKEN na resposta. O client nasce com o token — zero round-trip.
|
|
6
|
-
// (Padrão Rails/Laravel/Livewire: token no HTML inicial.)
|
|
7
|
-
|
|
8
|
-
import { randomBytes } from 'crypto'
|
|
9
|
-
|
|
10
|
-
export const CSRF_COOKIE = 'XSRF-TOKEN'
|
|
11
|
-
|
|
12
|
-
/** Gera um token CSRF hex (mesmo formato do CsrfService do plugin). */
|
|
13
|
-
export function generateCsrfToken(bytes = 32): string {
|
|
14
|
-
return randomBytes(bytes).toString('hex')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Monta o Set-Cookie do token (NÃO httpOnly — o JS client precisa ler). */
|
|
18
|
-
export function buildCsrfCookie(token: string, secure: boolean): string {
|
|
19
|
-
const parts = [`${CSRF_COOKIE}=${token}`, 'Path=/', 'SameSite=Lax']
|
|
20
|
-
if (secure) parts.push('Secure')
|
|
21
|
-
return parts.join('; ')
|
|
22
|
-
}
|
|
1
|
+
// CSRF via SSR — gera o token no server e o entrega já no HTML.
|
|
2
|
+
//
|
|
3
|
+
// Em vez do client fazer fetch('/api/__csrf') após carregar (round-trip extra),
|
|
4
|
+
// o render SSR gera o token, injeta <meta name="csrf-token"> no <head> e seta o
|
|
5
|
+
// cookie XSRF-TOKEN na resposta. O client nasce com o token — zero round-trip.
|
|
6
|
+
// (Padrão Rails/Laravel/Livewire: token no HTML inicial.)
|
|
7
|
+
|
|
8
|
+
import { randomBytes } from 'crypto'
|
|
9
|
+
|
|
10
|
+
export const CSRF_COOKIE = 'XSRF-TOKEN'
|
|
11
|
+
|
|
12
|
+
/** Gera um token CSRF hex (mesmo formato do CsrfService do plugin). */
|
|
13
|
+
export function generateCsrfToken(bytes = 32): string {
|
|
14
|
+
return randomBytes(bytes).toString('hex')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Monta o Set-Cookie do token (NÃO httpOnly — o JS client precisa ler). */
|
|
18
|
+
export function buildCsrfCookie(token: string, secure: boolean): string {
|
|
19
|
+
const parts = [`${CSRF_COOKIE}=${token}`, 'Path=/', 'SameSite=Lax']
|
|
20
|
+
if (secure) parts.push('Secure')
|
|
21
|
+
return parts.join('; ')
|
|
22
|
+
}
|