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