Initial commit

This commit is contained in:
Bohdan Denysenko 2024-12-29 10:56:43 +00:00 • committed by GitHub
commit 864f37a652
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 45596 additions and 0 deletions

2
.example.env Normal file
View file

@ -0,0 +1,2 @@
VITE_HOST=localhost
VITE_PORT=4000

27
.gitignore vendored Normal file
View file

@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
.env
__generated__/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# :zzz: template
## Stack
- ChakraUI
- React
- TanStackRouter
- Express
- Prisma - MariaDB (todo)
- GraphQL
- Vite
`npx @chakra-ui/cli snippet add --outdir app/client/components/ui/`
`npx graphql-code-generator init`
### Useful links:
- Theming for ChakraUI-v3 (no docs) -> [link](https://github.com/chakra-ui/chakra-ui/discussions/9009)
- TanStackRouter -> [link](https://tanstack.com/router/latest)

View file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,74 @@
"use client"
import type { GroupProps, SlotRecipeProps } from "@chakra-ui/react"
import { Avatar as ChakraAvatar, Group } from "@chakra-ui/react"
import * as React from "react"
type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>
export interface AvatarProps extends ChakraAvatar.RootProps {
name?: string
src?: string
srcSet?: string
loading?: ImageProps["loading"]
icon?: React.ReactElement
fallback?: React.ReactNode
}
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
function Avatar(props, ref) {
const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
props
return (
<ChakraAvatar.Root ref={ref} {...rest}>
<AvatarFallback name={name} icon={icon}>
{fallback}
</AvatarFallback>
<ChakraAvatar.Image src={src} srcSet={srcSet} loading={loading} />
{children}
</ChakraAvatar.Root>
)
},
)
interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
name?: string
icon?: React.ReactElement
}
const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
function AvatarFallback(props, ref) {
const { name, icon, children, ...rest } = props
return (
<ChakraAvatar.Fallback ref={ref} {...rest}>
{children}
{name != null && children == null && <>{getInitials(name)}</>}
{name == null && children == null && (
<ChakraAvatar.Icon asChild={!!icon}>{icon}</ChakraAvatar.Icon>
)}
</ChakraAvatar.Fallback>
)
},
)
function getInitials(name: string) {
const names = name.trim().split(" ")
const firstName = names[0] != null ? names[0] : ""
const lastName = names.length > 1 ? names[names.length - 1] : ""
return firstName && lastName
? `${firstName.charAt(0)}${lastName.charAt(0)}`
: firstName.charAt(0)
}
interface AvatarGroupProps extends GroupProps, SlotRecipeProps<"avatar"> {}
export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
function AvatarGroup(props, ref) {
const { size, variant, borderless, ...rest } = props
return (
<ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
<Group gap="0" spaceX="-3" ref={ref} {...rest} />
</ChakraAvatar.PropsProvider>
)
},
)

View file

@ -0,0 +1,40 @@
import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react"
import {
AbsoluteCenter,
Button as ChakraButton,
Span,
Spinner,
} from "@chakra-ui/react"
import * as React from "react"
interface ButtonLoadingProps {
loading?: boolean
loadingText?: React.ReactNode
}
export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
function Button(props, ref) {
const { loading, disabled, loadingText, children, ...rest } = props
return (
<ChakraButton disabled={loading || disabled} ref={ref} {...rest}>
{loading && !loadingText ? (
<>
<AbsoluteCenter display="inline-flex">
<Spinner size="inherit" color="inherit" />
</AbsoluteCenter>
<Span opacity={0}>{children}</Span>
</>
) : loading && loadingText ? (
<>
<Spinner size="inherit" color="inherit" />
{loadingText}
</>
) : (
children
)}
</ChakraButton>
)
},
)

View file

