feature(public,docs): new public website and docs
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { ListPropertiesIcon } from '@/components/events/list-properties-icon';
|
||||
import { Pagination } from '@/components/pagination';
|
||||
import { ProfileAvatar } from '@/components/profiles/profile-avatar';
|
||||
import { DialogContent } from '@/components/ui/dialog';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Tooltiper } from '@/components/ui/tooltip';
|
||||
import { WidgetTable } from '@/components/widget-table';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { api } from '@/trpc/client';
|
||||
import { getProfileName } from '@/utils/getters';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
|
||||
import { popModal } from '.';
|
||||
import { ModalHeader } from './Modal/Container';
|
||||
|
||||
interface Props extends IChartInput {
|
||||
step: number;
|
||||
}
|
||||
|
||||
function usePrevious(value: any) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export default function FunnelStepDetails(props: Props) {
|
||||
const [data] = api.chart.funnelStep.useSuspenseQuery(props);
|
||||
const pathname = usePathname();
|
||||
const prev = usePrevious(pathname);
|
||||
const { organizationSlug, projectId } = useAppParams();
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (prev && prev !== pathname) {
|
||||
popModal();
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<DialogContent className="p-0">
|
||||
<div className="p-4">
|
||||
<ModalHeader title="Profiles" />
|
||||
<Pagination
|
||||
count={data.length}
|
||||
take={50}
|
||||
cursor={page}
|
||||
setCursor={setPage}
|
||||
/>
|
||||
</div>
|
||||
<ScrollArea className="max-h-[60vh]">
|
||||
<WidgetTable
|
||||
data={data.slice(page * 50, page * 50 + 50)}
|
||||
keyExtractor={(item) => item.id}
|
||||
columns={[
|
||||
{
|
||||
name: 'Name',
|
||||
render(profile) {
|
||||
return (
|
||||
<Link
|
||||
href={`/${organizationSlug}/${projectId}/profiles/${profile.id}`}
|
||||
className="flex items-center gap-2 font-medium"
|
||||
>
|
||||
<ProfileAvatar size="sm" {...profile} />
|
||||
{getProfileName(profile)}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
render(profile) {
|
||||
return <ListPropertiesIcon {...profile.properties} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Last seen',
|
||||
render(profile) {
|
||||
return (
|
||||
<Tooltiper
|
||||
asChild
|
||||
content={profile.createdAt.toLocaleString()}
|
||||
>
|
||||
<div className=" text-muted-foreground">
|
||||
{profile.createdAt.toLocaleTimeString()}
|
||||
</div>
|
||||
</Tooltiper>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
@@ -16,10 +16,7 @@ import { popModal } from '.';
|
||||
|
||||
type Props = {
|
||||
client: IServiceClient | null;
|
||||
framework:
|
||||
| (typeof frameworks.website)[number]
|
||||
| (typeof frameworks.app)[number]
|
||||
| (typeof frameworks.backend)[number];
|
||||
framework: (typeof frameworks)[number];
|
||||
};
|
||||
|
||||
const Header = ({ framework }: Pick<Props, 'framework'>) => (
|
||||
@@ -29,7 +26,7 @@ const Header = ({ framework }: Pick<Props, 'framework'>) => (
|
||||
);
|
||||
|
||||
const Footer = ({ framework }: Pick<Props, 'framework'>) => (
|
||||
<SheetFooter>
|
||||
<SheetFooter className="absolute bottom-0 left-0 right-0 p-4">
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
className="flex-1"
|
||||
@@ -49,350 +46,21 @@ const Footer = ({ framework }: Pick<Props, 'framework'>) => (
|
||||
</SheetFooter>
|
||||
);
|
||||
|
||||
const Instructions = ({ framework, client }: Props) => {
|
||||
const { name } = framework;
|
||||
const clientId = client?.id || 'REPLACE_WITH_YOUR_CLIENT';
|
||||
const clientSecret = client?.secret || 'REPLACE_WITH_YOUR_SECRET';
|
||||
if (
|
||||
name === 'HTML / Script' ||
|
||||
name === 'React' ||
|
||||
name === 'Astro' ||
|
||||
name === 'Remix' ||
|
||||
name === 'Vue'
|
||||
) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<p>Copy the code below and insert it to you website</p>
|
||||
<Syntax
|
||||
code={`<script>
|
||||
window.op = window.op||function(...args){(window.op.q=window.op.q||[]).push(args);};
|
||||
window.op('init', {
|
||||
clientId: '${clientId}',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
</script>
|
||||
<script src="https://openpanel.dev/op1.js" defer async></script>`}
|
||||
/>
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
We have already added your client id to the snippet.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'Next.js') {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<p>Install dependencies</p>
|
||||
<Syntax code={'pnpm install @openpanel/nextjs'} />
|
||||
<p>Add OpenPanelComponent to your root layout</p>
|
||||
<Syntax
|
||||
code={`import { OpenPanelComponent } from '@openpanel/nextjs';
|
||||
|
||||
export default RootLayout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<OpenPanelComponent
|
||||
clientId="${clientId}"
|
||||
trackScreenViews={true}
|
||||
trackAttributes={true}
|
||||
trackOutgoingLinks={true}
|
||||
// If you have a user id, you can pass it here to identify the user
|
||||
// profileId={'123'}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}`}
|
||||
/>
|
||||
<p>
|
||||
This will track regular page views and outgoing links. You can also
|
||||
track custom events.
|
||||
</p>
|
||||
<Syntax
|
||||
code={`import { useOpenPanel } from '@openpanel/nextjs';
|
||||
|
||||
// Sends an event with payload foo: bar
|
||||
useOpenPanel().track('my_event', { foo: 'bar' });
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'Laravel') {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<p>Install dependencies</p>
|
||||
<Syntax code={'composer require bleckert/openpanel-laravel'} />
|
||||
<p>Add environment variables</p>
|
||||
<Syntax
|
||||
code={`OPENPANEL_CLIENT_ID=${clientId}
|
||||
OPENPANEL_CLIENT_SECRET=${clientSecret}`}
|
||||
/>
|
||||
<p>Usage</p>
|
||||
<Syntax
|
||||
code={`use Bleckert\\OpenpanelLaravel\\Openpanel;
|
||||
|
||||
$openpanel = app(Openpanel::class);
|
||||
|
||||
// Identify user
|
||||
$openpanel->setProfileId(1);
|
||||
|
||||
// Update user profile
|
||||
$openpanel->setProfile(
|
||||
id: 1,
|
||||
firstName: 'John Doe',
|
||||
// ...
|
||||
);
|
||||
|
||||
// Track event
|
||||
$openpanel->event(
|
||||
name: 'User registered',
|
||||
);
|
||||
`}
|
||||
/>
|
||||
<Alert>
|
||||
<AlertTitle>Shoutout!</AlertTitle>
|
||||
<AlertDescription>
|
||||
Huge shoutout to{' '}
|
||||
<a
|
||||
href="https://twitter.com/tbleckert"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
@tbleckert
|
||||
</a>{' '}
|
||||
for creating this package.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'Rest API') {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<strong>Authentication</strong>
|
||||
<p>You will need to pass your client ID and secret via headers.</p>
|
||||
<strong>Usage</strong>
|
||||
<p>Create a custom event called "my_event".</p>
|
||||
<Syntax
|
||||
code={`curl '${process.env.NEXT_PUBLIC_API_URL}/track' \\
|
||||
-H 'content-type: application/json' \\
|
||||
-H 'openpanel-client-id: ${clientId}' \\
|
||||
-H 'openpanel-client-secret: ${clientSecret}' \\
|
||||
--data-raw '{
|
||||
"type": "track",
|
||||
"payload": {
|
||||
"name": "my_event",
|
||||
"properties": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}'`}
|
||||
/>
|
||||
<p>The payload should be a JSON object with the following fields:</p>
|
||||
<ul className="list-inside list-disc">
|
||||
<li>
|
||||
"type" (string): track | identify | alias | increment |
|
||||
decrement
|
||||
</li>
|
||||
<li>"payload.name" (string): The name of the event.</li>
|
||||
<li>
|
||||
"payload.properties" (object): The properties of the
|
||||
event.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'Express') {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<strong>Install dependencies</strong>
|
||||
<Syntax code={'npm install @openpanel/express'} />
|
||||
|
||||
<strong>Usage</strong>
|
||||
<p>Connect the middleware to your app.</p>
|
||||
<Syntax
|
||||
code={`import express from 'express';
|
||||
import createOpenpanelMiddleware from '@openpanel/express';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
createOpenpanelMiddleware({
|
||||
clientId: '${clientId}',
|
||||
clientSecret: '${clientSecret}',
|
||||
// trackRequest(url) {
|
||||
// return url.includes('/v1')
|
||||
// },
|
||||
// getProfileId(req) {
|
||||
// return req.user.id
|
||||
// }
|
||||
})
|
||||
);
|
||||
|
||||
app.get('/sign-up', (req, res) => {
|
||||
// track sign up events
|
||||
req.op.track('sign-up', {
|
||||
email: req.body.email,
|
||||
});
|
||||
res.send('Hello World');
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Server is running on http://localhost:3000');
|
||||
});`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'Node') {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<strong>Install dependencies</strong>
|
||||
<Syntax code={'pnpm install @openpanel/sdk'} />
|
||||
|
||||
<strong>Create a instance</strong>
|
||||
<p>
|
||||
Create a new instance of OpenPanel. You can use this SDK in any JS
|
||||
environment. You should omit clientSecret if you use this on web!
|
||||
</p>
|
||||
<Syntax
|
||||
code={`import { OpenPanel } from '@openpanel/sdk';
|
||||
|
||||
const op = new OpenPanel({
|
||||
clientId: '${clientId}',
|
||||
// mostly for backend and apps that can't rely on CORS
|
||||
clientSecret: '${clientSecret}',
|
||||
});`}
|
||||
/>
|
||||
<strong>Usage</strong>
|
||||
<Syntax
|
||||
code={`import { op } from './openpanel';
|
||||
|
||||
// Sends an event with payload foo: bar
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
|
||||
// Identify with profile id
|
||||
op.identify({ profileId: '123' });
|
||||
|
||||
// or with additional data
|
||||
op.identify({
|
||||
profileId: '123',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john.doe@openpanel.dev',
|
||||
});
|
||||
|
||||
// Increment a property
|
||||
op.increment({ name: 'app_opened', profile_id: '123' }); // increment by 1
|
||||
op.increment({ name: 'app_opened', profile_id: '123', value: 5 }); // increment by 5
|
||||
|
||||
// Decrement a property
|
||||
op.decrement({ name: 'app_opened', profile_id: '123' }); // decrement by 1
|
||||
op.decrement({ name: 'app_opened', profile_id: '123', value: 5 }); // decrement by 5`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'React-Native') {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<strong>Install dependencies</strong>
|
||||
<p>Don't forget to install the peer dependencies as well!</p>
|
||||
<Syntax
|
||||
code={`pnpm install @openpanel/react-native
|
||||
npx expo install --pnpm expo-application expo-constants`}
|
||||
/>
|
||||
<strong>Create a instance</strong>
|
||||
<p>
|
||||
Create a new instance of OpenpanelSdk. You can use this SDK in any JS
|
||||
environment. You should omit clientSecret if you use this on web!
|
||||
</p>
|
||||
<Syntax
|
||||
code={`import { OpenPanel } from '@openpanel/react-native';
|
||||
|
||||
const op = new OpenPanel({
|
||||
clientId: '${clientId}',
|
||||
clientSecret: '${clientSecret}',
|
||||
});`}
|
||||
/>
|
||||
<strong>Usage</strong>
|
||||
<Syntax
|
||||
code={`import { op } from './openpanel';
|
||||
|
||||
// Sends an event with payload foo: bar
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
|
||||
// Identify with profile id
|
||||
op.identify({ profileId: '123' });
|
||||
|
||||
// or with additional data
|
||||
op.identify({
|
||||
profileId: '123',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john.doe@openpanel.dev',
|
||||
});
|
||||
|
||||
// Increment a property
|
||||
op.increment({ name: 'app_opened', profile_id: '123' }); // increment by 1
|
||||
op.increment({ name: 'app_opened', profile_id: '123', value: 5 }); // increment by 5
|
||||
|
||||
// Decrement a property
|
||||
op.decrement({ name: 'app_opened', profile_id: '123' }); // decrement by 1
|
||||
op.decrement({ name: 'app_opened', profile_id: '123', value: 5 }); // decrement by 5`}
|
||||
/>
|
||||
<strong>Navigation</strong>
|
||||
<p>
|
||||
Check out our{' '}
|
||||
<a
|
||||
href="https://github.com/Openpanel-dev/examples/tree/main/expo-app"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
example app
|
||||
</a>{' '}
|
||||
. See below for a quick demo.
|
||||
</p>
|
||||
<Syntax
|
||||
code={`function RootLayoutNav() {
|
||||
const pathname = usePathname()
|
||||
|
||||
useEffect(() => {
|
||||
op.screenView(pathname)
|
||||
}, [pathname])
|
||||
|
||||
const Instructions = ({ framework }: Props) => {
|
||||
return (
|
||||
<Stack>
|
||||
{/*... */}
|
||||
</Stack>
|
||||
<iframe
|
||||
className="w-full h-full"
|
||||
src={framework.href}
|
||||
title={framework.name}
|
||||
/>
|
||||
);
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default function InsdtructionsWithModalContent(props: Props) {
|
||||
export default function InstructionsWithModalContent(props: Props) {
|
||||
return (
|
||||
<SheetContent>
|
||||
<Header framework={props.framework} />
|
||||
<SheetContent className="p-0">
|
||||
<Instructions {...props} />
|
||||
<Footer framework={props.framework} />
|
||||
<Footer {...props} />
|
||||
</SheetContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,9 +62,6 @@ const modals = {
|
||||
VerifyEmail: dynamic(() => import('./VerifyEmail'), {
|
||||
loading: Loading,
|
||||
}),
|
||||
FunnelStepDetails: dynamic(() => import('./FunnelStepDetails'), {
|
||||
loading: Loading,
|
||||
}),
|
||||
DateRangerPicker: dynamic(() => import('./DateRangerPicker'), {
|
||||
loading: Loading,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user