From 04423605dc121f8763fdc93876ae8c16845d70b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20St=C3=BCckler?= <moritz.stueckler@gmail.com> Date: Fri, 21 Jan 2022 14:26:57 +0100 Subject: [PATCH 1/2] feat: redesign vote show --- .../ChallengeCandidateController.php | 4 +- resources/js/Pages/Public/Voting/Show.tsx | 132 ++++++++---------- resources/js/shared/Admin/MenuItem.tsx | 8 +- resources/js/shared/Button.tsx | 3 + resources/js/shared/Public/VotingCard.tsx | 54 +++++++ routes/web.php | 2 +- 6 files changed, 122 insertions(+), 81 deletions(-) create mode 100644 resources/js/shared/Public/VotingCard.tsx diff --git a/app/Http/Controllers/ChallengeCandidateController.php b/app/Http/Controllers/ChallengeCandidateController.php index 361395c2..46719b92 100644 --- a/app/Http/Controllers/ChallengeCandidateController.php +++ b/app/Http/Controllers/ChallengeCandidateController.php @@ -66,9 +66,9 @@ class ChallengeCandidateController extends Controller } - public function update($challengeId, $challengeCandidateId, Request $request) + public function update($candidateId, Request $request) { - $candidate = ChallengeCandidate::findOrFail($challengeCandidateId); + $candidate = ChallengeCandidate::findOrFail($candidateId); if (!Auth::user()->is_admin && Auth::user()->id !== $candidate->created_by) { return response()->json(['error' => 'Unauthenticated.'], 401); diff --git a/resources/js/Pages/Public/Voting/Show.tsx b/resources/js/Pages/Public/Voting/Show.tsx index 4f184659..2319c127 100644 --- a/resources/js/Pages/Public/Voting/Show.tsx +++ b/resources/js/Pages/Public/Voting/Show.tsx @@ -1,98 +1,78 @@ import React from "react"; import Layout from "../../Layouts/Public"; -import {Candidate} from "../ChallengeCandidates/Show"; -import {Inertia} from "@inertiajs/inertia"; +import { Candidate } from "../ChallengeCandidates/Show"; +import { Inertia } from "@inertiajs/inertia"; import route from "ziggy"; +import VotingCard from "../../../shared/Public/VotingCard"; +import Button from "../../../shared/Button"; interface Match { - id: number, - candidate1: Candidate, - votingId1: number, - candidate2: Candidate, - votingId2: number, + id: number; + candidate1: Candidate; + votingId1: number; + candidate2: Candidate; + votingId2: number; } -const VotingMatch = ({match, challengeId}: { match: Match, challengeId: number }) => { - +const VotingMatch = ({ + match, + challengeId, +}: { + match: Match; + challengeId: number; +}) => { function handleSubmit(winnerId) { - Inertia.post(route("public.challenges.voting.store", challengeId as any), { - id: match.id, - result: winnerId - }); + Inertia.post( + route("public.challenges.voting.store", challengeId as any), + { + id: match.id, + result: winnerId, + } + ); } return ( - <> - <div className={'flex'}> - <div onClick={() => handleSubmit(match.votingId1)}> - <div className="w-full"> - {match.candidate1.images?.length > 0 && ( - <img - src={`/${match.candidate1.images[0]?.path}`} - className={"w-full object-cover h-96"} - /> - )} - </div> - <section className="w-10/12 mx-auto"> - <div className="text-center"> - <h1 className=" text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl mt-8 mb-2"> - {match.candidate1.name} - </h1> - <span className="text-sm"> - von {match.candidate1.created_by.name} - </span> - </div> - <p className="mt-4 max-w-3xl mx-auto text-center text-xl text-gray-500"> - {match.candidate1.teaser} - </p> - - <p className="whitespace-pre-line mt-6 prose prose-indigo prose-lg text-gray-500 mx-auto mb-16"> - {match.candidate1.description} - </p> - </section> - </div> + <div className={"flex flex-col pt-8"}> + <h1 className="text-4xl font-extrabold tracking-tight text-gray-900"> + Abstimmung + </h1> + <p className="mt-4 text-base text-gray-500"> + Bitte wähle deinen Favorit aus diesen zwei Kandidaten aus. + </p> + <div className="flex my-8"> + <VotingCard + id={match.votingId1} + image={`/${match.candidate1.images[0]?.path}`} + title={match.candidate1.name} + user={match.candidate1.created_by.name} + teaser={match.candidate1.teaser} + onClick={handleSubmit} + /> - <div className={'flex flex-col'}> - <div className={'h-full flex flex-col justify-center'}> - VS. - </div> - <button className={'flex-grow mb-5'} onClick={() => handleSubmit(-1)}>Unentschieden</button> + <div className={"flex flex-col text-center justify-center p-3"}> + vs. </div> - <div onClick={() => handleSubmit(match.votingId2)}> - <div className="w-full"> - {match.candidate2.images?.length > 0 && ( - <img - src={`/${match.candidate2.images[0]?.path}`} - className={"w-full object-cover h-96"} - /> - )} - </div> - <section className="w-10/12 mx-auto"> - <div className="text-center"> - <h1 className=" text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl mt-8 mb-2"> - {match.candidate2.name} - </h1> - <span className="text-sm"> - von {match.candidate2.created_by.name} - </span> - </div> - <p className="mt-4 max-w-3xl mx-auto text-center text-xl text-gray-500"> - {match.candidate2.teaser} - </p> - - <p className="whitespace-pre-line mt-6 prose prose-indigo prose-lg text-gray-500 mx-auto mb-16"> - {match.candidate2.description} - </p> - </section> - </div> + <VotingCard + id={match.votingId2} + image={`/${match.candidate2.images[0]?.path}`} + title={match.candidate2.name} + user={match.candidate2.created_by.name} + teaser={match.candidate2.teaser} + onClick={handleSubmit} + /> </div> - </> + <Button className="mx-auto" + onClick={() => handleSubmit(-1)} + > + Unentschieden + </Button> + </div> ); }; VotingMatch.layout = (page: JSX.Element) => ( - <Layout children={page} title="Idee ansehen"/> + <Layout children={page} title="Abstimmung" /> ); export default VotingMatch; diff --git a/resources/js/shared/Admin/MenuItem.tsx b/resources/js/shared/Admin/MenuItem.tsx index 477d4e76..0f19600b 100644 --- a/resources/js/shared/Admin/MenuItem.tsx +++ b/resources/js/shared/Admin/MenuItem.tsx @@ -12,10 +12,14 @@ export default ({ target: string; icon: string; }) => { - const isActive = route().current(target); + const isActive = route().current().startsWith(target); + return ( <div className="mb-4"> - <InertiaLink href={route(target)} className="flex items-center group py-3"> + <InertiaLink + href={route(target)} + className="flex items-center group py-3" + > <Icon name={icon} className={`w-4 h-4 mr-2 fill-current ${ diff --git a/resources/js/shared/Button.tsx b/resources/js/shared/Button.tsx index 87931d77..4e4051d8 100644 --- a/resources/js/shared/Button.tsx +++ b/resources/js/shared/Button.tsx @@ -5,6 +5,7 @@ interface ButtonProps { className?: string; processing?: boolean; children: React.ReactNode; + onClick?: (any) => void; } export default function Button({ @@ -12,6 +13,7 @@ export default function Button({ className = "", processing, children, + onClick }: ButtonProps) { return ( <button @@ -22,6 +24,7 @@ export default function Button({ } ` + className } disabled={processing} + {...(onClick ? { onClick } : {})} > {children} </button> diff --git a/resources/js/shared/Public/VotingCard.tsx b/resources/js/shared/Public/VotingCard.tsx new file mode 100644 index 00000000..e4a6302a --- /dev/null +++ b/resources/js/shared/Public/VotingCard.tsx @@ -0,0 +1,54 @@ +import React from "react"; + +const VotingCard = ({ + id, + onClick, + image, + title, + teaser, + user, +}: { + id: number; + onClick: (winnerId) => void; + image: string; + title: string; + teaser: string; + user: string; +}) => { + return ( + <div + className="flex-1 cursor-pointer group relative bg-white border border-gray-200 rounded-lg flex flex-col overflow-hidden" + onClick={() => { + onClick(id); + }} + > + <div className="w-full"> + {image && ( + <div className="aspect-[3/2] bg-gray-200 group-hover:opacity-75"> + <img + src={image} + className="w-full h-full object-center object-cover sm:w-full sm:h-full" + /> + </div> + )} + </div> + <section className="w-10/12 mx-auto pb-3"> + <div className="text-center"> + {title && ( + <h1 className=" text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl mt-8 mb-2"> + {title} + </h1> + )} + <span className="text-sm">von {user}</span> + </div> + {teaser && ( + <p className="mt-4 max-w-3xl mx-auto text-center text-xl text-gray-500"> + {teaser} + </p> + )} + </section> + </div> + ); +}; + +export default VotingCard; diff --git a/routes/web.php b/routes/web.php index ddd6e0bf..e1179514 100644 --- a/routes/web.php +++ b/routes/web.php @@ -53,7 +53,7 @@ Route::middleware(['verified', 'admin'])->group(function () { Route::get('/admin/candidates', [ChallengeCandidateController::class, 'adminIndex'])->name('admin.candidates'); Route::get('/admin/candidates/edit/{candidateId}', [ChallengeCandidateController::class, 'adminEdit'])->name('admin.candidates.edit'); - Route::post('/admin/candidates/update/{candidateId}', [ChallengeController::class, 'update'])->name('admin.candidates.update'); + Route::post('/admin/candidates/update/{candidateId}', [ChallengeCandidateController::class, 'update'])->name('admin.candidates.update'); }); -- GitLab From ef1cd1e738ae87e041d2147530e11833f5c08abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20St=C3=BCckler?= <moritz.stueckler@gmail.com> Date: Fri, 21 Jan 2022 14:48:39 +0100 Subject: [PATCH 2/2] feat: add terms checkbox --- .../Pages/Public/ChallengeCandidates/Edit.tsx | 7 ++++ resources/js/shared/Public/CheckboxInput.tsx | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 resources/js/shared/Public/CheckboxInput.tsx diff --git a/resources/js/Pages/Public/ChallengeCandidates/Edit.tsx b/resources/js/Pages/Public/ChallengeCandidates/Edit.tsx index bd54f961..bb9f748f 100644 --- a/resources/js/Pages/Public/ChallengeCandidates/Edit.tsx +++ b/resources/js/Pages/Public/ChallengeCandidates/Edit.tsx @@ -6,6 +6,7 @@ import TextInput from "../../../shared/Public/TextInput"; import RadioInput from "../../../shared/Public/RadioInput"; import Dropzone from "../../../shared/Dropzone"; import Button from "../../../shared/Button"; +import CheckboxInput from "../../../shared/Public/CheckboxInput"; import route from "ziggy"; const ChallengeCandidatesEdit = ({ challengeId }) => { @@ -92,6 +93,12 @@ const ChallengeCandidatesEdit = ({ challengeId }) => { title="Team-Einreichung" subtitle="Handelt es sich um eine Gruppe von Personen?" /> + <CheckboxInput + className="sm:col-span-6 mt-4" + name="terms" + title="AGB gelesen?" + subtitle="Ich habe die AGB gelesen und zur Kenntnis genommen." + /> </div> <div className="pt-5 flex justify-end"> diff --git a/resources/js/shared/Public/CheckboxInput.tsx b/resources/js/shared/Public/CheckboxInput.tsx new file mode 100644 index 00000000..3a8ab59e --- /dev/null +++ b/resources/js/shared/Public/CheckboxInput.tsx @@ -0,0 +1,33 @@ +import React from "react"; + +interface CheckboxInputProps { + name?: string; + className?: string; + title?: string; + subtitle?: string; +} + +export default (props: CheckboxInputProps) => { + const { name, title, subtitle, className } = props; + + return ( + <div + className={`relative flex items-start ${className ? className : ""}`} + > + <div className="flex items-center h-5"> + <input + id={name} + name={name} + type="checkbox" + className="focus:ring-flush-mahogany-500 h-4 w-4 text-flush-mahogany-600 border-gray-300 rounded" + /> + </div> + <div className="ml-3 text-sm"> + <label htmlFor="offers" className="font-medium text-gray-700"> + {title} + </label> + <p className="text-gray-500">{subtitle}</p> + </div> + </div> + ); +}; -- GitLab