@ -0,0 +1,25 @@
import { Checkbox as ChakraCheckbox } from "@chakra-ui/react"
import * as React from "react"
export interface CheckboxProps extends ChakraCheckbox.RootProps {
icon?: React.ReactNode
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
rootRef?: React.Ref<HTMLLabelElement>
}
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
function Checkbox(props, ref) {
const { icon, children, inputProps, rootRef, ...rest } = props
return (
<ChakraCheckbox.Root ref={rootRef} {...rest}>
<ChakraCheckbox.HiddenInput ref={ref} {...inputProps} />
<ChakraCheckbox.Control>
{icon || <ChakraCheckbox.Indicator />}
</ChakraCheckbox.Control>
{children != null && (
<ChakraCheckbox.Label>{children}</ChakraCheckbox.Label>
)}
</ChakraCheckbox.Root>
)
},
)

View file

@ -0,0 +1,17 @@
import type { ButtonProps } from "@chakra-ui/react"
import { IconButton as ChakraIconButton } from "@chakra-ui/react"
import * as React from "react"
import { LuX } from "react-icons/lu"
export type CloseButtonProps = ButtonProps
export const CloseButton = React.forwardRef<
HTMLButtonElement,
CloseButtonProps
>(function CloseButton(props, ref) {
return (
<ChakraIconButton variant="ghost" aria-label="Close" ref={ref} {...props}>
{props.children ?? <LuX />}
</ChakraIconButton>
)
})

View file

@ -0,0 +1,67 @@
"use client"
import type { IconButtonProps } from "@chakra-ui/react"
import { ClientOnly, IconButton, Skeleton } from "@chakra-ui/react"
import { ThemeProvider, useTheme } from "next-themes"
import type { ThemeProviderProps } from "next-themes"
import * as React from "react"
import { LuMoon, LuSun } from "react-icons/lu"
export interface ColorModeProviderProps extends ThemeProviderProps {}
export function ColorModeProvider(props: ColorModeProviderProps) {
return (
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
)
}
export function useColorMode() {
const { resolvedTheme, setTheme } = useTheme()
const toggleColorMode = () => {
setTheme(resolvedTheme === "light" ? "dark" : "light")
}
return {
colorMode: resolvedTheme,
setColorMode: setTheme,
toggleColorMode,
}
}
export function useColorModeValue<T>(light: T, dark: T) {
const { colorMode } = useColorMode()
return colorMode === "light" ? light : dark
}
export function ColorModeIcon() {
const { colorMode } = useColorMode()
return colorMode === "light" ? <LuSun /> : <LuMoon />
}
interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
export const ColorModeButton = React.forwardRef<
HTMLButtonElement,
ColorModeButtonProps
>(function ColorModeButton(props, ref) {
const { toggleColorMode } = useColorMode()
return (
<ClientOnly fallback={<Skeleton boxSize="8" />}>
<IconButton
onClick={toggleColorMode}
variant="ghost"
aria-label="Toggle color mode"
size="sm"
ref={ref}
{...props}
css={{
_icon: {
width: "5",
height: "5",
},
}}
>
<ColorModeIcon />
</IconButton>
</ClientOnly>
)
})

View file

@ -0,0 +1,62 @@
import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react"
import { CloseButton } from "./close-button"
import * as React from "react"
interface DialogContentProps extends ChakraDialog.ContentProps {
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
backdrop?: boolean
}
export const DialogContent = React.forwardRef<
HTMLDivElement,
DialogContentProps
>(function DialogContent(props, ref) {
const {
children,
portalled = true,
portalRef,
backdrop = true,
...rest
} = props
return (
<Portal disabled={!portalled} container={portalRef}>
{backdrop && <ChakraDialog.Backdrop />}
<ChakraDialog.Positioner>
<ChakraDialog.Content ref={ref} {...rest} asChild={false}>
{children}
</ChakraDialog.Content>
</ChakraDialog.Positioner>
</Portal>
)
})
export const DialogCloseTrigger = React.forwardRef<
HTMLButtonElement,
ChakraDialog.CloseTriggerProps
>(function DialogCloseTrigger(props, ref) {
return (
<ChakraDialog.CloseTrigger
position="absolute"
top="2"
insetEnd="2"
{...props}
asChild
>
<CloseButton size="sm" ref={ref}>
{props.children}
</CloseButton>
</ChakraDialog.CloseTrigger>
)
})
export const DialogRoot = ChakraDialog.Root
export const DialogFooter = ChakraDialog.Footer
export const DialogHeader = ChakraDialog.Header
export const DialogBody = ChakraDialog.Body
export const DialogBackdrop = ChakraDialog.Backdrop
export const DialogTitle = ChakraDialog.Title
export const DialogDescription = ChakraDialog.Description
export const DialogTrigger = ChakraDialog.Trigger
export const DialogActionTrigger = ChakraDialog.ActionTrigger

View file

@ -0,0 +1,52 @@
import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react"
import { CloseButton } from "./close-button"
import * as React from "react"
interface DrawerContentProps extends ChakraDrawer.ContentProps {
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
offset?: ChakraDrawer.ContentProps["padding"]
}
export const DrawerContent = React.forwardRef<
HTMLDivElement,
DrawerContentProps
>(function DrawerContent(props, ref) {
const { children, portalled = true, portalRef, offset, ...rest } = props
return (
<Portal disabled={!portalled} container={portalRef}>
<ChakraDrawer.Positioner padding={offset}>
<ChakraDrawer.Content ref={ref} {...rest} asChild={false}>
{children}
</ChakraDrawer.Content>
</ChakraDrawer.Positioner>
</Portal>
)
})
export const DrawerCloseTrigger = React.forwardRef<
HTMLButtonElement,
ChakraDrawer.CloseTriggerProps
>(function DrawerCloseTrigger(props, ref) {
return (
<ChakraDrawer.CloseTrigger
position="absolute"
top="2"
insetEnd="2"
{...props}
asChild
>
<CloseButton size="sm" ref={ref} />
</ChakraDrawer.CloseTrigger>
)
})
export const DrawerTrigger = ChakraDrawer.Trigger
export const DrawerRoot = ChakraDrawer.Root
export const DrawerFooter = ChakraDrawer.Footer
export const DrawerHeader = ChakraDrawer.Header
export const DrawerBody = ChakraDrawer.Body
export const DrawerBackdrop = ChakraDrawer.Backdrop
export const DrawerDescription = ChakraDrawer.Description
export const DrawerTitle = ChakraDrawer.Title
export const DrawerActionTrigger = ChakraDrawer.ActionTrigger

View file

@ -0,0 +1,33 @@
import { Field as ChakraField } from "@chakra-ui/react"
import * as React from "react"
export interface FieldProps extends Omit<ChakraField.RootProps, "label"> {
label?: React.ReactNode
helperText?: React.ReactNode
errorText?: React.ReactNode
optionalText?: React.ReactNode
}
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
function Field(props, ref) {
const { label, children, helperText, errorText, optionalText, ...rest } =
props
return (
<ChakraField.Root ref={ref} {...rest}>
{label && (
<ChakraField.Label>
{label}
<ChakraField.RequiredIndicator fallback={optionalText} />
</ChakraField.Label>
)}
{children}
{helperText && (
<ChakraField.HelperText>{helperText}</ChakraField.HelperText>
)}
{errorText && (
<ChakraField.ErrorText>{errorText}</ChakraField.ErrorText>
)}
</ChakraField.Root>
)
},
)

View file

@ -0,0 +1,50 @@
import type { BoxProps, InputElementProps } from "@chakra-ui/react"
import { Group, InputElement } from "@chakra-ui/react"
import * as React from "react"
export interface InputGroupProps extends BoxProps {
startElementProps?: InputElementProps
endElementProps?: InputElementProps
startElement?: React.ReactNode
endElement?: React.ReactNode
children: React.ReactElement
startOffset?: InputElementProps["paddingStart"]
endOffset?: InputElementProps["paddingEnd"]
}
export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
function InputGroup(props, ref) {
const {
startElement,
startElementProps,
endElement,
endElementProps,
children,
startOffset = "6px",
endOffset = "6px",
...rest
} = props
return (
<Group ref={ref} {...rest}>
{startElement && (
<InputElement pointerEvents="none" {...startElementProps}>
{startElement}
</InputElement>
)}
{React.cloneElement(children, {
...(startElement && {
ps: `calc(var(--input-height) - ${startOffset})`,
}),
...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }),
...children.props,
})}
{endElement && (
<InputElement placement="end" {...endElementProps}>
{endElement}
</InputElement>
)}
</Group>
)
},
)

View file

@ -0,0 +1,27 @@
import { Link as ChakraLink } from "@chakra-ui/react";
import { createLink, LinkComponent } from "@tanstack/react-router";
import * as React from "react";
interface ChakraLinkProps
extends Omit<React.ComponentPropsWithoutRef<typeof ChakraLink>, "href"> {}
const ChakraLinkComponent = React.forwardRef<
HTMLAnchorElement,
ChakraLinkProps
>((props, ref) => {
return <ChakraLink ref={ref} {...props} />;
});
const CreatedLinkComponent = createLink(ChakraLinkComponent);
export const Link: LinkComponent<typeof ChakraLinkComponent> = (props) => {
return (
<CreatedLinkComponent
textDecoration={"underline"}
_hover={{ textDecoration: "none" }}
_focus={{ textDecoration: "none" }}
preload={"intent"}
{...props}
/>
);
};

View file

@ -0,0 +1,59 @@
import { Popover as ChakraPopover, Portal } from "@chakra-ui/react"
import { CloseButton } from "./close-button"
import * as React from "react"
interface PopoverContentProps extends ChakraPopover.ContentProps {
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
}
export const PopoverContent = React.forwardRef<
HTMLDivElement,
PopoverContentProps
>(function PopoverContent(props, ref) {
const { portalled = true, portalRef, ...rest } = props
return (
<Portal disabled={!portalled} container={portalRef}>
<ChakraPopover.Positioner>
<ChakraPopover.Content ref={ref} {...rest} />
</ChakraPopover.Positioner>
</Portal>
)
})
export const PopoverArrow = React.forwardRef<
HTMLDivElement,
ChakraPopover.ArrowProps
>(function PopoverArrow(props, ref) {
return (
<ChakraPopover.Arrow {...props} ref={ref}>
<ChakraPopover.ArrowTip />
</ChakraPopover.Arrow>
)
})
export const PopoverCloseTrigger = React.forwardRef<
HTMLButtonElement,
ChakraPopover.CloseTriggerProps
>(function PopoverCloseTrigger(props, ref) {
return (
<ChakraPopover.CloseTrigger
position="absolute"
top="1"
insetEnd="1"
{...props}
asChild
ref={ref}
>
<CloseButton size="sm" />
</ChakraPopover.CloseTrigger>
)
})
export const PopoverTitle = ChakraPopover.Title
export const PopoverDescription = ChakraPopover.Description
export const PopoverFooter = ChakraPopover.Footer
export const PopoverHeader = ChakraPopover.Header
export const PopoverRoot = ChakraPopover.Root
export const PopoverBody = ChakraPopover.Body
export const PopoverTrigger = ChakraPopover.Trigger

View file

@ -0,0 +1,13 @@
"use client";
import { Theme, ChakraProvider, defaultSystem } from "@chakra-ui/react";
import { ColorModeProvider, type ColorModeProviderProps } from "./color-mode";
export function Provider(props: ColorModeProviderProps) {
return (
<ChakraProvider value={defaultSystem}>
<ColorModeProvider forcedTheme="dark" />
<Theme colorPalette="cyan">{props.children}</Theme>
</ChakraProvider>
);
}

View file

@ -0,0 +1,24 @@
import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react"
import * as React from "react"
export interface RadioProps extends ChakraRadioGroup.ItemProps {
rootRef?: React.Ref<HTMLDivElement>
inputProps?: React.InputHTMLAttributes<HTMLInputElement>
}
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
function Radio(props, ref) {
const { children, inputProps, rootRef, ...rest } = props
return (
<ChakraRadioGroup.Item ref={rootRef} {...rest}>
<ChakraRadioGroup.ItemHiddenInput ref={ref} {...inputProps} />
<ChakraRadioGroup.ItemIndicator />
{children && (
<ChakraRadioGroup.ItemText>{children}</ChakraRadioGroup.ItemText>
)}
</ChakraRadioGroup.Item>
)
},
)
export const RadioGroup = ChakraRadioGroup.Root

View file

@ -0,0 +1,82 @@
import { Slider as ChakraSlider, For, HStack } from "@chakra-ui/react"
import * as React from "react"
export interface SliderProps extends ChakraSlider.RootProps {
marks?: Array<number | { value: number; label: React.ReactNode }>
label?: React.ReactNode
showValue?: boolean
}
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
function Slider(props, ref) {
const { marks: marksProp, label, showValue, ...rest } = props
const value = props.defaultValue ?? props.value
const marks = marksProp?.map((mark) => {
if (typeof mark === "number") return { value: mark, label: undefined }
return mark
})
const hasMarkLabel = !!marks?.some((mark) => mark.label)
return (
<ChakraSlider.Root ref={ref} thumbAlignment="center" {...rest}>
{label && !showValue && (
<ChakraSlider.Label>{label}</ChakraSlider.Label>
)}
{label && showValue && (
<HStack justify="space-between">
<ChakraSlider.Label>{label}</ChakraSlider.Label>
<ChakraSlider.ValueText />
</HStack>
)}
<ChakraSlider.Control data-has-mark-label={hasMarkLabel || undefined}>
<ChakraSlider.Track>
<ChakraSlider.Range />
</ChakraSlider.Track>
<SliderThumbs value={value} />
<SliderMarks marks={marks} />
</ChakraSlider.Control>
</ChakraSlider.Root>
)
},
)
function SliderThumbs(props: { value?: number[] }) {
const { value } = props
return (
<For each={value}>
{(_, index) => (
<ChakraSlider.Thumb key={index} index={index}>
<ChakraSlider.HiddenInput />
</ChakraSlider.Thumb>
)}
</For>
)
}
interface SliderMarksProps {
marks?: Array<number | { value: number; label: React.ReactNode }>
}
const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
function SliderMarks(props, ref) {
const { marks } = props
if (!marks?.length) return null
return (
<ChakraSlider.MarkerGroup ref={ref}>
{marks.map((mark, index) => {
const value = typeof mark === "number" ? mark : mark.value
const label = typeof mark === "number" ? undefined : mark.label
return (
<ChakraSlider.Marker key={index} value={value}>
<ChakraSlider.MarkerIndicator />
{label}
</ChakraSlider.Marker>
)
})}
</ChakraSlider.MarkerGroup>
)
},
)

View file

@ -0,0 +1,46 @@
import { Tooltip as ChakraTooltip, Portal } from "@chakra-ui/react"
import * as React from "react"
export interface TooltipProps extends ChakraTooltip.RootProps {
showArrow?: boolean
portalled?: boolean
portalRef?: React.RefObject<HTMLElement>
content: React.ReactNode
contentProps?: ChakraTooltip.ContentProps
disabled?: boolean
}
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
function Tooltip(props, ref) {
const {
showArrow,
children,
disabled,
portalled,
content,
contentProps,
portalRef,
...rest
} = props
if (disabled) return children
return (
<ChakraTooltip.Root {...rest}>
<ChakraTooltip.Trigger asChild>{children}</ChakraTooltip.Trigger>
<Portal disabled={!portalled} container={portalRef}>
<ChakraTooltip.Positioner>
<ChakraTooltip.Content ref={ref} {...contentProps}>
{showArrow && (
<ChakraTooltip.Arrow>
<ChakraTooltip.ArrowTip />
</ChakraTooltip.Arrow>
)}
{content}
</ChakraTooltip.Content>
</ChakraTooltip.Positioner>
</Portal>
</ChakraTooltip.Root>
)
},
)

