Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ PORT=3002
QUICK_CREATE_ENABLED=true
RATE_LIMIT_ENABLED=false
SECRET_PASSWORD=abcdef1234567890abcdef1234567890
SMTP_HOST=localhost
SMTP_HOST=0.0.0.0
SMTP_PORT=1025
SMTP_REJECT_UNAUTHORIZED=false
SUPPORT_EMAIL=support@rallly.co
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@vercel/functions": "^3.3.6",
"ai": "^6.0.87",
"arctic": "^3.7.0",
"better-auth": "^1.4.7",
"better-auth": "^1.6.0",
"calendar-link": "^2.6.0",
"class-variance-authority": "^0.7.1",
"color-hash": "^2.0.2",
Expand Down
9 changes: 5 additions & 4 deletions apps/web/public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"continue": "Continue",
"configuredByEnvironmentVariable": "This setting has been configured by environment variable.",
"copied": "Copied",
"createdBy": "by <b>{name}</b>",
"delete": "Delete",
"deleteDate": "Delete date",
"deletedPoll": "Deleted poll",
Expand Down Expand Up @@ -236,7 +235,6 @@
"paginationPage": "Page {currentPage, number} of {totalPages, number}",
"paginationNext": "Next",
"searchPollsPlaceholder": "Search polls by title...",
"poll": "Poll",
"sendFeedbackDesc": "Share your feedback with us.",
"sendFeedbackSuccess": "Thank you for your feedback!",
"searchEventsPlaceholder": "Search events by title...",
Expand Down Expand Up @@ -580,7 +578,6 @@
"openPollCount": "{count, plural, other {# open}}",
"pollStatusClosed": "Closed",
"pollStatusScheduled": "Scheduled",
"pollStatusClosedDescription": "Votes cannot be submitted or edited at this time",
"closePoll": "Close",
"pollStatusCanceled": "Canceled",
"schedulePoll": "Schedule",
Expand Down Expand Up @@ -619,5 +616,9 @@
"unmuteNotifications": "Unmute notifications",
"muteNotifications": "Mute notifications",
"creatingPoll": "Creating poll...",
"createPollPollCreated": "Poll created"
"createPollPollCreated": "Poll created",
"viewPoll": "View poll",
"responseOptions": "Response Options",
"viewResults": "View results",
"pollClosedDescription": "No more responses are being accepted."
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"use client";
import Discussion from "@/components/discussion";
import { EventCard } from "@/components/event-card";
import { PollBranding } from "@/components/poll/poll-branding";
import { PollFooter } from "@/components/poll/poll-footer";
import { PollViewTracker } from "@/components/poll/poll-view-tracker";
import { ResponsiveResults } from "@/components/poll/responsive-results";
import { ScheduledEvent } from "@/components/poll/scheduled-event";
import { VotingForm } from "@/components/poll/voting-form";
import { usePoll } from "@/contexts/poll";

import { GuestPollAlert } from "./guest-poll-alert";

export function AdminPage() {
Expand All @@ -16,11 +15,13 @@ export function AdminPage() {

return (
<div className="space-y-3 lg:space-y-4">
{poll.space?.showBranding && poll.space.primaryColor ? (
<PollBranding primaryColor={poll.space.primaryColor} />
) : null}
{/* Track poll views */}
<PollViewTracker pollId={poll.id} />
<GuestPollAlert />
<EventCard />
<ScheduledEvent />
<VotingForm>
<ResponsiveResults />
</VotingForm>
Expand Down
9 changes: 5 additions & 4 deletions apps/web/src/app/[locale]/(space)/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
SidebarSeparator,
} from "@rallly/ui/sidebar";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { Settings2Icon } from "lucide-react";
import { SettingsIcon } from "lucide-react";
import Link from "next/link";
import { NavUser } from "@/components/nav-user";
import { LicenseLimitWarning } from "@/features/licensing/components/license-limit-warning";
Expand All @@ -35,6 +35,7 @@ export default async function Layout({
const helpers = await createPrivateSSRHelper();

await Promise.all([
helpers.billing.getTier.prefetch(),
helpers.user.getAuthed.prefetch(),
helpers.spaces.list.prefetch(),
]);
Expand All @@ -61,9 +62,9 @@ export default async function Layout({
<ControlPanelMenuItem />
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/settings/preferences">
<Settings2Icon />
<Trans i18nKey="preferences" defaults="Preferences" />
<Link href="/settings/profile">
<SettingsIcon />
<Trans i18nKey="settings" defaults="Settings" />
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { ArrowUpRightIcon, CrownIcon } from "lucide-react";
import Link from "next/link";
import Discussion from "@/components/discussion";
import { EventCard } from "@/components/event-card";
import { PollBranding } from "@/components/poll/poll-branding";
import { PollFooter } from "@/components/poll/poll-footer";
import { PollViewTracker } from "@/components/poll/poll-view-tracker";
import { ResponsiveResults } from "@/components/poll/responsive-results";
import { ScheduledEvent } from "@/components/poll/scheduled-event";
import { VotingForm } from "@/components/poll/voting-form";
import { useUser } from "@/components/user-provider";
import { usePoll } from "@/contexts/poll";
Expand Down Expand Up @@ -51,12 +51,13 @@ export function InvitePage() {

return (
<div className="page-bg-gray-100 h-dvh overflow-auto p-3 lg:p-6 dark:bg-gray-900">
<div className="page-bg-gray-100 hidden" />
{poll.space?.showBranding && poll.space.primaryColor ? (
<PollBranding primaryColor={poll.space.primaryColor} />
) : null}
<PollViewTracker pollId={poll.id} />
<div className="mx-auto w-full max-w-4xl space-y-3">
<GoToApp />
<EventCard />
<ScheduledEvent />
<VotingForm>
<ResponsiveResults />
</VotingForm>
Expand Down
6 changes: 2 additions & 4 deletions apps/web/src/components/empty-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ export function EmptyState({

export function EmptyStateIcon({ children }: { children: React.ReactNode }) {
return (
<div className="mb-4 inline-flex rounded-full border p-4">
<Icon size="xl" className="opacity-50">
{children}
</Icon>
<div className="mb-4 inline-flex rounded-xl bg-muted p-3 shadow-xs ring-1 ring-button-outline ring-inset">
<Icon size="lg">{children}</Icon>
</div>
);
}
Expand Down
175 changes: 86 additions & 89 deletions apps/web/src/components/event-card.tsx
Original file line number Diff line number Diff line change
@@ -1,111 +1,108 @@
"use client";
import { Alert, AlertDescription, AlertTitle } from "@rallly/ui/alert";
import { Card, CardContent, CardDescription } from "@rallly/ui/card";
import { Icon } from "@rallly/ui/icon";
import { CircleStopIcon, DotIcon, MapPinIcon } from "lucide-react";
import { Card, CardContent } from "@rallly/ui/card";
import { MapPinIcon, User2Icon } from "lucide-react";
import {
EventMetaDescription,
EventMetaItem,
EventMetaList,
EventMetaTitle,
} from "@/components/event-meta";
import TruncatedLinkify from "@/components/poll/truncated-linkify";
import VoteIcon from "@/components/poll/vote-icon";
import { PollStatusBadge } from "@/components/poll-status";
import { RandomGradientBar } from "@/components/random-gradient-bar";
import { usePoll } from "@/contexts/poll";
import { Trans, useTranslation } from "@/i18n/client";
import { dayjs } from "@/lib/dayjs";
import { SpaceIcon } from "@/features/space/components/space-icon";
import { Trans } from "@/i18n/client";

function IconGuide() {
function IconDescriptionList({
children,
...props
}: React.HTMLAttributes<HTMLDListElement>) {
return (
<ul className="flex items-center gap-x-3 whitespace-nowrap text-sm">
<li className="flex items-center gap-1.5">
<VoteIcon type="yes" />
<Trans i18nKey="yes" />
</li>
<li className="flex items-center gap-1.5">
<VoteIcon type="ifNeedBe" />
<Trans i18nKey="ifNeedBe" />
</li>
<li className="flex items-center gap-1.5">
<VoteIcon type="no" />
<Trans i18nKey="no" />
</li>
</ul>
<dl
className="flex flex-col gap-x-2 gap-y-1 text-muted-foreground text-xs"
{...props}
>
{children}
</dl>
);
}

function IconDescription({
icon,
label,
}: {
icon: React.ReactNode;
label: React.ReactNode;
}) {
return (
<div className="flex items-center gap-1">
<dt>{icon}</dt>
<dd>{label}</dd>
</div>
);
}

export function EventCard() {
const poll = usePoll();
const { t } = useTranslation();
return (
<>
<Card>
<RandomGradientBar />
<CardContent className="space-y-4 sm:space-y-6">
<div className="flex flex-col items-start gap-4 lg:flex-row lg:justify-between">
<div>
<h1 data-testid="poll-title" className="font-semibold text-lg">
{poll.title}
</h1>
<CardDescription>
<span className="flex items-center gap-0.5 whitespace-nowrap text-muted-foreground text-sm">
<span>
<Trans
i18nKey="createdBy"
values={{
name: poll.user?.name ?? t("guest"),
}}
components={{
b: <span />,
}}
/>
</span>
<Icon>
<DotIcon />
</Icon>
<span className="whitespace-nowrap">
<Trans
i18nKey="createdTime"
values={{ relativeTime: dayjs(poll.createdAt).fromNow() }}
/>
</span>
</span>
</CardDescription>
<Card>
<RandomGradientBar />
<CardContent className="flex flex-col gap-x-8 gap-y-4 md:flex-row">
<div className="flex-1">
{poll.space?.showBranding && poll.space.image ? (
<div className="mb-2">
<SpaceIcon
name={poll.space.name}
src={poll.space.image}
size="lg"
/>
<p className="mt-2 font-medium text-muted-foreground text-sm">
{poll.space.name}
</p>
</div>
{poll.status !== "open" ? (
<PollStatusBadge status={poll.status} />
) : null}
) : null}
<div className="flex items-start justify-between gap-2">
<EventMetaTitle>{poll.title}</EventMetaTitle>
</div>
{poll.description ? (
<p className="min-w-0 whitespace-pre-wrap text-pretty text-sm leading-relaxed">
<TruncatedLinkify>{poll.description}</TruncatedLinkify>
</p>
<EventMetaDescription>{poll.description}</EventMetaDescription>
) : null}
<div className="flex flex-col justify-between gap-4 sm:flex-row sm:flex-wrap sm:items-center">
<IconGuide />
<EventMetaList className="mt-4">
{poll.user ? (
<EventMetaItem>
<User2Icon />
{poll.user.name}
</EventMetaItem>
) : null}
{poll.location ? (
<p className="truncate whitespace-nowrap text-muted-foreground text-sm">
<Icon>
<MapPinIcon className="-mt-0.5 mr-1.5 inline-block" />
</Icon>
<EventMetaItem>
<MapPinIcon />
<TruncatedLinkify>{poll.location}</TruncatedLinkify>
</p>
</EventMetaItem>
) : null}
</div>
</CardContent>
</Card>
{poll.status === "closed" ? (
<Alert>
<CircleStopIcon />
<AlertTitle>
<Trans i18nKey="pollStatusClosed" defaults="Closed" />
</AlertTitle>
<AlertDescription>
<p>
<Trans
i18nKey="pollStatusClosedDescription"
defaults="Votes cannot be submitted or edited at this time"
/>
</p>
</AlertDescription>
</Alert>
) : null}
</>
</EventMetaList>
</div>
<div className="w-full md:w-1/3">
<h2 className="mb-2 font-medium text-sm">
<Trans i18nKey="responseOptions" defaults="Response Options" />
</h2>
<IconDescriptionList aria-label="Response Options">
<IconDescription
icon={<VoteIcon type="yes" />}
label={<Trans i18nKey="yes" defaults="Yes" />}
/>
<IconDescription
icon={<VoteIcon type="ifNeedBe" />}
label={<Trans i18nKey="ifNeedBe" defaults="If need be" />}
/>
<IconDescription
icon={<VoteIcon type="no" />}
label={<Trans i18nKey="no" defaults="No" />}
/>
</IconDescriptionList>
</div>
</CardContent>
</Card>
);
}
Loading
Loading