Commit 6eb6c36e authored by dasunx's avatar dasunx

automated answer ui updated, custom hooks for debounce, polling and timeout has been added

parent 79c3cb92
/* PrismJS 1.25.0
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+bash+basic+batch+bbcode+bicep+birb+bison+bnf+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+clojure+cmake+cobol+coffeescript+concurnas+csp+coq+crystal+css-extras+csv+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gherkin+git+glsl+gn+go+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+matlab+maxscript+mel+mermaid+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+iecst+stylus+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-highlight+line-numbers+custom-class+highlight-keywords+normalize-whitespace */
code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}
pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)}
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
......@@ -3,11 +3,14 @@ import { publicFetch } from '../../util/fetcher'
import AutomatedAnswer from '../automated-answer'
import NoAutomatedAnswer from '../automated-answer/no-automated-answer'
import { Spinner } from '../icons'
import { useInterval } from '../../hooks/useInterval'
import styles from './automated-answer-container.module.css'
const AutomatedAnswerContainer = ({ question_id }) => {
const [loading, setLoading] = useState(true)
const [polled, setPolled] = useState(500)
const [tries, setTries] = useState(5)
const [answer, setAnswer] = useState()
const [isAnswerGenerating, setIsAnswertGenerating] = useState(false)
const [error, setError] = useState({
......@@ -17,27 +20,37 @@ const AutomatedAnswerContainer = ({ question_id }) => {
message: ''
})
useEffect(() => {
const fetchAutomatedAnswer = async () => {
try {
const response = await publicFetch.get(`automatedanswer/${question_id}`)
if (response.status == 200) {
console.log('RESPONSE IS HERE AND IT IS')
const { isLoading, isComplete } = response.data.automatedanswer
if (isLoading && !isComplete) {
setIsAnswertGenerating(true)
setServerError(102)
} else {
setAnswer(response.data)
}
} else setServerError({ response })
} catch (error) {
setServerError(error)
}
setLoading(false)
const fetchAutomatedAnswer = async () => {
if (tries > 0) {
setTries(tries - 1)
setPolled(null)
}
fetchAutomatedAnswer()
}, [question_id])
try {
const response = await publicFetch.get(`automatedanswer/${question_id}`)
if (response.status == 200) {
console.log('RESPONSE IS HERE AND IT IS')
const { isLoading, isComplete } = response.data.automatedanswer
if (isLoading && !isComplete) {
setIsAnswertGenerating(true)
setServerError(102)
setPolled(1000)
} else {
setIsAnswertGenerating(false)
setAnswer(response.data)
setPolled(null)
}
} else setServerError({ response })
} catch (error) {
setServerError(error)
}
setLoading(false)
}
useInterval(async () => {
console.log('Checking')
await fetchAutomatedAnswer()
}, polled)
const setServerError = (error) => {
if (error == 102) {
......@@ -73,6 +86,7 @@ const AutomatedAnswerContainer = ({ question_id }) => {
question_id={question_id}
error={error}
isGenerating={true}
bindIsGenerating={setIsAnswertGenerating}
/>
)
} else if (answer != null) {
......@@ -83,6 +97,7 @@ const AutomatedAnswerContainer = ({ question_id }) => {
question_id={question_id}
error={error}
isGenerating={false}
bindIsGenerating={setIsAnswertGenerating}
/>
)
}
......@@ -95,7 +110,7 @@ const AutomatedAnswerContainer = ({ question_id }) => {
<h2>Automated Answer</h2>
</div>
</div>
<div className={styles.wrapper}>{getBody()}</div>
<div className={styles.wrapper}>{getBody(answer)}</div>
</div>
)
}
......
import React, { useContext } from 'react'
import React, { useContext, useState } from 'react'
import { FetchContext } from '../../../store/fetch'
import styles from './no-automated-answer.module.css'
import { Spinner } from '../../icons'
const NoAutomatedAnswer = ({ question_id, error, isGenerating }) => {
const NoAutomatedAnswer = ({
question_id,
error,
isGenerating,
bindIsGenerating,
setPollingTime
}) => {
const [isAnswerGenerating, setIsAnswerGenerating] = useState(isGenerating)
const { authAxios } = useContext(FetchContext)
const req_body = {
question_id
}
const requestAutomatedAnswer = async () => {
try {
const response = await authAxios.post('automatedanswer', req_body)
console.log(response)
if (response.status == 201) {
setIsAnswerGenerating(true)
bindIsGenerating(true)
setPollingTime(1000)
}
} catch (err) {
console.log(err)
}
}
const { status, action, button, message } = error
return (
<div className={styles.no_automated_answer}>
<h1 className={styles.title}>{message}</h1>
{isGenerating ? (
{isAnswerGenerating ? (
<Spinner className={styles.spinner} />
) : (
<button className={styles.generate_btn}>{button}</button>
<button
className={styles.generate_btn}
onClick={requestAutomatedAnswer}
>
{button}
</button>
)}
</div>
)
......
......@@ -11,6 +11,7 @@
box-shadow: inset 0 1px 0 0 rgb(255 255 255 / 40%);
padding: 0.8em;
margin: 1em;
cursor: pointer;
}
.title {
......
import React from 'react'
import React, { useEffect } from 'react'
import styles from './BlogArticle.module.css'
const BlogArticle = ({ article }) => {
import Prism from 'prismjs'
const BlogArticle = ({ article, source }) => {
useEffect(() => {
console.log(source)
if (source === 'Medium') {
var childs = document.getElementById('description').children
var i = 0
for (i = 0; i <= childs.length; i++) {
try {
if (childs[i].tagName == 'PRE') {
hljs.highlightElement(childs[i])
}
} catch (error) {
console.log('ALL PRE TAGS ARE PRETTIFIED')
}
}
Prism.highlightAll()
}
}, [])
const createMarkup = () => {
return { __html: article.description }
}
......@@ -9,6 +28,7 @@ const BlogArticle = ({ article }) => {
<div className={styles.blog_article}>
<span className={styles.title}>{article.title}</span>
<div
id="description"
className={styles.description}
dangerouslySetInnerHTML={createMarkup()}
></div>
......
......@@ -87,14 +87,36 @@
pre {
padding: 20px;
background-color: var(black);
font-size: 1.1em;
font-family: 'Roboto Mono', monospace !important;
max-width: 780px;
}
pre::first-line {
font-weight: bold;
}
pre::-webkit-scrollbar {
width: 5px;
height: 8px;
}
/* Track */
pre::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
pre::-webkit-scrollbar-thumb {
width: 5px;
background: rgb(44, 0, 95);
}
/* Handle on hover */
pre::-webkit-scrollbar-thumb:hover {
background: #555;
}
ul {
margin-left: 10px;
padding: 0;
......
import React, { useState } from 'react'
import AutomatedAnswerSwiper from '../automated-answer-swiper/AutomatedAnswerSwiper'
import BlogArticle from '../blog-article/BlogArticle'
import NoResource from '../no-resource/NoResource'
import ResourcePreview from '../resource-preview/ResourcePreview'
import ShowHide from '../show-hide/ShowHide'
import styles from './BlogsWrapper.module.css'
......@@ -10,26 +12,26 @@ const BlogsWrapper = ({ source, articles, resources }) => {
<>
{articles && articles.length > 0 ? (
<>
<h1 className={styles.h1}>
Here {articles.length > 1 ? 'are' : 'is'} {articles.length} article
{articles.length > 1 && 's'} I found on {source}
<ShowHide show={show} onClick={() => setShow(!show)} />
</h1>
<ResourcePreview
articles={articles}
source={source}
show={show}
setShow={setShow}
/>
{show && (
<div className={styles.blogs_wrapper}>
<AutomatedAnswerSwiper>
{articles.map((article, i) => {
return <BlogArticle key={i} article={article} />
return (
<BlogArticle key={i} article={article} source={source} />
)
})}
</AutomatedAnswerSwiper>
</div>
)}
</>
) : (
<h1>
No {source} articles found for this question. Please refer the extra
resouces to find more info
</h1>
<NoResource source={source} />
)}
</>
)
......
.blogs_wrapper {
margin-top: 0.5em;
background-color: #2d2d2d;
padding: 0.8em;
border-radius: 2px;
......
import React from 'react'
const CopyIcon = (props) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" {...props}>
<g data-name="Layer 3">
<path d="M40.63,13H12.38A4.69,4.69,0,0,0,7.7,17.67V57.31A4.69,4.69,0,0,0,12.38,62H40.63a4.69,4.69,0,0,0,4.69-4.69V17.67A4.69,4.69,0,0,0,40.63,13Zm2.69,44.33A2.69,2.69,0,0,1,40.63,60H12.38A2.69,2.69,0,0,1,9.7,57.31V17.67A2.69,2.69,0,0,1,12.38,15H40.63a2.69,2.69,0,0,1,2.69,2.69Z" />
<path d="M51.74,2H23.26a4.58,4.58,0,0,0-4.58,4.57v3.55a1,1,0,0,0,2,0V6.57A2.58,2.58,0,0,1,23.26,4H51.74A2.57,2.57,0,0,1,54.3,6.57V46.44A2.58,2.58,0,0,1,51.74,49H48.5a1,1,0,0,0,0,2h3.24a4.58,4.58,0,0,0,4.57-4.58V6.57A4.57,4.57,0,0,0,51.74,2Z" />
</g>
</svg>
)
}
export default CopyIcon
import React from 'react'
const TelegramLogo = (props) => {
return (
<svg
fill="none"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 500 500"
{...props}
>
<path
d="M250 500c138.071 0 250-111.929 250-250S388.071 0 250 0 0 111.929 0 250s111.929 250 250 250z"
fill="#34aadf"
/>
<path
d="M104.047 247.832s125-51.3 168.352-69.364c16.619-7.225 72.977-30.347 72.977-30.347s26.012-10.115 23.844 14.451c-.723 10.116-6.503 45.52-12.283 83.815-8.671 54.191-18.064 113.439-18.064 113.439s-1.445 16.619-13.728 19.509-32.515-10.115-36.127-13.006c-2.891-2.167-54.191-34.682-72.977-50.578-5.058-4.335-10.838-13.005.722-23.121 26.012-23.844 57.081-53.468 75.867-72.254 8.671-8.671 17.341-28.902-18.786-4.336-51.3 35.405-101.878 68.642-101.878 68.642s-11.561 7.225-33.237.722c-21.677-6.502-46.966-15.173-46.966-15.173s-17.34-10.838 12.284-22.399z"
fill="#fff"
/>
</svg>
)
}
export default TelegramLogo
......@@ -6,4 +6,6 @@ export { default as Logo } from './Logo'
export { default as Menu } from './Menu'
export { default as Search } from './Search'
export { default as Spinner } from './Spinner'
export { default as World } from './World'
\ No newline at end of file
export { default as World } from './World'
export { default as TelegramLogo } from './TelegramLogo'
export { default as CopyIcon } from './Copy'
......@@ -4,9 +4,11 @@
margin-left: auto;
}
.tagContainer {
.tagContainer_wrapper {
position: sticky;
top: 74px;
}
.tagContainer {
display: flex;
flex-direction: column;
padding-left: 12px;
......
......@@ -6,29 +6,33 @@ import Tag from '../../tag'
import { Spinner } from '../../icons'
import styles from './extra.module.css'
import TelegramBot from '../../telegram-bot-container/TelegramBot'
const Extra = ({ marginTop = 24 }) => {
const { tagState } = useContext(TagContext)
return (
<div className={styles.container}>
<div
className={styles.tagContainer}
style={{ marginTop: `${marginTop}px` }}
>
<h2>Popular Tags</h2>
{!tagState && (
<div className="loading">
<Spinner />
<div className={styles.tagContainer_wrapper}>
<div
className={styles.tagContainer}
style={{ marginTop: `${marginTop}px` }}
>
<h2>Popular Tags</h2>
{!tagState && (
<div className="loading">
<Spinner />
</div>
)}
<div className={styles.popularTags}>
{tagState?.map((tag) => (
<Tag key={tag._id} count={tag.count}>
{tag._id}
</Tag>
))}
</div>
)}
<div className={styles.popularTags}>
{tagState?.map((tag) => (
<Tag key={tag._id} count={tag.count}>
{tag._id}
</Tag>
))}
</div>
<TelegramBot />
</div>
</div>
)
......
import React from 'react'
import cn from 'classnames'
import CONST from '../../constants'
import useWindowSize from '../../hooks/useWindowSize'
......@@ -13,6 +12,7 @@ import styles from './layout.module.css'
const Layout = ({ extra = true, children }) => {
const size = useWindowSize()
return (
<div className={styles.layout}>
<Header />
......
import React from 'react'
import Styles from './NoResource.module.css'
const NoResource = ({ source, message }) => {
return (
<div className={Styles.noresource}>
<h1>
No resources found from <b>{source}</b> for this question. Please refer
the extra resouces section to find more info.
</h1>
</div>
)
}
export default NoResource
.noresource {
padding: 20px 5px;
background-color: #1e225f;
color: #418df7;
margin: 10px 0;
border-radius: 5px;
h1 {
font-size: 18px;
}
}
import React, { useState, useEffect } from 'react'
import Styles from './ResourcePreview.module.css'
import ShowHide from '../show-hide/ShowHide'
const ResourcePreview = ({ articles, source, show, setShow }) => {
const [thumbnail, setThumbnail] = useState('')
const getThumbnail = () => {
articles.forEach((element) => {
if (
!(
element.thumbnail == 'undefined' ||
element.thumbnail == undefined ||
element.thumbnail == null ||
element.thumbnail == '' ||
!element.thumbnail
)
) {
setThumbnail(element.thumbnail)
return
}
})
}
useEffect(() => {
getThumbnail()
}, [])
const onError = (e) => {
console.log('ON ERROR')
e.target.src =
'https://www.mtu.edu/cs/images/trees-dark-blue-icons-6x6-card1200.jpg'
}
return (
<div
className={
show ? Styles.article_preview_hide : Styles.article_preview_active
}
>
<div className={show ? Styles.preview : Styles.preview}>
<img
src={thumbnail}
onError={onError}
alt="article image"
className={Styles.preview_img}
/>
</div>
<div className={Styles.action_bar}>
<h1 className={Styles.title}>
Here {articles.length > 1 ? 'are' : 'is'} {articles.length} article
{articles.length > 1 && 's'} I found on {source}
</h1>
<ShowHide show={show} onClick={() => setShow(!show)} />
</div>
</div>
)
}
export default ResourcePreview
.article_preview_active {
border-radius: 10px;
height: 220px;
background-color: blue;
margin: 10px 0;
position: relative;
display: flex;
flex-direction: column-reverse;
overflow: hidden;
transition: all 0.5s cubic-bezier(0.47, 0, 0.75, 0.72);
}
.article_preview_hide {
border-radius: 10px 10px 0 0;
height: 120px;
background-color: blue;
position: relative;
display: flex;
flex-direction: column-reverse;
overflow: hidden;
transition: all 0.5s cubic-bezier(0.47, 0, 0.75, 0.72);
}
.title {
font-size: 2em !important;
display: flex;
justify-content: space-between;
align-items: center;
}
.action_bar {
display: flex;
flex-direction: column;
align-items: center;
z-index: 2;
background: linear-gradient(0deg, #752d87, transparent);
padding: 80px 0 10px 0;
}
.preview {
position: absolute;
top: 0;
}
.preview_img {
max-width: 100%;
}
......@@ -8,11 +8,11 @@ const ShowHide = ({ onClick, show }) => {
<div onClick={onClick} className={styles.show_hide}>
{show ? (
<span>
<EyeCloseIcon width={24} height={24} color="red" /> Hide
<span>Hide</span> <EyeCloseIcon width={24} height={24} color="red" />
</span>
) : (
<span>
<EyeIcon width={24} height={24} color="white" /> Show
<span>Show</span> <EyeIcon width={24} height={24} color="white" />
</span>
)}
</div>
......
.show_hide {
cursor: pointer;
font-size: 1.5rem;
background-color: var(--black-800);
padding: 2px 10px;
background-color: #2427296b;
padding: 4px 24px;
border-radius: 5px;
margin: auto;
border: 2px solid black;
width: 160px;
align-items: center;
span {
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-evenly;
* {
margin-right: 5px;
}
......
import React, { useEffect, useState } from 'react'
import React, { useState } from 'react'
import SimilarQuestion from './similar-question'
import { Title } from '../styled-text-spans/index'
import styles from './similar-question-suggestions.module.css'
import useDebounce from '../../hooks/useDebounce'
import { Spinner } from '../icons'
const SimilarQuestionSuggestions = ({ title, description, tags }) => {
const [isLoading, setIsLoading] = useState(true)
const [similarQuestions, setSimilarQuestions] = useState([
{
url: 'https://dasunx.com',
......@@ -40,19 +44,28 @@ const SimilarQuestionSuggestions = ({ title, description, tags }) => {
comments: 6
}
])
useEffect(() => {
// TODO - Fetch similar questions from backend using the title/ description
console.log('TITLE CHANGED')
}, [])
useDebounce(
() => {
setIsLoading(false)
},
1000,
[title]
)
return (
<div className={styles.container}>
<h1>
Similar questions -{' '}
<Title title={title} textColor="var(--main-purple)" />
</h1>
{similarQuestions.map((question, index) => {
return <SimilarQuestion question={question} key={index} />
})}
{isLoading ? (
<Spinner className={styles.spinner} />
) : (
similarQuestions.map((question, index) => {
return <SimilarQuestion question={question} key={index} />
})
)}
</div>
)
}
......
......@@ -9,3 +9,11 @@
flex-direction: column;
max-width: 950px;
}
.spinner {
width: 60px;
height: 60px;
justify-content: center;
text-align: center;
margin: auto;
}
import React from 'react'
import React, { useEffect } from 'react'
import Prism from 'prismjs'
import styles from './stof-answer.module.css'
const StackOverflowAnswer = ({ stof }) => {
useEffect(() => {
// document.querySelectorAll('pre code').forEach((block) => {
// Prism.highlightElement(block)
// })
// Prism.highlightAll()
hljs.highlightAll()
Prism.highlightAll()
}, [stof])
const createMarkup = () => {
return { __html: stof.content }
}
......
......@@ -7,6 +7,10 @@
font-family: 'Open Sans', sans-serif;
margin-bottom: 3em;
color: white;
img {
max-width: 780px;
}
}
.h {
......
import React from 'react'
import { TelegramLogo, CopyIcon } from '../icons'
import Styles from './TelegramBot.module.css'
const TelegramBot = () => {
const [isCopiedTag, setIsCopiedTag] = React.useState({
isCopied: false,
isError: false,
message: ''
})
const { isCopied, isError, message } = isCopiedTag
const onClick = () => {
navigator.clipboard.writeText('probexpertbot').then(
() => {
setIsCopiedTag({
isCopied: true,
isError: false,
message: 'Copied!'
})
},
() => {
setIsCopiedTag({
isCopied: false,
isError: true,
message: 'Error!'
})
}
)
}
return (
<div className={Styles.telegram_bot_container}>
<div className={Styles.telegram_bot_title}>
Use ProbExpert Telegram bot
</div>
<a href="https://t.me/probexpertbot" target="_blank">
<TelegramLogo className={Styles.telegram_bot_logo} />
</a>
<div className={Styles.telegram_bot_tag}>
<span>@probexpertbot</span>{' '}
<CopyIcon className={Styles.telegram_bot_tag_icon} onClick={onClick} />
</div>
{isCopied ? (
<div className={Styles.telegram_bot_tag_copied}>{message}</div>
) : (
isError && (
<div className={Styles.telegram_bot_tag_error}>{message}</div>
)
)}
</div>
)
}
export default TelegramBot
.telegram_bot_container {
display: flex;
flex-direction: column;
align-items: center;
background: #f5f5f5;
margin: 20px 0;
border-radius: 10px;
padding: 20px 10px;
gap: 15px;
}
.telegram_bot_title {
font-size: 16px;
color: #0c0cc4;
font-weight: 600;
/* text-transform: capitalize; */
text-transform: uppercase;
text-align: center;
}
.telegram_bot_logo {
width: 80px;
height: 80px;
}
.telegram_bot_tag {
padding: 10px;
display: flex;
flex-direction: row;
justify-content: space-evenly;
background-color: white;
margin: 10px 0;
border-radius: 10px;
width: 70%;
font-family: 'Roboto Mono';
span {
font-size: 14px;
}
}
.telegram_bot_tag_icon {
width: 20px;
cursor: pointer;
}
.telegram_bot_tag_copied {
color: green;
font-weight: 600;
}
.telegram_bot_tag_error {
color: red;
font-weight: 600;
}
import { useEffect } from 'react'
import useTimeout from './useTimeout'
export default function useDebounce(callback, delay, dependencies) {
const { reset, clear } = useTimeout(callback, delay)
useEffect(reset, [...dependencies, reset])
useEffect(clear, [])
}
import { useEffect, useRef } from 'react'
export function useInterval(callback, delay) {
const savedCallback = useRef()
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
function tick() {
savedCallback.current()
}
if (delay != null) {
const id = setInterval(tick, delay)
return () => {
clearInterval(id)
}
}
}, [callback, delay])
}
import { useCallback, useEffect, useRef } from 'react'
export default function useTimeout(callback, delay) {
const callbackRef = useRef(callback)
const timeoutRef = useRef()
useEffect(() => {
callbackRef.current = callback
}, [callback])
const set = useCallback(() => {
timeoutRef.current = setTimeout(() => callbackRef.current(), delay)
}, [delay])
const clear = useCallback(() => {
timeoutRef.current && clearTimeout(timeoutRef.current)
}, [])
useEffect(() => {
set()
return clear
}, [delay, set, clear])
const reset = useCallback(() => {
clear()
set()
}, [clear, set])
return { reset, clear }
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><g data-name="Layer 3"><path d="M40.63,13H12.38A4.69,4.69,0,0,0,7.7,17.67V57.31A4.69,4.69,0,0,0,12.38,62H40.63a4.69,4.69,0,0,0,4.69-4.69V17.67A4.69,4.69,0,0,0,40.63,13Zm2.69,44.33A2.69,2.69,0,0,1,40.63,60H12.38A2.69,2.69,0,0,1,9.7,57.31V17.67A2.69,2.69,0,0,1,12.38,15H40.63a2.69,2.69,0,0,1,2.69,2.69Z"/><path d="M51.74,2H23.26a4.58,4.58,0,0,0-4.58,4.57v3.55a1,1,0,0,0,2,0V6.57A2.58,2.58,0,0,1,23.26,4H51.74A2.57,2.57,0,0,1,54.3,6.57V46.44A2.58,2.58,0,0,1,51.74,49H48.5a1,1,0,0,0,0,2h3.24a4.58,4.58,0,0,0,4.57-4.58V6.57A4.57,4.57,0,0,0,51.74,2Z"/></g></svg>
\ No newline at end of file
<svg fill="none" height="2500" width="2500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path d="M250 500c138.071 0 250-111.929 250-250S388.071 0 250 0 0 111.929 0 250s111.929 250 250 250z" fill="#34aadf"/><path d="M104.047 247.832s125-51.3 168.352-69.364c16.619-7.225 72.977-30.347 72.977-30.347s26.012-10.115 23.844 14.451c-.723 10.116-6.503 45.52-12.283 83.815-8.671 54.191-18.064 113.439-18.064 113.439s-1.445 16.619-13.728 19.509-32.515-10.115-36.127-13.006c-2.891-2.167-54.191-34.682-72.977-50.578-5.058-4.335-10.838-13.005.722-23.121 26.012-23.844 57.081-53.468 75.867-72.254 8.671-8.671 17.341-28.902-18.786-4.336-51.3 35.405-101.878 68.642-101.878 68.642s-11.561 7.225-33.237.722c-21.677-6.502-46.966-15.173-46.966-15.173s-17.34-10.838 12.284-22.399z" fill="#fff"/></svg>
\ No newline at end of file
This diff is collapsed.
......@@ -22,6 +22,7 @@
"formik": "^2.1.5",
"next": "^9.4.4",
"nprogress": "^0.2.0",
"prism-themes": "^1.9.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-tagsinput": "^3.19.0",
......
......@@ -15,18 +15,17 @@ import '../styles/variables.css'
import '../styles/nprogress.css'
import 'react-tagsinput/react-tagsinput.css'
import '../styles/app.css'
import '../styles/dev.css'
import '../styles/automatedanswer.css'
import 'prism-themes/themes/prism-night-owl.min.css'
Router.events.on('routeChangeStart', () => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
function MyApp({ Component, pageProps }) {
const {
ref,
isComponentVisible,
setIsComponentVisible
} = useComponentVisible(false)
const { ref, isComponentVisible, setIsComponentVisible } =
useComponentVisible(false)
const [authScreen, setAuthScreen] = useState(null)
......
......@@ -48,6 +48,7 @@ class MyDocument extends Document {
{/* favicon */}
<link rel="shortcut icon" href="/images/logo.svg" />
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script>
</Head>
<body>
<Main />
......
......@@ -95,7 +95,7 @@ const QuestionDetail = ({ questionId, title }) => {
</PostWrapper>
<AutomatedAnswerContainer question_id={questionId} />
{question.answers.length > 0 && (
<AnswerContainer
answersCount={question.answers.length}
......
......@@ -52,12 +52,12 @@
}
}
.s-prose *:not(.s-code-block) > code {
/* .s-prose *:not(.s-code-block) > code {
padding: 2px 4px;
color: var(--white);
background-color: var(--black-600);
border-radius: 3px;
}
} */
sup,
sub {
......
This diff is collapsed.
This diff is collapsed.
......@@ -2,7 +2,7 @@ import axios from 'axios'
const baseURL =
process.env.NODE_ENV === 'development'
? 'http://localhost:8080/api'
? 'http://localhost:5000/api'
: `https://${process.env.SITE_NAME}/api`
const publicFetch = axios.create({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment