Reviews.jsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. // components
  2. import Spring from '@components/Spring';
  3. import ReviewItem from '@components/ReviewItem';
  4. import TabButton from '@ui/TabButton';
  5. import ScrollContainer from '@components/ScrollContainer';
  6. import AddFormContainer from '@components/AddFormContainer';
  7. import CustomRating from '@ui/CustomRating';
  8. // hooks
  9. import {useState, useRef, useEffect} from 'react';
  10. import useMeasure from 'react-use-measure';
  11. import {useForm, Controller} from 'react-hook-form';
  12. // utils
  13. import classNames from 'classnames';
  14. import dayjs from 'dayjs';
  15. // data placeholder
  16. import reviews from '@db/reviews';
  17. const Reviews = ({standalone, wrapperClass}) => {
  18. const Wrapper = standalone ? Spring : 'div';
  19. const [isFormOpen, setIsFormOpen] = useState(false);
  20. const {control, handleSubmit, formState: {errors}, register, reset} = useForm({
  21. defaultValues: {
  22. title: '',
  23. text: '',
  24. rating: 5
  25. }
  26. });
  27. const [activeTab, setActiveTab] = useState('latest');
  28. const [headerRef, {height: headerHeight}] = useMeasure();
  29. const [footerRef, {height: footerHeight}] = useMeasure();
  30. const trackRef = useRef(null);
  31. const onReset = () => {
  32. reset();
  33. setIsFormOpen(false);
  34. }
  35. const onSubmit = (data) => {
  36. // do something with data
  37. onReset();
  38. }
  39. const sortedData = [...reviews];
  40. const getReviews = () => {
  41. if (activeTab === 'latest') {
  42. return sortedData.sort((a, b) => dayjs(b.date).diff(dayjs(a.date)));
  43. } else {
  44. return sortedData.sort((a, b) => (a.liked < b.liked) ? 1 : -1);
  45. }
  46. }
  47. useEffect(() => {
  48. trackRef.current && trackRef.current.scrollTo(0, 0);
  49. }, [activeTab, isFormOpen]);
  50. return (
  51. <Wrapper className={standalone ? 'card h-3' : wrapperClass}>
  52. <div className={`${standalone ? 'card_header' : ''} d-flex flex-column g-16`}
  53. ref={headerRef}
  54. style={{paddingBottom: 24}}>
  55. <h3>Reviews</h3>
  56. <div className="tab-nav col-2">
  57. <TabButton title="Latest"
  58. active={activeTab === 'latest'}
  59. onClick={() => setActiveTab('latest')}/>
  60. <TabButton title="Helpful"
  61. active={activeTab === 'helpful'}
  62. onClick={() => setActiveTab('helpful')}/>
  63. </div>
  64. </div>
  65. <ScrollContainer height={headerHeight + footerHeight}>
  66. <div className="track" style={{padding: standalone ? '0 var(--card-padding)' : 0}} ref={trackRef}>
  67. {
  68. standalone &&
  69. <AddFormContainer open={isFormOpen}>
  70. <form onSubmit={handleSubmit(onSubmit)} style={{marginBottom: 30}}>
  71. <div className="d-flex flex-column g-10">
  72. <input className={classNames('field', {'field--error': errors.title})}
  73. type="text"
  74. placeholder="Title"
  75. {...register('title', {required: true})}/>
  76. <textarea className={classNames('field', {'field--error': errors.text})}
  77. placeholder="Your review"
  78. {...register('text', {required: true})}/>
  79. </div>
  80. <div className="d-flex align-items-center justify-content-between"
  81. style={{margin: '15px 0 20px'}}>
  82. <span className="label h6">Your rating:</span>
  83. <Controller name="rating"
  84. control={control}
  85. render={({field}) => (
  86. <CustomRating readOnly={false}
  87. value={field.value}
  88. onChange={(value) => field.onChange(value)}/>
  89. )}/>
  90. </div>
  91. <div className="d-grid col-2 g-10">
  92. <button className="btn" type="submit">
  93. Submit
  94. </button>
  95. <button className="btn btn--outlined" type="reset" onClick={onReset}>
  96. Cancel
  97. </button>
  98. </div>
  99. </form>
  100. </AddFormContainer>
  101. }
  102. <div className="d-flex flex-column g-24">
  103. {
  104. getReviews().map((review, index) => (
  105. <ReviewItem review={review} index={index} key={review.id}/>
  106. ))
  107. }
  108. </div>
  109. </div>
  110. </ScrollContainer>
  111. {
  112. standalone &&
  113. <div className={standalone ? 'card-padded' : 'pt-30'} ref={footerRef}>
  114. <button className="btn w-100" onClick={() => setIsFormOpen(true)} disabled={isFormOpen}>
  115. Leave a review
  116. </button>
  117. </div>
  118. }
  119. </Wrapper>
  120. )
  121. }
  122. export default Reviews