Skip to content

Commit dc0404d

Browse files
authored
Merge pull request #3731 from ClickHouse/docs_feedback
feedback widget
2 parents 0e5abd5 + b1b7020 commit dc0404d

File tree

3 files changed

+254
-13
lines changed

3 files changed

+254
-13
lines changed

src/components/Feedback/index.jsx

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import { IconButton, Popover, Title, RadioGroup, TextAreaField, Button, IconButton, Panel, Text} from '@clickhouse/click-ui/bundled'
3+
import { getGoogleAnalyticsUserIdFromBrowserCookie } from '../../lib/google/google'
4+
import { useColorMode } from '@docusaurus/theme-common'
5+
import styles from './styles.module.scss'
6+
import clsx from 'clsx'
7+
8+
export default function Feedback() {
9+
const [open, setOpen] = useState(false);
10+
const [negResponse, setNegResponse] = useState('');
11+
const [comment, setComment] = useState('');
12+
const [selected, setSelected] = useState('');
13+
14+
const handleClick = (response) => {
15+
//send row with google id, up or down
16+
setSelected(response ? 'pos': 'neg')
17+
if (!response) {
18+
setOpen(true);
19+
}
20+
};
21+
22+
const handleSubmit = async (response) => {
23+
setOpen(false);
24+
25+
let gaId = getGoogleAnalyticsUserIdFromBrowserCookie('_ga')
26+
const data = {
27+
page_url: window.location.href,
28+
date: new Date().toISOString().replace('T', ' ').slice(0, 19),
29+
sentiment: response ? 'Positive' : 'Negative',
30+
reason: response ? '' : negResponse,
31+
google_id: gaId || 'anonymous',
32+
comment,
33+
};
34+
35+
const insertQuery = `
36+
INSERT INTO docs_feedback.feedback (page_url, date, sentiment, reason, google_id, comment)
37+
VALUES
38+
('${data.page_url}', '${data.date}', '${data.sentiment}', '${data.reason}', '${data.google_id}', '${data.comment.replace(/'/g, "''")}')
39+
`;
40+
try {
41+
const response = await fetch('https://sql-clickhouse.clickhouse.com', {
42+
method: 'POST',
43+
headers: {
44+
'Content-Type': 'text/plain',
45+
},
46+
body: insertQuery,
47+
headers: {
48+
'x-clickhouse-user': 'docs_feedback',
49+
'x-clickhouse-key': '',
50+
'Content-Type': 'text/plain',
51+
}
52+
});
53+
54+
if (!response.ok) {
55+
console.error('Failed to insert feedback:', await response.text());
56+
} else {
57+
console.log('Feedback submitted successfully');
58+
}
59+
} catch (err) {
60+
console.error('Error submitting feedback:', err);
61+
}
62+
}
63+
64+
const popoverRef = useRef();
65+
66+
useEffect(() => {
67+
function handleClickOutside(event) {
68+
if (popoverRef.current && !popoverRef.current.contains(event.target)) {
69+
if (open) {
70+
handleSubmit(false);
71+
}
72+
}
73+
}
74+
75+
if (open) {
76+
document.addEventListener('mousedown', handleClickOutside);
77+
}
78+
79+
return () => {
80+
document.removeEventListener('mousedown', handleClickOutside);
81+
};
82+
}, [open]);
83+
84+
85+
86+
const negative_feedback = (
87+
<Popover.Content side='left' align='end' showArrow={true}>
88+
<div ref={popoverRef}>
89+
<div className='flex justify-between items-center mb-[4px]'>
90+
<Title size='md'>Why was is not helpful?</Title>
91+
<IconButton size='xs' icon='cross' onClick={() => handleSubmit(false) } type='ghost'/>
92+
</div>
93+
94+
<RadioGroup
95+
orientation='vertical'
96+
value={negResponse}
97+
onValueChange={setNegResponse}
98+
>
99+
<div className='flex mt-[4px] flex-col'>
100+
<RadioGroup.Item value='missing_info' label='Missing information' />
101+
<RadioGroup.Item value='confusing' label='Hard to follow or confusing'/>
102+
<RadioGroup.Item value='inaccurate' label="Inaccurate, out of date, or doesn't work"/>
103+
<RadioGroup.Item value='other' label='Something else'/>
104+
105+
<div className='mt-[10px] mb-[10px]'>
106+
<TextAreaField placeholder='Additional feedback (optional)' type="text" value={comment} onChange={(value) => setComment(value)}/>
107+
</div>
108+
<Button label='Submit' onClick={() => handleSubmit(false)}/>
109+
</div>
110+
</RadioGroup>
111+
</div>
112+
</Popover.Content>
113+
)
114+
115+
const { colorMode } = useColorMode();
116+
117+
const getStroke = (highlighted, colorMode) => {
118+
if (colorMode === 'light') {
119+
return highlighted ? '#000' : '#696E79';
120+
} else {
121+
return highlighted ? '#FAFF69' : '#B3B6BD';
122+
}
123+
};
124+
125+
const ThumbsUp = ({ highlighted, colorMode }) => (
126+
<svg
127+
width="16"
128+
height="16"
129+
viewBox="0 0 16 16"
130+
fill="none"
131+
xmlns="http://www.w3.org/2000/svg"
132+
role="img"
133+
aria-label="thumbs-up"
134+
style={{ display: 'block', margin: 'auto' }}
135+
>
136+
<g clipPath="url(#clip0_110_3987)">
137+
<path
138+
d="M2 6.5H5V13H2C1.86739 13 1.74021 12.9473 1.64645 12.8536C1.55268 12.7598 1.5 12.6326 1.5 12.5V7C1.5 6.86739 1.55268 6.74021 1.64645 6.64645C1.74021 6.55268 1.86739 6.5 2 6.5Z"
139+
stroke={getStroke(highlighted, colorMode)}
140+
strokeLinecap="round"
141+
strokeLinejoin="round"
142+
/>
143+
<path
144+
d="M5 6.5L7.5 1.5C8.03043 1.5 8.53914 1.71071 8.91421 2.08579C9.28929 2.46086 9.5 2.96957 9.5 3.5V5H13.5C13.6419 5.00004 13.7821 5.03026 13.9113 5.08865C14.0406 5.14704 14.156 5.23227 14.2498 5.33867C14.3436 5.44507 14.4137 5.57021 14.4555 5.70579C14.4972 5.84136 14.5096 5.98426 14.4919 6.125L13.7419 12.125C13.7114 12.3666 13.5939 12.5888 13.4113 12.7499C13.2286 12.911 12.9935 12.9999 12.75 13H5"
145+
stroke={getStroke(highlighted, colorMode)}
146+
strokeLinecap="round"
147+
strokeLinejoin="round"
148+
/>
149+
</g>
150+
<defs>
151+
<clipPath id="clip0_110_3987">
152+
<rect width="16" height="16" fill="white" />
153+
</clipPath>
154+
</defs>
155+
</svg>
156+
);
157+
158+
159+
const ThumbsDown = ({ highlighted, colorMode }) => (
160+
<svg
161+
width="16"
162+
height="16"
163+
viewBox="0 0 16 16"
164+
fill="none"
165+
xmlns="http://www.w3.org/2000/svg"
166+
role="img"
167+
aria-label="thumbs-down"
168+
style={{ display: 'block', margin: 'auto' }}
169+
>
170+
<g clipPath="url(#clip0_110_3992)">
171+
<path
172+
d="M2 3H5V9.5H2C1.86739 9.5 1.74021 9.44732 1.64645 9.35355C1.55268 9.25979 1.5 9.13261 1.5 9L1.5 3.5C1.5 3.36739 1.55268 3.24021 1.64645 3.14645C1.74021 3.05268 1.86739 3 2 3Z"
173+
stroke={getStroke(highlighted, colorMode)}
174+
strokeLinecap="round"
175+
strokeLinejoin="round"
176+
/>
177+
<path
178+
d="M5 9.5L7.5 14.5C8.03043 14.5 8.53914 14.2893 8.91421 13.9142C9.28929 13.5391 9.5 13.0304 9.5 12.5V11H13.5C13.6419 11 13.7821 10.9697 13.9113 10.9114C14.0406 10.853 14.156 10.7677 14.2498 10.6613C14.3436 10.5549 14.4137 10.4298 14.4555 10.2942C14.4972 10.1586 14.5096 10.0157 14.4919 9.875L13.7419 3.875C13.7114 3.63339 13.5939 3.41119 13.4113 3.25009C13.2286 3.08899 12.9935 3.00007 12.75 3L5 3"
179+
stroke={getStroke(highlighted, colorMode)}
180+
strokeLinecap="round"
181+
strokeLinejoin="round"
182+
/>
183+
</g>
184+
<defs>
185+
<clipPath id="clip0_110_3992">
186+
<rect width="16" height="16" fill="white" />
187+
</clipPath>
188+
</defs>
189+
</svg>
190+
);
191+
192+
193+
return (
194+
<Panel hasBorder alignItems='start'>
195+
<Popover open={open} >
196+
<Popover.Trigger>
197+
<div>
198+
<Text size='lg' color='muted' weight='semibold'>Was this page helpful?</Text>
199+
</div>
200+
<div className='mt-[8px] flex'>
201+
<div className={clsx( styles.Button, colorMode === 'dark' ? styles.dark : styles.light, selected === 'pos' && styles.selected )} onClick={() => { handleClick(true); handleSubmit(true)}}>
202+
<ThumbsUp highlighted={selected === 'pos'} colorMode={colorMode}/>
203+
</div>
204+
205+
<div className={clsx( styles.Button, colorMode === 'dark' ? styles.dark : styles.light, selected === 'neg' && styles.selected )} onClick={() => handleClick(false)}>
206+
<ThumbsDown highlighted={selected === 'neg'} colorMode={colorMode}/>
207+
</div>
208+
</div>
209+
210+
</Popover.Trigger>
211+
{
212+
selected === 'neg' && negative_feedback
213+
}
214+
</Popover>
215+
</Panel>
216+
);
217+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.Button {
2+
width: 40px;
3+
height: 40px;
4+
display: flex;
5+
align-items: center;
6+
justify-content: center;
7+
border-radius: 4px;
8+
cursor: pointer;
9+
margin-right: 8px;
10+
border: 1px solid;
11+
background-color: transparent;
12+
13+
&.dark {
14+
border-color: #323232;
15+
background-color: transparent;
16+
17+
&.selected {
18+
background-color: #282828;
19+
}
20+
}
21+
22+
&.light {
23+
border-color: #E6E7E9;
24+
background-color: #fff;
25+
26+
&.selected {
27+
background-color: #F6F7FA;
28+
}
29+
}
30+
}

src/theme/DocItem/TOC/Desktop/index.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,10 @@ import {useDoc} from '@docusaurus/plugin-content-docs/client';
44
import TOC from '@theme/TOC';
55
import clsx from "clsx";
66
import styles from './styles.module.css'
7-
import IconClose from '@theme/Icon/Close';
7+
import Feedback from '../../../../components/Feedback';
88

99
export default function DocItemTOCDesktop() {
1010
const {toc, frontMatter} = useDoc();
11-
const [isClosed, setClosed] = useState(true)
12-
13-
useEffect(() => {
14-
const closed =
15-
window.localStorage.getItem('doc-cloud-card-banner') === 'closed'
16-
if (!isClosed || closed !== isClosed) {
17-
setClosed(closed)
18-
}
19-
}, [])
20-
2111
return (
2212
<div className={clsx(styles.docTOCContainer, 'theme-doc-toc-desktop-container')}>
2313
<TOC
@@ -26,14 +16,18 @@ export default function DocItemTOCDesktop() {
2616
maxHeadingLevel={frontMatter.toc_max_heading_level}
2717
className={clsx(styles.docTOC, ThemeClassNames.docs.docTocDesktop)}
2818
/>
29-
{!isClosed && (<div className={styles.docCloudCard}>
19+
<div style={{'marginTop': toc.length < 7 ? '150px': '32px'}}>
20+
<Feedback/>
21+
</div>
22+
23+
<div className={styles.docCloudCard}>
3024
<div className={styles.docCloudCardHeader}>
3125
<h6>Try ClickHouse Cloud for FREE</h6>
3226
</div>
3327
<p className={styles.docCloudCardContent}>Separation of storage and compute, automatic scaling, built-in SQL console, and lots more. $300 in free credits when signing up.</p>
3428
<a href='https://console.clickhouse.cloud/signUp?loc=doc-card-banner'
3529
className={clsx(styles.docCloudCardLink, 'click-button primary-btn')}>Try it for Free</a>
36-
</div>)}
30+
</div>
3731
</div>
3832
);
3933
}

0 commit comments

Comments
 (0)