diff --git a/package.json b/package.json index bd90641f1..2af44957b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@radix-ui/react-popover": "^1.0.3", "@sentry/nextjs": "^7.33.0", "@svgr/webpack": "^6.2.1", - "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.9", "@tanstack/react-query": "^4.16.1", "@trpc/client": "^10.0.0-rc.8", diff --git a/public/locales/ca/app.json b/public/locales/ca/app.json index f373fb74d..19031448d 100644 --- a/public/locales/ca/app.json +++ b/public/locales/ca/app.json @@ -97,7 +97,7 @@ "profileUser": "Perfil - {{username}}", "requiredNameError": "El nom és obligatori", "save": "Desa", - "saveInstruction": "Selecciona la teva disponibilitat i prem {{save}}", + "saveInstruction": "Selecciona la teva disponibilitat i prem {{action}}", "share": "Compartir", "shareDescription": "Envia aquest enllaç als participants perquè puguin votar a l'enquesta.", "shareLink": "Comparteix via enllaç", diff --git a/public/locales/cs/app.json b/public/locales/cs/app.json index c37dbdc9a..a7c800e8b 100644 --- a/public/locales/cs/app.json +++ b/public/locales/cs/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Jméno je vyžadováno", "resendVerificationCode": "Znovu odeslat ověřovací kód", "save": "Uložit", - "saveInstruction": "Vyberte svou dostupnost a klikněte na {{save}}", + "saveInstruction": "Vyberte svou dostupnost a klikněte na {{action}}", "share": "Sdílet", "shareDescription": "Tento odkaz zašlete vašim účastníkům, aby mohli v anketě hlasovat.", "shareLink": "Odkaz ke sdílení", diff --git a/public/locales/da/app.json b/public/locales/da/app.json index c7c869fc9..b1db95289 100644 --- a/public/locales/da/app.json +++ b/public/locales/da/app.json @@ -90,7 +90,7 @@ "profileUser": "Profil - {{username}}", "requiredNameError": "Navn er påkrævet", "save": "Gem", - "saveInstruction": "Vælg din tilgængelighed og klik {{save}}", + "saveInstruction": "Vælg din tilgængelighed og klik {{action}}", "share": "Del", "shareDescription": "Giv dette link til dine deltagere for at give dem mulighed for at stemme på din afstemning.", "shareLink": "Del via link", diff --git a/public/locales/de/app.json b/public/locales/de/app.json index 5bd1527d7..78e8b8427 100644 --- a/public/locales/de/app.json +++ b/public/locales/de/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Bitte gib einen Namen an", "resendVerificationCode": "Bestätigungscode erneut senden", "save": "Speichern", - "saveInstruction": "Wähle deine Verfügbarkeit und klicke auf {{save}}", + "saveInstruction": "Wähle deine Verfügbarkeit und klicke auf {{action}}", "share": "Teilen", "shareDescription": "Gib diesen Link deinen Teilnehmern damit sie an deiner Umfrage teilnehmen können.", "shareLink": "Über Link teilen", diff --git a/public/locales/en/app.json b/public/locales/en/app.json index d26fe0921..b59570b8c 100644 --- a/public/locales/en/app.json +++ b/public/locales/en/app.json @@ -51,9 +51,13 @@ "linkHasExpired": "Your link has expired or is no longer valid", "loading": "Loading…", "loadingParticipants": "Loading participants…", + "validEmail": "Please enter a valid email", + "newParticipantFormDescription": "Fill in the form below to submit your votes.", + "optional": "optional", "location": "Location", "locationPlaceholder": "Joe's Coffee Shop", "lockPoll": "Lock poll", + "response": "Response", "login": "Login", "logout": "Logout", "manage": "Manage", @@ -87,9 +91,17 @@ "participantCount_few": "{{count}} participants", "participantCount_many": "{{count}} participants", "participantCount_one": "{{count}} participant", + "requiredString": "“{{name}}” is required", "participantCount_other": "{{count}} participants", "participantCount_two": "{{count}} participants", "participantCount_zero": "{{count}} participants", + "optionCount_few": "{{count}} options", + "optionCount_many": "{{count}} options", + "optionCount_one": "{{count}} option", + "optionCount_other": "{{count}} options", + "optionCount_two": "{{count}} options", + "optionCount_zero": "{{count}} options", + "newParticipant": "New participant", "pollHasBeenLocked": "This poll has been locked", "pollHasBeenVerified": "Your poll has been verified", "pollOwnerNotice": "Hey {{name}}, looks like you are the owner of this poll.", @@ -102,7 +114,7 @@ "requiredNameError": "Name is required", "resendVerificationCode": "Resend verification code", "save": "Save", - "saveInstruction": "Select your availability and click {{save}}", + "saveInstruction": "Select your availability and click {{action}}", "share": "Share", "shareDescription": "Give this link to your participants to allow them to vote on your poll.", "shareLink": "Share via link", diff --git a/public/locales/es/app.json b/public/locales/es/app.json index 57c0dce80..7e47a3671 100644 --- a/public/locales/es/app.json +++ b/public/locales/es/app.json @@ -102,7 +102,7 @@ "requiredNameError": "El nombre es obligatorio", "resendVerificationCode": "Reenviar el código de verificación", "save": "Guardar", - "saveInstruction": "Selecciona tu disponibilidad y haz clic en {{save}}", + "saveInstruction": "Selecciona tu disponibilidad y haz clic en {{action}}", "share": "Compartir", "shareDescription": "Da este enlace a tus participantes para permitirles votar en tu encuesta.", "shareLink": "Compartir con un enlace", diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index d1aa9d6a0..c5a455374 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -90,7 +90,7 @@ "profileUser": "نمایه - {{username}}", "requiredNameError": "نام الزامی است", "save": "ذخیره", - "saveInstruction": "مشخص کنید چه زمان‌هایی برایتان مقدور است و روی {{save}} کلیک کنید", + "saveInstruction": "مشخص کنید چه زمان‌هایی برایتان مقدور است و روی {{action}} کلیک کنید", "share": "هم‌رسانی", "shareDescription": "این لینک را به شرکت‌کنندگان بدهید تا بتوانند در نظرسنجی شما شرکت کنند.", "shareLink": "هم‌رسانی با پیوند", diff --git a/public/locales/fi/app.json b/public/locales/fi/app.json index 88e40bf76..b39eac799 100644 --- a/public/locales/fi/app.json +++ b/public/locales/fi/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Nimi vaaditaan", "resendVerificationCode": "Lähetä vahvistuskoodi uudelleen", "save": "Tallenna", - "saveInstruction": "Valitse sinulle sopivat vaihtoehdot ja napsauta {{save}}", + "saveInstruction": "Valitse sinulle sopivat vaihtoehdot ja napsauta {{action}}", "share": "Jaa", "shareDescription": "Anna tämä linkki osallistujille, jotta he voivat äänestää kyselyssäsi.", "shareLink": "Jaa linkin välityksellä", diff --git a/public/locales/fr/app.json b/public/locales/fr/app.json index 090eafc5c..e10620115 100644 --- a/public/locales/fr/app.json +++ b/public/locales/fr/app.json @@ -99,7 +99,7 @@ "requiredNameError": "Le nom est obligatoire", "resendVerificationCode": "Renvoyer le code de vérification", "save": "Sauvegarder", - "saveInstruction": "Sélectionnez votre disponibilité et cliquez sur {{save}}", + "saveInstruction": "Sélectionnez votre disponibilité et cliquez sur {{action}}", "share": "Partager", "shareDescription": "Donnez ce lien à vos participants pour leur permettre de voter sur votre sondage.", "shareLink": "Partager via un lien", diff --git a/public/locales/hr/app.json b/public/locales/hr/app.json index 9fc5af85c..d3a0814e9 100644 --- a/public/locales/hr/app.json +++ b/public/locales/hr/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Ime je obavezno", "resendVerificationCode": "Ponovno pošalji poruku za potvrdu", "save": "Pohrani", - "saveInstruction": "Odaberite termine koji vam odgovaraju i kliknite na {{save}}", + "saveInstruction": "Odaberite termine koji vam odgovaraju i kliknite na {{action}}", "share": "Podijeli", "shareDescription": "Pošaljite ovu poveznicu sudionicima kako biste im omogućili glasanje u vašoj anketi.", "shareLink": "Podijeli putem poveznice", diff --git a/public/locales/hu/app.json b/public/locales/hu/app.json index 6d78fc670..6a676aaf8 100644 --- a/public/locales/hu/app.json +++ b/public/locales/hu/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Név megadása kötelező", "resendVerificationCode": "Hitelesítő kód újraküldése", "save": "Mentés", - "saveInstruction": "Válaszd ki mikor érsz rá és kattints a {{save}} gombra", + "saveInstruction": "Válaszd ki mikor érsz rá és kattints a {{action}} gombra", "share": "Megosztás", "shareDescription": "Küldd el ezt a linket a résztvevőidnek, hogy tudjanak szavazatokat leadni.", "shareLink": "Megosztás linkkel", diff --git a/public/locales/it/app.json b/public/locales/it/app.json index a371c6c38..d8ffb6441 100644 --- a/public/locales/it/app.json +++ b/public/locales/it/app.json @@ -90,7 +90,7 @@ "profileUser": "Profilo - {{username}}", "requiredNameError": "Il nome è obbligatorio", "save": "Salva", - "saveInstruction": "Seleziona la tua disponibilità e clicca su {{save}}", + "saveInstruction": "Seleziona la tua disponibilità e clicca su {{action}}", "share": "Condividi", "shareDescription": "Dai questo link ai partecipanti per permette loro di votare al tuo sondaggio.", "shareLink": "Condividi via link", diff --git a/public/locales/ko/app.json b/public/locales/ko/app.json index 0589a569f..a07d71ee8 100644 --- a/public/locales/ko/app.json +++ b/public/locales/ko/app.json @@ -90,7 +90,7 @@ "profileUser": "프로필 - {{username}}", "requiredNameError": "이름을 입력해주세요.", "save": "저장하기", - "saveInstruction": "가능여부를 선택한 후 {{save}} 를 클릭하세요", + "saveInstruction": "가능여부를 선택한 후 {{action}} 를 클릭하세요", "share": "공유하기", "shareDescription": "이 링크를 참여자들에게 전달하여 투표하도록 하세요", "shareLink": "링크 공유하기", diff --git a/public/locales/nl/app.json b/public/locales/nl/app.json index e2805a06a..fb2de4a01 100644 --- a/public/locales/nl/app.json +++ b/public/locales/nl/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Naam is verplicht", "resendVerificationCode": "Verificatiecode opnieuw versturen", "save": "Opslaan", - "saveInstruction": "Selecteer je beschikbaarheid en klik op {{save}}", + "saveInstruction": "Selecteer je beschikbaarheid en klik op {{action}}", "share": "Delen", "shareDescription": "Geef deze link aan je deelnemers zodat ze op je poll kunnen stemmen.", "shareLink": "Deel via link", diff --git a/public/locales/pl/app.json b/public/locales/pl/app.json index e36eea324..c0e26db25 100644 --- a/public/locales/pl/app.json +++ b/public/locales/pl/app.json @@ -94,7 +94,7 @@ "profileUser": "Profil - {{username}}", "requiredNameError": "Imię jest wymagane", "save": "Zapisz", - "saveInstruction": "Wybierz swoją dostępność i kliknij {{save}}", + "saveInstruction": "Wybierz swoją dostępność i kliknij {{action}}", "share": "Udostępnij", "shareDescription": "Przekaż ten link uczestnikom, aby mogli zagłosować na ankietę.", "shareLink": "Udostępnij link", diff --git a/public/locales/pt-BR/app.json b/public/locales/pt-BR/app.json index 029bc4e53..46ef2abcc 100644 --- a/public/locales/pt-BR/app.json +++ b/public/locales/pt-BR/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Nome é obrigatório", "resendVerificationCode": "Reenviar código de verificação", "save": "Salvar", - "saveInstruction": "Selecione sua disponibilidade e clique {{save}}", + "saveInstruction": "Selecione sua disponibilidade e clique {{action}}", "share": "Compartilhar", "shareDescription": "Dê este link para os seus participantes para permitir que eles votem na sua enquete.", "shareLink": "Compartilhar via link", diff --git a/public/locales/pt/app.json b/public/locales/pt/app.json index 658632c2c..d592ed99e 100644 --- a/public/locales/pt/app.json +++ b/public/locales/pt/app.json @@ -90,7 +90,7 @@ "profileUser": "Perfil - {{username}}", "requiredNameError": "O nome é obrigatório", "save": "Guardar", - "saveInstruction": "Selecione a sua disponibilidade e clique em {{save}}", + "saveInstruction": "Selecione a sua disponibilidade e clique em {{action}}", "share": "Partilhar", "shareDescription": "Dê este link aos seus participantes para permitir que eles votem na sua sondagem.", "shareLink": "Partilhar via link", diff --git a/public/locales/ru/app.json b/public/locales/ru/app.json index 412f2354a..f48f5bc3c 100644 --- a/public/locales/ru/app.json +++ b/public/locales/ru/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Необходимо указать имя", "resendVerificationCode": "Отправить код ещё раз", "save": "Сохранить", - "saveInstruction": "Укажите когда вы доступны и нажмите {{save}}", + "saveInstruction": "Укажите когда вы доступны и нажмите {{action}}", "share": "Поделиться", "shareDescription": "Поделитесь этой ссылкой с вашими участниками, чтобы они смогли ответить на ваш опрос.", "shareLink": "Поделиться с помощью ссылки", diff --git a/public/locales/sk/app.json b/public/locales/sk/app.json index 93a9eed95..3f73dd0e2 100644 --- a/public/locales/sk/app.json +++ b/public/locales/sk/app.json @@ -99,7 +99,7 @@ "requiredNameError": "Požadované je meno", "resendVerificationCode": "Znovu odoslať verifikačný kód", "save": "Uložiť", - "saveInstruction": "Vyberte svoju dostupnosť a kliknite na {{save}}", + "saveInstruction": "Vyberte svoju dostupnosť a kliknite na {{action}}", "share": "Zdielať", "shareDescription": "Tento odkaz zašlite vašim účastníkom, aby mohli v ankete hlasovať.", "shareLink": "Zdieľať cez odkaz", diff --git a/public/locales/sv/app.json b/public/locales/sv/app.json index 8df4b55ce..b66c3615b 100644 --- a/public/locales/sv/app.json +++ b/public/locales/sv/app.json @@ -102,7 +102,7 @@ "requiredNameError": "Namn är obligatoriskt", "resendVerificationCode": "Skicka verifieringskoden igen", "save": "Spara", - "saveInstruction": "Välj din tillgänglighet och klicka på {{save}}", + "saveInstruction": "Välj din tillgänglighet och klicka på {{action}}", "share": "Dela", "shareDescription": "Ge den här länken till dina deltagare så att de kan delta i din förfrågan.", "shareLink": "Dela via länk", diff --git a/public/locales/tr/app.json b/public/locales/tr/app.json index e918a761f..75e108088 100644 --- a/public/locales/tr/app.json +++ b/public/locales/tr/app.json @@ -69,7 +69,7 @@ "participantCount_other": "{{count}} katılımcı", "pollOwnerNotice": "Selam {{name}}, görünüşe göre bu anketin sahibi sensin.", "profileUser": "Profil - {{username}}", - "saveInstruction": "Uygunluk durumunuzu seçin ve {{save}} düğmesine tıklayın", + "saveInstruction": "Uygunluk durumunuzu seçin ve {{action}} düğmesine tıklayın", "shareDescription": "Anketinizde oy kullanmalarına izin vermek için bu bağlantıyı katılımcılarınıza verin.", "stepSummary": "Aşama: {{current}} / {{total}}", "sunday": "Pazar", diff --git a/public/locales/zh/app.json b/public/locales/zh/app.json index c086e2ee2..9d6d76e7a 100644 --- a/public/locales/zh/app.json +++ b/public/locales/zh/app.json @@ -101,7 +101,7 @@ "requiredNameError": "姓名为必填项", "resendVerificationCode": "重新发送验证码", "save": "保存", - "saveInstruction": "选择你有空的时间并点击 {{save}}", + "saveInstruction": "选择你有空的时间并点击 {{action}}", "share": "分享", "shareDescription": "其他人可以通过此链接成为参与者進行投票", "shareLink": "分享链接", diff --git a/src/components/auth/login-modal.tsx b/src/components/auth/login-modal.tsx index 7f2994092..f20ff076a 100644 --- a/src/components/auth/login-modal.tsx +++ b/src/components/auth/login-modal.tsx @@ -13,7 +13,7 @@ export const LoginModal: React.VoidFunctionComponent<{ const [defaultEmail, setDefaultEmail] = React.useState(""); return ( -
+
diff --git a/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx b/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx index e4e82d614..5a1f75985 100644 --- a/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx +++ b/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx @@ -111,64 +111,69 @@ const MonthCalendar: React.VoidFunctionComponent = ({ ); })}
-
+
{datepicker.days.map((day, i) => { return ( - + +
); })}
diff --git a/src/components/home/hero.tsx b/src/components/home/hero.tsx index bd3e81246..283797662 100644 --- a/src/components/home/hero.tsx +++ b/src/components/home/hero.tsx @@ -29,13 +29,13 @@ const Hero: React.VoidFunctionComponent = () => {
{t("getStarted")} {t("liveDemo")} diff --git a/src/components/home/poll-demo.tsx b/src/components/home/poll-demo.tsx index 29837227e..ae1006509 100644 --- a/src/components/home/poll-demo.tsx +++ b/src/components/home/poll-demo.tsx @@ -39,7 +39,7 @@ const PollDemo: React.VoidFunctionComponent = () => { const { dayjs } = useDayjs(); return (
diff --git a/src/components/layouts/page-layout.tsx b/src/components/layouts/page-layout.tsx index d69d8a153..20e73d2a9 100644 --- a/src/components/layouts/page-layout.tsx +++ b/src/components/layouts/page-layout.tsx @@ -25,7 +25,7 @@ const Menu: React.VoidFunctionComponent<{ className: string }> = ({ = ({ {t("blog")} {t("support")} @@ -66,7 +66,7 @@ const PageLayout: React.VoidFunctionComponent = ({
- + @@ -74,15 +74,15 @@ const PageLayout: React.VoidFunctionComponent = ({
- + - - + +
diff --git a/src/components/layouts/standard-layout/mobile-navigation.tsx b/src/components/layouts/standard-layout/mobile-navigation.tsx index b44658f37..c947220b4 100644 --- a/src/components/layouts/standard-layout/mobile-navigation.tsx +++ b/src/components/layouts/standard-layout/mobile-navigation.tsx @@ -42,7 +42,7 @@ export const MobileNavigation = (props: { className?: string }) => { return (
{
{user ? null : ( - + {t("app:login")} @@ -83,7 +83,7 @@ export const MobileNavigation = (props: { className?: string }) => { role="button" data-testid="user" className={clsx( - "group inline-flex w-full items-center space-x-2 rounded-lg px-2 py-1 text-left transition-colors hover:bg-slate-500/10 active:bg-slate-500/20", + "group inline-flex w-full items-center space-x-2 rounded px-2 py-1 text-left transition-colors hover:bg-slate-500/10 active:bg-slate-500/20", { "opacity-50": isUpdating, }, @@ -105,7 +105,7 @@ export const MobileNavigation = (props: { className?: string }) => {
)} {footer === undefined ? ( -
+
{cancelText ? ( + +
+ +
+ ); +}; + +export const useNewParticipantModal = () => { + const modalContext = useModalContext(); + + const showNewParticipantModal = (props: NewParticipantModalProps) => { + return modalContext.render({ + content: function Content({ close }) { + return ( + { + props.onSubmit?.(data); + close(); + }} + onCancel={close} + /> + ); + }, + footer: null, + }); + }; + + return showNewParticipantModal; +}; diff --git a/src/components/poll.tsx b/src/components/poll.tsx index 22ad4f2b2..c1c1f8485 100644 --- a/src/components/poll.tsx +++ b/src/components/poll.tsx @@ -49,27 +49,23 @@ export const Poll = (props: { children?: React.ReactNode }) => {
{props.children} {poll.demo ? ( -
-
- -
+
+
{t("demoPollNotice")}
) : null} {poll.closed ? (
-
- -
+
{t("pollHasBeenLocked")}
) : null}
-
+
{preventWidows(poll.title)} @@ -77,14 +73,14 @@ export const Poll = (props: { children?: React.ReactNode }) => {
{poll.description ? ( -
+
{preventWidows(poll.description)}
) : null} {poll.location ? ( -
+
{t("location")}
@@ -98,17 +94,15 @@ export const Poll = (props: { children?: React.ReactNode }) => {
- {t("yes")} + {t("yes")} - - {t("ifNeedBe")} - + {t("ifNeedBe")} - {t("no")} + {t("no")}
diff --git a/src/components/poll/desktop-poll.tsx b/src/components/poll/desktop-poll.tsx index c9ed78799..2b2c29c83 100644 --- a/src/components/poll/desktop-poll.tsx +++ b/src/components/poll/desktop-poll.tsx @@ -5,10 +5,9 @@ import { useMeasure } from "react-use"; import ArrowLeft from "@/components/icons/arrow-left.svg"; import ArrowRight from "@/components/icons/arrow-right.svg"; -import Check from "@/components/icons/check.svg"; -import Plus from "@/components/icons/plus-sm.svg"; import { Button } from "../button"; +import { useNewParticipantModal } from "../new-participant-modal"; import { useParticipants } from "../participants-provider"; import { usePoll } from "../poll-context"; import TimeZonePicker from "../time-zone-picker"; @@ -21,8 +20,6 @@ import { useUpdateParticipantMutation, } from "./mutations"; -const MotionButton = motion(Button); - const minSidebarWidth = 200; const Poll: React.VoidFunctionComponent = () => { @@ -37,17 +34,16 @@ const Poll: React.VoidFunctionComponent = () => { const [editingParticipantId, setEditingParticipantId] = React.useState(null); - const actionColumnWidth = 100; - const columnWidth = 90; + const columnWidth = 80; const numberOfVisibleColumns = Math.min( options.length, - Math.floor((width - (minSidebarWidth + actionColumnWidth)) / columnWidth), + Math.floor((width - minSidebarWidth) / columnWidth), ); const sidebarWidth = Math.min( - width - (numberOfVisibleColumns * columnWidth + actionColumnWidth), - 300, + width - numberOfVisibleColumns * columnWidth, + 275, ); const availableSpace = Math.min( @@ -66,8 +62,7 @@ const Poll: React.VoidFunctionComponent = () => { const [shouldShowNewParticipantForm, setShouldShowNewParticipantForm] = React.useState(!poll.closed && !userAlreadyVoted); - const pollWidth = - sidebarWidth + options.length * columnWidth + actionColumnWidth; + const pollWidth = sidebarWidth + options.length * columnWidth; const addParticipant = useAddParticipantMutation(); @@ -88,7 +83,7 @@ const Poll: React.VoidFunctionComponent = () => { const updateParticipant = useUpdateParticipantMutation(); - const participantListContainerRef = React.useRef(null); + const showNewParticipantModal = useNewParticipantModal(); return ( { goToPreviousPage, numberOfColumns: numberOfVisibleColumns, availableSpace, - actionColumnWidth, maxScrollPosition, }} > @@ -112,8 +106,65 @@ const Poll: React.VoidFunctionComponent = () => { ref={ref} >
+
+
+ {shouldShowNewParticipantForm || editingParticipantId ? ( + }} + /> + ) : ( +
+
+ {t("participantCount", { count: participants.length })} +
+ {poll.closed ? null : ( + + )} +
+ )} +
+
+
+ {t("optionCount", { count: options.length })} +
+ {maxScrollPosition > 0 ? ( +
+ + +
+ ) : null} +
+
{poll.timeZone ? ( -
+
{t("timeZone")} @@ -131,112 +182,82 @@ const Poll: React.VoidFunctionComponent = () => {
-
- {t("participantCount", { count: participants.length })} -
- - {scrollPosition > 0 ? ( - - - - ) : null} - -
+ >
-
- {maxScrollPosition > 0 ? ( - - {scrollPosition < maxScrollPosition ? ( - { - goToNextPage(); - }} - > - - - ) : null} - - ) : null} -
- {participants.length > 0 ? ( -
- {participants.map((participant, i) => { - return ( - { - setEditingParticipantId( - isEditing ? participant.id : null, - ); - }} - onSubmit={async ({ name, votes }) => { - await updateParticipant.mutateAsync({ - participantId: participant.id, - pollId: poll.id, +
+ + {shouldShowNewParticipantForm && + !poll.closed && + !editingParticipantId ? ( + + { + showNewParticipantModal({ votes, - name, + onSubmit: () => { + setShouldShowNewParticipantForm(false); + }, }); }} /> - ); - })} -
- ) : null} - {shouldShowNewParticipantForm && - !poll.closed && - !editingParticipantId ? ( - { - await addParticipant.mutateAsync({ - name, - votes, - pollId: poll.id, - }); - setShouldShowNewParticipantForm(false); - }} - /> - ) : null} - {!poll.closed ? ( -
- {shouldShowNewParticipantForm || editingParticipantId ? ( -
- + }} + onSubmit={async ({ votes }) => { + await updateParticipant.mutateAsync({ + participantId: participant.id, + pollId: poll.id, + votes, + }); + setEditingParticipantId(null); + }} + /> + ); + })} +
+ + {shouldShowNewParticipantForm || editingParticipantId ? ( + +
-
- }} - /> -
-
- ) : ( -
- {userAlreadyVoted ? ( -
- -
{t("alreadyVoted")}
-
- ) : null}
- )} -
- ) : null} + + ) : null} +
diff --git a/src/components/poll/desktop-poll/participant-row-form.tsx b/src/components/poll/desktop-poll/participant-row-form.tsx index 115a31972..0a06677a0 100644 --- a/src/components/poll/desktop-poll/participant-row-form.tsx +++ b/src/components/poll/desktop-poll/participant-row-form.tsx @@ -3,30 +3,27 @@ import { useTranslation } from "next-i18next"; import * as React from "react"; import { Controller, useForm } from "react-hook-form"; -import ArrowLeft from "@/components/icons/arrow-left.svg"; -import ArrowRight from "@/components/icons/arrow-right.svg"; - -import { requiredString } from "../../../utils/form-validation"; -import { Button } from "../../button"; -import NameInput from "../../name-input"; import { usePoll } from "../../poll-context"; import { normalizeVotes } from "../mutations"; import { ParticipantForm, ParticipantFormSubmitted } from "../types"; +import UserAvatar from "../user-avatar"; import { VoteSelector } from "../vote-selector"; import ControlledScrollArea from "./controlled-scroll-area"; import { usePollContext } from "./poll-context"; export interface ParticipantRowFormProps { + name?: string; defaultValues?: Partial; onSubmit: (data: ParticipantFormSubmitted) => Promise; className?: string; + isYou?: boolean; onCancel?: () => void; } const ParticipantRowForm: React.ForwardRefRenderFunction< HTMLFormElement, ParticipantRowFormProps -> = ({ defaultValues, onSubmit, className, onCancel }, ref) => { +> = ({ defaultValues, onSubmit, name, isYou, className, onCancel }, ref) => { const { t } = useTranslation("app"); const { columnWidth, @@ -34,20 +31,11 @@ const ParticipantRowForm: React.ForwardRefRenderFunction< sidebarWidth, numberOfColumns, goToNextPage, - goToPreviousPage, - maxScrollPosition, - setScrollPosition, } = usePollContext(); const { options, optionIds } = usePoll(); - const { - handleSubmit, - control, - formState: { errors, submitCount }, - reset, - } = useForm({ + const { handleSubmit, control } = useForm({ defaultValues: { - name: "", votes: [], ...defaultValues, }, @@ -71,49 +59,15 @@ const ParticipantRowForm: React.ForwardRefRenderFunction<
{ + onSubmit={handleSubmit(async ({ votes }) => { await onSubmit({ - name, votes: normalizeVotes(optionIds, votes), }); - reset(); })} - className={clsx("flex h-14 shrink-0", className)} + className={clsx("flex h-12 shrink-0", className)} > -
- ( -
- 0, - })} - placeholder={t("yourName")} - {...field} - onKeyDown={(e) => { - if (e.code === "Tab" && scrollPosition > 0) { - e.preventDefault(); - setScrollPosition(0); - setTimeout(() => { - checkboxRefs.current[0]?.focus(); - }, 100); - } - }} - onKeyPress={(e) => { - if (e.code === "Enter") { - e.preventDefault(); - checkboxRefs.current[0]?.focus(); - } - }} - /> -
- )} - control={control} - /> +
+
{ if ( @@ -161,30 +116,6 @@ const ParticipantRowForm: React.ForwardRefRenderFunction< ); }} /> - {maxScrollPosition > 0 ? ( -
- - -
- ) : null} ); }; diff --git a/src/components/poll/desktop-poll/participant-row.tsx b/src/components/poll/desktop-poll/participant-row.tsx index 43dffd1e3..0db5bcfd4 100644 --- a/src/components/poll/desktop-poll/participant-row.tsx +++ b/src/components/poll/desktop-poll/participant-row.tsx @@ -1,13 +1,15 @@ import { Participant, Vote, VoteType } from "@prisma/client"; import clsx from "clsx"; +import { useTranslation } from "next-i18next"; import * as React from "react"; -import CompactButton from "@/components/compact-button"; +import DotsVertical from "@/components/icons/dots-vertical.svg"; import Pencil from "@/components/icons/pencil-alt.svg"; import Trash from "@/components/icons/trash.svg"; import { usePoll } from "@/components/poll-context"; import { useUser } from "@/components/user-provider"; +import Dropdown, { DropdownItem } from "../../dropdown"; import { ParticipantFormSubmitted } from "../types"; import { useDeleteParticipantModal } from "../use-delete-participant-modal"; import UserAvatar from "../user-avatar"; @@ -18,7 +20,9 @@ import { usePollContext } from "./poll-context"; export interface ParticipantRowProps { participant: Participant & { votes: Vote[] }; + className?: string; editMode?: boolean; + disableEditing?: boolean; onChangeEditMode?: (editMode: boolean) => void; onSubmit?: (data: ParticipantFormSubmitted) => Promise; } @@ -31,6 +35,7 @@ export const ParticipantRowView: React.VoidFunctionComponent<{ onEdit?: () => void; onDelete?: () => void; columnWidth: number; + className?: string; sidebarWidth: number; isYou?: boolean; participantId: string; @@ -39,6 +44,7 @@ export const ParticipantRowView: React.VoidFunctionComponent<{ editable, votes, onEdit, + className, onDelete, sidebarWidth, columnWidth, @@ -46,46 +52,49 @@ export const ParticipantRowView: React.VoidFunctionComponent<{ color, participantId, }) => { + const { t } = useTranslation("app"); return (
- + {editable ? ( -
- - +
+ + + + } + > + + +
) : null}
- + {votes.map((vote, i) => { return (
@@ -102,6 +111,8 @@ const ParticipantRow: React.VoidFunctionComponent = ({ participant, editMode, onSubmit, + className, + disableEditing, onChangeEditMode, }) => { const { columnWidth, sidebarWidth } = usePollContext(); @@ -115,20 +126,22 @@ const ParticipantRow: React.VoidFunctionComponent = ({ const isUnclaimed = !participant.userId; - const canEdit = !poll.closed && (admin || isYou || isUnclaimed); + const canEdit = + !disableEditing && !poll.closed && (admin || isYou || isUnclaimed); if (editMode) { return ( { const type = getVote(participant.id, optionId); return type ? { optionId, type } : undefined; }), }} - onSubmit={async ({ name, votes }) => { - await onSubmit?.({ name, votes }); + isYou={isYou} + onSubmit={async ({ votes }) => { + await onSubmit?.({ votes }); onChangeEditMode?.(false); }} onCancel={() => onChangeEditMode?.(false)} @@ -140,6 +153,7 @@ const ParticipantRow: React.VoidFunctionComponent = ({ { return getVote(participant.id, optionId); diff --git a/src/components/poll/desktop-poll/poll-context.ts b/src/components/poll/desktop-poll/poll-context.ts index ce8bddde8..4e4851bfe 100644 --- a/src/components/poll/desktop-poll/poll-context.ts +++ b/src/components/poll/desktop-poll/poll-context.ts @@ -11,7 +11,6 @@ export const PollContext = React.createContext<{ sidebarWidth: number; numberOfColumns: number; availableSpace: number | string; - actionColumnWidth: number; goToNextPage: () => void; goToPreviousPage: () => void; }>({ @@ -26,7 +25,6 @@ export const PollContext = React.createContext<{ availableSpace: "auto", goToNextPage: noop, goToPreviousPage: noop, - actionColumnWidth: 0, }); export const usePollContext = () => React.useContext(PollContext); diff --git a/src/components/poll/mobile-poll.tsx b/src/components/poll/mobile-poll.tsx index 17f574f77..ae0113cc8 100644 --- a/src/components/poll/mobile-poll.tsx +++ b/src/components/poll/mobile-poll.tsx @@ -1,31 +1,25 @@ import { Listbox } from "@headlessui/react"; -import clsx from "clsx"; import { AnimatePresence, motion } from "framer-motion"; import { useTranslation } from "next-i18next"; import * as React from "react"; -import { Controller, FormProvider, useForm } from "react-hook-form"; +import { FormProvider, useForm } from "react-hook-form"; import smoothscroll from "smoothscroll-polyfill"; -import Check from "@/components/icons/check.svg"; import ChevronDown from "@/components/icons/chevron-down.svg"; import Pencil from "@/components/icons/pencil-alt.svg"; import PlusCircle from "@/components/icons/plus-circle.svg"; import Trash from "@/components/icons/trash.svg"; import { usePoll } from "@/components/poll-context"; +import { You } from "@/components/you"; -import { requiredString } from "../../utils/form-validation"; import { Button } from "../button"; import { styleMenuItem } from "../menu-styles"; -import NameInput from "../name-input"; +import { useNewParticipantModal } from "../new-participant-modal"; import { useParticipants } from "../participants-provider"; import TimeZonePicker from "../time-zone-picker"; import { isUnclaimed, useUser } from "../user-provider"; import GroupedOptions from "./mobile-poll/grouped-options"; -import { - normalizeVotes, - useAddParticipantMutation, - useUpdateParticipantMutation, -} from "./mutations"; +import { normalizeVotes, useUpdateParticipantMutation } from "./mutations"; import { ParticipantForm } from "./types"; import { useDeleteParticipantModal } from "./use-delete-participant-modal"; import UserAvatar from "./user-avatar"; @@ -55,12 +49,11 @@ const MobilePoll: React.VoidFunctionComponent = () => { const form = useForm({ defaultValues: { - name: "", votes: [], }, }); - const { reset, handleSubmit, control, formState } = form; + const { reset, handleSubmit, formState } = form; const [selectedParticipantId, setSelectedParticipantId] = React.useState(); @@ -78,36 +71,36 @@ const MobilePoll: React.VoidFunctionComponent = () => { const updateParticipant = useUpdateParticipantMutation(); - const addParticipant = useAddParticipantMutation(); const confirmDeleteParticipant = useDeleteParticipantModal(); + const showNewParticipantModal = useNewParticipantModal(); + return (
{ + onSubmit={handleSubmit(async ({ votes }) => { if (selectedParticipant) { await updateParticipant.mutateAsync({ pollId: poll.id, participantId: selectedParticipant.id, - name, votes: normalizeVotes(optionIds, votes), }); setIsEditing(false); } else { - const newParticipant = await addParticipant.mutateAsync({ - pollId: poll.id, - name, + showNewParticipantModal({ votes: normalizeVotes(optionIds, votes), + onSubmit: async ({ id }) => { + setSelectedParticipantId(id); + setIsEditing(false); + }, }); - setSelectedParticipantId(newParticipant.id); - setIsEditing(false); } })} >
- {!isEditing ? ( + {selectedParticipantId || !isEditing ? ( { @@ -168,22 +161,8 @@ const MobilePoll: React.VoidFunctionComponent = () => {
) : ( -
- ( - - )} - /> +
+
)} {isEditing ? ( @@ -212,7 +191,6 @@ const MobilePoll: React.VoidFunctionComponent = () => { onClick={() => { setIsEditing(true); reset({ - name: selectedParticipant.name, votes: optionIds.map((optionId) => ({ optionId, type: getVote(selectedParticipant.id, optionId), @@ -250,7 +228,6 @@ const MobilePoll: React.VoidFunctionComponent = () => { disabled={poll.closed} onClick={() => { reset({ - name: "", votes: [], }); setIsEditing(true); @@ -296,13 +273,12 @@ const MobilePoll: React.VoidFunctionComponent = () => { >
diff --git a/src/components/poll/mobile-poll/poll-option.tsx b/src/components/poll/mobile-poll/poll-option.tsx index 3a475ca76..59d9a74ff 100644 --- a/src/components/poll/mobile-poll/poll-option.tsx +++ b/src/components/poll/mobile-poll/poll-option.tsx @@ -102,7 +102,7 @@ const PollOptionVoteSummary: React.VoidFunctionComponent<{ optionId: string }> =
{participantsWhoVotedYes.map(({ name }, i) => (
-
+
=
{participantsWhoVotedIfNeedBe.map(({ name }, i) => (
-
+
= ))} {participantsWhoVotedNo.map(({ name }, i) => (
-
+
= ({ exit={{ opacity: 0, x: -10 }} type="button" onTouchStart={(e) => e.stopPropagation()} - className="flex min-w-0 justify-end gap-1 overflow-hidden rounded-lg p-2 active:bg-slate-500/10" + className="flex min-w-0 justify-end gap-1 overflow-hidden p-1 active:bg-slate-500/10" onClick={(e) => { e.stopPropagation(); setExpanded((value) => !value); @@ -262,10 +262,10 @@ const PollOption: React.VoidFunctionComponent = ({ {editable ? (
) : ( diff --git a/src/components/poll/mutations.ts b/src/components/poll/mutations.ts index 009099579..771d4e7b4 100644 --- a/src/components/poll/mutations.ts +++ b/src/components/poll/mutations.ts @@ -24,13 +24,9 @@ export const useAddParticipantMutation = () => { queryClient.setQueryData( ["polls.participants.list", { pollId: participant.pollId }], (existingParticipants = []) => { - return [...existingParticipants, participant]; + return [participant, ...existingParticipants]; }, ); - queryClient.invalidateQueries([ - "polls.participants.list", - { pollId: participant.pollId }, - ]); }, }); }; diff --git a/src/components/poll/poll-subheader.tsx b/src/components/poll/poll-subheader.tsx index 22de7d68c..5fbf1c485 100644 --- a/src/components/poll/poll-subheader.tsx +++ b/src/components/poll/poll-subheader.tsx @@ -11,7 +11,7 @@ const PollSubheader: React.VoidFunctionComponent = () => { const { t } = useTranslation("app"); const { dayjs } = useDayjs(); return ( -
+
= data-testid="popularity-score" className="flex items-center gap-1 text-sm font-bold tabular-nums" > - + ; } diff --git a/src/components/poll/unverified-poll-notice.tsx b/src/components/poll/unverified-poll-notice.tsx index e370daa11..fff62d30d 100644 --- a/src/components/poll/unverified-poll-notice.tsx +++ b/src/components/poll/unverified-poll-notice.tsx @@ -13,7 +13,7 @@ export const UnverifiedPollNotice = () => { return (
-
+
string) | null>(null); const colors = [ - "bg-fuchsia-300", - "bg-purple-400", - "bg-primary-400", - "bg-blue-400", + "bg-violet-400", "bg-sky-400", "bg-cyan-400", - "bg-sky-400", "bg-blue-400", - "bg-primary-400", + "bg-indigo-400", "bg-purple-400", "bg-fuchsia-400", "bg-pink-400", @@ -46,9 +42,9 @@ export const UserAvatarProvider: React.VoidFunctionComponent<{ const res = { "": defaultColor, }; - for (let i = 0; i < names.length; i++) { + for (let i = names.length - 1; i >= 0; i--) { const name = names[i].trim().toLowerCase(); - const color = colors[(seedValue + i) % colors.length]; + const color = colors[(seedValue + names.length - i) % colors.length]; res[name] = color; } return res; @@ -90,15 +86,14 @@ const UserAvatarInner: React.VoidFunctionComponent = ({ return ( {trimmedName[0]?.toUpperCase()} @@ -124,9 +119,7 @@ const UserAvatar: React.VoidFunctionComponent = ({ )} > -
- {forwardedProps.name} -
+
{forwardedProps.name}
{isYou ? {t("you")} : null}
); diff --git a/src/components/poll/vote-selector.tsx b/src/components/poll/vote-selector.tsx index b91e7029e..b1af5fdff 100644 --- a/src/components/poll/vote-selector.tsx +++ b/src/components/poll/vote-selector.tsx @@ -1,6 +1,5 @@ import { VoteType } from "@prisma/client"; import clsx from "clsx"; -import { AnimatePresence, motion } from "framer-motion"; import * as React from "react"; import VoteIcon from "./vote-icon"; @@ -37,17 +36,7 @@ export const VoteSelector = React.forwardRef< onBlur={onBlur} onKeyDown={onKeyDown} className={clsx( - "group relative inline-flex h-9 w-full items-center justify-center overflow-hidden rounded-md border bg-white transition-all hover:ring-4 focus-visible:border-0 focus-visible:ring-2 focus-visible:ring-primary-500", - { - "border-green-200 bg-green-50 hover:ring-green-100/50 active:bg-green-100/50": - value === "yes", - "border-amber-200 bg-amber-50 hover:ring-amber-100/50 active:bg-amber-100/50": - value === "ifNeedBe", - "border-gray-200 bg-gray-50 hover:ring-gray-100/50 active:bg-gray-100/50": - value === "no", - "border-gray-200 hover:ring-gray-100/50 active:bg-gray-100/50": - value === undefined, - }, + "btn-default relative items-center justify-center overflow-hidden px-0", className, )} onClick={() => { @@ -55,18 +44,7 @@ export const VoteSelector = React.forwardRef< }} ref={ref} > - - - - - + ); }); diff --git a/src/components/switch.tsx b/src/components/switch.tsx index f3404fce5..f0f6d187d 100644 --- a/src/components/switch.tsx +++ b/src/components/switch.tsx @@ -19,7 +19,7 @@ const Switch: React.VoidFunctionComponent = ({ checked={checked} onChange={onChange} className={clsx( - "relative inline-flex h-6 w-10 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75", + "relative inline-flex h-6 w-10 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out", { "bg-gray-200": !checked, "bg-green-500": checked, diff --git a/src/components/text-input.tsx b/src/components/text-input.tsx index fe0f149b1..7ff094415 100644 --- a/src/components/text-input.tsx +++ b/src/components/text-input.tsx @@ -20,7 +20,7 @@ export const TextInput = React.forwardRef( ref={ref} type="text" className={clsx( - "appearance-none rounded-md border border-gray-300 text-slate-700 shadow-sm placeholder:text-slate-400 focus:border-primary-500 focus:ring-1 focus:ring-primary-500", + "appearance-none rounded border border-gray-300 text-slate-700 shadow-sm placeholder:text-slate-400", className, { "px-2 py-1": size === "md", diff --git a/src/components/you.tsx b/src/components/you.tsx new file mode 100644 index 000000000..536154b5a --- /dev/null +++ b/src/components/you.tsx @@ -0,0 +1,8 @@ +import { useTranslation } from "next-i18next"; + +import UserAvatar from "./poll/user-avatar"; + +export const You = () => { + const { t } = useTranslation("app"); + return ; +}; diff --git a/src/pages/admin/[urlId].tsx b/src/pages/admin/[urlId].tsx index 77ea25bb6..a03319a0b 100644 --- a/src/pages/admin/[urlId].tsx +++ b/src/pages/admin/[urlId].tsx @@ -15,6 +15,7 @@ import { trpcNext } from "@/utils/trpc"; import { withPageTranslations } from "@/utils/with-page-translations"; import { AdminControls } from "../../components/admin-control"; +import ModalProvider from "../../components/modal/modal-provider"; const PollPageLoader: NextPage = () => { const { query } = useRouter(); @@ -35,11 +36,13 @@ const PollPageLoader: NextPage = () => { -
- - - -
+ +
+ + + +
+
diff --git a/src/pages/p/[urlId].tsx b/src/pages/p/[urlId].tsx index 503696e21..59b75bb3d 100644 --- a/src/pages/p/[urlId].tsx +++ b/src/pages/p/[urlId].tsx @@ -13,6 +13,7 @@ import { trpcNext } from "@/utils/trpc"; import { withPageTranslations } from "@/utils/with-page-translations"; import { ParticipantLayout } from "../../components/layouts/participant-layout"; +import ModalProvider from "../../components/modal/modal-provider"; import { DayjsProvider } from "../../utils/dayjs"; const Page: NextPage = () => { @@ -36,17 +37,19 @@ const Page: NextPage = () => { -
- {user.id === poll.user.id ? ( - - ← {t("goToAdmin")} - - ) : null} - -
+ +
+ {user.id === poll.user.id ? ( + + ← {t("goToAdmin")} + + ) : null} + +
+
diff --git a/src/server/routers/polls/participants.ts b/src/server/routers/polls/participants.ts index 8141f9b97..629ac2a24 100644 --- a/src/server/routers/polls/participants.ts +++ b/src/server/routers/polls/participants.ts @@ -20,7 +20,7 @@ export const participants = createRouter() }, orderBy: [ { - createdAt: "asc", + createdAt: "desc", }, { name: "desc" }, ], @@ -86,7 +86,6 @@ export const participants = createRouter() input: z.object({ pollId: z.string(), participantId: z.string(), - name: z.string(), votes: z .object({ optionId: z.string(), @@ -94,7 +93,7 @@ export const participants = createRouter() }) .array(), }), - resolve: async ({ input: { pollId, participantId, votes, name } }) => { + resolve: async ({ input: { pollId, participantId, votes } }) => { const participant = await prisma.participant.update({ where: { id: participantId, @@ -112,7 +111,6 @@ export const participants = createRouter() })), }, }, - name, }, include: { votes: true, diff --git a/src/utils/form-validation.ts b/src/utils/form-validation.ts index fd824b40c..124095565 100644 --- a/src/utils/form-validation.ts +++ b/src/utils/form-validation.ts @@ -1,4 +1,31 @@ +import { useTranslation } from "next-i18next"; + +/** + * @deprecated Use form validation hook instead + */ export const requiredString = (value: string) => !!value.trim(); +/** + * @deprecated Use form validation hook instead + */ export const validEmail = (value: string) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); + +export const useFormValidation = () => { + const { t } = useTranslation("app"); + + return { + requiredString: (name?: string) => (value: string) => { + if (!value.trim()) { + return t("requiredString", { name }); + } + }, + + validEmail: (value: string) => { + const isValidEmail = validEmail(value); + if (!isValidEmail) { + return t("validEmail"); + } + }, + }; +}; diff --git a/style.css b/style.css index 5b61972a5..0e05fddf3 100644 --- a/style.css +++ b/style.css @@ -26,15 +26,22 @@ h2 { @apply text-xl; } - input { - @apply outline-none; - } label { @apply mb-1 block text-sm text-slate-800; } + + a, + button, + input, + select, + textarea { + @apply rounded outline-none transition-shadow focus:border-transparent focus:ring-2 focus:ring-slate-300; + } + + a, button { - @apply outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1; + @apply focus:ring-offset-2 focus:ring-offset-white; } #floating-ui-root { @@ -44,13 +51,13 @@ @layer components { .text-link { - @apply rounded-sm font-medium text-primary-500 outline-none hover:text-primary-500 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1; + @apply rounded font-medium text-primary-500 outline-none hover:text-primary-500 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-1; } .formField { @apply mb-4; } .input { - @apply appearance-none rounded-md border border-gray-200 px-2 py-1 text-slate-700 placeholder:text-slate-400 focus:border-primary-500 focus:ring-1 focus:ring-primary-500; + @apply appearance-none border px-2 py-1 text-slate-700 placeholder:text-slate-400; } input.input { @apply h-9; @@ -59,20 +66,20 @@ @apply input px-3 py-3; } .input-error { - @apply border-rose-500 ring-1 ring-rose-400 focus:border-rose-400 focus:ring-rose-500; + @apply focus:ring-rose-500; } .checkbox { @apply h-4 w-4 rounded border-slate-300 text-primary-500 shadow-sm focus:ring-primary-500; } .btn { - @apply inline-flex h-9 select-none items-center justify-center whitespace-nowrap rounded-md border px-3 font-medium shadow-sm transition-all; + @apply inline-flex h-9 select-none items-center justify-center whitespace-nowrap border px-3 font-medium shadow-sm; } a.btn { @apply cursor-pointer hover:no-underline; } .btn-default { - @apply btn bg-white text-slate-700 hover:bg-gray-50 active:bg-slate-100; + @apply btn bg-white text-slate-700 hover:bg-gray-50 active:bg-gray-100; } a.btn-default { @@ -90,7 +97,7 @@ } .btn-primary { text-shadow: rgb(0 0 0 / 20%) 0px 1px 1px; - @apply btn border-primary-600 bg-primary-500 text-white hover:bg-opacity-90 focus-visible:ring-primary-500 active:bg-primary-600; + @apply btn border-primary-600 bg-primary-500 text-white hover:bg-opacity-90 active:bg-primary-600; } a.btn-primary { @@ -102,7 +109,7 @@ } .segment-button button { - @apply inline-flex grow items-center justify-center border-t border-b border-r bg-gray-50 px-4 transition-colors first:rounded-l first:border-l last:rounded-r hover:bg-slate-50 focus:z-10 focus-visible:ring-2 focus-visible:ring-offset-0 active:bg-slate-100; + @apply inline-flex grow items-center justify-center border-t border-b border-r bg-gray-50 px-4 transition-colors first:rounded-r-none first:border-l last:rounded-l-none hover:bg-slate-50 focus:z-10 active:bg-slate-100; } .segment-button .segment-button-active { diff --git a/tailwind.config.js b/tailwind.config.js index 7447abce5..acdf8b613 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -45,9 +45,5 @@ module.exports = { }, }, }, - plugins: [ - require("@tailwindcss/forms"), - require("@tailwindcss/typography"), - require("tailwindcss-animate"), - ], + plugins: [require("@tailwindcss/typography"), require("tailwindcss-animate")], }; diff --git a/tests/mobile-test.spec.ts b/tests/mobile-test.spec.ts index 0177b803f..fcf2ae4a4 100644 --- a/tests/mobile-test.spec.ts +++ b/tests/mobile-test.spec.ts @@ -6,13 +6,16 @@ test("should be able to vote and comment on a poll", async ({ page }) => { await expect(page.locator('text="Lunch Meeting"')).toBeVisible(); - await page.click("text='New'"); + await page.click('text="New"'); await page.click("data-testid=poll-option >> nth=0"); await page.click("data-testid=poll-option >> nth=1"); await page.click("data-testid=poll-option >> nth=3"); - await page.type('[placeholder="Your name…"]', "Test user"); - await page.click("text=Save"); + await page.getByText("Continue").click(); + + await page.getByPlaceholder("Jessie Smith").type("Test user"); + await page.getByText("Save").click(); + await expect(page.locator("data-testid=user")).toBeVisible(); await expect( page.locator("data-testid=participant-selector").locator("text=You"), diff --git a/tests/vote-and-comment.spec.ts b/tests/vote-and-comment.spec.ts index b563e01b9..8661a9eb0 100644 --- a/tests/vote-and-comment.spec.ts +++ b/tests/vote-and-comment.spec.ts @@ -11,16 +11,20 @@ test("should be able to vote and comment on a poll", async ({ page }) => { await expect(page.locator('text="Lunch Meeting"')).toBeVisible(); - await page.type('[placeholder="Your name…"]', "Test user"); // There is a hidden checkbox (nth=0) that exists so that the behaviour of the form is consistent even // when we only have a single option/checkbox. await page.locator("data-testid=vote-selector >> nth=0").click(); await page.locator("data-testid=vote-selector >> nth=2").click(); + await page.click("button >> text='Continue'"); + + await page.type('[placeholder="Jessie Smith"]', "Test user"); await page.click("text='Save'"); + await expect(page.locator("text='Test user'")).toBeVisible(); await expect( - page.locator("data-testid=participant-row >> nth=4").locator("text=You"), + page.locator("data-testid=participant-row >> nth=0").locator("text=You"), ).toBeVisible(); + await page.type( "[placeholder='Leave a comment on this poll (visible to everyone)']", "This is a comment!", diff --git a/yarn.lock b/yarn.lock index a781724c4..52f388fbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1932,13 +1932,6 @@ dependencies: tslib "^2.4.0" -"@tailwindcss/forms@^0.5.3": - version "0.5.3" - resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.3.tgz#e4d7989686cbcaf416c53f1523df5225332a86e7" - integrity sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q== - dependencies: - mini-svg-data-uri "^1.2.3" - "@tailwindcss/typography@^0.5.9": version "0.5.9" resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.9.tgz#027e4b0674929daaf7c921c900beee80dbad93e8" @@ -5868,11 +5861,6 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-svg-data-uri@^1.2.3: - version "1.4.3" - resolved "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz" - integrity sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA== - minimatch@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz"