feat: add survey usage button
This commit is contained in:
parent
cc0bd73ed1
commit
2b75a8edad
37
src/client/components/CodeBlock.tsx
Normal file
37
src/client/components/CodeBlock.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { LuCopy, LuCopyCheck } from 'react-icons/lu';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
|
export const CodeBlock: React.FC<{
|
||||||
|
code: string;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleCopy = useEvent(() => {
|
||||||
|
window.navigator.clipboard.writeText(props.code);
|
||||||
|
|
||||||
|
toast(t('Copied into clipboard!'));
|
||||||
|
|
||||||
|
setCopied(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="group relative overflow-auto">
|
||||||
|
<pre className="rounded-sm border border-zinc-800 bg-zinc-900 p-3 pr-12 text-sm">
|
||||||
|
<code>{props.code}</code>
|
||||||
|
</pre>
|
||||||
|
<Button
|
||||||
|
className="absolute right-1 top-1 bg-opacity-50 opacity-0 transition-opacity group-hover:opacity-100 dark:bg-opacity-50"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
Icon={copied ? LuCopyCheck : LuCopy}
|
||||||
|
onClick={handleCopy}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
CodeBlock.displayName = 'CodeBlock';
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { LuCalendar } from 'react-icons/lu';
|
import { LuCalendar, LuDownloadCloud } from 'react-icons/lu';
|
||||||
import { cn } from '@/utils/style';
|
import { cn } from '@/utils/style';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||||
@ -131,7 +131,7 @@ export const SurveyDownloadBtn: React.FC<SurveyDownloadBtnProps> = React.memo(
|
|||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>{t('Download')}</Button>
|
<Button Icon={LuDownloadCloud}>{t('Download')}</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
@ -53,7 +53,7 @@ export type SurveyEditFormValues = z.infer<typeof addFormSchema>;
|
|||||||
function generateDefaultItem() {
|
function generateDefaultItem() {
|
||||||
return {
|
return {
|
||||||
label: 'New Field',
|
label: 'New Field',
|
||||||
name: 'field-' + generateRandomString(4),
|
name: 'field_' + generateRandomString(4),
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
59
src/client/components/survey/SurveyUsageBtn.tsx
Normal file
59
src/client/components/survey/SurveyUsageBtn.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
DialogHeader,
|
||||||
|
Dialog,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { LuCode2 } from 'react-icons/lu';
|
||||||
|
import { trpc } from '@/api/trpc';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { CodeBlock } from '../CodeBlock';
|
||||||
|
import { generateSurveyExampleCode } from '@/utils/survey';
|
||||||
|
|
||||||
|
interface SurveyUsageBtnProps {
|
||||||
|
surveyId: string;
|
||||||
|
}
|
||||||
|
export const SurveyUsageBtn: React.FC<SurveyUsageBtnProps> = React.memo(
|
||||||
|
(props) => {
|
||||||
|
const { surveyId } = props;
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { data: info } = trpc.survey.get.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
surveyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fields = info?.payload.items ?? [];
|
||||||
|
|
||||||
|
const exampleCode = generateSurveyExampleCode(window.location.origin, info);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="outline" Icon={LuCode2}>
|
||||||
|
{t('Usage')}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t('Example code')}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{t('Add this example code into your project')}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<CodeBlock code="npm install tianji-client-sdk" />
|
||||||
|
|
||||||
|
<CodeBlock code={exampleCode} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
SurveyUsageBtn.displayName = 'SurveyUsageBtn';
|
@ -12,7 +12,7 @@
|
|||||||
"translation:extract": "i18next-toolkit extract",
|
"translation:extract": "i18next-toolkit extract",
|
||||||
"translation:scan": "i18next-toolkit scan",
|
"translation:scan": "i18next-toolkit scan",
|
||||||
"translation:translate": "i18next-toolkit translate",
|
"translation:translate": "i18next-toolkit translate",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "moonrailgun <moonrailgun@gmail.com>",
|
"author": "moonrailgun <moonrailgun@gmail.com>",
|
||||||
|
@ -20,6 +20,7 @@ import { DataTable, createColumnHelper } from '@/components/DataTable';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { SurveyDownloadBtn } from '@/components/survey/SurveyDownloadBtn';
|
import { SurveyDownloadBtn } from '@/components/survey/SurveyDownloadBtn';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { SurveyUsageBtn } from '@/components/survey/SurveyUsageBtn';
|
||||||
|
|
||||||
type SurveyResultItem =
|
type SurveyResultItem =
|
||||||
AppRouterOutput['survey']['resultList']['items'][number];
|
AppRouterOutput['survey']['resultList']['items'][number];
|
||||||
@ -124,14 +125,15 @@ function PageComponent() {
|
|||||||
<ScrollArea className="h-full overflow-hidden p-4">
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
|
|
||||||
<div className="mb-4 ">
|
<div className="mb-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t('Count')}</CardTitle>
|
<CardTitle>{t('Count')}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex justify-between">
|
<CardContent className="flex justify-between">
|
||||||
<div>{count}</div>
|
<div>{count}</div>
|
||||||
<div>
|
<div className="flex gap-2">
|
||||||
|
<SurveyUsageBtn surveyId={surveyId} />
|
||||||
<SurveyDownloadBtn surveyId={surveyId} />
|
<SurveyDownloadBtn surveyId={surveyId} />
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
28
src/client/utils/__snapshots__/survey.spec.ts.snap
Normal file
28
src/client/utils/__snapshots__/survey.spec.ts.snap
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`survey > example code 1`] = `
|
||||||
|
"import { submitSurvey, initOpenapiSDK } from 'tianji-client-sdk';
|
||||||
|
|
||||||
|
initOpenapiSDK('https://example.com');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit Survey into Test
|
||||||
|
*
|
||||||
|
* @param {text} textField Text
|
||||||
|
*/
|
||||||
|
async function submitForm(textField) {
|
||||||
|
try {
|
||||||
|
await submitSurvey(
|
||||||
|
'<workspaceId>',
|
||||||
|
'<surveyId>',
|
||||||
|
{
|
||||||
|
textField, // Text
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log('Submit success');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
};"
|
||||||
|
`;
|
25
src/client/utils/survey.spec.ts
Normal file
25
src/client/utils/survey.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { describe, test, expect } from 'vitest';
|
||||||
|
import { generateSurveyExampleCode } from './survey';
|
||||||
|
|
||||||
|
describe('survey', () => {
|
||||||
|
test('example code', () => {
|
||||||
|
expect(
|
||||||
|
generateSurveyExampleCode('https://example.com', {
|
||||||
|
id: '<surveyId>',
|
||||||
|
workspaceId: '<workspaceId>',
|
||||||
|
name: 'Test',
|
||||||
|
payload: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'textField',
|
||||||
|
label: 'Text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
})
|
||||||
|
).matchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
38
src/client/utils/survey.ts
Normal file
38
src/client/utils/survey.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { AppRouterOutput } from '@/api/trpc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate survey example code
|
||||||
|
*/
|
||||||
|
export function generateSurveyExampleCode(
|
||||||
|
host: string,
|
||||||
|
info?: AppRouterOutput['survey']['get']
|
||||||
|
): string {
|
||||||
|
const fields = info?.payload.items ?? [];
|
||||||
|
|
||||||
|
const exampleCode = `import { submitSurvey, initOpenapiSDK } from 'tianji-client-sdk';
|
||||||
|
|
||||||
|
initOpenapiSDK('${host}');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit Survey into ${info?.name ?? 'Tianji'}
|
||||||
|
*
|
||||||
|
* ${fields.map((field) => `@param {${field.type}} ${field.name} ${field.label}`).join('\n * ')}
|
||||||
|
*/
|
||||||
|
async function submitForm(${fields.map((field) => field.name).join(', ')}) {
|
||||||
|
try {
|
||||||
|
await submitSurvey(
|
||||||
|
'${info?.workspaceId}',
|
||||||
|
'${info?.id}',
|
||||||
|
{
|
||||||
|
${fields.map((field) => `${field.name}, // ${field.label}`).join('\n ')}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log('Submit success');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
};`;
|
||||||
|
|
||||||
|
return exampleCode;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user