Skip to content
Closed
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
12 changes: 6 additions & 6 deletions apps/code/snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -561,17 +561,17 @@ snapshots:
git-dialogs--sync-success--light:
hash: v1.k4693efd2.8d79d77c9338aeaa73734836f0e17fde987f6d3239914f4013889d3110e5088d.Rq-w2o1uhes_a5hUWqMXeiDkfnoPCZPt932LkEQXCco
loops-loopslistview--comprehensive--dark:
hash: v1.k4693efd2.3c2a6b87092674a3d395a6f5d36889ea231696d788b159f357be0926f3804c8a.Ihzlq7XIy9rRoA7DTbpLePlBhjP34o8ppgP__NXx-Yw
hash: v1.k4693efd2.b9223e477701b5ef4587310a1de71500dd5214222531648ef76d66766b049a9f.2lckIm16WmmwZv1xVZnUL42InIHDzemQwU3EJzzw9_U
loops-loopslistview--comprehensive--light:
hash: v1.k4693efd2.42c2994def56651de08f5be20e98dbe67bfbdb03e7edd8bdf008401468c1653a.EnH7NwNkvclS7g5Htf7OCZuYEm7fI5W6hEr4DmwZC8I
hash: v1.k4693efd2.1f172c831aefdfd4b3bae97af84f80aae3c09c4919f6b1b68315240cc482af50.hnrwbv8il_Ay6DcYxmQXNtxKU3rqOIWAkjnGB8n4AT4
loops-loopslistview--long-mixed-list--dark:
hash: v1.k4693efd2.384486583f8d9b66a4bf1d9cc45036a07310cb9e1943ea5b52ff466f55a03d7b.F_sXrJ13XWHzAF8yhoKun_k4BY8Jcb3EDtT0L1I6nKw
hash: v1.k4693efd2.654250bc74fff46c496747eb13031e0e7a7ef264be5bb7be41451d82a497a515.vvrxXbbABkVY3hcHS3oThQ339JFgwIzB8E4n1usVWf0
loops-loopslistview--long-mixed-list--light:
hash: v1.k4693efd2.d80a0c55c4490276df53f781ac88ed9d9c6ec282bc14ddb80140b3ddcc44d32f.L6hw9ZixUiyTFKOybYzmjfUSIOayvmhueMTcmYt-IWo
hash: v1.k4693efd2.a8e53087efdaf58e351809775aaf410f53a781a97e10e973ee53fd0db2f94e73.7GcprWAYAgATLtNckNBd-44EyctqKNcb4FXiHA08QJs
loops-loopslistview--with-builder-sessions--dark:
hash: v1.k4693efd2.bc59812bf49ff161cf20e75dcf043367ae3d2817aa2c2cf91c91d9771c6b62f8.yOBz-llmwDVay7GIHlMwlMT3EuKjkoW0Vwmwvi4noCM
hash: v1.k4693efd2.84cfc28bedc22a6728ba4ecf69274ab03537faeed5333775991716bf0b5f439e.fHsD0XKPnud-oDM4PLJ7edCbMnVcW6uVMdMHBYp3K08
loops-loopslistview--with-builder-sessions--light:
hash: v1.k4693efd2.fb97e3fe3205ddf71d98c4e35446c73e394aaa9761d3bb35b146ddafc438df4d.UUEWc0G12NQibZTyskIHGS62mB36gKJ594GrJp1E9II
hash: v1.k4693efd2.fb6c9b85bd660a6be4645cf7ba8fe3ab08a169e3823e5354325b7d7ecbe13aba.4FbKrdxiUN1PZOGwFa_kl7Rpz3Z8DnTVNJvUM_3FIp0
scouts-scoutsfleetlist--filtered-to-you--dark:
hash: v1.k4693efd2.a3af6e76ef36598eaa347bedc4430878ed62754660166398e0576a9978cd084b.x3Ky-O6HOw0srWdOmnnKJwFNpmGmQVWip0t7CwVaRXY
scouts-scoutsfleetlist--filtered-to-you--light:
Expand Down
20 changes: 14 additions & 6 deletions packages/ui/src/features/canvas/components/WebsiteChannelLoops.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
<Flex
direction="column"
gap="6"
className="mx-auto w-full max-w-3xl px-8 py-8"
className="@container mx-auto w-full max-w-3xl px-8 py-8"
>
<Flex align="center" justify="between" gap="3">
<Flex direction="column" gap="1" className="min-w-0 max-w-[70%]">
<Flex align="center" gap="2">
<div className="flex @min-[640px]:flex-row flex-col items-start @min-[640px]:items-center justify-between gap-3">
<Flex
direction="column"
gap="1"
className="w-full min-w-0 @min-[640px]:max-w-[70%]"
>
<Flex align="center" gap="2" wrap="wrap">
<Heading className="font-bold text-2xl">
Automate #{contextName}
</Heading>
Expand Down Expand Up @@ -119,7 +123,7 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
<PlusIcon size={14} />
Create manually
</Button>
</Flex>
</div>