0
app/client/fragments.ts Normal file
View file

46
app/client/main.tsx Normal file
View file

@ -0,0 +1,46 @@
import "@client/assets/styles/index.css";
import "@client/assets/styles/nerd-fonts-icons.css";
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "@client/components/ui/provider";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
// TanStackRouter - Import the generated route tree
import { routeTree } from "./routeTree.gen";
// Create a new router instance
const router = createRouter({ routeTree });
// Register the router instance for type safety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
// Apollo
const HOST = import.meta.env.VITE_HOST || "localhost";
const PORT = import.meta.env.VITE_PORT || "3000";
const uri = `http://${HOST}:${PORT}/graphql`;
console.log(uri);
const client = new ApolloClient({
uri,
cache: new InMemoryCache(),
connectToDevTools: true,
});
const rootElement = document.getElementById("root")!;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<ApolloProvider client={client}>
<Provider>
<RouterProvider router={router} />
</Provider>
</ApolloProvider>
</StrictMode>,
);
}

View file

@ -0,0 +1,33 @@
import { Button, HStack, Stack } from "@chakra-ui/react";
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
import React from "react";
const TanStackRouterDevtools =
process.env.NODE_ENV === "production"
? () => null // Render nothing in production
: React.lazy(() =>
// Lazy load in development
import("@tanstack/router-devtools").then((res) => ({
default: res.TanStackRouterDevtools,
// For Embedded Mode
// default: res.TanStackRouterDevtoolsPanel
})),
);
export const Route = createRootRoute({
component: () => (
<Stack padding={3}>
<HStack>
<Link to="/">
<Button>Home</Button>
</Link>
<Link to="/about">
<Button>About</Button>
</Link>
</HStack>
<hr style={{ padding: "4px" }} />
<Outlet />
<TanStackRouterDevtools />
</Stack>
),
});

View file

@ -0,0 +1,21 @@
import { Text, Icon, Stack } from "@chakra-ui/react";
import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/about")({
component: About,
});
function About() {
return (
<Stack flexDirection={"row"} alignItems={"center"}>
<Text>Hello from About!</Text>
<Text>And NerdFonts!</Text>
<Icon>
<i className="nf nf-fa-heart" />
</Icon>
<Icon>
<i className="nf nf-custom-vim" />
</Icon>
</Stack>
);
}

View file

@ -0,0 +1,20 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import { gql, useQuery } from "@apollo/client";
export const Route = createLazyFileRoute("/book")({
component: RouteComponent,
});
function RouteComponent() {
return <div>"/book"</div>;
}
const getBookQuery = gql`
query BookOne {
booksMany {
id
author
title
}
}
`;

View file

@ -0,0 +1,41 @@
import { Text, Stack } from "@chakra-ui/react";
import { createLazyFileRoute } from "@tanstack/react-router";
import { gql, useQuery } from "@apollo/client";
export const Route = createLazyFileRoute("/")({
component: Index,
});
function Index() {
const { data } = useQuery(getBooksQuery);
return (
<Stack>
<Text>Hello world!</Text>
<Text>This is my template!</Text>
<Stack>
{data && (
<>
{data.booksMany.map((book: any) => (
<Stack key={book.id}>
<Text>{book.title}</Text>
<Text>{book.author}</Text>
</Stack>
))}
</>
)}
</Stack>
</Stack>
);
}
const getBooksQuery = gql`
query BooksMany {
booksMany {
id
author
title
}
}
`;

139
app/client/routeTree.gen.ts Normal file
View file

@ -0,0 +1,139 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createFileRoute } from '@tanstack/react-router'
// Import Routes
import { Route as rootRoute } from './pages/__root'
// Create Virtual Routes
const BookLazyImport = createFileRoute('/book')()
const AboutLazyImport = createFileRoute('/about')()
const IndexLazyImport = createFileRoute('/')()
// Create/Update Routes
const BookLazyRoute = BookLazyImport.update({
id: '/book',
path: '/book',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./pages/book.lazy').then((d) => d.Route))
const AboutLazyRoute = AboutLazyImport.update({
id: '/about',
path: '/about',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./pages/about.lazy').then((d) => d.Route))
const IndexLazyRoute = IndexLazyImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./pages/index.lazy').then((d) => d.Route))
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexLazyImport
parentRoute: typeof rootRoute
}
'/about': {
id: '/about'
path: '/about'
fullPath: '/about'
preLoaderRoute: typeof AboutLazyImport
parentRoute: typeof rootRoute
}
'/book': {
id: '/book'
path: '/book'
fullPath: '/book'
preLoaderRoute: typeof BookLazyImport
parentRoute: typeof rootRoute
}
}
}
// Create and export the route tree
export interface FileRoutesByFullPath {
'/': typeof IndexLazyRoute
'/about': typeof AboutLazyRoute
'/book': typeof BookLazyRoute
}
export interface FileRoutesByTo {
'/': typeof IndexLazyRoute
'/about': typeof AboutLazyRoute
'/book': typeof BookLazyRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexLazyRoute
'/about': typeof AboutLazyRoute
'/book': typeof BookLazyRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/about' | '/book'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/about' | '/book'
id: '__root__' | '/' | '/about' | '/book'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexLazyRoute: typeof IndexLazyRoute
AboutLazyRoute: typeof AboutLazyRoute
BookLazyRoute: typeof BookLazyRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexLazyRoute: IndexLazyRoute,
AboutLazyRoute: AboutLazyRoute,
BookLazyRoute: BookLazyRoute,
}
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
/* ROUTE_MANIFEST_START
{
"routes": {
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/about",
"/book"
]
},
"/": {
"filePath": "index.lazy.tsx"
},
"/about": {
"filePath": "about.lazy.tsx"
},
"/book": {
"filePath": "book.lazy.tsx"
}
}
}
ROUTE_MANIFEST_END */

8
app/client/tsconfig.json Normal file
View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx"
}
}

1
app/client/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

View file

@ -0,0 +1,15 @@
/* gql imports */
import { QueryResolvers } from "@generated/graphql";
/* Query */
import { BooksManyResolver } from "./resolvers/booksManyResolver";
import { BookOneResolver } from "./resolvers/bookOneResolver";
export const Query: QueryResolvers = {
booksMany: BooksManyResolver,
bookOne: BookOneResolver,
};
export const Mutation = {};
export const Subscription = {};

View file

@ -0,0 +1,13 @@
import { BookInput, Book } from "@generated/graphql";
import { books } from "./booksManyResolver";
export async function BookOneResolver(
_: any,
{ ...input }: BookInput,
): Promise<Book> {
console.log("Called 'BookOneResolver' resolver...");
const book = books.filter((item) => item.id === input.id)[0];
return book;
}

View file

@ -0,0 +1,20 @@
import { Book } from "@generated/graphql";
export const books = [
{
id: "1",
title: "some-title-1",
author: "someone",
},
{
id: "2",
title: "some-title-1",
author: "someone",
},
];
export async function BooksManyResolver(_: any): Promise<Book[]> {
console.log("Called 'BooksManyResolver' resolver...");
return books;
}

View file

@ -0,0 +1,14 @@
type Query {
booksMany: [Book]!
bookOne(input: BookInput!): [Book]!
}
type Book {
id: ID!
title: String!
author: String!
}
input BookInput {
id: ID!
}

54
app/server/main.ts Normal file
View file

