mirror of
https://github.com/trycua/computer.git
synced 2026-01-02 03:20:22 -06:00
2
docs/.env.example
Normal file
2
docs/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_POSTHOG_API_KEY=
|
||||
NEXT_PUBLIC_POSTHOG_HOST=
|
||||
@@ -34,6 +34,14 @@ A `source.config.ts` config file has been included, you can customise different
|
||||
|
||||
Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details.
|
||||
|
||||
## Setup Telemetry
|
||||
|
||||
We use PostHog for telemetry to improve the clarity and structure of our documentation. Start by copying the `.env.example` and adding in your PostHog API key and host.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js and Fumadocs, take a look at the following
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"mermaid": "^11.8.1",
|
||||
"next": "15.3.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"posthog-js": "^1.276.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"remark": "^15.0.1",
|
||||
|
||||
47
docs/pnpm-lock.yaml
generated
47
docs/pnpm-lock.yaml
generated
@@ -29,6 +29,9 @@ importers:
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
posthog-js:
|
||||
specifier: ^1.276.0
|
||||
version: 1.276.0
|
||||
react:
|
||||
specifier: ^19.1.0
|
||||
version: 19.1.0
|
||||
@@ -489,6 +492,9 @@ packages:
|
||||
resolution: {integrity: sha512-6yB0117ZjsgNevZw3LP+bkrZa9mU/POPVaXgzMPOBbBc35w2P3R+1vMMhEfC06kYCpd5bf0jodBaTkYQW5TVeQ==}
|
||||
engines: {node: '>= 20.0.0'}
|
||||
|
||||
'@posthog/core@1.3.0':
|
||||
resolution: {integrity: sha512-hxLL8kZNHH098geedcxCz8y6xojkNYbmJEW+1vFXsmPcExyCXIUUJ/34X6xa9GcprKxd0Wsx3vfJQLQX4iVPhw==}
|
||||
|
||||
'@radix-ui/number@1.1.1':
|
||||
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
||||
|
||||
@@ -1221,6 +1227,9 @@ packages:
|
||||
confbox@0.2.2:
|
||||
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
|
||||
|
||||
core-js@3.46.0:
|
||||
resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==}
|
||||
|
||||
cose-base@1.0.3:
|
||||
resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
|
||||
|
||||
@@ -1492,6 +1501,9 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fflate@0.4.8:
|
||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
||||
|
||||
fumadocs-core@15.5.1:
|
||||
resolution: {integrity: sha512-5eJPJw+BFWFdgrtWPQ9aAZAhhsyuZAwth8OjBd9R77sXoIoae4Y4lJZMq3BeSpJZcuIAOVbSCS+pJhsBAoXJ8g==}
|
||||
peerDependencies:
|
||||
@@ -2012,6 +2024,20 @@ packages:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
posthog-js@1.276.0:
|
||||
resolution: {integrity: sha512-FYZE1037LrAoKKeUU0pUL7u8WwNK2BVeg5TFApwquVPUdj9h7u5Z077A313hPN19Ar+7Y+VHxqYqdHc4VNsVgw==}
|
||||
peerDependencies:
|
||||
'@rrweb/types': 2.0.0-alpha.17
|
||||
rrweb-snapshot: 2.0.0-alpha.17
|
||||
peerDependenciesMeta:
|
||||
'@rrweb/types':
|
||||
optional: true
|
||||
rrweb-snapshot:
|
||||
optional: true
|
||||
|
||||
preact@10.27.2:
|
||||
resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==}
|
||||
|
||||
prettier@3.6.2:
|
||||
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -2317,6 +2343,9 @@ packages:
|
||||
vscode-uri@3.0.8:
|
||||
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
|
||||
|
||||
web-vitals@4.2.4:
|
||||
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
|
||||
|
||||
yallist@5.0.0:
|
||||
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2642,6 +2671,8 @@ snapshots:
|
||||
|
||||
'@orama/orama@3.1.7': {}
|
||||
|
||||
'@posthog/core@1.3.0': {}
|
||||
|
||||
'@radix-ui/number@1.1.1': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.2': {}
|
||||
@@ -3378,6 +3409,8 @@ snapshots:
|
||||
|
||||
confbox@0.2.2: {}
|
||||
|
||||
core-js@3.46.0: {}
|
||||
|
||||
cose-base@1.0.3:
|
||||
dependencies:
|
||||
layout-base: 1.0.2
|
||||
@@ -3702,6 +3735,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fflate@0.4.8: {}
|
||||
|
||||
fumadocs-core@15.5.1(@types/react@19.1.8)(next@15.3.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.6.1
|
||||
@@ -4566,6 +4601,16 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
posthog-js@1.276.0:
|
||||
dependencies:
|
||||
'@posthog/core': 1.3.0
|
||||
core-js: 3.46.0
|
||||
fflate: 0.4.8
|
||||
preact: 10.27.2
|
||||
web-vitals: 4.2.4
|
||||
|
||||
preact@10.27.2: {}
|
||||
|
||||
prettier@3.6.2: {}
|
||||
|
||||
property-information@7.1.0: {}
|
||||
@@ -4934,6 +4979,8 @@ snapshots:
|
||||
|
||||
vscode-uri@3.0.8: {}
|
||||
|
||||
web-vitals@4.2.4: {}
|
||||
|
||||
yallist@5.0.0: {}
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ChevronDown, CodeXml, ExternalLink } from 'lucide-react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
import { PageFeedback } from '@/components/page-feedback';
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
@@ -270,6 +271,7 @@ export default async function Page(props: {
|
||||
a: createRelativeLink(source, page),
|
||||
})}
|
||||
/>
|
||||
<PageFeedback />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
|
||||
75
docs/src/app/api/posthog/[...path]/route.ts
Normal file
75
docs/src/app/api/posthog/[...path]/route.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ path: string[] }> }
|
||||
) {
|
||||
const { path } = await params;
|
||||
const url = new URL(request.url);
|
||||
|
||||
const targetUrl = `${process.env.NEXT_PUBLIC_POSTHOG_HOST}/${path.join('/')}${url.search}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(targetUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': request.headers.get('Content-Type') || 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Handle 204 No Content responses
|
||||
if (response.status === 204) {
|
||||
return new NextResponse(null, { status: 204 });
|
||||
}
|
||||
|
||||
const data = await response.arrayBuffer();
|
||||
return new NextResponse(data, {
|
||||
status: response.status,
|
||||
headers: {
|
||||
'Content-Type': response.headers.get('Content-Type') || 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('PostHog proxy error:', error);
|
||||
return new NextResponse('Error proxying request', { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ path: string[] }> }
|
||||
) {
|
||||
const { path } = await params;
|
||||
const url = new URL(request.url);
|
||||
|
||||
const targetUrl = `${process.env.NEXT_PUBLIC_POSTHOG_HOST}/${path.join('/')}${url.search}`;
|
||||
|
||||
try {
|
||||
const body = await request.arrayBuffer();
|
||||
const contentType = request.headers.get('Content-Type') || 'application/x-www-form-urlencoded';
|
||||
|
||||
const response = await fetch(targetUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
// Handle 204 No Content responses
|
||||
if (response.status === 204) {
|
||||
return new NextResponse(null, { status: 204 });
|
||||
}
|
||||
|
||||
const data = await response.arrayBuffer();
|
||||
return new NextResponse(data, {
|
||||
status: response.status,
|
||||
headers: {
|
||||
'Content-Type': response.headers.get('Content-Type') || 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('PostHog proxy error:', error);
|
||||
return new NextResponse('Error proxying request', { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,11 @@ import './global.css';
|
||||
import { RootProvider } from 'fumadocs-ui/provider';
|
||||
import { Inter } from 'next/font/google';
|
||||
import type { ReactNode } from 'react';
|
||||
import { PHProvider, PostHogPageView } from '@/providers/posthog-provider';
|
||||
import { AnalyticsTracker } from '@/components/analytics-tracker';
|
||||
import { CookieConsent } from '@/components/cookie-consent';
|
||||
import { Footer } from '@/components/footer';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
@@ -14,9 +19,17 @@ export default function Layout({ children }: { children: ReactNode }) {
|
||||
<link rel="icon" href="/docs/favicon.ico" sizes="any" />
|
||||
</head>
|
||||
<body className="flex min-h-screen flex-col">
|
||||
<RootProvider search={{ options: { api: '/docs/api/search' } }}>
|
||||
{children}
|
||||
</RootProvider>
|
||||
<PHProvider>
|
||||
<Suspense fallback={null}>
|
||||
<PostHogPageView />
|
||||
</Suspense>
|
||||
<AnalyticsTracker />
|
||||
<RootProvider search={{ options: { api: '/docs/api/search' } }}>
|
||||
{children}
|
||||
</RootProvider>
|
||||
<Footer />
|
||||
<CookieConsent />
|
||||
</PHProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
71
docs/src/components/analytics-tracker.tsx
Normal file
71
docs/src/components/analytics-tracker.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
export function AnalyticsTracker() {
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const link = target.closest('a');
|
||||
|
||||
if (!link) return;
|
||||
|
||||
const href = link.href;
|
||||
const text = link.textContent || link.getAttribute('aria-label') || '';
|
||||
|
||||
if (href.includes('github.com/trycua')) {
|
||||
posthog.capture('github_link_clicked', {
|
||||
url: href,
|
||||
link_text: text,
|
||||
page: window.location.pathname,
|
||||
});
|
||||
}
|
||||
|
||||
if (href.includes('discord.com/invite') || href.includes('discord.gg')) {
|
||||
posthog.capture('discord_link_clicked', {
|
||||
url: href,
|
||||
link_text: text,
|
||||
page: window.location.pathname,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
(href.includes('trycua.com') && !href.includes('trycua.com/docs')) ||
|
||||
href.includes('cua.ai')
|
||||
) {
|
||||
posthog.capture('main_website_clicked', {
|
||||
url: href,
|
||||
link_text: text,
|
||||
page: window.location.pathname,
|
||||
});
|
||||
}
|
||||
|
||||
if (link.hostname && link.hostname !== window.location.hostname) {
|
||||
if (
|
||||
href.includes('github.com/trycua') ||
|
||||
href.includes('discord.com') ||
|
||||
href.includes('trycua.com') ||
|
||||
href.includes('cua.ai')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.capture('external_link_clicked', {
|
||||
url: href,
|
||||
link_text: text,
|
||||
page: window.location.pathname,
|
||||
domain: link.hostname,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
44
docs/src/components/cookie-consent.tsx
Normal file
44
docs/src/components/cookie-consent.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
export function CookieConsent() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user has already accepted cookies
|
||||
const hasAccepted = localStorage.getItem('cookie-consent');
|
||||
if (!hasAccepted) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAccept = () => {
|
||||
localStorage.setItem('cookie-consent', 'accepted');
|
||||
setIsVisible(false);
|
||||
|
||||
// Track cookie acceptance
|
||||
posthog.capture('cookie_consent_accepted', {
|
||||
page: window.location.pathname,
|
||||
});
|
||||
};
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-fd-background border-t border-fd-border shadow-lg">
|
||||
<div className="container mx-auto px-4 py-2 flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||
<p className="text-xs text-fd-muted-foreground">
|
||||
This site uses cookies for website functionality, analytics, and personalized content.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
className="px-4 py-1 text-xs bg-fd-primary text-fd-primary-foreground rounded hover:opacity-90 transition-opacity whitespace-nowrap"
|
||||
>
|
||||
Okay
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
docs/src/components/footer.tsx
Normal file
16
docs/src/components/footer.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="mt-auto border-t border-fd-border py-4">
|
||||
<div className="container mx-auto px-4 flex justify-end">
|
||||
<a
|
||||
href="https://www.cua.ai/cookie-policy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-fd-muted-foreground hover:text-fd-foreground transition-colors"
|
||||
>
|
||||
Cookie Policy
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
53
docs/src/components/page-feedback.tsx
Normal file
53
docs/src/components/page-feedback.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import posthog from 'posthog-js';
|
||||
import { ThumbsUp, ThumbsDown } from 'lucide-react';
|
||||
|
||||
export function PageFeedback() {
|
||||
const [feedback, setFeedback] = useState<'helpful' | 'not_helpful' | null>(null);
|
||||
|
||||
const handleFeedback = (isHelpful: boolean) => {
|
||||
const feedbackType = isHelpful ? 'helpful' : 'not_helpful';
|
||||
setFeedback(feedbackType);
|
||||
|
||||
posthog.capture(`page_feedback_${feedbackType}`, {
|
||||
page: window.location.pathname,
|
||||
page_title: document.title,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-8 pt-4 border-t border-fd-border">
|
||||
{feedback === null ? (
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||
<p className="text-sm text-fd-muted-foreground">Was this page helpful?</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleFeedback(true)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-sm hover:bg-fd-accent rounded transition-colors"
|
||||
aria-label="This page was helpful"
|
||||
>
|
||||
<ThumbsUp className="w-4 h-4" />
|
||||
Yes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleFeedback(false)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-sm hover:bg-fd-accent rounded transition-colors"
|
||||
aria-label="This page was not helpful"
|
||||
>
|
||||
<ThumbsDown className="w-4 h-4" />
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-fd-muted-foreground text-left">
|
||||
{feedback === 'helpful'
|
||||
? 'Thanks for your feedback!'
|
||||
: 'Thanks for your feedback. We\'ll work on improving this page.'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,12 @@ import * as TabsComponents from 'fumadocs-ui/components/tabs';
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
import { Mermaid } from './components/mermaid';
|
||||
import IOU from './components/iou';
|
||||
import {
|
||||
EditableCodeBlock,
|
||||
EditableValue,
|
||||
EditableForm,
|
||||
EditableInput,
|
||||
} from './components/editable-code-block';
|
||||
|
||||
// use this function to get MDX components, you will need it for rendering MDX
|
||||
export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
||||
@@ -10,6 +16,10 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
||||
...defaultMdxComponents,
|
||||
Mermaid,
|
||||
IOU,
|
||||
EditableCodeBlock,
|
||||
EditableValue,
|
||||
EditableForm,
|
||||
EditableInput,
|
||||
...TabsComponents,
|
||||
...components,
|
||||
};
|
||||
|
||||
40
docs/src/providers/posthog-provider.tsx
Normal file
40
docs/src/providers/posthog-provider.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
'use client';
|
||||
|
||||
import posthog from 'posthog-js';
|
||||
import { PostHogProvider } from 'posthog-js/react';
|
||||
import { useEffect } from 'react';
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_API_KEY!, {
|
||||
api_host: '/docs/api/posthog',
|
||||
ui_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
person_profiles: 'always',
|
||||
capture_pageview: false,
|
||||
capture_pageleave: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function PHProvider({ children }: { children: React.ReactNode }) {
|
||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
|
||||
}
|
||||
|
||||
export function PostHogPageView(): null {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname) {
|
||||
let url = window.origin + pathname;
|
||||
if (searchParams && searchParams.toString()) {
|
||||
url = url + `?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
posthog.capture('$pageview', {
|
||||
$current_url: url,
|
||||
});
|
||||
}
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user