{isLoading ? (
<LoopsSkeleton />
Expand All @@ -140,7 +144,11 @@ export function WebsiteChannelLoops({ channelId }: { channelId: string }) {
</Flex>
</Flex>
) : (
<LoopsEmptyState contextName={contextName} />
<LoopsEmptyState
contextName={contextName}
onCreate={startBlank}
disabledReason={limitReason}
/>
)}
</Flex>
</div>
Expand Down
17 changes: 13 additions & 4 deletions packages/ui/src/features/loops/components/LoopRow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CaretRightIcon, RepeatIcon } from "@phosphor-icons/react";
import type { LoopSchemas } from "@posthog/api-client/loops";
import { formatRelativeTimeLong } from "@posthog/shared";
import type { UserBasic } from "@posthog/shared/domain-types";
import { userDisplayName } from "@posthog/ui/features/canvas/utils/userDisplay";
import { Badge } from "@posthog/ui/primitives/Badge";
Expand Down Expand Up @@ -71,11 +72,19 @@ export function LoopRow({
{description}
</Text>
) : null}
{metadata.length > 0 ? (
<Text className="mt-0.5 text-(--accent-11) text-[11px] leading-snug">
{metadata.join(" · ")}
<Flex align="center" gap="1" wrap="wrap" className="mt-0.5">
{metadata.length > 0 ? (
<Text className="text-(--accent-11) text-[11px] leading-snug">
{metadata.join(" · ")}
</Text>
) : null}
{metadata.length > 0 ? (
<Text className="text-[11px] text-gray-9">·</Text>
) : null}
<Text className="text-[11px] text-gray-9">
Created {formatRelativeTimeLong(loop.created_at)}
</Text>
) : null}
</Flex>
</Flex>
</Flex>
<Flex align="center" gap="3" className="shrink-0">
Expand Down
36 changes: 17 additions & 19 deletions packages/ui/src/features/loops/components/LoopTemplatesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export function LoopTemplatesSection({
useState<LoopTemplateCategory>("engineering");

return (
<Flex direction="column" gap="3">
<Flex align="center" justify="between" gap="3">
<div className="@container flex flex-col gap-3">
<div className="flex @min-[480px]:flex-row flex-col items-start @min-[480px]:items-center justify-between gap-3">
<Text className="font-medium text-[12px] text-gray-10 uppercase tracking-wide">
Start from a template
</Text>
Expand All @@ -49,19 +49,20 @@ export function LoopTemplatesSection({
</button>
))}
</Flex>
</Flex>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
</div>
<div className="grid @min-[640px]:grid-cols-2 grid-cols-1 gap-3">
{LOOP_TEMPLATES.filter(
(template) => template.category === templateCategory,
).map((template) => (
<TemplateCard
key={template.id}
template={template}
onSelect={() => onSelect(template)}
/>
<div key={template.id} className="@container">
<TemplateCard
template={template}
onSelect={() => onSelect(template)}
/>
</div>
))}
</div>
</Flex>
</div>
);
}

