initial layout
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export * from "./ui/LoginPage"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export interface ILoginPageProps {
|
||||
message?: string
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
.root {
|
||||
}
|
||||
|
||||
.form {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 520px;
|
||||
max-height: 720px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include typography.font-header-l;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client"
|
||||
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { FunctionComponent } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
import Link from "next/link"
|
||||
|
||||
import api from "@shared/api"
|
||||
import { useCookie } from "@shared/hooks/useCookie"
|
||||
import {
|
||||
ACCESS_TOKEN_COOKIE,
|
||||
REFRESH_TOKEN_COOKIE,
|
||||
} from "@shared/lib/constants"
|
||||
import { Button, Form, TextField } from "@shared/ui"
|
||||
|
||||
import { ILoginPageProps } from "../model/LoginPage.d"
|
||||
import styles from "./LoginPage.module.scss"
|
||||
|
||||
interface ILoginFormData {
|
||||
login: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export const LoginPage: FunctionComponent<
|
||||
ILoginPageProps
|
||||
> = (): JSX.Element => {
|
||||
const [, setAccessTokenCookie] = useCookie(ACCESS_TOKEN_COOKIE)
|
||||
const [, setRefreshTokenCookie] = useCookie(REFRESH_TOKEN_COOKIE)
|
||||
|
||||
const { mutate, isPending } = api.useMutation("post", "/auth/login", {
|
||||
onSuccess: ({ access, refresh, user }) => {
|
||||
setAccessTokenCookie(access)
|
||||
setRefreshTokenCookie(refresh)
|
||||
console.log("Login successful:", user)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Login failed:", error)
|
||||
},
|
||||
})
|
||||
|
||||
const { register, handleSubmit } = useForm<ILoginFormData>()
|
||||
|
||||
const onSubmit = (data: ILoginFormData): void => {
|
||||
mutate({
|
||||
body: {
|
||||
username: data.login,
|
||||
password: data.password,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root} data-testid="LoginPage">
|
||||
<Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
||||
<h1 className={styles.title}>Вход</h1>
|
||||
<div className={styles.fields}>
|
||||
<TextField
|
||||
id="login"
|
||||
label="Логин"
|
||||
placeholder="Ваш логин"
|
||||
{...register("login")}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
label="Пароль"
|
||||
placeholder="Ваш пароль"
|
||||
type="password"
|
||||
{...register("password")}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="primary" disabled={isPending}>
|
||||
Войти
|
||||
</Button>
|
||||
<div className={styles.actions}>
|
||||
<Link className={styles.link} href="/register">
|
||||
Нет аккаунта? Зарегистрироваться
|
||||
</Link>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/RegisterPage"
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface IRegisterPageProps {
|
||||
message?: string
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
.root {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.form {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 520px;
|
||||
max-height: 820px;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include typography.font-header-l;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
"use client"
|
||||
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { FunctionComponent } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
import { useHookFormMask } from "use-mask-input"
|
||||
|
||||
import api from "@shared/api"
|
||||
import { Button, Form, TextField } from "@shared/ui"
|
||||
|
||||
import { IRegisterPageProps } from "../model/RegisterPage.d"
|
||||
import styles from "./RegisterPage.module.scss"
|
||||
|
||||
interface IRegisterFormData {
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
phone_number?: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
export const RegisterPage: FunctionComponent<
|
||||
IRegisterPageProps
|
||||
> = (): JSX.Element => {
|
||||
const { mutate, isPending } = api.useMutation("post", "/auth/register", {
|
||||
onSuccess: (data) => {
|
||||
console.log("Register successful:", data)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Register failed:", error)
|
||||
},
|
||||
})
|
||||
|
||||
const { register, handleSubmit } = useForm<IRegisterFormData>()
|
||||
const registerWithMask = useHookFormMask(register)
|
||||
|
||||
const onSubmit = (data: IRegisterFormData): void => {
|
||||
const phone = data.phone_number?.trim()
|
||||
const avatar = data.avatar?.trim()
|
||||
|
||||
mutate({
|
||||
body: {
|
||||
username: data.username,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
phone_number: phone || undefined,
|
||||
avatar: avatar || undefined,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root} data-testid="RegisterPage">
|
||||
<Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
||||
<h1 className={styles.title}>Регистрация</h1>
|
||||
<div className={styles.fields}>
|
||||
<TextField
|
||||
id="username"
|
||||
label="Имя пользователя"
|
||||
placeholder="Ваш никнейм"
|
||||
{...register("username")}
|
||||
/>
|
||||
<TextField
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="you@example.com"
|
||||
type="email"
|
||||
{...register("email")}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
label="Пароль"
|
||||
placeholder="Придумайте пароль"
|
||||
type="password"
|
||||
{...register("password")}
|
||||
/>
|
||||
<TextField
|
||||
id="first_name"
|
||||
label="Имя"
|
||||
placeholder="Ваше имя"
|
||||
{...register("first_name")}
|
||||
/>
|
||||
<TextField
|
||||
id="last_name"
|
||||
label="Фамилия"
|
||||
placeholder="Ваша фамилия"
|
||||
{...register("last_name")}
|
||||
/>
|
||||
<TextField
|
||||
id="phone_number"
|
||||
label="Номер телефона"
|
||||
placeholder="+7 (___) ___-__-__"
|
||||
{...registerWithMask("phone_number", "+7 (999) 999-99-99")}
|
||||
/>
|
||||
<TextField
|
||||
id="avatar"
|
||||
label="Аватар"
|
||||
placeholder="Ссылка на изображение"
|
||||
{...register("avatar")}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="primary" disabled={isPending}>
|
||||
Создать аккаунт
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+3346
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
]
|
||||
}
|
||||
@@ -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("|"))
|
||||
@@ -0,0 +1,3 @@
|
||||
import { QueryClient } from "@tanstack/react-query"
|
||||
|
||||
export const queryClient = new QueryClient()
|
||||
@@ -14,6 +14,10 @@
|
||||
|
||||
body {
|
||||
background-color: #f8f8f8;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: #121212;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Alert"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { AlertProps } from "react-bootstrap/Alert"
|
||||
|
||||
export interface IAlertProps extends AlertProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Badge"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { BadgeProps } from "react-bootstrap/Badge"
|
||||
|
||||
export interface IBadgeProps extends BadgeProps {}
|
||||
@@ -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"
|
||||
Vendored
+1
-3
@@ -1,3 +1 @@
|
||||
export interface IButtonProps {
|
||||
message?: string
|
||||
}
|
||||
export * from "./model/Button.d"
|
||||
|
||||
@@ -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 @@
|
||||
export * from "./Button"
|
||||
export * from "./ui/Button"
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { ButtonProps } from "react-bootstrap/Button"
|
||||
|
||||
export interface IButtonProps extends ButtonProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Card"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { CardProps } from "react-bootstrap/Card"
|
||||
|
||||
export interface ICardProps extends CardProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Checkbox"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { FormCheckProps } from "react-bootstrap/FormCheck"
|
||||
|
||||
export interface ICheckboxProps extends Omit<FormCheckProps, "type"> {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Form"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { FormProps } from "react-bootstrap/Form"
|
||||
|
||||
export interface IFormProps extends FormProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Modal"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { ModalProps } from "react-bootstrap/Modal"
|
||||
|
||||
export interface IModalProps extends ModalProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Pagination"
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Radio"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { FormCheckProps } from "react-bootstrap/FormCheck"
|
||||
|
||||
export interface IRadioProps extends Omit<FormCheckProps, "type"> {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Select"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { FormSelectProps } from "react-bootstrap/FormSelect"
|
||||
|
||||
export interface ISelectProps extends FormSelectProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Table"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { TableProps } from "react-bootstrap/Table"
|
||||
|
||||
export interface ITableProps extends TableProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/Tabs"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { TabsProps } from "react-bootstrap/Tabs"
|
||||
|
||||
export interface ITabsProps extends TabsProps {}
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/TextField"
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { FormControlProps } from "react-bootstrap/FormControl"
|
||||
|
||||
export interface ITextFieldProps extends FormControlProps {
|
||||
id: string
|
||||
label?: string
|
||||
undertitle?: string
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user