@ -0,0 +1,54 @@
import "dotenv/config";
import express from "express";
import cors from "cors";
import ViteExpress from "vite-express";
import { ApolloServer } from "@apollo/server";
import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer";
import { expressMiddleware } from "@apollo/server/express4";
import http from "http";
import fs from "fs";
import path from "path";
import { Query, Mutation, Subscription } from "./graphql/graphql";
const __dirname = import.meta.dirname;
const HOST = process.env.VITE_HOST || "localhost";
const PORT = parseInt(process.env.VITE_PORT || "3000");
const MOTD = `\n\n\n\x1b[35m-server is listening-\x1b[0m\n\nhttp://${HOST}:${PORT}\nhttp://localhost:${PORT}\n\n\n`;
const app = express();
const server = http.createServer(app);
const typeDefs = fs.readFileSync(
path.join(__dirname, "graphql", "schema.graphql"),
"utf8",
);
const resolvers = {
Query,
// Mutation,
// Subscription,
};
const apollo = new ApolloServer({
typeDefs,
resolvers,
plugins: [ApolloServerPluginDrainHttpServer({ httpServer: server })],
});
await apollo.start();
app.use(
"/graphql",
cors<cors.CorsRequest>(),
express.json(),
expressMiddleware(apollo),
);
await new Promise<void>((resolve) =>
server.listen({ hostname: HOST, port: PORT }, resolve),
);
console.log(MOTD);
ViteExpress.bind(app, server);

19
codegen.ts Normal file
View file

@ -0,0 +1,19 @@
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
overwrite: true,
schema: "./app/server/graphql/schema.graphql",
documents: "./app/client/fragments.ts",
generates: {
"./__generated__/": {
preset: "client",
plugins: ["typescript", "typescript-resolvers"],
},
"./graphql.schema.json": {
plugins: ["introspection"],
},
},
ignoreNoDocuments: true,
};
export default config;

1246
graphql.schema.json Normal file

File diff suppressed because it is too large Load diff

13
index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/ln-logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ln</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/app/client/main.tsx"></script>
</body>
</html>

10800
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

49
package.json Normal file
View file

@ -0,0 +1,49 @@
{
"name": "ln",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "nodemon -w app/server -x tsx app/server/main.ts",
"start": "NODE_ENV=production tsx app/server/main.ts",
"build": "vite build",
"gql-types": "graphql-codegen --config codegen.ts"
},
"dependencies": {
"@apollo/client": "^3.12.2",
"@apollo/server": "^4.11.2",
"@chakra-ui/react": "^3.2.3",
"@emotion/react": "^11.13.5",
"@tanstack/react-router": "^1.85.0",
"@tanstack/router-vite-plugin": "^1.84.4",
"@types/cors": "^2.8.17",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"graphql": "^16.9.0",
"next-themes": "^0.4.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.4.0",
"tsx": "^4.3.0",
"typescript": "^5.3.2",
"vite-express": "^0.20.0"
},
"devDependencies": {
"@graphql-codegen/cli": "5.0.3",
"@graphql-codegen/typescript": "^4.1.2",
"@graphql-codegen/typescript-resolvers": "^4.4.1",
"@tanstack/router-devtools": "^1.85.0",
"@tanstack/router-plugin": "^1.84.4",
"@types/express": "^4.17.21",
"@types/node": "^20.9.3",
"@types/react": "^18.0.38",
"@types/react-dom": "^18.2.16",
"@vitejs/plugin-react": "^4.2.0",
"nodemon": "^3.0.1",
"vite": "^5.0.2",
"vite-tsconfig-paths": "^5.1.4",
"@graphql-codegen/introspection": "4.0.3",
"@graphql-codegen/client-preset": "4.5.1"
}
}

26
tsconfig.json Normal file
View file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"paths": {
"@app/*": ["./app/*"],
"@client/*": ["./app/client/*"],
"@server/*": ["./app/server/*"],
"@generated/*": ["./__generated__/*"],
"@root/*": ["./*"]
}
}
}

6
tsr.config.json Normal file
View file

@ -0,0 +1,6 @@
{
"routesDirectory": "./app/client/pages",
"generatedRouteTree": "./app/client/routeTree.gen.ts",
"routeFileIgnorePrefix": "-",
"quoteStyle": "single"
}

10
vite.config.ts Normal file
View file

@ -0,0 +1,10 @@
// vite.config.ts
import { defineConfig } from "vite";
import viteReact from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import tsConfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [TanStackRouterVite(), viteReact(), tsConfigPaths()],
});