Expand All @@ -80,7 +81,7 @@ function TemplateCard({
<button
type="button"
onClick={onSelect}
className="flex items-start gap-2.5 rounded-(--radius-3) border border-border bg-(--color-panel-solid) p-3 text-left transition-colors hover:border-(--gray-6) hover:bg-(--gray-2)"
className="@container flex h-full w-full @min-[300px]:flex-row flex-col items-start gap-2.5 overflow-hidden rounded-(--radius-3) border border-border bg-(--color-panel-solid) p-3 text-left transition-colors hover:border-(--gray-6) hover:bg-(--gray-2)"
>
<Flex
align="center"
Expand All @@ -97,20 +98,17 @@ function TemplateCard({
{template.description}
</Text>
<Flex
align="center"
justify="between"
align="start"
gap="3"
className="mt-0.5 w-full text-(--accent-11)"
className="mt-0.5 w-full @min-[400px]:flex-row flex-col text-(--accent-11)"
>
<Flex align="center" className="min-w-0 gap-1.5">
<TriggerIcon size={11} className="shrink-0" />
<Text className="truncate text-[11px]">
{template.triggerLabel}
</Text>
<Text className="text-[11px]">{template.triggerLabel}</Text>
</Flex>
<Flex align="center" className="shrink-0 gap-1.5">
<Flex align="center" className="min-w-0 gap-1.5">
<PlugsIcon size={11} className="shrink-0" />
<Text className="text-[11px]">
<Text className="min-w-0 text-[11px]">
Works with {template.worksWith.join(" · ")}
</Text>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ function ScheduleTriggerFields({
type="time"
disabled={disabled}
value={time}
className="h-8 rounded-(--radius-2) border border-border bg-(--color-panel-solid) px-2.5 text-[13px] text-gray-12"
className="h-8 rounded-(--radius-2) border border-border bg-transparent px-2.5 text-[13px] text-gray-12"
onChange={(e) => {
if (!e.target.value) return;
setRecurring(frequency, e.target.value, weekday);
Expand All @@ -448,7 +448,7 @@ function ScheduleTriggerFields({
<input
type="datetime-local"
disabled={disabled}
className="h-8 rounded-(--radius-2) border border-border bg-(--color-panel-solid) px-2.5 text-[13px] text-gray-12"
className="h-8 rounded-(--radius-2) border border-border bg-transparent px-2.5 text-[13px] text-gray-12"
value={config.run_at ? toDatetimeLocal(config.run_at) : ""}
onChange={(e) =>
onChange({
Expand Down
37 changes: 37 additions & 0 deletions packages/ui/src/features/loops/components/LoopsEmptyState.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Theme } from "@radix-ui/themes";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { LoopsEmptyState } from "./LoopsEmptyState";

describe("LoopsEmptyState", () => {
it("starts loop creation from the primary CTA", async () => {
const onCreate = vi.fn();
render(
<Theme>
<LoopsEmptyState onCreate={onCreate} />
</Theme>,
);

await userEvent.click(
screen.getByRole("button", { name: "Create a loop" }),
);

expect(onCreate).toHaveBeenCalledOnce();
});

it("disables creation when the project reached its loop limit", () => {
render(
<Theme>
<LoopsEmptyState
onCreate={vi.fn()}
disabledReason="This project reached its loop limit."
/>
</Theme>,
);

expect(
screen.getByRole("button", { name: "Create a loop" }),
).toBeDisabled();
});
});
112 changes: 68 additions & 44 deletions packages/ui/src/features/loops/components/LoopsEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArrowSquareOutIcon } from "@phosphor-icons/react";
import { ArrowSquareOutIcon, PlusIcon } from "@phosphor-icons/react";
import { loopHog } from "@posthog/ui/assets/hedgehogs";
import { Button } from "@posthog/ui/primitives/Button";
import { openUrlInBrowser } from "@posthog/ui/utils/browser";
Expand All @@ -15,51 +15,75 @@ const GETTING_STARTED_STEPS = [

/** The illustrated getting-started card shown when there are no loops yet. `contextName`
* tweaks the copy for a context's Loops tab. */
export function LoopsEmptyState({ contextName }: { contextName?: string }) {
export function LoopsEmptyState({
contextName,
onCreate,
disabledReason,
}: {
contextName?: string;
onCreate: () => void;
disabledReason?: string | null;
}) {
return (
<Flex
align="center"
className="rounded-(--radius-3) border border-gray-6 border-dashed px-8 py-8"
>
<Flex justify="center" className="w-2/5 shrink-0">
<img src={loopHog} alt="" className="h-auto w-52 object-contain" />
</Flex>
<Flex direction="column" align="start" gap="4" className="min-w-0 flex-1">
<Flex direction="column" gap="1">
<Text className="font-semibold text-[16px] text-gray-12">
{contextName
? `Create a loop for #${contextName}`
: "Create your first loop"}
</Text>
<Text className="text-[13px] text-gray-11 leading-relaxed">
Set it up once and it keeps running on its own, even with your
laptop closed.
</Text>
<div className="@container">
<div className="flex @min-[720px]:flex-row flex-col items-center @min-[720px]:gap-0 gap-6 rounded-(--radius-3) border border-gray-6 border-dashed @min-[720px]:px-8 px-5 py-8">
<Flex justify="center" className="@min-[720px]:w-2/5 w-full shrink-0">
<img src={loopHog} alt="" className="h-auto w-52 object-contain" />
</Flex>
<div className="flex flex-col gap-2">
{GETTING_STARTED_STEPS.map((step, index) => (
<div key={step} className="flex items-center gap-2.5">
<Flex
align="center"
justify="center"
className="size-5 shrink-0 rounded-full border border-(--gray-7) font-medium text-[11px] text-gray-11"
>
{index + 1}
</Flex>
<Text className="text-[13px] text-gray-11">{step}</Text>
</div>
))}
</div>
<Button
variant="outline"
color="gray"
size="2"
onClick={() => void openUrlInBrowser(LOOPS_DOCS_URL)}
<Flex
direction="column"
align="start"
gap="4"
className="min-w-0 flex-1"
>
Learn more
<ArrowSquareOutIcon size={14} />
</Button>
</Flex>
</Flex>
<Flex direction="column" gap="1">
<Text className="font-semibold text-[16px] text-gray-12">
{contextName
? `Create a loop for #${contextName}`
: "Create your first loop"}
</Text>
<Text className="text-[13px] text-gray-11 leading-relaxed">
Set it up once and it keeps running on its own, even with your
laptop closed.
</Text>
</Flex>
<div className="flex flex-col gap-2">
{GETTING_STARTED_STEPS.map((step, index) => (
<div key={step} className="flex items-center gap-2.5">
<Flex
align="center"
justify="center"
className="size-5 shrink-0 rounded-full border border-(--gray-7) font-medium text-[11px] text-gray-11"
>
{index + 1}
</Flex>
<Text className="text-[13px] text-gray-11">{step}</Text>
</div>
))}
</div>
<Flex gap="2" wrap="wrap">
<Button
variant="solid"
size="2"
onClick={onCreate}
disabled={disabledReason != null}
disabledReason={disabledReason}
>
<PlusIcon size={14} />
Create a loop
</Button>
<Button
variant="outline"
color="gray"
size="2"
onClick={() => void openUrlInBrowser(LOOPS_DOCS_URL)}
>
Learn more
<ArrowSquareOutIcon size={14} />
</Button>
</Flex>
</Flex>
</div>
</div>
);
}
Loading
Loading