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.
Files changed (103) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +59 -18
  3. package/copy-of-vitnode-app/.vscode/settings.json +10 -0
  4. package/copy-of-vitnode-app/README.md +62 -0
  5. package/copy-of-vitnode-app/api/.gitignore_template +32 -0
  6. package/copy-of-vitnode-app/api/drizzle.config.ts +12 -0
  7. package/copy-of-vitnode-app/api/src/index.ts +27 -0
  8. package/copy-of-vitnode-app/api/src/vitnode.api.config.ts +39 -0
  9. package/copy-of-vitnode-app/api/tsconfig.json +17 -0
  10. package/copy-of-vitnode-app/api-bun/src/index.ts +16 -0
  11. package/copy-of-vitnode-app/api-single-app/drizzle.config.ts +12 -0
  12. package/copy-of-vitnode-app/api-single-app/src/routes/api/$.ts +10 -0
  13. package/copy-of-vitnode-app/api-single-app/src/server/api-bridge.ts +11 -0
  14. package/copy-of-vitnode-app/api-single-app/src/server/vitnode-api.server.ts +29 -0
  15. package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +28 -0
  16. package/copy-of-vitnode-app/docker/docker-compose.yml +32 -0
  17. package/copy-of-vitnode-app/eslint/.prettierrc.mjs +1 -1
  18. package/copy-of-vitnode-app/eslint/eslint.config.mjs +18 -2
  19. package/copy-of-vitnode-app/{root → eslint-react}/.prettierrc.mjs +1 -1
  20. package/copy-of-vitnode-app/eslint-react/eslint.config.mjs +19 -0
  21. package/copy-of-vitnode-app/monorepo/.gitignore_template +41 -0
  22. package/copy-of-vitnode-app/monorepo/apps/api/.env.example +12 -0
  23. package/copy-of-vitnode-app/monorepo/apps/web/.env.example +1 -0
  24. package/copy-of-vitnode-app/monorepo/turbo.json +53 -0
  25. package/copy-of-vitnode-app/root/.env.example +9 -5
  26. package/copy-of-vitnode-app/root/.gitignore_template +41 -0
  27. package/copy-of-vitnode-app/root/global.d.ts +10 -0
  28. package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +21 -0
  29. package/copy-of-vitnode-app/root/src/components/main-footer.tsx +44 -0
  30. package/copy-of-vitnode-app/root/src/components/main-header.tsx +4 -0
  31. package/copy-of-vitnode-app/root/src/lib/i18n.ts +37 -0
  32. package/copy-of-vitnode-app/root/src/locales/app.ts +4 -0
  33. package/copy-of-vitnode-app/root/src/locales/packages.ts +10 -0
  34. package/copy-of-vitnode-app/root/src/router.tsx +71 -0
  35. package/copy-of-vitnode-app/root/src/routes/__root.tsx +109 -0
  36. package/copy-of-vitnode-app/root/src/routes/_admin/admin.core.index.tsx +20 -0
  37. package/copy-of-vitnode-app/root/src/routes/_admin.tsx +65 -0
  38. package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +196 -0
  39. package/copy-of-vitnode-app/root/src/routes/_main.tsx +29 -0
  40. package/copy-of-vitnode-app/root/src/server/messages.server.ts +8 -0
  41. package/copy-of-vitnode-app/root/src/start.ts +5 -0
  42. package/copy-of-vitnode-app/root/src/styles.css +149 -0
  43. package/copy-of-vitnode-app/root/src/vitnode.config.ts +20 -0
  44. package/copy-of-vitnode-app/root/src/vitnode.server.config.ts +12 -0
  45. package/copy-of-vitnode-app/root/tsconfig.json +29 -16
  46. package/copy-of-vitnode-app/root/tsr.config.json +3 -0
  47. package/copy-of-vitnode-app/root/vite.config.ts +22 -0
  48. package/copy-of-vitnode-plugin/root/.swcrc +26 -0
  49. package/copy-of-vitnode-plugin/root/global.d.ts +21 -0
  50. package/copy-of-vitnode-plugin/root/npmignore.template +17 -0
  51. package/copy-of-vitnode-plugin/root/tsconfig.build.json +5 -0
  52. package/copy-of-vitnode-plugin/root/tsconfig.json +25 -0
  53. package/dist/src/create/create-package-json.js +266 -56
  54. package/dist/src/create/create-vitnode.js +191 -28
  55. package/dist/src/create/package-versions.js +49 -0
  56. package/dist/src/helpers/get-available-package-managers.js +6 -6
  57. package/dist/src/helpers/get-package-json.js +2 -2
  58. package/dist/src/helpers/get-package-manager-from-root.js +15 -0
  59. package/dist/src/helpers/get-vitnode-package-version.js +9 -0
  60. package/dist/src/helpers/init-git.js +38 -0
  61. package/dist/src/helpers/init-vitnode.js +33 -0
  62. package/dist/src/helpers/install-dependencies.js +55 -15
  63. package/dist/src/helpers/is-folder-empty.js +25 -26
  64. package/dist/src/helpers/is-online.js +16 -16
  65. package/dist/src/helpers/is-writeable.js +3 -3
  66. package/dist/src/helpers/spawn-command.js +7 -0
  67. package/dist/src/helpers/validate-pkg.js +1 -1
  68. package/dist/src/helpers/with-If.js +1 -0
  69. package/dist/src/index.js +30 -26
  70. package/dist/src/plugin/create/add-plugin-to-config.js +231 -0
  71. package/dist/src/plugin/create/add-plugin-to-workspace.js +74 -0
  72. package/dist/src/plugin/create/create-package-json.js +55 -0
  73. package/dist/src/plugin/create/create-plugin-vitnode.js +101 -0
  74. package/dist/src/plugin/create/route-templates.js +186 -0
  75. package/dist/src/plugin/index.js +17 -6
  76. package/dist/src/plugin/questions.js +14 -0
  77. package/dist/src/plugin/validation.js +76 -0
  78. package/dist/src/prepare/prepare.js +11 -8
  79. package/dist/src/questions.js +59 -15
  80. package/dist/src/validation.js +15 -15
  81. package/dist/tsconfig.build.tsbuildinfo +1 -0
  82. package/package.json +22 -12
  83. package/copy-of-vitnode-app/root/README.md +0 -1
  84. package/copy-of-vitnode-app/root/drizzle.config.ts +0 -12
  85. package/copy-of-vitnode-app/root/gitignore_template +0 -43
  86. package/copy-of-vitnode-app/root/next.config.ts +0 -11
  87. package/copy-of-vitnode-app/root/postcss.config.mjs +0 -8
  88. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/[...rest]/page.tsx +0 -5
  89. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/layout.tsx +0 -10
  90. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/not-found.tsx +0 -5
  91. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/page.tsx +0 -113
  92. package/copy-of-vitnode-app/root/src/app/[locale]/admin/(auth)/layout.tsx +0 -10
  93. package/copy-of-vitnode-app/root/src/app/[locale]/admin/not-found.tsx +0 -5
  94. package/copy-of-vitnode-app/root/src/app/[locale]/admin/page.tsx +0 -5
  95. package/copy-of-vitnode-app/root/src/app/[locale]/layout.tsx +0 -36
  96. package/copy-of-vitnode-app/root/src/app/api/[...route]/route.ts +0 -18
  97. package/copy-of-vitnode-app/root/src/app/favicon.ico +0 -0
  98. package/copy-of-vitnode-app/root/src/app/global-error.tsx +0 -27
  99. package/copy-of-vitnode-app/root/src/app/global.css +0 -128
  100. package/copy-of-vitnode-app/root/src/app/layout.tsx +0 -9
  101. package/copy-of-vitnode-app/root/src/app/not-found.tsx +0 -13
  102. package/copy-of-vitnode-app/root/src/vitnode.api.config.ts +0 -5
  103. 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,10 @@
1
+ /// <reference types="use-intl" />
2
+
3
+ import core from '@vitnode/core/locales/en.json' with { type: 'json' }
4
+
5
+
6
+ declare module 'use-intl' {
7
+ interface AppConfig {
8
+ Messages: typeof core
9
+ }
10
+ }
@@ -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,4 @@
1
+ import { MainHeader as MainHeaderContent } from "@vitnode/core/tanstack/layout";
2
+
3
+
4
+ export const MainHeader = () => <MainHeaderContent />;
@@ -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,4 @@
1
+ import type { AppMessagesMap } from "@vitnode/core/lib/i18n/types";
2
+
3
+
4
+ export const appMessages: AppMessagesMap = {};
@@ -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);
@@ -0,0 +1,5 @@
1
+ import { createVitNodeStart } from "@vitnode/core/tanstack/start";
2
+
3
+ import { vitNodeConfig } from "@/vitnode.config";
4
+
5
+ export const startInstance = createVitNodeStart({ config: vitNodeConfig });