Initial commit
This commit is contained in:
commit
864f37a652
48 changed files with 45596 additions and 0 deletions
2
.example.env
Normal file
2
.example.env
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
VITE_HOST=localhost
|
||||
VITE_PORT=4000
|
||||
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal 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
21
README.md
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/client/assets/fonts/symbols/SymbolsNerdFont-Regular.ttf
Normal file
BIN
app/client/assets/fonts/symbols/SymbolsNerdFont-Regular.ttf
Normal file
Binary file not shown.
BIN
app/client/assets/fonts/symbols/SymbolsNerdFontMono-Regular.ttf
Normal file
BIN
app/client/assets/fonts/symbols/SymbolsNerdFontMono-Regular.ttf
Normal file
Binary file not shown.
0
app/client/assets/styles/index.css
Normal file
0
app/client/assets/styles/index.css
Normal file
32281
app/client/assets/styles/nerd-fonts-icons.css
Normal file
32281
app/client/assets/styles/nerd-fonts-icons.css
Normal file
File diff suppressed because it is too large
Load diff
74
app/client/components/ui/avatar.tsx
Normal file
74
app/client/components/ui/avatar.tsx
Normal 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>
|
||||
)
|
||||
},
|
||||
)
|
||||
40
app/client/components/ui/button.tsx
Normal file
40
app/client/components/ui/button.tsx
Normal 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>
|
||||
)
|
||||
},
|
||||
)
|
||||
25
app/client/components/ui/checkbox.tsx
Normal file
25
app/client/components/ui/checkbox.tsx
Normal 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>
|
||||
)
|
||||
},
|
||||
)
|
||||
17
app/client/components/ui/close-button.tsx
Normal file
17
app/client/components/ui/close-button.tsx
Normal 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>
|
||||
)
|
||||
})
|
||||
67
app/client/components/ui/color-mode.tsx
Normal file
67
app/client/components/ui/color-mode.tsx
Normal 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>
|
||||
)
|
||||
})
|
||||
62
app/client/components/ui/dialog.tsx
Normal file
62
app/client/components/ui/dialog.tsx
Normal 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
|
||||
52
app/client/components/ui/drawer.tsx
Normal file
52
app/client/components/ui/drawer.tsx
Normal 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
|
||||
33
app/client/components/ui/field.tsx
Normal file
33
app/client/components/ui/field.tsx
Normal 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>
|
||||
)
|
||||
},
|
||||
)
|
||||
50
app/client/components/ui/input-group.tsx
Normal file
50
app/client/components/ui/input-group.tsx
Normal 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>
|
||||
)
|
||||
},
|
||||
)
|
||||
27
app/client/components/ui/link.tsx
Normal file
27
app/client/components/ui/link.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
59
app/client/components/ui/popover.tsx
Normal file
59
app/client/components/ui/popover.tsx
Normal 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
|
||||
13
app/client/components/ui/provider.tsx
Normal file
13
app/client/components/ui/provider.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
24
app/client/components/ui/radio.tsx
Normal file
24
app/client/components/ui/radio.tsx
Normal 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
|
||||
82
app/client/components/ui/slider.tsx
Normal file
82
app/client/components/ui/slider.tsx
Normal 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>
|
||||
)
|
||||
},
|
||||
)
|
||||
46
app/client/components/ui/tooltip.tsx
Normal file
46
app/client/components/ui/tooltip.tsx
Normal 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
0
app/client/fragments.ts
Normal file
46
app/client/main.tsx
Normal file
46
app/client/main.tsx
Normal 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>,
|
||||
);
|
||||
}
|
||||
33
app/client/pages/__root.tsx
Normal file
33
app/client/pages/__root.tsx
Normal 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>
|
||||
),
|
||||
});
|
||||
21
app/client/pages/about.lazy.tsx
Normal file
21
app/client/pages/about.lazy.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
20
app/client/pages/book.lazy.tsx
Normal file
20
app/client/pages/book.lazy.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
`;
|
||||
41
app/client/pages/index.lazy.tsx
Normal file
41
app/client/pages/index.lazy.tsx
Normal 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
139
app/client/routeTree.gen.ts
Normal 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
8
app/client/tsconfig.json
Normal 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
1
app/client/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
15
app/server/graphql/graphql.ts
Normal file
15
app/server/graphql/graphql.ts
Normal 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 = {};
|
||||
13
app/server/graphql/resolvers/bookOneResolver.ts
Normal file
13
app/server/graphql/resolvers/bookOneResolver.ts
Normal 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;
|
||||
}
|
||||
20
app/server/graphql/resolvers/booksManyResolver.ts
Normal file
20
app/server/graphql/resolvers/booksManyResolver.ts
Normal 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;
|
||||
}
|
||||
14
app/server/graphql/schema.graphql
Normal file
14
app/server/graphql/schema.graphql
Normal 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
54
app/server/main.ts
Normal 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
19
codegen.ts
Normal 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
1246
graphql.schema.json
Normal file
File diff suppressed because it is too large
Load diff
13
index.html
Normal file
13
index.html
Normal 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
10800
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
49
package.json
Normal file
49
package.json
Normal 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
26
tsconfig.json
Normal 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
6
tsr.config.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"routesDirectory": "./app/client/pages",
|
||||
"generatedRouteTree": "./app/client/routeTree.gen.ts",
|
||||
"routeFileIgnorePrefix": "-",
|
||||
"quoteStyle": "single"
|
||||
}
|
||||
10
vite.config.ts
Normal file
10
vite.config.ts
Normal 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()],
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue