initial layout

This commit is contained in:
Daniil
2026-01-19 23:19:58 +03:00
parent 749fda017c
commit 4688f65c5a
81 changed files with 4343 additions and 16 deletions
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
import createClient from "openapi-react-query"
import createFetchClient, { Middleware } from "openapi-fetch"
import { ACCESS_TOKEN_REGEXP, API_URL } from "@shared/lib/constants"
import { paths } from "./__generated__/openapi.types"
const isServer = typeof window === "undefined"
const getAccessTokenFromCookieHeader = (
cookieHeader: string | null,
): string | undefined => {
if (!cookieHeader) return
const token = cookieHeader.replace(ACCESS_TOKEN_REGEXP, "$1")
return token.length ? token : undefined
}
export const fetchClient = createFetchClient<paths>({
baseUrl: API_URL,
// credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
const middleware: Middleware = {
async onRequest({ request }) {
if (request.headers.has("Authorization")) return
let token: string | undefined
if (isServer) {
// In middleware/edge runtime there is no `next/headers` request scope.
token = getAccessTokenFromCookieHeader(request.headers.get("cookie"))
if (!token) {
try {
const { cookies } = await import("next/headers")
token = (await cookies()).get("access_token")?.value
} catch {
// Not in a request scope (e.g. middleware/edge or build-time).
}
}
} else {
token = document.cookie.replace(ACCESS_TOKEN_REGEXP, "$1")
}
if (token?.length) request.headers.set("Authorization", `Bearer ${token}`)
},
async onError({ error }) {
return new Error("Oops, fetch failed", { cause: error })
},
}
fetchClient.use(middleware)
export const api = createClient(fetchClient)
export default api
+29
View File
@@ -0,0 +1,29 @@
"use server"
import { fetchClient } from "."
export const pingServer = async (): Promise<boolean> => {
try {
await fetchClient.GET("/api/ping/")
return true
} catch (error) {
console.error("Ping server error:", error)
return false
}
}
export const verifyToken = async (token: string): Promise<boolean> => {
console.log("Verifying token:", token)
try {
const resp = await fetchClient.GET("/api/users/me/", {
headers: {
Authorization: `Bearer ${token}`,
},
})
console.log("Verify token response:", resp)
return true
} catch (error) {
console.error("Verify token error:", error)
return false
}
}
@@ -0,0 +1,18 @@
"use client"
import { QueryClientProvider as QueryClientProviderTanstack } from "@tanstack/react-query"
import { JSX, Suspense } from "react"
import { queryClient } from "@shared/lib/query_client"
export const QueryClientProvider = ({
children,
}: {
children: React.ReactNode
}): JSX.Element => {
return (
<QueryClientProviderTanstack client={queryClient}>
{children}
</QueryClientProviderTanstack>
)
}
+13
View File
@@ -0,0 +1,13 @@
import Cookies from "js-cookie"
export const useCookie = (key: string, defaultValue: string | null = null) => {
const getCookie = Cookies.get(key) || defaultValue
const setCookie = (value: string) => Cookies.set(key, value)
const removeCookie = () => Cookies.remove(key)
return [getCookie, setCookie, removeCookie] as [
string | null,
(value: string) => void,
() => void,
]
}
+37
View File
@@ -0,0 +1,37 @@
export const ACCESS_TOKEN_COOKIE = "access_token"
export const REFRESH_TOKEN_COOKIE = "refresh_token"
export const ACCESS_TOKEN_REGEXP = new RegExp(
/(?:(?:^|.*;\s*)access_token\s*=\s*([^;]*).*$)|^.*$/,
)
export const API_URL = process.env.NEXT_PUBLIC_API_URL
// Paths that can be accessed without authentication and without redirecting to login
export const ESSENTIAL_PATHS = [
".*/login.*",
".*/register.*",
".*/reset-password.*",
".*/recover.*",
]
// Paths that are excluded from authentication checks
export const EXCLUDED_PATHS = [
"^/public/.*",
"^/_next.*",
"^/static.*",
"^/fonts.*",
".*/api.*",
".*/logout.*",
".*/confirm-email.*",
".*/recover.*",
".*/manifest.json.*",
".*/android.*",
".*/apple.*",
".*/favicon.*",
".*/workbox.*",
".*/sw.js.*",
]
export const ENTRY_PATHS_REGEXP = new RegExp(ESSENTIAL_PATHS.join("|"))
export const EXCLUDE_PATHS_REGEXP = new RegExp(EXCLUDED_PATHS.join("|"))
+3
View File
@@ -0,0 +1,3 @@
import { QueryClient } from "@tanstack/react-query"
export const queryClient = new QueryClient()
View File
+4
View File
@@ -14,6 +14,10 @@
body {
background-color: #f8f8f8;
@media (prefers-color-scheme: dark) {
background-color: #121212;
}
}
:root {
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Alert"
+3
View File
@@ -0,0 +1,3 @@
import type { AlertProps } from "react-bootstrap/Alert"
export interface IAlertProps extends AlertProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { IAlertProps } from "../model/Alert.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapAlert from "react-bootstrap/Alert"
export const Alert = forwardRef<HTMLDivElement, IAlertProps>(
(props, ref): JSX.Element => <BootstrapAlert ref={ref} {...props} />,
)
Alert.displayName = "Alert"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Badge"
+3
View File
@@ -0,0 +1,3 @@
import type { BadgeProps } from "react-bootstrap/Badge"
export interface IBadgeProps extends BadgeProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { IBadgeProps } from "../model/Badge.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapBadge from "react-bootstrap/Badge"
export const Badge = forwardRef<HTMLSpanElement, IBadgeProps>(
(props, ref): JSX.Element => <BootstrapBadge ref={ref} {...props} />,
)
Badge.displayName = "Badge"
+1 -3
View File
@@ -1,3 +1 @@
export interface IButtonProps {
message?: string
}
export * from "./model/Button.d"
+1 -7
View File
@@ -1,7 +1 @@
import BootstrapButton, { ButtonProps } from "react-bootstrap/Button"
export const Button = (props: ButtonProps) => (
<BootstrapButton variant="primary" {...props}>
{props.children}
</BootstrapButton>
)
export * from "./ui/Button"
+1 -1
View File
@@ -1 +1 @@
export * from "./Button"
export * from "./ui/Button"
+3
View File
@@ -0,0 +1,3 @@
import type { ButtonProps } from "react-bootstrap/Button"
export interface IButtonProps extends ButtonProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { IButtonProps } from "../model/Button.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapButton from "react-bootstrap/Button"
export const Button = forwardRef<HTMLButtonElement, IButtonProps>(
(props, ref): JSX.Element => <BootstrapButton ref={ref} {...props} />,
)
Button.displayName = "Button"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Card"
+3
View File
@@ -0,0 +1,3 @@
import type { CardProps } from "react-bootstrap/Card"
export interface ICardProps extends CardProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { ICardProps } from "../model/Card.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapCard from "react-bootstrap/Card"
export const Card = forwardRef<HTMLDivElement, ICardProps>(
(props, ref): JSX.Element => <BootstrapCard ref={ref} {...props} />,
)
Card.displayName = "Card"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Checkbox"
+3
View File
@@ -0,0 +1,3 @@
import type { FormCheckProps } from "react-bootstrap/FormCheck"
export interface ICheckboxProps extends Omit<FormCheckProps, "type"> {}
+15
View File
@@ -0,0 +1,15 @@
"use client"
import type { ICheckboxProps } from "../model/Checkbox.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapFormCheck from "react-bootstrap/FormCheck"
export const Checkbox = forwardRef<HTMLInputElement, ICheckboxProps>(
(props, ref): JSX.Element => (
<BootstrapFormCheck ref={ref} type="checkbox" {...props} />
),
)
Checkbox.displayName = "Checkbox"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Form"
+3
View File
@@ -0,0 +1,3 @@
import type { FormProps } from "react-bootstrap/Form"
export interface IFormProps extends FormProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { IFormProps } from "../model/Form.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapForm from "react-bootstrap/Form"
export const Form = forwardRef<HTMLFormElement, IFormProps>(
(props, ref): JSX.Element => <BootstrapForm ref={ref} {...props} />,
)
Form.displayName = "Form"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Modal"
+3
View File
@@ -0,0 +1,3 @@
import type { ModalProps } from "react-bootstrap/Modal"
export interface IModalProps extends ModalProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { IModalProps } from "../model/Modal.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapModal from "react-bootstrap/Modal"
export const Modal = forwardRef<HTMLDivElement, IModalProps>(
(props, ref): JSX.Element => <BootstrapModal ref={ref} {...props} />,
)
Modal.displayName = "Modal"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Pagination"
+3
View File
@@ -0,0 +1,3 @@
import type { PaginationProps } from "react-bootstrap/Pagination"
export interface IPaginationProps extends PaginationProps {}
@@ -0,0 +1,13 @@
"use client"
import type { IPaginationProps } from "../model/Pagination.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapPagination from "react-bootstrap/Pagination"
export const Pagination = forwardRef<HTMLUListElement, IPaginationProps>(
(props, ref): JSX.Element => <BootstrapPagination ref={ref} {...props} />,
)
Pagination.displayName = "Pagination"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Radio"
+3
View File
@@ -0,0 +1,3 @@
import type { FormCheckProps } from "react-bootstrap/FormCheck"
export interface IRadioProps extends Omit<FormCheckProps, "type"> {}
+15
View File
@@ -0,0 +1,15 @@
"use client"
import type { IRadioProps } from "../model/Radio.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapFormCheck from "react-bootstrap/FormCheck"
export const Radio = forwardRef<HTMLInputElement, IRadioProps>(
(props, ref): JSX.Element => (
<BootstrapFormCheck ref={ref} type="radio" {...props} />
),
)
Radio.displayName = "Radio"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Select"
+3
View File
@@ -0,0 +1,3 @@
import type { FormSelectProps } from "react-bootstrap/FormSelect"
export interface ISelectProps extends FormSelectProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { ISelectProps } from "../model/Select.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapFormSelect from "react-bootstrap/FormSelect"
export const Select = forwardRef<HTMLSelectElement, ISelectProps>(
(props, ref): JSX.Element => <BootstrapFormSelect ref={ref} {...props} />,
)
Select.displayName = "Select"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Table"
+3
View File
@@ -0,0 +1,3 @@
import type { TableProps } from "react-bootstrap/Table"
export interface ITableProps extends TableProps {}
+13
View File
@@ -0,0 +1,13 @@
"use client"
import type { ITableProps } from "../model/Table.d"
import type { JSX } from "react"
import { forwardRef } from "react"
import BootstrapTable from "react-bootstrap/Table"
export const Table = forwardRef<HTMLTableElement, ITableProps>(
(props, ref): JSX.Element => <BootstrapTable ref={ref} {...props} />,
)
Table.displayName = "Table"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/Tabs"
+3
View File
@@ -0,0 +1,3 @@
import type { TabsProps } from "react-bootstrap/Tabs"
export interface ITabsProps extends TabsProps {}
+18
View File
@@ -0,0 +1,18 @@
"use client"
import type { ITabsProps } from "../model/Tabs.d"
import type { ForwardRefExoticComponent, JSX, RefAttributes } from "react"
import { forwardRef } from "react"
import BootstrapTabs from "react-bootstrap/Tabs"
const BootstrapTabsWithRef =
BootstrapTabs as unknown as ForwardRefExoticComponent<
ITabsProps & RefAttributes<HTMLDivElement>
>
export const Tabs = forwardRef<HTMLDivElement, ITabsProps>(
(props, ref): JSX.Element => <BootstrapTabsWithRef ref={ref} {...props} />,
)
Tabs.displayName = "Tabs"
+1
View File
@@ -0,0 +1 @@
export * from "./ui/TextField"
+7
View File
@@ -0,0 +1,7 @@
import type { FormControlProps } from "react-bootstrap/FormControl"
export interface ITextFieldProps extends FormControlProps {
id: string
label?: string
undertitle?: string
}
+28
View File
@@ -0,0 +1,28 @@
"use client"
import type { ITextFieldProps } from "../model/TextField.d"
import type { JSX } from "react"
import React, { forwardRef } from "react"
import BootstrapForm from "react-bootstrap/Form"
export const TextField = forwardRef<HTMLInputElement, ITextFieldProps>(
({ id, label, undertitle, ...props }, ref): JSX.Element => (
<React.Fragment>
{label && <BootstrapForm.Label htmlFor={id}>{label}</BootstrapForm.Label>}
<BootstrapForm.Control
id={id}
ref={ref}
{...props}
aria-describedby={`${id}-undertitle`}
/>
{undertitle && (
<BootstrapForm.Text id={`${id}-undertitle`} muted>
{undertitle}
</BootstrapForm.Text>
)}
</React.Fragment>
),
)
TextField.displayName = "TextField"
+12
View File
@@ -1 +1,13 @@
export * from "./Alert"
export * from "./Badge"
export * from "./Button"
export * from "./Card"
export * from "./Checkbox"
export * from "./Form"
export * from "./TextField"
export * from "./Modal"
export * from "./Pagination"
export * from "./Radio"
export * from "./Select"
export * from "./Table"
export * from "./Tabs"