Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/validator": "^13.15.3",
"@vercel/og": "^0.0.20",
"canvas-confetti": "^1.9.3",
"capture-node": "^2.2.0",
"chrono-node": "^2.7.6",
"classnames": "^2.3.1",
"easymde": "^2.18.0",
Expand Down
138 changes: 80 additions & 58 deletions apps/web/pages/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PageType, PageTypeToLabel } from "@changes-page/supabase/types/page";
import { PlusIcon, UserGroupIcon } from "@heroicons/react/solid";
import classNames from "classnames";
import { InferGetServerSidePropsType } from "next";
import type { InferGetServerSidePropsType } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, type JSX } from "react";
import { type JSX, useEffect } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { PrimaryRouterButton } from "../../components/core/buttons.component";
import { notifyError } from "../../components/core/toast.component";
Expand All @@ -13,6 +13,7 @@ import AuthLayout from "../../components/layout/auth-layout.component";
import Page from "../../components/layout/page.component";
import Changelog from "../../components/marketing/changelog";
import { ROUTES } from "../../data/routes.data";
import { getPageScreenshotUrl } from "../../utils/capture";
import { getAppBaseURL } from "../../utils/helpers";
import { withSupabase } from "../../utils/supabase/withSupabase";
import { useUserData } from "../../utils/useUser";
Expand All @@ -33,16 +34,26 @@ export const getServerSideProps = withSupabase(async (_, { supabase }) => {
)
.order("updated_at", { ascending: false });

const screenshots = pages.map((page) =>
getPageScreenshotUrl(
page.page_settings?.custom_domain
? `https://${page.page_settings.custom_domain}`
: `https://${page.url_slug}.changes.page`
)
);

return {
props: {
pages: pages ?? [],
screenshots,
error,
},
};
});

export default function Pages({
pages,
screenshots,
error,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { billingDetails, user } = useUserData();
Expand Down Expand Up @@ -80,7 +91,7 @@ export default function Pages({
message="Get started by creating your first page."
buttonLink={
billingDetails?.has_active_subscription
? `/pages/new`
? "/pages/new"
: `/api/billing/redirect-to-checkout?return_url=${getAppBaseURL()}/pages`
}
buttonLabel={
Expand All @@ -107,69 +118,79 @@ export default function Pages({
{pages.length ? (
<div className="overflow-hidden shadow rounded-md bg-white dark:bg-gray-900 border dark:border-gray-800">
<ul className="divide-y divide-gray-200 dark:divide-gray-800">
{pages.map((page) => (
{pages.map((page, idx) => (
<li
key={page.id}
className="relative group hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-200"
>
<div className="flex items-center justify-between p-6">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<span
className={classNames(
page.type === PageType.announcements &&
"bg-blue-100 dark:bg-blue-700",
page.type === PageType.changelogs &&
"bg-teal-100 dark:bg-teal-700",
page.type === PageType.releases &&
"bg-rose-100 dark:bg-rose-700",
page.type === PageType.updates &&
"bg-amber-100 dark:bg-amber-700",
page.type === PageType.announcements &&
"text-blue-500 dark:text-blue-100",
page.type === PageType.changelogs &&
"text-teal-500 dark:text-teal-100",
page.type === PageType.releases &&
"text-rose-500 dark:text-rose-100",
page.type === PageType.updates &&
"text-amber-500 dark:text-amber-100",
"inline-flex px-2 py-1 text-xs font-bold rounded-md"
)}
>
{PageTypeToLabel[page.type]}
</span>
{page.teams && page.user_id !== user?.id && (
<div className="flex items-center gap-1 text-gray-500 dark:text-gray-400">
<UserGroupIcon className="h-4 w-4" />
<span className="text-xs font-medium">
Editor ({page.teams.name})
</span>
</div>
)}
<div className="flex items-center gap-4">
<div className="flex-shrink-0 hidden sm:block">
<img
className="w-48 h-18 object-cover rounded-md border border-gray-200 dark:border-gray-700"
src={screenshots[idx]}
alt={`Screenshot of ${page.title}`}
loading="lazy"
/>
</div>
<div>
<h3 className="text-lg font-bold tracking-tight text-gray-900 dark:text-white">
<Link
href={`${ROUTES.PAGES}/${page.id}`}
className="focus:outline-none hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors duration-200"
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<span
className={classNames(
page.type === PageType.announcements &&
"bg-blue-100 dark:bg-blue-700",
page.type === PageType.changelogs &&
"bg-teal-100 dark:bg-teal-700",
page.type === PageType.releases &&
"bg-rose-100 dark:bg-rose-700",
page.type === PageType.updates &&
"bg-amber-100 dark:bg-amber-700",
page.type === PageType.announcements &&
"text-blue-500 dark:text-blue-100",
page.type === PageType.changelogs &&
"text-teal-500 dark:text-teal-100",
page.type === PageType.releases &&
"text-rose-500 dark:text-rose-100",
page.type === PageType.updates &&
"text-amber-500 dark:text-amber-100",
"inline-flex px-2 py-1 text-xs font-bold rounded-md"
)}
>
<span
className="absolute inset-0"
aria-hidden="true"
/>
{page.title}
</Link>
</h3>
{page.description && (
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
{page.description}
{PageTypeToLabel[page.type]}
</span>
{page.teams && page.user_id !== user?.id && (
<div className="flex items-center gap-1 text-gray-500 dark:text-gray-400">
<UserGroupIcon className="h-4 w-4" />
<span className="text-xs font-medium">
Editor ({page.teams.name})
</span>
</div>
)}
</div>
<div>
<h3 className="text-lg font-bold tracking-tight text-gray-900 dark:text-white">
<Link
href={`${ROUTES.PAGES}/${page.id}`}
className="focus:outline-none hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors duration-200"
>
<span
className="absolute inset-0"
aria-hidden="true"
/>
{page.title}
</Link>
</h3>
{page.description && (
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
{page.description}
</p>
)}
<p className="mt-2 text-xs text-gray-400 dark:text-gray-500">
{page.page_settings?.custom_domain
? page.page_settings.custom_domain
: `${page.url_slug}.changes.page`}
</p>
)}
<p className="mt-2 text-xs text-gray-400 dark:text-gray-500">
{page.page_settings?.custom_domain
? page.page_settings.custom_domain
: `${page.url_slug}.changes.page`}
</p>
</div>
</div>
</div>
<div className="ml-4 flex-shrink-0">
Expand All @@ -178,6 +199,7 @@ export default function Pages({
fill="currentColor"
viewBox="0 0 20 20"
>
<title>Go to page</title>
<path
fillRule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
Expand Down
14 changes: 14 additions & 0 deletions apps/web/utils/capture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Capture } from "capture-node";

export function getPageScreenshotUrl(url: string) {
const capture = new Capture(
process.env.CAPTURE_API_KEY,
process.env.CAPTURE_API_SECRET
);

return capture.buildImageUrl(url, {
vw: 1280,
vh: 720,
scaleFactor: 1.5,
});
}
35 changes: 35 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.