-
Notifications
You must be signed in to change notification settings - Fork 10
Challenge tabs #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Challenge tabs #90
Changes from 2 commits
1e841a5
14b30be
1be63b8
f86e7dd
aada9cc
f9f77f4
5cb02e5
1c9dd6a
63afbfc
275a3a6
423999f
a277544
8140d41
a9b9d3a
ec678c3
ebb4cc9
ae61fe4
2d476a0
0f1a795
acbb1a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { Card, Heading, Text } from 'ui'; | ||
| import Image from 'next/image'; | ||
| import clsx from 'clsx'; | ||
| import type { Opinion } from '../../../types/types'; | ||
|
|
||
| type OpinionItemProps = Opinion; | ||
|
|
||
| export const OpinionItem = ({ | ||
| author, | ||
| avatar, | ||
| rating, | ||
| comment, | ||
| }: OpinionItemProps) => { | ||
| return ( | ||
| <div className="p-2"> | ||
| <Card tag="div"> | ||
| <div className="flex flex-col md:flex-row justify-center items-center"> | ||
| <Image | ||
| src={avatar} | ||
| alt="" | ||
| width={1000} | ||
| height={1000} | ||
| className="w-full h-full rounded-full w-24 h-24 object-cover" | ||
| /> | ||
| <div className="md:pl-5"> | ||
| <Heading tag="h3" size="large" className="font-bold"> | ||
| {author} | ||
| </Heading> | ||
| <div className="flex items-center"> | ||
| {Array.from({ length: 5 }, (_, i) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would extract this 5 to some constant. Also I'd create an array with some meaningful name and put it outside of the component as it is static. |
||
| const starClass = | ||
| i < Math.round(rating) ? 'text-yellow-400' : 'text-gray-300'; | ||
| return ( | ||
| <svg | ||
| aria-hidden="true" | ||
| className={clsx('w-5 h-5', starClass)} | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| key={i} | ||
| > | ||
| {/*<title>First star</title>*/} | ||
|
ssynowiec marked this conversation as resolved.
Outdated
|
||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| ); | ||
| })} | ||
| </div> | ||
| <Text size="medium" tag="p" variant="default" position="left"> | ||
| {comment} | ||
| </Text> | ||
| </div> | ||
| </div> | ||
| </Card> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { OpinionItem } from 'molecules/opinionItem/opinionItem'; | ||
| import type { Opinion } from '../../../types/types'; | ||
| import { NewOpinionForm } from 'organisms/newOpinionForm/newOpinionForm'; | ||
| import { OpinionStats } from 'molecules/opinonStats/opinionStats'; | ||
| import { ProtectedComponent } from 'organisms/protectedPage/protectedComponent/protectedComponent'; | ||
|
|
||
| const opinions: Opinion[] = [ | ||
| { | ||
| author: 'Jan Kowalski', | ||
| avatar: | ||
| 'https://images.unsplash.com/photo-1633332755192-727a05c4013d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2960&q=80', | ||
| rating: 5, | ||
| comment: | ||
| 'Zadanie programistyczne, które otrzymałem, było dobrze sformułowane i precyzyjnie opisywało wymagania dotyczące tworzenia aplikacji internetowej. Zadanie miało na celu zaimplementowanie funkcjonalności wyszukiwania produktów w bazie danych i wyświetlenia wyników na stronie internetowej.', | ||
| }, | ||
| { | ||
| author: 'Jan Kowalski', | ||
| avatar: | ||
| 'https://images.unsplash.com/photo-1633332755192-727a05c4013d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2960&q=80', | ||
| rating: 5, | ||
| comment: | ||
| 'Zadanie programistyczne, które otrzymałem, było dobrze sformułowane i precyzyjnie opisywało wymagania dotyczące tworzenia aplikacji internetowej. Zadanie miało na celu zaimplementowanie funkcjonalności wyszukiwania produktów w bazie danych i wyświetlenia wyników na stronie internetowej.', | ||
| }, | ||
| { | ||
| author: 'Jan Kowalski', | ||
| avatar: | ||
| 'https://images.unsplash.com/photo-1633332755192-727a05c4013d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2960&q=80', | ||
| rating: 4, | ||
| comment: | ||
| 'Zadanie programistyczne, które otrzymałem, było dobrze sformułowane i precyzyjnie opisywało wymagania dotyczące tworzenia aplikacji internetowej. Zadanie miało na celu zaimplementowanie funkcjonalności wyszukiwania produktów w bazie danych i wyświetlenia wyników na stronie internetowej.', | ||
| }, | ||
| ]; | ||
|
|
||
| export const Opinions = () => { | ||
| return ( | ||
| <> | ||
| <div className="flex flex-col md:flex-row"> | ||
| <div className="p-2 w-full md:w-1/2"> | ||
| <ProtectedComponent info="Zaloguj się aby dodać opinię."> | ||
| <NewOpinionForm /> | ||
| </ProtectedComponent> | ||
| </div> | ||
| <OpinionStats opinions={opinions} /> | ||
| </div> | ||
| {opinions.map((opinion, id) => ( | ||
| <OpinionItem | ||
| key={id} | ||
| author={opinion.author} | ||
| avatar={opinion.avatar} | ||
| comment={opinion.comment} | ||
| rating={opinion.rating} | ||
| /> | ||
|
ssynowiec marked this conversation as resolved.
Outdated
|
||
| ))} | ||
| </> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { Card, Heading } from 'ui'; | ||
| import type { Opinion } from '../../../types/types'; | ||
| import { PolishPlurals } from 'utils/plurals/PolishPlurals'; | ||
|
|
||
| type OpinionStatsProps = { | ||
| opinions: Opinion[]; | ||
| }; | ||
|
|
||
| export const OpinionStats = ({ opinions }: OpinionStatsProps) => { | ||
| return ( | ||
| <div className="p-2 w-full md:w-1/2"> | ||
| <Card tag="div"> | ||
| <div className="flex flex-col"> | ||
| <Heading tag="h3" size="large" className="font-bold"> | ||
| Statystyki | ||
| </Heading> | ||
|
|
||
| <div className="flex items-center py-3"> | ||
| <svg | ||
| aria-hidden="true" | ||
| className="w-5 h-5 text-yellow-400" | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>First star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className="w-5 h-5 text-yellow-400" | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>Second star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className="w-5 h-5 text-yellow-400" | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>Third star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className="w-5 h-5 text-yellow-400" | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>Fourth star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className="w-5 h-5 text-gray-300 dark:text-gray-300" | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>Fifth star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <p className="ml-2 text-sm font-medium">4.95 / 5</p> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calculate the average, based on the opinions. And map it to display the stars instead of copying them. |
||
| </div> | ||
| <p className="text-sm font-medium text-gray-500 dark:text-gray-400"> | ||
| {PolishPlurals(opinions.length)} | ||
| </p> | ||
| </div> | ||
| </Card> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { Tab } from '@headlessui/react'; | ||
| import { Opinions } from 'molecules/opinions/opinions'; | ||
| import clsx from 'clsx'; | ||
| import type { ReactNode } from 'react'; | ||
|
|
||
| type TabObject = { | ||
| label: string; | ||
| content: ReactNode; | ||
| }; | ||
|
|
||
| export const Tabs = () => { | ||
| const tabs: TabObject[] = [ | ||
| { | ||
| label: 'Opinie', | ||
| content: <Opinions />, | ||
| }, | ||
| { | ||
| label: 'Rozwiązania', | ||
| content: 'Tab 2', | ||
| }, | ||
| { | ||
| label: 'Komentarze', | ||
| content: 'Komentarze', | ||
| }, | ||
| ]; | ||
|
ssynowiec marked this conversation as resolved.
Outdated
ssynowiec marked this conversation as resolved.
Outdated
|
||
|
|
||
| return ( | ||
| <div className="w-full px-2 py-16 sm:px-0"> | ||
| <Tab.Group> | ||
| <Tab.List className="flex flex-col md:flex-row border-b-2 mb-3"> | ||
| {tabs.map((tab) => ( | ||
| <Tab | ||
| className={({ selected }) => | ||
| clsx( | ||
| 'w-full rounded-tl-lg rounded-tr-lg py-2.5 text-sm font-medium text-blue-700', | ||
| 'ring-white ring-opacity-60 ring-offset-2 ring-offset-blue-400 focus:outline-none focus:ring-2 hover:bg-blue-50 transition ease-in-out duration-150', | ||
| selected ? 'border-2 border-blue-500 shadow' : 'text-black', | ||
| ) | ||
| } | ||
| > | ||
| {tab.label} | ||
| </Tab> | ||
| ))} | ||
| </Tab.List> | ||
| <Tab.Panels className="w-full"> | ||
| {tabs.map((tab) => ( | ||
| <Tab.Panel>{tab.content}</Tab.Panel> | ||
| ))} | ||
| </Tab.Panels> | ||
| </Tab.Group> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { Button, Card, Heading } from 'ui'; | ||
| import clsx from 'clsx'; | ||
| import { useAddOpinionForm } from 'organisms/newOpinionForm/useAddOpinionForm'; | ||
| import { TextArea } from 'ui/components/organisms/textArea/textArea'; | ||
|
|
||
| export const NewOpinionForm = () => { | ||
|
ssynowiec marked this conversation as resolved.
Outdated
|
||
| const { register, errors } = useAddOpinionForm(); | ||
|
|
||
| return ( | ||
|
ssynowiec marked this conversation as resolved.
|
||
| <Card tag="div"> | ||
| <div className="flex flex-col"> | ||
| <Heading tag="h3" size="large" className="font-bold"> | ||
| Dodaj opinię o zadaniu | ||
| </Heading> | ||
| <form> | ||
| <div className="pt-3"> | ||
| <TextArea | ||
| {...register('opinion')} | ||
| isError={Boolean(errors.opinion)} | ||
| label="Twoja opinia" | ||
| errorMessage={errors.opinion?.message || ''} | ||
| /> | ||
| </div> | ||
| <div className="flex flex-col md:flex-row justify-between items-center pt-5"> | ||
| <div className="flex justify-center items-center"> | ||
| <svg | ||
| aria-hidden="true" | ||
| className={clsx('w-10 h-10 text-gray-300')} | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>First star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
|
ssynowiec marked this conversation as resolved.
Outdated
|
||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className={clsx('w-10 h-10 text-gray-300')} | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>First star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className={clsx('w-10 h-10 text-gray-300')} | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>First star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className={clsx('w-10 h-10 text-gray-300')} | ||
|
ssynowiec marked this conversation as resolved.
Outdated
|
||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>First star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| <svg | ||
| aria-hidden="true" | ||
| className={clsx('w-10 h-10 text-gray-300')} | ||
| fill="currentColor" | ||
| viewBox="0 0 20 20" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <title>First star</title> | ||
| <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path> | ||
| </svg> | ||
| </div> | ||
| <div className="md:w-1/2 flex items-center justify-center"> | ||
| <Button variant="primary" fullWidth={true} type="submit"> | ||
| Prześlij opinię | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </form> | ||
| </div> | ||
| </Card> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { useForm } from 'react-hook-form'; | ||
|
|
||
| type OpinionFormValues = { | ||
| rating: number; | ||
| opinion: string; | ||
| }; | ||
|
|
||
| export const useAddOpinionForm = () => { | ||
| const { | ||
| register, | ||
| formState: { errors }, | ||
| } = useForm<OpinionFormValues>({ | ||
| defaultValues: { | ||
| rating: 0, | ||
| opinion: '', | ||
| }, | ||
| }); | ||
|
|
||
| return { | ||
| register, | ||
| errors, | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import type { ReactNode } from 'react'; | ||
| import { useUser } from '@auth0/nextjs-auth0'; | ||
| import { Card, Heading, Link } from 'ui'; | ||
|
|
||
| type ProtectedComponentProps = { | ||
| info?: string; | ||
| children: ReactNode; | ||
| }; | ||
|
|
||
| export const ProtectedComponent = ({ | ||
| info = 'Ta opcja dostępna jest tylko dla zalogowanych użytkowników.', | ||
| children, | ||
| }: ProtectedComponentProps) => { | ||
| const { isLoading, user } = useUser(); | ||
|
|
||
| if (!isLoading && !user) { | ||
|
ssynowiec marked this conversation as resolved.
Outdated
|
||
| return ( | ||
| <Card tag="div"> | ||
| <div className="flex flex-col justify-center items-center h-full"> | ||
| <Heading tag="h3" size="large"> | ||
| {info} | ||
| </Heading> | ||
| <Link href="/" variant="primary"> | ||
| Zaloguj się | ||
| </Link> | ||
| </div> | ||
| </Card> | ||
| ); | ||
| } | ||
|
|
||
| if (isLoading || !user) return <div>Loading ...</div>; | ||
|
|
||
| return <>{children}</>; | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.