create-vitnode-app 1.2.0-canary.9 → 2.0.0-canary.10
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/LICENSE.md +1 -1
- package/README.md +59 -18
- package/copy-of-vitnode-app/.vscode/settings.json +10 -0
- package/copy-of-vitnode-app/README.md +62 -0
- package/copy-of-vitnode-app/api/.gitignore_template +32 -0
- package/copy-of-vitnode-app/api/drizzle.config.ts +12 -0
- package/copy-of-vitnode-app/api/src/index.ts +27 -0
- package/copy-of-vitnode-app/api/src/vitnode.api.config.ts +39 -0
- package/copy-of-vitnode-app/api/tsconfig.json +17 -0
- package/copy-of-vitnode-app/api-bun/src/index.ts +16 -0
- package/copy-of-vitnode-app/api-single-app/drizzle.config.ts +12 -0
- package/copy-of-vitnode-app/api-single-app/src/routes/api/$.ts +10 -0
- package/copy-of-vitnode-app/api-single-app/src/server/api-bridge.ts +11 -0
- package/copy-of-vitnode-app/api-single-app/src/server/vitnode-api.server.ts +29 -0
- package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +28 -0
- package/copy-of-vitnode-app/docker/docker-compose.yml +32 -0
- package/copy-of-vitnode-app/eslint/.prettierrc.mjs +1 -1
- package/copy-of-vitnode-app/eslint/eslint.config.mjs +18 -2
- package/copy-of-vitnode-app/{root → eslint-react}/.prettierrc.mjs +1 -1
- package/copy-of-vitnode-app/eslint-react/eslint.config.mjs +19 -0
- package/copy-of-vitnode-app/monorepo/.gitignore_template +41 -0
- package/copy-of-vitnode-app/monorepo/apps/api/.env.example +12 -0
- package/copy-of-vitnode-app/monorepo/apps/web/.env.example +1 -0
- package/copy-of-vitnode-app/monorepo/turbo.json +53 -0
- package/copy-of-vitnode-app/root/.env.example +9 -5
- package/copy-of-vitnode-app/root/.gitignore_template +41 -0
- package/copy-of-vitnode-app/root/global.d.ts +10 -0
- package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +21 -0
- package/copy-of-vitnode-app/root/src/components/main-footer.tsx +44 -0
- package/copy-of-vitnode-app/root/src/components/main-header.tsx +4 -0
- package/copy-of-vitnode-app/root/src/lib/i18n.ts +37 -0
- package/copy-of-vitnode-app/root/src/locales/app.ts +4 -0
- package/copy-of-vitnode-app/root/src/locales/packages.ts +10 -0
- package/copy-of-vitnode-app/root/src/router.tsx +71 -0
- package/copy-of-vitnode-app/root/src/routes/__root.tsx +109 -0
- package/copy-of-vitnode-app/root/src/routes/_admin/admin.core.index.tsx +20 -0
- package/copy-of-vitnode-app/root/src/routes/_admin.tsx +65 -0
- package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +196 -0
- package/copy-of-vitnode-app/root/src/routes/_main.tsx +29 -0
- package/copy-of-vitnode-app/root/src/server/messages.server.ts +8 -0
- package/copy-of-vitnode-app/root/src/start.ts +5 -0
- package/copy-of-vitnode-app/root/src/styles.css +149 -0
- package/copy-of-vitnode-app/root/src/vitnode.config.ts +20 -0
- package/copy-of-vitnode-app/root/src/vitnode.server.config.ts +12 -0
- package/copy-of-vitnode-app/root/tsconfig.json +29 -16
- package/copy-of-vitnode-app/root/tsr.config.json +3 -0
- package/copy-of-vitnode-app/root/vite.config.ts +22 -0
- package/copy-of-vitnode-plugin/root/.swcrc +26 -0
- package/copy-of-vitnode-plugin/root/global.d.ts +21 -0
- package/copy-of-vitnode-plugin/root/npmignore.template +17 -0
- package/copy-of-vitnode-plugin/root/tsconfig.build.json +5 -0
- package/copy-of-vitnode-plugin/root/tsconfig.json +25 -0
- package/dist/src/create/create-package-json.js +266 -56
- package/dist/src/create/create-vitnode.js +191 -28
- package/dist/src/create/package-versions.js +49 -0
- package/dist/src/helpers/get-available-package-managers.js +6 -6
- package/dist/src/helpers/get-package-json.js +2 -2
- package/dist/src/helpers/get-package-manager-from-root.js +15 -0
- package/dist/src/helpers/get-vitnode-package-version.js +9 -0
- package/dist/src/helpers/init-git.js +38 -0
- package/dist/src/helpers/init-vitnode.js +33 -0
- package/dist/src/helpers/install-dependencies.js +55 -15
- package/dist/src/helpers/is-folder-empty.js +25 -26
- package/dist/src/helpers/is-online.js +16 -16
- package/dist/src/helpers/is-writeable.js +3 -3
- package/dist/src/helpers/spawn-command.js +7 -0
- package/dist/src/helpers/validate-pkg.js +1 -1
- package/dist/src/helpers/with-If.js +1 -0
- package/dist/src/index.js +30 -26
- package/dist/src/plugin/create/add-plugin-to-config.js +231 -0
- package/dist/src/plugin/create/add-plugin-to-workspace.js +74 -0
- package/dist/src/plugin/create/create-package-json.js +55 -0
- package/dist/src/plugin/create/create-plugin-vitnode.js +101 -0
- package/dist/src/plugin/create/route-templates.js +186 -0
- package/dist/src/plugin/index.js +17 -6
- package/dist/src/plugin/questions.js +14 -0
- package/dist/src/plugin/validation.js +76 -0
- package/dist/src/prepare/prepare.js +11 -8
- package/dist/src/questions.js +59 -15
- package/dist/src/validation.js +15 -15
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +22 -12
- package/copy-of-vitnode-app/root/README.md +0 -1
- package/copy-of-vitnode-app/root/drizzle.config.ts +0 -12
- package/copy-of-vitnode-app/root/gitignore_template +0 -43
- package/copy-of-vitnode-app/root/next.config.ts +0 -11
- package/copy-of-vitnode-app/root/postcss.config.mjs +0 -8
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/[...rest]/page.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/layout.tsx +0 -10
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/not-found.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/page.tsx +0 -113
- package/copy-of-vitnode-app/root/src/app/[locale]/admin/(auth)/layout.tsx +0 -10
- package/copy-of-vitnode-app/root/src/app/[locale]/admin/not-found.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/admin/page.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/layout.tsx +0 -36
- package/copy-of-vitnode-app/root/src/app/api/[...route]/route.ts +0 -18
- package/copy-of-vitnode-app/root/src/app/favicon.ico +0 -0
- package/copy-of-vitnode-app/root/src/app/global-error.tsx +0 -27
- package/copy-of-vitnode-app/root/src/app/global.css +0 -128
- package/copy-of-vitnode-app/root/src/app/layout.tsx +0 -9
- package/copy-of-vitnode-app/root/src/app/not-found.tsx +0 -13
- package/copy-of-vitnode-app/root/src/vitnode.api.config.ts +0 -5
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules
|
|
3
|
+
|
|
4
|
+
# Turbo
|
|
5
|
+
.turbo
|
|
6
|
+
|
|
7
|
+
# Test & Build
|
|
8
|
+
/coverage
|
|
9
|
+
/.output/
|
|
10
|
+
/.nitro/
|
|
11
|
+
/.vite/
|
|
12
|
+
/.tanstack/
|
|
13
|
+
/out/
|
|
14
|
+
/build
|
|
15
|
+
*.tsbuildinfo
|
|
16
|
+
|
|
17
|
+
# Generated by the VitNode Vite plugin and the TanStack route generator
|
|
18
|
+
src/*.gen.ts
|
|
19
|
+
|
|
20
|
+
# Misc
|
|
21
|
+
.DS_Store
|
|
22
|
+
*.pem
|
|
23
|
+
/.pnp
|
|
24
|
+
.pnp.js
|
|
25
|
+
npm-debug.log*
|
|
26
|
+
yarn-debug.log*
|
|
27
|
+
yarn-error.log*
|
|
28
|
+
|
|
29
|
+
# Local env files
|
|
30
|
+
.env
|
|
31
|
+
.env.local
|
|
32
|
+
.env.development.local
|
|
33
|
+
.env.test.local
|
|
34
|
+
.env.production.local
|
|
35
|
+
|
|
36
|
+
# Others
|
|
37
|
+
.vercel
|
|
38
|
+
.env*.local
|
|
39
|
+
|
|
40
|
+
# Local storage adapter uploads
|
|
41
|
+
/public/uploads
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AdminShellContent } from "@vitnode/core/tanstack/admin";
|
|
2
|
+
import { useAppNavigate } from "@vitnode/core/tanstack/auth";
|
|
3
|
+
import { LanguageSwitcher } from "@vitnode/core/tanstack/layout";
|
|
4
|
+
|
|
5
|
+
import { adminNav } from "@/admin-nav.gen";
|
|
6
|
+
|
|
7
|
+
export const AdminShell = ({ children }: { children: React.ReactNode }) => {
|
|
8
|
+
const navigate = useAppNavigate();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<AdminShellContent
|
|
12
|
+
languageSwitcher={<LanguageSwitcher />}
|
|
13
|
+
nav={adminNav}
|
|
14
|
+
onNavigate={href => {
|
|
15
|
+
void navigate(href);
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</AdminShellContent>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VITNODE_DOCS_URL,
|
|
3
|
+
VITNODE_SPONSOR_URL,
|
|
4
|
+
VITNODE_WEBSITE_URL,
|
|
5
|
+
} from "@vitnode/core/lib/docs-links";
|
|
6
|
+
import { Heart } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
export const MainFooter = () => (
|
|
9
|
+
<footer className="border-t">
|
|
10
|
+
<div className="text-muted-foreground container mx-auto flex flex-col items-center justify-between gap-3 px-4 py-6 text-sm sm:flex-row">
|
|
11
|
+
<p>
|
|
12
|
+
Powered by{" "}
|
|
13
|
+
<a
|
|
14
|
+
className="text-foreground font-medium underline-offset-4 hover:underline"
|
|
15
|
+
href={VITNODE_WEBSITE_URL}
|
|
16
|
+
rel="noopener noreferrer"
|
|
17
|
+
target="_blank"
|
|
18
|
+
>
|
|
19
|
+
VitNode
|
|
20
|
+
</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<nav aria-label="VitNode" className="flex items-center gap-5">
|
|
24
|
+
<a
|
|
25
|
+
className="hover:text-foreground transition-colors"
|
|
26
|
+
href={VITNODE_DOCS_URL}
|
|
27
|
+
rel="noopener noreferrer"
|
|
28
|
+
target="_blank"
|
|
29
|
+
>
|
|
30
|
+
Docs
|
|
31
|
+
</a>
|
|
32
|
+
<a
|
|
33
|
+
className="hover:text-foreground flex items-center gap-1.5 transition-colors"
|
|
34
|
+
href={VITNODE_SPONSOR_URL}
|
|
35
|
+
rel="noopener noreferrer"
|
|
36
|
+
target="_blank"
|
|
37
|
+
>
|
|
38
|
+
<Heart className="size-3.5" />
|
|
39
|
+
Sponsor
|
|
40
|
+
</a>
|
|
41
|
+
</nav>
|
|
42
|
+
</div>
|
|
43
|
+
</footer>
|
|
44
|
+
);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one bootstrap adapter this application still owns.
|
|
3
|
+
*
|
|
4
|
+
* Auth, the AdminCP session and the AdminCP user search all reach the API
|
|
5
|
+
* through `@vitnode/core`'s own default transports now, so none of them needs a
|
|
6
|
+
* module here. Messages cannot work that way, for two reasons that only hold
|
|
7
|
+
* in app source:
|
|
8
|
+
*
|
|
9
|
+
* - `loadIntlMessages` reads *this* application's locale barrels, which are the
|
|
10
|
+
* app's own files plus whichever packages it installed. A package cannot know
|
|
11
|
+
* them.
|
|
12
|
+
* - `createServerFn` is extracted by the TanStack Start compiler, and the
|
|
13
|
+
* compiler only runs over the application's source. `@vitnode/core` ships
|
|
14
|
+
* precompiled, so a server function declared there would never have its
|
|
15
|
+
* handler extracted and would resolve to `undefined` at runtime.
|
|
16
|
+
*
|
|
17
|
+
* `configureIntl` is where the two meet: the app hands core the fetch and its
|
|
18
|
+
* configured languages, and everything else in the package reads them back
|
|
19
|
+
* through `getIntlRuntime()`.
|
|
20
|
+
*/
|
|
21
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
22
|
+
import { configureIntl, validateIntlInput } from "@vitnode/core/tanstack/i18n";
|
|
23
|
+
import { IntlProvider } from "use-intl";
|
|
24
|
+
|
|
25
|
+
import { loadIntlMessages } from "@/server/messages.server";
|
|
26
|
+
import { vitNodeConfig } from "@/vitnode.config";
|
|
27
|
+
|
|
28
|
+
export const getIntlMessages = createServerFn()
|
|
29
|
+
.validator(validateIntlInput)
|
|
30
|
+
.handler(async ({ data }) => await loadIntlMessages(data));
|
|
31
|
+
|
|
32
|
+
export const { localeRouting } = configureIntl({
|
|
33
|
+
fetchMessages: async input => await getIntlMessages({ data: input }),
|
|
34
|
+
|
|
35
|
+
hostIntlProvider: IntlProvider,
|
|
36
|
+
i18n: vitNodeConfig.i18n,
|
|
37
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LocaleMessagesMap } from "@vitnode/core/lib/i18n/types";
|
|
2
|
+
|
|
3
|
+
import { CONFIG_PLUGIN as CORE } from "@vitnode/core/config";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const packageMessages: Record<string, LocaleMessagesMap> = {
|
|
7
|
+
[CORE.pluginId]: {
|
|
8
|
+
en: async () => await import("@vitnode/core/locales/en.json"),
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AnyRouter } from "@tanstack/react-router";
|
|
2
|
+
|
|
3
|
+
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
|
4
|
+
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
|
|
5
|
+
import { createVitNodeQueryClient } from "@vitnode/core/lib/query-client";
|
|
6
|
+
import { createLocaleRewrite } from "@vitnode/core/tanstack/i18n";
|
|
7
|
+
import { pageHead } from "@vitnode/core/tanstack/metadata";
|
|
8
|
+
import { RoutePendingSpinner } from "@vitnode/core/tanstack/pending";
|
|
9
|
+
import {
|
|
10
|
+
pluginRouteSpecs,
|
|
11
|
+
withPluginRoutes,
|
|
12
|
+
} from "@vitnode/core/tanstack/plugin-routes";
|
|
13
|
+
import {
|
|
14
|
+
withCoreAdminRoutes,
|
|
15
|
+
withCoreMainRoutes,
|
|
16
|
+
withCoreRootRoutes,
|
|
17
|
+
} from "@vitnode/core/tanstack/routes";
|
|
18
|
+
|
|
19
|
+
import { localeRouting } from "./lib/i18n";
|
|
20
|
+
import { pluginRouteSources } from "./plugin-routes.gen";
|
|
21
|
+
import { Route as adminShellRoute } from "./routes/_admin";
|
|
22
|
+
import { Route as mainShellRoute } from "./routes/_main";
|
|
23
|
+
import { routeTree as fileRouteTree } from "./routeTree.gen";
|
|
24
|
+
|
|
25
|
+
const loadContentRegistry = async () =>
|
|
26
|
+
(await import("./content-registry.gen")).contentRegistry;
|
|
27
|
+
|
|
28
|
+
const routeTree = withCoreRootRoutes(
|
|
29
|
+
withCoreAdminRoutes(
|
|
30
|
+
withCoreMainRoutes(
|
|
31
|
+
withPluginRoutes(fileRouteTree, pluginRouteSpecs(pluginRouteSources), {
|
|
32
|
+
mountUnder: { admin: adminShellRoute, main: mainShellRoute },
|
|
33
|
+
pageHead,
|
|
34
|
+
}),
|
|
35
|
+
{ localeRouting, mountUnder: mainShellRoute, pageHead },
|
|
36
|
+
),
|
|
37
|
+
{ loadContentRegistry, mountUnder: adminShellRoute, pageHead },
|
|
38
|
+
),
|
|
39
|
+
{ localeRouting, mountUnder: fileRouteTree, pageHead },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export function getRouter() {
|
|
43
|
+
const queryClient = createVitNodeQueryClient();
|
|
44
|
+
|
|
45
|
+
const holder: { current?: AnyRouter } = {};
|
|
46
|
+
|
|
47
|
+
const router = createTanStackRouter({
|
|
48
|
+
context: { queryClient },
|
|
49
|
+
defaultPendingComponent: RoutePendingSpinner,
|
|
50
|
+
defaultPendingMs: 150,
|
|
51
|
+
defaultPendingMinMs: 300,
|
|
52
|
+
defaultPreload: "intent",
|
|
53
|
+
defaultPreloadStaleTime: 0,
|
|
54
|
+
defaultStaleReloadMode: "blocking",
|
|
55
|
+
rewrite: createLocaleRewrite(() => holder.current),
|
|
56
|
+
routeTree,
|
|
57
|
+
scrollRestoration: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
holder.current = router;
|
|
61
|
+
|
|
62
|
+
setupRouterSsrQueryIntegration({ queryClient, router });
|
|
63
|
+
|
|
64
|
+
return router;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
declare module "@tanstack/react-router" {
|
|
68
|
+
interface Register {
|
|
69
|
+
router: ReturnType<typeof getRouter>;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
import { TanStackDevtools } from "@tanstack/react-devtools";
|
|
4
|
+
import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools";
|
|
5
|
+
import {
|
|
6
|
+
createRootRouteWithContext,
|
|
7
|
+
HeadContent,
|
|
8
|
+
Outlet,
|
|
9
|
+
Scripts,
|
|
10
|
+
} from "@tanstack/react-router";
|
|
11
|
+
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
|
|
12
|
+
import { ThemeScript } from "@vitnode/core/components/theme-script";
|
|
13
|
+
import {
|
|
14
|
+
intlQueryOptions,
|
|
15
|
+
publicPathnameOf,
|
|
16
|
+
resolveLocale,
|
|
17
|
+
useLocale,
|
|
18
|
+
} from "@vitnode/core/tanstack/i18n";
|
|
19
|
+
import { VitNodeRootProviders } from "@vitnode/core/tanstack/layout";
|
|
20
|
+
|
|
21
|
+
import type { Locale } from "@/vitnode.config";
|
|
22
|
+
|
|
23
|
+
import { vitNodeConfig } from "@/vitnode.config";
|
|
24
|
+
|
|
25
|
+
import appCss from "../styles.css?url";
|
|
26
|
+
|
|
27
|
+
const { debug, editor, i18n, metadata, theme } = vitNodeConfig;
|
|
28
|
+
|
|
29
|
+
export interface RootRouterContext {
|
|
30
|
+
queryClient: QueryClient;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Route = createRootRouteWithContext<RootRouterContext>()({
|
|
34
|
+
beforeLoad: ({ location }) => ({
|
|
35
|
+
locale: resolveLocale<Locale>(publicPathnameOf(location)),
|
|
36
|
+
}),
|
|
37
|
+
component: RootComponent,
|
|
38
|
+
head: () => ({
|
|
39
|
+
links: [
|
|
40
|
+
{ href: appCss, rel: "stylesheet" },
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
href: "/favicon.ico",
|
|
44
|
+
rel: "icon",
|
|
45
|
+
sizes: "32x32",
|
|
46
|
+
type: "image/x-icon",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
meta: [
|
|
50
|
+
{ charSet: "utf-8" },
|
|
51
|
+
{ content: "width=device-width, initial-scale=1", name: "viewport" },
|
|
52
|
+
{ title: metadata.title },
|
|
53
|
+
],
|
|
54
|
+
}),
|
|
55
|
+
loader: async ({ context }) => {
|
|
56
|
+
await context.queryClient.query({
|
|
57
|
+
...intlQueryOptions({ locale: context.locale }),
|
|
58
|
+
staleTime: "static",
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
shellComponent: RootDocument,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function RootComponent() {
|
|
65
|
+
return (
|
|
66
|
+
<VitNodeRootProviders
|
|
67
|
+
config={{ debug, editor, locales: i18n.locales, theme }}
|
|
68
|
+
>
|
|
69
|
+
<Outlet />
|
|
70
|
+
</VitNodeRootProviders>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
75
|
+
const locale = useLocale();
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<html lang={locale} suppressHydrationWarning>
|
|
79
|
+
<head>
|
|
80
|
+
<HeadContent />
|
|
81
|
+
<ThemeScript {...theme} />
|
|
82
|
+
</head>
|
|
83
|
+
|
|
84
|
+
<body suppressHydrationWarning>
|
|
85
|
+
{children}
|
|
86
|
+
|
|
87
|
+
{import.meta.env.DEV ? (
|
|
88
|
+
<TanStackDevtools
|
|
89
|
+
config={{
|
|
90
|
+
position: "bottom-right",
|
|
91
|
+
}}
|
|
92
|
+
plugins={[
|
|
93
|
+
{
|
|
94
|
+
name: "TanStack Router",
|
|
95
|
+
render: <TanStackRouterDevtoolsPanel />,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "TanStack Query",
|
|
99
|
+
render: <ReactQueryDevtoolsPanel />,
|
|
100
|
+
},
|
|
101
|
+
]}
|
|
102
|
+
/>
|
|
103
|
+
) : null}
|
|
104
|
+
|
|
105
|
+
<Scripts />
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { adminBreadcrumb } from "@vitnode/core/tanstack/admin";
|
|
3
|
+
import {
|
|
4
|
+
AdminDashboardRouteContent,
|
|
5
|
+
loadAdminDashboardRoute,
|
|
6
|
+
} from "@vitnode/core/tanstack/admin/dashboard";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export const Route = createFileRoute("/_admin/admin/core/")({
|
|
10
|
+
loader: async ({ context }) => await loadAdminDashboardRoute(context),
|
|
11
|
+
component: AdminDashboardRoute,
|
|
12
|
+
|
|
13
|
+
staticData: {
|
|
14
|
+
breadcrumb: adminBreadcrumb({ segments: ["core"] }),
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function AdminDashboardRoute() {
|
|
19
|
+
return <AdminDashboardRouteContent {...Route.useLoaderData()} />;
|
|
20
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
|
|
2
|
+
import {
|
|
3
|
+
ADMIN_ENTRY_PATH,
|
|
4
|
+
adminReturnToFor,
|
|
5
|
+
canEnterAdmin,
|
|
6
|
+
ensureAdminAccess,
|
|
7
|
+
loadAdminMessages,
|
|
8
|
+
preloadAdminAccess,
|
|
9
|
+
} from "@vitnode/core/tanstack/admin";
|
|
10
|
+
import { pageHead } from "@vitnode/core/tanstack/metadata";
|
|
11
|
+
|
|
12
|
+
import { AdminShell } from "@/components/admin-shell";
|
|
13
|
+
|
|
14
|
+
export const Route = createFileRoute("/_admin")({
|
|
15
|
+
beforeLoad: async ({ context, location, preload }) => {
|
|
16
|
+
const access = preload
|
|
17
|
+
? await preloadAdminAccess(context.queryClient)
|
|
18
|
+
: await ensureAdminAccess(context.queryClient);
|
|
19
|
+
|
|
20
|
+
if (!canEnterAdmin(access)) {
|
|
21
|
+
// TanStack Router's own control-flow signal: `redirect()` returns a typed
|
|
22
|
+
// redirect object that the router catches and turns into a navigation
|
|
23
|
+
// (or, during SSR, a 302). Throwing it is what stops the guard.
|
|
24
|
+
//
|
|
25
|
+
// `/admin` is a sibling leaf, not a child of this route, so this cannot
|
|
26
|
+
// loop: the sign-in page's own guard only redirects *away* on a granted
|
|
27
|
+
// session, and `sanitizeAdminReturnTo` rejects `/admin` as a target.
|
|
28
|
+
//
|
|
29
|
+
// Cast because `/admin` is not in this router's type table: it is
|
|
30
|
+
// `@vitnode/core`'s code-based route now, mounted by `withCoreRootRoutes`,
|
|
31
|
+
// and code-based routes are outside the generated tree's types. The
|
|
32
|
+
// *runtime* is unaffected, and `ADMIN_ENTRY_PATH` is the package's own
|
|
33
|
+
// constant - so the path and the sign-in route that serves it are still one
|
|
34
|
+
// fact in one place.
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
36
|
+
throw redirect({
|
|
37
|
+
search: { returnTo: adminReturnToFor(location) },
|
|
38
|
+
to: ADMIN_ENTRY_PATH,
|
|
39
|
+
} as unknown as Parameters<typeof redirect>[0]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Merged into the context of everything below, so a child route reads
|
|
43
|
+
// `context.adminAccess` already narrowed to the granted half of the union.
|
|
44
|
+
// It is the same object the guard decided on, from the same cache entry, so
|
|
45
|
+
// a page cannot disagree with the guard that let it render.
|
|
46
|
+
return { adminAccess: access };
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
loader: async ({ context }) => {
|
|
50
|
+
const { adminNav } = await import("@/admin-nav.gen");
|
|
51
|
+
|
|
52
|
+
await loadAdminMessages({ ...context, namespaces: adminNav.namespaces });
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
head: () => pageHead({ robots: "noindex, nofollow" }),
|
|
56
|
+
component: AdminLayout,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function AdminLayout() {
|
|
60
|
+
return (
|
|
61
|
+
<AdminShell>
|
|
62
|
+
<Outlet />
|
|
63
|
+
</AdminShell>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { LucideIcon } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import type { RegisteredRouter } from "@tanstack/react-router";
|
|
4
|
+
|
|
5
|
+
import { createFileRoute, Link } from "@tanstack/react-router";
|
|
6
|
+
import { LogoVitNode } from "@vitnode/core/components/logo-vitnode";
|
|
7
|
+
import { buttonVariants } from "@vitnode/core/components/ui/button";
|
|
8
|
+
import {
|
|
9
|
+
VITNODE_DOCS_URL,
|
|
10
|
+
VITNODE_WEBSITE_URL,
|
|
11
|
+
} from "@vitnode/core/lib/docs-links";
|
|
12
|
+
import { useSessionQuery } from "@vitnode/core/tanstack/auth";
|
|
13
|
+
import { pageHead } from "@vitnode/core/tanstack/metadata";
|
|
14
|
+
import { cn } from "cn";
|
|
15
|
+
import {
|
|
16
|
+
ArrowUpRight,
|
|
17
|
+
BookOpen,
|
|
18
|
+
Globe,
|
|
19
|
+
ShieldCheck,
|
|
20
|
+
UserRoundPlus,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
|
|
23
|
+
const REGISTER_HREF = "/register";
|
|
24
|
+
const ADMIN_HREF = "/admin";
|
|
25
|
+
|
|
26
|
+
export const Route = createFileRoute("/_main/")({
|
|
27
|
+
head: () =>
|
|
28
|
+
pageHead({
|
|
29
|
+
description: "Your VitNode app is up and running.",
|
|
30
|
+
robots: "index, follow",
|
|
31
|
+
title: "Welcome",
|
|
32
|
+
}),
|
|
33
|
+
component: HomeRoute,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
interface NextStep {
|
|
37
|
+
description: string;
|
|
38
|
+
external?: boolean;
|
|
39
|
+
href: string;
|
|
40
|
+
icon: LucideIcon;
|
|
41
|
+
title: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const registerStep: NextStep = {
|
|
45
|
+
description:
|
|
46
|
+
"Create a member account. On a fresh install the first one becomes the administrator.",
|
|
47
|
+
href: REGISTER_HREF,
|
|
48
|
+
icon: UserRoundPlus,
|
|
49
|
+
title: "Register",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const sharedSteps: NextStep[] = [
|
|
53
|
+
{
|
|
54
|
+
description: "Settings, members, roles, plugins and content live here.",
|
|
55
|
+
href: ADMIN_HREF,
|
|
56
|
+
icon: ShieldCheck,
|
|
57
|
+
title: "AdminCP",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
description: "Configuration, routing, plugins and deployment guides.",
|
|
61
|
+
external: true,
|
|
62
|
+
href: VITNODE_DOCS_URL,
|
|
63
|
+
icon: BookOpen,
|
|
64
|
+
title: "Documentation",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
description: "Project website, release notes and community links.",
|
|
68
|
+
external: true,
|
|
69
|
+
href: VITNODE_WEBSITE_URL,
|
|
70
|
+
icon: Globe,
|
|
71
|
+
title: "vitnode.com",
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
function HomeRoute() {
|
|
76
|
+
const { data } = useSessionQuery();
|
|
77
|
+
const user = data?.user ?? null;
|
|
78
|
+
const steps = user ? sharedSteps : [registerStep, ...sharedSteps];
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="container mx-auto flex max-w-3xl flex-col gap-14 px-4 py-16 sm:py-24">
|
|
82
|
+
<section className="flex flex-col items-center gap-6 text-center">
|
|
83
|
+
<LogoVitNode className="size-14" idPrefix="welcome-logo" small />
|
|
84
|
+
|
|
85
|
+
<div className="flex flex-col gap-3">
|
|
86
|
+
<h1 className="text-4xl font-semibold tracking-tight text-balance sm:text-5xl">
|
|
87
|
+
Your VitNode app is running
|
|
88
|
+
</h1>
|
|
89
|
+
|
|
90
|
+
<p className="text-muted-foreground mx-auto max-w-xl text-lg leading-relaxed text-pretty">
|
|
91
|
+
{user
|
|
92
|
+
? `You are signed in as ${user.name}. Open the AdminCP to configure the site.`
|
|
93
|
+
: "Create an account or sign in, then open the AdminCP to configure the site. On a fresh install the first registered member becomes the administrator."}
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div className="flex flex-col gap-3 sm:flex-row">
|
|
98
|
+
{user ? null : (
|
|
99
|
+
<Link
|
|
100
|
+
className={cn(buttonVariants({ size: "lg" }), "px-5")}
|
|
101
|
+
to={REGISTER_HREF}
|
|
102
|
+
>
|
|
103
|
+
<UserRoundPlus />
|
|
104
|
+
Create an account
|
|
105
|
+
</Link>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<Link
|
|
109
|
+
className={cn(
|
|
110
|
+
buttonVariants({
|
|
111
|
+
size: "lg",
|
|
112
|
+
variant: user ? "default" : "outline",
|
|
113
|
+
}),
|
|
114
|
+
"px-5",
|
|
115
|
+
)}
|
|
116
|
+
to={ADMIN_HREF}
|
|
117
|
+
>
|
|
118
|
+
<ShieldCheck />
|
|
119
|
+
Open AdminCP
|
|
120
|
+
</Link>
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
|
|
124
|
+
<section className="flex flex-col gap-4">
|
|
125
|
+
<h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
|
|
126
|
+
Next steps
|
|
127
|
+
</h2>
|
|
128
|
+
|
|
129
|
+
<div
|
|
130
|
+
className={cn(
|
|
131
|
+
"grid gap-3",
|
|
132
|
+
steps.length === 3 ? "sm:grid-cols-3" : "sm:grid-cols-2",
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
135
|
+
{steps.map(step => (
|
|
136
|
+
<NextStepCard key={step.href} {...step} />
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
</section>
|
|
140
|
+
|
|
141
|
+
<p className="text-muted-foreground text-center text-sm text-pretty">
|
|
142
|
+
This page lives in <code>src/routes/_main/index.tsx</code>. Replace it
|
|
143
|
+
whenever you are ready.
|
|
144
|
+
</p>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function NextStepCard({
|
|
150
|
+
description,
|
|
151
|
+
external = false,
|
|
152
|
+
href,
|
|
153
|
+
icon: Icon,
|
|
154
|
+
title,
|
|
155
|
+
}: NextStep) {
|
|
156
|
+
const className =
|
|
157
|
+
"group bg-card hover:bg-muted/50 focus-visible:border-ring focus-visible:ring-ring/50 flex flex-col gap-3 rounded-xl border p-5 transition-colors outline-none focus-visible:ring-3";
|
|
158
|
+
|
|
159
|
+
const content = (
|
|
160
|
+
<>
|
|
161
|
+
<div className="flex items-center justify-between">
|
|
162
|
+
<span className="bg-primary/10 text-primary flex size-9 items-center justify-center rounded-lg">
|
|
163
|
+
<Icon className="size-4.5" />
|
|
164
|
+
</span>
|
|
165
|
+
|
|
166
|
+
<ArrowUpRight className="text-muted-foreground size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div className="flex flex-col gap-1">
|
|
170
|
+
<span className="font-medium">{title}</span>
|
|
171
|
+
<span className="text-muted-foreground text-sm leading-relaxed text-pretty">
|
|
172
|
+
{description}
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
</>
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (external) {
|
|
179
|
+
return (
|
|
180
|
+
<a
|
|
181
|
+
className={className}
|
|
182
|
+
href={href}
|
|
183
|
+
rel="noopener noreferrer"
|
|
184
|
+
target="_blank"
|
|
185
|
+
>
|
|
186
|
+
{content}
|
|
187
|
+
</a>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<Link<RegisteredRouter, string, string> className={className} href={href}>
|
|
193
|
+
{content}
|
|
194
|
+
</Link>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
|
2
|
+
import { MainBreadcrumb } from "@vitnode/core/tanstack/breadcrumb";
|
|
3
|
+
import {
|
|
4
|
+
loadMainShell,
|
|
5
|
+
ThemeLayoutContent,
|
|
6
|
+
} from "@vitnode/core/tanstack/layout";
|
|
7
|
+
|
|
8
|
+
import { MainFooter } from "@/components/main-footer";
|
|
9
|
+
import { MainHeader } from "@/components/main-header";
|
|
10
|
+
|
|
11
|
+
export const Route = createFileRoute("/_main")({
|
|
12
|
+
loader: async ({ context }) => await loadMainShell(context),
|
|
13
|
+
component: MainLayout,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function MainLayout() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex min-h-svh flex-col [&>main]:flex-1">
|
|
19
|
+
<ThemeLayoutContent
|
|
20
|
+
breadcrumb={<MainBreadcrumb />}
|
|
21
|
+
header={<MainHeader />}
|
|
22
|
+
>
|
|
23
|
+
<Outlet />
|
|
24
|
+
</ThemeLayoutContent>
|
|
25
|
+
|
|
26
|
+
<MainFooter />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "@tanstack/react-start/server-only";
|
|
2
|
+
import { createIntlMessagesLoader } from "@vitnode/core/tanstack/i18n/server";
|
|
3
|
+
|
|
4
|
+
import { vitNodeServerConfig } from "@/vitnode.server.config";
|
|
5
|
+
|
|
6
|
+
export type { IntlMessages } from "@vitnode/core/tanstack/i18n/server";
|
|
7
|
+
|
|
8
|
+
export const loadIntlMessages = createIntlMessagesLoader(vitNodeServerConfig);
|