Commit 3b9e6dee authored by Ekanayake P.M.D.P IT18013610's avatar Ekanayake P.M.D.P IT18013610

Merge branch 'it18013610' into 'master'

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

See merge request !11
parents ca28827d 6eb6c36e
/* 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