diff --git a/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx b/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx
index a7e72b515..3081cd21c 100644
--- a/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx
+++ b/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx
@@ -1,10 +1,11 @@
"use client";
import { PollStatus } from "@rallly/database";
import { cn } from "@rallly/ui";
+import { Badge } from "@rallly/ui/badge";
+import { Button } from "@rallly/ui/button";
import { Icon } from "@rallly/ui/icon";
import { RadioCards, RadioCardsItem } from "@rallly/ui/radio-pills";
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
-import dayjs from "dayjs";
import { CalendarPlusIcon, CheckIcon, LinkIcon, UserIcon } from "lucide-react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
@@ -132,31 +133,33 @@ function CopyLinkButton({ pollId }: { pollId: string }) {
const [, copy] = useCopyToClipboard();
const [didCopy, setDidCopy] = React.useState(false);
- if (didCopy) {
- return (
-
-
-
-
- );
- }
-
return (
-
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
);
}
@@ -208,38 +211,36 @@ function PollsListView({
}
return (
-
+
{table.getRowModel().rows.map((row) => (
-
-
-
-
+
+
+
+
{row.original.title}
-
+
+
-
))}
diff --git a/apps/web/src/app/components/page-layout.tsx b/apps/web/src/app/components/page-layout.tsx
index d09dc71fa..fc6f47541 100644
--- a/apps/web/src/app/components/page-layout.tsx
+++ b/apps/web/src/app/components/page-layout.tsx
@@ -50,7 +50,7 @@ export function PageHeader({
className?: string;
variant?: "default" | "ghost";
}) {
- return
{children}
;
+ return {children}
;
}
export function PageSection({ children }: { children?: React.ReactNode }) {
diff --git a/apps/web/src/components/discussion/discussion.tsx b/apps/web/src/components/discussion/discussion.tsx
index 2232a4a99..c3443e6d2 100644
--- a/apps/web/src/components/discussion/discussion.tsx
+++ b/apps/web/src/components/discussion/discussion.tsx
@@ -16,6 +16,7 @@ import {
DropdownMenuTrigger,
} from "@rallly/ui/dropdown-menu";
import { Icon } from "@rallly/ui/icon";
+import { Input } from "@rallly/ui/input";
import { Textarea } from "@rallly/ui/textarea";
import dayjs from "dayjs";
import {
@@ -27,6 +28,11 @@ import { useTranslation } from "next-i18next";
import * as React from "react";
import { Controller, useForm } from "react-hook-form";
+import {
+ Participant,
+ ParticipantAvatar,
+ ParticipantName,
+} from "@/components/participant";
import { useParticipants } from "@/components/participants-provider";
import { Trans } from "@/components/trans";
import { usePermissions } from "@/contexts/permissions";
@@ -36,9 +42,7 @@ import { usePostHog } from "@/utils/posthog";
import { trpc } from "@/utils/trpc/client";
import { requiredString } from "../../utils/form-validation";
-import NameInput from "../name-input";
import TruncatedLinkify from "../poll/truncated-linkify";
-import UserAvatar from "../poll/user-avatar";
import { useUser } from "../user-provider";
interface CommentForm {
@@ -119,7 +123,13 @@ function NewCommentForm({
control={control}
rules={{ validate: requiredString }}
render={({ field }) => (
-
+
)}
/>
@@ -203,11 +213,15 @@ function DiscussionInner() {
-
+
+
+ {comment.authorName}
+ {session.ownsObject(comment) ? (
+
+
+
+ ) : null}
+
{dayjs(comment.createdAt).fromNow()}
diff --git a/apps/web/src/components/name-input.tsx b/apps/web/src/components/name-input.tsx
deleted file mode 100644
index 867d64bcd..000000000
--- a/apps/web/src/components/name-input.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { cn } from "@rallly/ui";
-import { Input, InputProps } from "@rallly/ui/input";
-import { useTranslation } from "next-i18next";
-import * as React from "react";
-
-import UserAvatar from "./poll/user-avatar";
-
-interface NameInputProps extends InputProps {
- value?: string;
- defaultValue?: string;
- error?: boolean;
-}
-
-const NameInput = React.forwardRef
(function (
- { value, defaultValue, className, error, ...forwardProps },
- ref,
-) {
- const { t } = useTranslation();
- return (
-
- {value ? (
-
- ) : null}
-
-
- );
-});
-
-NameInput.displayName = "NameInput";
-
-export default NameInput;
diff --git a/apps/web/src/components/optimized-avatar-image.tsx b/apps/web/src/components/optimized-avatar-image.tsx
index dfe657fce..0764172d9 100644
--- a/apps/web/src/components/optimized-avatar-image.tsx
+++ b/apps/web/src/components/optimized-avatar-image.tsx
@@ -1,4 +1,5 @@
"use client";
+import { cn } from "@rallly/ui";
import { Avatar, AvatarFallback, AvatarImage } from "@rallly/ui/avatar";
import Image from "next/image";
import React from "react";
@@ -36,7 +37,16 @@ export function OptimizedAvatarImage({
/>
) : null
) : null}
- {!src || !isLoaded ? {name[0]} : null}
+ {!src || !isLoaded ? (
+ = 48,
+ })}
+ >
+ {name[0]}
+
+ ) : null}
);
}
diff --git a/apps/web/src/components/participant-avatar-bar.tsx b/apps/web/src/components/participant-avatar-bar.tsx
index 13a3bde1c..cb436dff1 100644
--- a/apps/web/src/components/participant-avatar-bar.tsx
+++ b/apps/web/src/components/participant-avatar-bar.tsx
@@ -1,7 +1,7 @@
import { cn } from "@rallly/ui";
import { Tooltip, TooltipContent, TooltipTrigger } from "@rallly/ui/tooltip";
-import { ColoredAvatar } from "@/components/poll/user-avatar";
+import { ParticipantAvatar } from "@/components/participant";
interface ParticipantAvatarBarProps {
participants: { name: string }[];
@@ -15,25 +15,25 @@ export const ParticipantAvatarBar = ({
const visibleCount = participants.length > max ? max - 1 : max;
const hiddenCount = participants.length - visibleCount;
return (
-
+
{participants.slice(0, visibleCount).map((participant, index) => (
- -
-
+
-
+
{participant.name}
))}
{hiddenCount > 1 ? (
- -
+
-
diff --git a/apps/web/src/components/participant-dropdown.tsx b/apps/web/src/components/participant-dropdown.tsx
index 81272867b..76b09148f 100644
--- a/apps/web/src/components/participant-dropdown.tsx
+++ b/apps/web/src/components/participant-dropdown.tsx
@@ -40,8 +40,6 @@ import { useFormValidation } from "@/utils/form-validation";
import { usePostHog } from "@/utils/posthog";
import { trpc } from "@/utils/trpc/client";
-import { Participant } from ".prisma/client";
-
export const ParticipantDropdown = ({
participant,
onEdit,
@@ -50,7 +48,12 @@ export const ParticipantDropdown = ({
align,
}: {
disabled?: boolean;
- participant: Participant;
+ participant: {
+ name: string;
+ userId?: string;
+ email?: string;
+ id: string;
+ };
align?: "start" | "end";
onEdit: () => void;
children: React.ReactNode;
diff --git a/apps/web/src/components/participant.tsx b/apps/web/src/components/participant.tsx
new file mode 100644
index 000000000..c19ee50eb
--- /dev/null
+++ b/apps/web/src/components/participant.tsx
@@ -0,0 +1,55 @@
+import { cn } from "@rallly/ui";
+import { Avatar, AvatarFallback, getColor } from "@rallly/ui/avatar";
+import React from "react";
+
+export function Participant({ children }: { children: React.ReactNode }) {
+ return
{children}
;
+}
+
+export const ParticipantAvatar = ({
+ size = 20,
+ name,
+}: {
+ size?: number;
+ name: string;
+}) => {
+ const color = getColor(name);
+
+ return (
+
+
+ {name[0]}
+
+
+ );
+};
+
+export const ParticipantName = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ const ref = React.useRef(null);
+ const [isTruncated, setIsTruncated] = React.useState(false);
+ return (
+ {
+ if (ref.current) {
+ setIsTruncated(ref.current.scrollWidth > ref.current.clientWidth);
+ }
+ }}
+ onMouseLeave={() => {
+ if (isTruncated) {
+ setIsTruncated(false);
+ }
+ }}
+ className={cn("truncate text-sm font-medium", {
+ "hover:-translate-x-2 hover:cursor-pointer hover:overflow-visible hover:whitespace-nowrap hover:rounded-md hover:bg-white hover:p-2":
+ isTruncated,
+ })}
+ >
+ {children}
+
+ );
+};
diff --git a/apps/web/src/components/poll/desktop-poll.tsx b/apps/web/src/components/poll/desktop-poll.tsx
index 351a6ce5a..6d03e3c3c 100644
--- a/apps/web/src/components/poll/desktop-poll.tsx
+++ b/apps/web/src/components/poll/desktop-poll.tsx
@@ -304,7 +304,13 @@ const DesktopPoll: React.FunctionComponent = () => {
return (
{
const { t } = useTranslation();
@@ -50,6 +50,8 @@ const ParticipantRowForm = ({
};
}, [form]);
+ const participantName = name ?? t("you");
+
return (
- {name ? (
-
- ) : (
-
- )}
+
+
+ {participantName}
+
{!isNew ? (
diff --git a/apps/web/src/components/poll/desktop-poll/participant-row.tsx b/apps/web/src/components/poll/desktop-poll/participant-row.tsx
index 371b07a19..4cb5b0b21 100644
--- a/apps/web/src/components/poll/desktop-poll/participant-row.tsx
+++ b/apps/web/src/components/poll/desktop-poll/participant-row.tsx
@@ -1,22 +1,34 @@
-import { Participant, VoteType } from "@rallly/database";
+import type { VoteType } from "@rallly/database";
import { cn } from "@rallly/ui";
+import { Badge } from "@rallly/ui/badge";
import { Button } from "@rallly/ui/button";
import { Icon } from "@rallly/ui/icon";
import { MoreHorizontalIcon } from "lucide-react";
import * as React from "react";
+import {
+ Participant,
+ ParticipantAvatar,
+ ParticipantName,
+} from "@/components/participant";
import { ParticipantDropdown } from "@/components/participant-dropdown";
import { usePoll } from "@/components/poll-context";
+import { Trans } from "@/components/trans";
import { useUser } from "@/components/user-provider";
import { usePermissions } from "@/contexts/permissions";
import { Vote } from "@/utils/trpc/types";
-import UserAvatar from "../user-avatar";
import VoteIcon from "../vote-icon";
import ParticipantRowForm from "./participant-row-form";
export interface ParticipantRowProps {
- participant: Participant & { votes: Vote[] };
+ participant: {
+ id: string;
+ name: string;
+ userId?: string;
+ email?: string;
+ votes: Vote[];
+ };
className?: string;
editMode?: boolean;
onChangeEditMode?: (editMode: boolean) => void;
@@ -41,7 +53,15 @@ export const ParticipantRowView: React.FunctionComponent<{
className="sticky left-0 z-10 h-12 bg-white px-4"
>
-
+
+
+ {name}
+ {isYou ? (
+
+
+
+ ) : null}
+
{action}
|
diff --git a/apps/web/src/components/poll/mobile-poll.tsx b/apps/web/src/components/poll/mobile-poll.tsx
index 342159ec3..7b6aee7dd 100644
--- a/apps/web/src/components/poll/mobile-poll.tsx
+++ b/apps/web/src/components/poll/mobile-poll.tsx
@@ -16,6 +16,8 @@ import * as React from "react";
import smoothscroll from "smoothscroll-polyfill";
import { TimesShownIn } from "@/components/clock";
+import { OptimizedAvatarImage } from "@/components/optimized-avatar-image";
+import { Participant, ParticipantName } from "@/components/participant";
import { ParticipantDropdown } from "@/components/participant-dropdown";
import { useVotingForm } from "@/components/poll/voting-form";
import { useOptions, usePoll } from "@/components/poll-context";
@@ -25,7 +27,6 @@ import { usePermissions } from "@/contexts/permissions";
import { useVisibleParticipants } from "../participants-provider";
import { useUser } from "../user-provider";
import GroupedOptions from "./mobile-poll/grouped-options";
-import UserAvatar, { YouAvatar } from "./user-avatar";
if (typeof window !== "undefined") {
smoothscroll.polyfill();
@@ -100,11 +101,18 @@ const MobilePoll: React.FunctionComponent = () => {
{visibleParticipants.map((participant) => (
-
+
+
+ {participant.name}
+ {session.ownsObject(participant) && (
+
+
+
+ )}
+
))}
@@ -112,7 +120,10 @@ const MobilePoll: React.FunctionComponent = () => {
) : (
)}
{isEditing ? (
@@ -131,7 +142,12 @@ const MobilePoll: React.FunctionComponent = () => {
{
votingForm.setEditingParticipantId(selectedParticipant.id);
}}
diff --git a/apps/web/src/components/poll/mobile-poll/poll-option.tsx b/apps/web/src/components/poll/mobile-poll/poll-option.tsx
index 00eb8f55a..b179f2dbf 100644
--- a/apps/web/src/components/poll/mobile-poll/poll-option.tsx
+++ b/apps/web/src/components/poll/mobile-poll/poll-option.tsx
@@ -8,8 +8,8 @@ import * as React from "react";
import { useToggle } from "react-use";
import { useTranslation } from "@/app/i18n/client";
+import { ParticipantAvatar } from "@/components/participant";
import { useParticipants } from "@/components/participants-provider";
-import { UserAvatar } from "@/components/user";
import { usePoll } from "@/contexts/poll";
import { useRole } from "@/contexts/role";
@@ -51,11 +51,11 @@ const PollOptionVoteSummary: React.FunctionComponent<{ optionId: string }> = ({
{participantsWhoVotedYes.map(({ name }, i) => (
{name}
@@ -66,7 +66,7 @@ const PollOptionVoteSummary: React.FunctionComponent<{ optionId: string }> = ({
{participantsWhoVotedIfNeedBe.map(({ name }, i) => (
-
+
= ({
{participantsWhoVotedNo.map(({ name }, i) => (
-
+
{adjustTimeZone(start, !poll.timeZone).format("LL")};
+ return (
+ {adjustTimeZone(start, !poll.timeZone).format("dddd, LL")}
+ );
}
function DateIcon({ start }: { start: Date }) {
@@ -20,7 +24,7 @@ function DateIcon({ start }: { start: Date }) {
const d = adjustTimeZone(start, !poll.timeZone);
return (