Linting, component clean up
This commit is contained in:
parent
ba732dcd71
commit
e49ca2e749
25 changed files with 97 additions and 87 deletions
|
@ -27,6 +27,7 @@ function Auth({
|
||||||
const signText = signingIn ? "In" : "Up"
|
const signText = signingIn ? "In" : "Up"
|
||||||
const [username, setUsername] = useState("")
|
const [username, setUsername] = useState("")
|
||||||
const [password, setPassword] = useState("")
|
const [password, setPassword] = useState("")
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
const queryParams = useSearchParams()
|
const queryParams = useSearchParams()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -40,6 +41,7 @@ function Auth({
|
||||||
|
|
||||||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
setSubmitting(true)
|
||||||
|
|
||||||
const res = await signIn("credentials", {
|
const res = await signIn("credentials", {
|
||||||
username,
|
username,
|
||||||
|
@ -54,6 +56,7 @@ function Auth({
|
||||||
type: "error",
|
type: "error",
|
||||||
message: res.error
|
message: res.error
|
||||||
})
|
})
|
||||||
|
setSubmitting(false)
|
||||||
} else {
|
} else {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.push("/new")
|
router.push("/new")
|
||||||
|
@ -126,7 +129,7 @@ function Auth({
|
||||||
width="100%"
|
width="100%"
|
||||||
aria-label="Password"
|
aria-label="Password"
|
||||||
/>
|
/>
|
||||||
<Button width={"100%"} type="submit">
|
<Button width={"100%"} type="submit" loading={submitting}>
|
||||||
Sign {signText}
|
Sign {signText}
|
||||||
</Button>
|
</Button>
|
||||||
{isGithubEnabled ? <hr style={{ width: "100%" }} /> : null}
|
{isGithubEnabled ? <hr style={{ width: "100%" }} /> : null}
|
||||||
|
|
|
@ -9,7 +9,8 @@ import { Spinner } from "@components/spinner"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
function FileDropdown({
|
function FileDropdown({
|
||||||
files, loading
|
files,
|
||||||
|
loading
|
||||||
}: {
|
}: {
|
||||||
files: Pick<PostWithFiles, "files">["files"]
|
files: Pick<PostWithFiles, "files">["files"]
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
|
|
@ -12,9 +12,7 @@ type Props = {
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function MarkdownPreview({
|
function MarkdownPreview({ height = 500, fileId, content = "", title }: Props) {
|
||||||
height = 500, fileId, content = "", title
|
|
||||||
}: Props) {
|
|
||||||
const [preview, setPreview] = useState<string>(content)
|
const [preview, setPreview] = useState<string>(content)
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -25,9 +23,9 @@ function MarkdownPreview({
|
||||||
const body = fileId
|
const body = fileId
|
||||||
? undefined
|
? undefined
|
||||||
: JSON.stringify({
|
: JSON.stringify({
|
||||||
title: title || "",
|
title: title || "",
|
||||||
content: content
|
content: content
|
||||||
})
|
})
|
||||||
|
|
||||||
const resp = await fetchWithUser(path, {
|
const resp = await fetchWithUser(path, {
|
||||||
method: method,
|
method: method,
|
||||||
|
@ -61,7 +59,8 @@ function MarkdownPreview({
|
||||||
export default memo(MarkdownPreview)
|
export default memo(MarkdownPreview)
|
||||||
|
|
||||||
export function StaticPreview({
|
export function StaticPreview({
|
||||||
preview, height = 500
|
preview,
|
||||||
|
height = 500
|
||||||
}: {
|
}: {
|
||||||
preview: string
|
preview: string
|
||||||
height: string | number
|
height: string | number
|
||||||
|
@ -72,6 +71,7 @@ export function StaticPreview({
|
||||||
dangerouslySetInnerHTML={{ __html: preview }}
|
dangerouslySetInnerHTML={{ __html: preview }}
|
||||||
style={{
|
style={{
|
||||||
height
|
height
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ function Description({ onChange, description }: props) {
|
||||||
label="Description"
|
label="Description"
|
||||||
maxLength={256}
|
maxLength={256}
|
||||||
width="100%"
|
width="100%"
|
||||||
placeholder="An optional description of your post" />
|
placeholder="An optional description of your post"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ import clsx from "clsx"
|
||||||
// TODO: clean up
|
// TODO: clean up
|
||||||
|
|
||||||
function FormattingIcons({
|
function FormattingIcons({
|
||||||
textareaRef, className
|
textareaRef,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
textareaRef?: RefObject<TextareaMarkdownRef>
|
textareaRef?: RefObject<TextareaMarkdownRef>
|
||||||
className?: string
|
className?: string
|
||||||
|
@ -26,7 +27,8 @@ function FormattingIcons({
|
||||||
const handleLinkClick = () => textareaRef?.current?.trigger("link")
|
const handleLinkClick = () => textareaRef?.current?.trigger("link")
|
||||||
const handleImageClick = () => textareaRef?.current?.trigger("image")
|
const handleImageClick = () => textareaRef?.current?.trigger("image")
|
||||||
const handleCodeClick = () => textareaRef?.current?.trigger("code")
|
const handleCodeClick = () => textareaRef?.current?.trigger("code")
|
||||||
const handleListClick = () => textareaRef?.current?.trigger("unordered-list")
|
const handleListClick = () =>
|
||||||
|
textareaRef?.current?.trigger("unordered-list")
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
icon: <Bold />,
|
icon: <Bold />,
|
||||||
|
@ -81,7 +83,8 @@ function FormattingIcons({
|
||||||
iconRight={icon}
|
iconRight={icon}
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
onClick={action}
|
onClick={action}
|
||||||
buttonType="secondary" />
|
buttonType="secondary"
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,7 +31,8 @@ function Title({ onChange, title }: props) {
|
||||||
label="Title"
|
label="Title"
|
||||||
className={styles.labelAndInput}
|
className={styles.labelAndInput}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
labelClassName={styles.labelAndInput} />
|
labelClassName={styles.labelAndInput}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
export default function NewLayout({ children }: { children: React.ReactNode }) {
|
import { ChildrenProps } from "src/app/providers"
|
||||||
|
|
||||||
|
export default function NewLayout({ children }: ChildrenProps) {
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import NewPost from "src/app/(posts)/new/components/new"
|
||||||
import "./react-datepicker.css"
|
import "./react-datepicker.css"
|
||||||
|
|
||||||
export default function New() {
|
export default function New() {
|
||||||
return <NewPost />
|
return <NewPost />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dynamic = "force-static"
|
export const dynamic = "force-static"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import CreatedAgoBadge from "@components/badges/created-ago-badge"
|
import CreatedAgoBadge from "@components/badges/created-ago-badge"
|
||||||
import ExpirationBadge from "@components/badges/expiration-badge"
|
import ExpirationBadge from "@components/badges/expiration-badge"
|
||||||
import VisibilityBadge from "@components/badges/visibility-badge"
|
import VisibilityBadge from "@components/badges/visibility-badge"
|
||||||
import Link from "@components/link"
|
|
||||||
import Skeleton from "@components/skeleton"
|
import Skeleton from "@components/skeleton"
|
||||||
import styles from "./title.module.css"
|
import styles from "./title.module.css"
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,16 @@ import { PostButtons } from "./components/header/post-buttons"
|
||||||
import styles from "./layout.module.css"
|
import styles from "./layout.module.css"
|
||||||
import { PostTitle } from "./components/header/title"
|
import { PostTitle } from "./components/header/title"
|
||||||
import { getPost } from "./get-post"
|
import { getPost } from "./get-post"
|
||||||
|
import { PropsWithChildren } from "react"
|
||||||
|
|
||||||
export default async function PostLayout({
|
export default async function PostLayout({
|
||||||
children,
|
children,
|
||||||
params
|
params
|
||||||
}: {
|
}: PropsWithChildren<{
|
||||||
params: {
|
params: {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
children: React.ReactNode
|
}>) {
|
||||||
}) {
|
|
||||||
const { post } = (await getPost(params.id)) as {
|
const { post } = (await getPost(params.id)) as {
|
||||||
post: PostWithFilesAndAuthor
|
post: PostWithFilesAndAuthor
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { getCurrentUser } from "@lib/server/session"
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
|
import { PropsWithChildren } from "react"
|
||||||
|
|
||||||
export default async function AdminLayout({
|
export default async function AdminLayout({
|
||||||
children
|
children
|
||||||
}: {
|
}: PropsWithChildren<unknown>) {
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
const user = await getCurrentUser()
|
const user = await getCurrentUser()
|
||||||
const isAdmin = user?.role === "admin"
|
const isAdmin = user?.role === "admin"
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from "react"
|
||||||
import styles from "./badge.module.css"
|
import styles from "./badge.module.css"
|
||||||
type BadgeProps = {
|
type BadgeProps = {
|
||||||
type: "primary" | "secondary" | "error" | "warning"
|
type: "primary" | "secondary" | "error" | "warning"
|
||||||
children: React.ReactNode
|
|
||||||
} & React.HTMLAttributes<HTMLDivElement>
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
|
const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
|
||||||
|
|
|
@ -16,11 +16,9 @@ type Props = {
|
||||||
visibility: string
|
visibility: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const VisibilityControl = ({
|
function VisibilityControl({
|
||||||
authorId,
|
authorId, postId, visibility: postVisibility
|
||||||
postId,
|
}: Props) {
|
||||||
visibility: postVisibility
|
|
||||||
}: Props) => {
|
|
||||||
const { session } = useSessionSWR()
|
const { session } = useSessionSWR()
|
||||||
const isAuthor = session?.user && session?.user?.id === authorId
|
const isAuthor = session?.user && session?.user?.id === authorId
|
||||||
const [visibility, setVisibility] = useState<string>(postVisibility)
|
const [visibility, setVisibility] = useState<string>(postVisibility)
|
||||||
|
@ -32,16 +30,13 @@ const VisibilityControl = ({
|
||||||
|
|
||||||
const sendRequest = useCallback(
|
const sendRequest = useCallback(
|
||||||
async (visibility: string, password?: string) => {
|
async (visibility: string, password?: string) => {
|
||||||
const res = await fetchWithUser(
|
const res = await fetchWithUser(`/api/post/${postId}`, {
|
||||||
`/api/post/${postId}`,
|
method: "PUT",
|
||||||
{
|
headers: {
|
||||||
method: "PUT",
|
"Content-Type": "application/json"
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json"
|
body: JSON.stringify({ visibility, password })
|
||||||
},
|
})
|
||||||
body: JSON.stringify({ visibility, password })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
|
@ -129,8 +124,7 @@ const VisibilityControl = ({
|
||||||
creating={true}
|
creating={true}
|
||||||
isOpen={passwordModalVisible}
|
isOpen={passwordModalVisible}
|
||||||
onClose={onClosePasswordModal}
|
onClose={onClosePasswordModal}
|
||||||
onSubmit={submitPassword}
|
onSubmit={submitPassword} />
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ type Props = {
|
||||||
height?: number | string
|
height?: number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
type Attrs = Omit<React.HTMLAttributes<HTMLDivElement>, keyof Props>
|
||||||
type ButtonDropdownProps = Props & Attrs
|
type ButtonDropdownProps = Props & Attrs
|
||||||
|
|
||||||
const ButtonDropdown: React.FC<
|
const ButtonDropdown: React.FC<
|
||||||
|
|
|
@ -5,7 +5,6 @@ export default function ButtonGroup({
|
||||||
verticalIfMobile,
|
verticalIfMobile,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode | React.ReactNode[]
|
|
||||||
verticalIfMobile?: boolean
|
verticalIfMobile?: boolean
|
||||||
} & React.HTMLAttributes<HTMLDivElement>) {
|
} & React.HTMLAttributes<HTMLDivElement>) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
// https://www.joshwcomeau.com/snippets/react-components/fade-in/
|
// https://www.joshwcomeau.com/snippets/react-components/fade-in/
|
||||||
import styles from "./fade.module.css"
|
import styles from "./fade.module.css"
|
||||||
|
|
||||||
const FadeIn = ({
|
function FadeIn({
|
||||||
duration = 300,
|
duration = 300, delay = 0, children, as, ...delegated
|
||||||
delay = 0,
|
|
||||||
children,
|
|
||||||
as,
|
|
||||||
...delegated
|
|
||||||
}: {
|
}: {
|
||||||
duration?: number
|
duration?: number
|
||||||
delay?: number
|
delay?: number
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
as?: React.ElementType
|
as?: React.ElementType
|
||||||
[key: string]: any
|
} & React.HTMLAttributes<HTMLElement>) {
|
||||||
}) => {
|
|
||||||
const Element = as || "div"
|
const Element = as || "div"
|
||||||
return (
|
return (
|
||||||
<Element
|
<Element
|
||||||
|
|
8
src/app/components/layout/index.tsx
Normal file
8
src/app/components/layout/index.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { PropsWithChildren } from "react"
|
||||||
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
|
export default function Layout({ children }: PropsWithChildren<unknown>) {
|
||||||
|
return <div className={styles.page}>{children}</div>
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
import styles from "./page.module.css"
|
|
||||||
|
|
||||||
export default function Page({ children }: { children: React.ReactNode }) {
|
|
||||||
return <div className={styles.page}>{children}</div>
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ import Skeleton from "@components/skeleton"
|
||||||
export const ListItemSkeleton = () => (
|
export const ListItemSkeleton = () => (
|
||||||
<li>
|
<li>
|
||||||
<Card style={{ overflowY: "scroll" }}>
|
<Card style={{ overflowY: "scroll" }}>
|
||||||
<div style={{display: 'flex', gap: 16}}>
|
<div style={{ display: "flex", gap: 16 }}>
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
{/* title */}
|
{/* title */}
|
||||||
<Skeleton width={80} height={32} />
|
<Skeleton width={80} height={32} />
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
import "@styles/globals.css"
|
import "@styles/globals.css"
|
||||||
import { Providers } from "./providers"
|
import { Providers } from "./providers"
|
||||||
import Page from "@components/page"
|
import Layout from "@components/layout"
|
||||||
import { Toasts } from "@components/toasts"
|
import { Toasts } from "@components/toasts"
|
||||||
import Header from "@components/header"
|
import Header from "@components/header"
|
||||||
import { Inter } from "@next/font/google"
|
import { Inter } from "@next/font/google"
|
||||||
|
import { PropsWithChildren } from "react"
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
||||||
|
|
||||||
interface RootLayoutProps {
|
export default async function RootLayout({
|
||||||
children: React.ReactNode
|
children
|
||||||
}
|
}: PropsWithChildren<unknown>) {
|
||||||
|
|
||||||
export default async function RootLayout({ children }: RootLayoutProps) {
|
|
||||||
return (
|
return (
|
||||||
// suppressHydrationWarning is required because of next-themes
|
// suppressHydrationWarning is required because of next-themes
|
||||||
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
||||||
<head />
|
<head />
|
||||||
<body>
|
<body>
|
||||||
<Toasts />
|
<Toasts />
|
||||||
<Page>
|
<Layout>
|
||||||
<Providers>
|
<Providers>
|
||||||
<Header />
|
<Header />
|
||||||
{children}
|
{children}
|
||||||
</Providers>
|
</Providers>
|
||||||
</Page>
|
</Layout>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,29 +3,40 @@
|
||||||
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
||||||
import { SessionProvider } from "next-auth/react"
|
import { SessionProvider } from "next-auth/react"
|
||||||
import { ThemeProvider } from "next-themes"
|
import { ThemeProvider } from "next-themes"
|
||||||
|
import { PropsWithChildren } from "react"
|
||||||
import { SWRConfig } from "swr"
|
import { SWRConfig } from "swr"
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export type ChildrenProps = {
|
||||||
|
children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Providers({ children }: ChildrenProps) {
|
||||||
return (
|
return (
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<SWRConfig
|
<RadixTooltip.Provider delayDuration={200}>
|
||||||
value={{
|
<ThemeProvider enableSystem defaultTheme="dark">
|
||||||
fetcher: async (url: string) => {
|
<SWRProvider>{children}</SWRProvider>
|
||||||
const data = await fetch(url).then((res) => res.json())
|
</ThemeProvider>
|
||||||
if (data.error) {
|
</RadixTooltip.Provider>
|
||||||
throw new Error(data.error)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
keepPreviousData: true
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RadixTooltip.Provider delayDuration={200}>
|
|
||||||
<ThemeProvider enableSystem defaultTheme="dark">
|
|
||||||
{children}
|
|
||||||
</ThemeProvider>
|
|
||||||
</RadixTooltip.Provider>
|
|
||||||
</SWRConfig>
|
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SWRProvider({ children }: PropsWithChildren<unknown>) {
|
||||||
|
return (
|
||||||
|
<SWRConfig
|
||||||
|
value={{
|
||||||
|
fetcher: async (url: string) => {
|
||||||
|
const data = await fetch(url).then((res) => res.json())
|
||||||
|
if (data.error) {
|
||||||
|
throw new Error(data.error)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
keepPreviousData: true
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SWRConfig>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { User } from "@prisma/client"
|
||||||
function Profile() {
|
function Profile() {
|
||||||
const { session } = useSessionSWR()
|
const { session } = useSessionSWR()
|
||||||
console.log(session)
|
console.log(session)
|
||||||
const { data: userData } = useSWR<User>(
|
const { data: userData } = useSWR<User>(
|
||||||
session?.user?.id ? `/api/user/${session?.user?.id}` : null
|
session?.user?.id ? `/api/user/${session?.user?.id}` : null
|
||||||
)
|
)
|
||||||
const [name, setName] = useState<string>(userData?.displayName || "")
|
const [name, setName] = useState<string>(userData?.displayName || "")
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
--small-gap: 4rem;
|
--small-gap: 4rem;
|
||||||
--big-gap: 4rem;
|
--big-gap: 4rem;
|
||||||
--main-content: 55rem;
|
--main-content: 50rem;
|
||||||
--radius: 8px;
|
--radius: 8px;
|
||||||
--inline-radius: 5px;
|
--inline-radius: 5px;
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ const providers = () => {
|
||||||
// @ts-expect-error TODO: fix types
|
// @ts-expect-error TODO: fix types
|
||||||
credentials: credentialsOptions() as unknown,
|
credentials: credentialsOptions() as unknown,
|
||||||
async authorize(credentials) {
|
async authorize(credentials) {
|
||||||
|
console.log("credentials")
|
||||||
if (!credentials || !credentials.username || !credentials.password) {
|
if (!credentials || !credentials.username || !credentials.password) {
|
||||||
throw new Error("Missing credentials")
|
throw new Error("Missing credentials")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue