Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
05442bd
Add teams page
arjunkomath Feb 8, 2025
9828f15
Refactor page settings to support team-based access and remove user_i…
arjunkomath Feb 8, 2025
294fa33
Enhance team management with owner and member views, page sharing, an…
arjunkomath Feb 8, 2025
1c2e044
Implement team invitation with email notifications
arjunkomath Feb 9, 2025
92fa63e
Add team invitation acceptance flow and update team views
arjunkomath Feb 9, 2025
a368f30
Remove subscription check for new posts
arjunkomath Feb 9, 2025
5625165
Merge pull request #59 from techulus/feat/teams
arjunkomath Feb 9, 2025
c482668
feat: Improve team invitation API with input validation and email pay…
arjunkomath Feb 9, 2025
38c2af0
fix: Update middleware
arjunkomath Feb 9, 2025
28b53b6
Remove unused revalidation logic
arjunkomath Feb 9, 2025
6df4d91
Add audit logs
arjunkomath Feb 9, 2025
98171f4
Make UI nicer
arjunkomath Feb 9, 2025
3fdbe7f
Show empty values in audit logs
arjunkomath Feb 9, 2025
58c8a18
Minor UI fixes, tracking teams feature
arjunkomath Feb 10, 2025
89dec93
Fix team button for non-pro users
arjunkomath Feb 10, 2025
038944e
Fix page title height on mobile
arjunkomath Feb 10, 2025
4994678
Fix start free trial button on teams page
arjunkomath Feb 10, 2025
9577554
Move team data fetch call to server
arjunkomath Feb 10, 2025
a4e8cc2
Add progress bar
arjunkomath Feb 10, 2025
afc7641
Add named function to progress bar component
arjunkomath Feb 10, 2025
3b2b1ee
Handle edge cases
arjunkomath Feb 11, 2025
a2f41bf
Fix showing team member details
arjunkomath Feb 11, 2025
291d16a
Bug fixes
arjunkomath Feb 12, 2025
0d38e1d
Fix page check
arjunkomath Feb 13, 2025
5883b44
Show reactions in page details view
arjunkomath Feb 14, 2025
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
3 changes: 0 additions & 3 deletions apps/page/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ This folder contains the pages app which renders user changelog pages.
### Environment Variables

```
# Revalidation
REVALIDATE_TOKEN=

# Supabase details from https://app.supabase.io
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
Expand Down
9 changes: 1 addition & 8 deletions apps/page/components/reactions.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { IReactions } from "@changes-page/supabase/types/page";
import { Transition } from "@headlessui/react";
import classNames from "classnames";
import { useCallback, useEffect, useState } from "react";
import { httpGet, httpPost } from "../utils/http";

export type IReactions = {
thumbs_up?: number;
thumbs_down?: number;
rocket?: number;
sad?: number;
heart?: number;
};

const ReactionsCounter = ({
postId,
aggregate,
Expand Down
7 changes: 0 additions & 7 deletions apps/page/pages/_sites/[site]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,6 @@ export default function Index({
);
}

// export async function getStaticPaths() {
// return {
// paths: [],
// fallback: "blocking",
// };
// }

export async function getServerSideProps({
params: { site },
}: {
Expand Down
37 changes: 0 additions & 37 deletions apps/page/pages/api/revalidate.ts

This file was deleted.

4 changes: 0 additions & 4 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ This folder contains the dashboard app for the project and all marketing pages.
```
NEXT_PUBLIC_PAGES_DOMAIN=http://localhost:3000

# Revalidation
REVALIDATE_ENDPOINT=
REVALIDATE_TOKEN=

# Supabase details from https://app.supabase.io
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
Expand Down
18 changes: 18 additions & 0 deletions apps/web/components/core/buttons.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export function PrimaryButton({
className,
disabled,
keyboardShortcut,
upgradeRequired,
}: {
label: string | JSX.Element;
type?: "button" | "submit" | "reset";
Expand All @@ -108,7 +109,24 @@ export function PrimaryButton({
disabled?: boolean;
className?: string;
keyboardShortcut?: string;
upgradeRequired?: boolean;
}) {
if (upgradeRequired) {
return (
<button
className={classNames(
"inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-gray-400 cursor-not-allowed",
className
)}
title="Please upgrade to a paid plan"
disabled
>
<LockClosedIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
{label}
</button>
);
}

return (
<button
className={classNames(
Expand Down
18 changes: 18 additions & 0 deletions apps/web/components/core/progress-bar.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Router from "next/router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

NProgress.configure({
minimum: 0.3,
easing: "ease",
speed: 500,
showSpinner: false,
});

Router.events.on("routeChangeStart", () => NProgress.start());
Router.events.on("routeChangeComplete", () => NProgress.done());
Router.events.on("routeChangeError", () => NProgress.done());

export default function ProgressBar() {
return null;
}
244 changes: 244 additions & 0 deletions apps/web/components/dialogs/manage-team-dialog.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import { ITeam } from "@changes-page/supabase/types/page";
import { Dialog, Transition } from "@headlessui/react";
import { PlusIcon } from "@heroicons/react/outline";
import { useFormik } from "formik";
import { useRouter } from "next/router";
import { Fragment, useEffect, useRef } from "react";
import { InferType } from "yup";
import { ROUTES } from "../../data/routes.data";
import { NewTeamSchema } from "../../data/schema";
import { track } from "../../utils/analytics";
import { useUserData } from "../../utils/useUser";
import { InlineErrorMessage } from "../forms/notification.component";

export default function ManageTeamDialog({
open,
setOpen,
team,
onSuccess,
onCancel,
}: {
open: boolean;
setOpen: (open: boolean) => void;
team?: ITeam;
onSuccess: () => void;
onCancel: () => void;
}) {
const router = useRouter();
const cancelButtonRef = useRef(null);
const { supabase, user } = useUserData();

useEffect(() => {
if (open) {
formik.resetForm();
}

if (team) {
formik.setValues({
name: team.name,
image: team.image,
});
}
}, [open, team]);

const formik = useFormik<InferType<typeof NewTeamSchema>>({
initialValues: {
name: "",
image: null,
},
validationSchema: NewTeamSchema,
onSubmit: async (values) => {
if (!user) {
router.push(ROUTES.LOGIN);
return;
}

if (!values.name) {
formik.setFieldError("name", "Name is required");
return;
}

if (team) {
await supabase
.from("teams")
.update({
name: values.name,
image: values.image,
})
.match({ id: team.id });
track("EditTeam");
onSuccess();
} else {
await supabase
.from("teams")
.insert([
{
name: values.name,
image: values.image,
owner_id: user.id,
},
])
.select();
track("CreateTeam");
onSuccess();
}

setOpen(false);
},
});

return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="fixed z-10 inset-0 overflow-y-auto"
initialFocus={cancelButtonRef}
onClose={setOpen}
>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-20 backdrop-blur transition-opacity" />
</Transition.Child>

{/* This element is to trick the browser into centering the modal contents. */}
<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white dark:bg-gray-900 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white dark:bg-gray-900 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-indigo-100 dark:bg-indigo-900 sm:mx-0 sm:h-10 sm:w-10">
<PlusIcon
className="h-6 w-6 text-indigo-600 dark:text-indigo-400"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
<Dialog.Title
as="h3"
className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-50"
>
{team ? "Edit Team" : "Create Team"}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500 dark:text-gray-400">
{team
? "Edit your team details."
: "Create a new team to manage your pages and posts."}
</p>
</div>

<div className="mt-3">
<form
className="space-y-8"
onSubmit={formik.handleSubmit}
>
<div className="mt-2 sm:mt-2 space-y-6 sm:space-y-5">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:pt-5">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 sm:mt-px sm:pt-2">
Name
</label>

<div className="mt-1 sm:col-span-2 sm:mt-0">
<input
type="text"
name="name"
id="name"
className="block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-800 dark:text-gray-100 dark:border-gray-700"
placeholder="My Team"
value={formik.values.name}
onChange={formik.handleChange}
/>

{formik.errors.name && formik.touched.name && (
<div className="mt-2">
<InlineErrorMessage
message={formik.errors.name}
/>
</div>
)}
</div>
</div>

<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 sm:mt-px sm:pt-2">
Image URL
</label>

<div className="mt-1 sm:col-span-2 sm:mt-0">
<input
type="url"
name="image"
id="image"
className="block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-800 dark:text-gray-100 dark:border-gray-700"
placeholder="https://example.com/image.png"
value={formik.values.image || ""}
onChange={formik.handleChange}
/>

{formik.errors.image && formik.touched.image && (
<div className="mt-2">
<InlineErrorMessage
message={formik.errors.image}
/>
</div>
)}
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

<div className="bg-gray-50 dark:bg-gray-900 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm disabled:cursor-not-allowed disabled:bg-gray-600"
disabled={formik.isSubmitting}
onClick={() => {
formik.submitForm();
}}
>
{formik.isSubmitting ? "Saving..." : "Confirm"}
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-700 shadow-sm px-4 py-2 text-base font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
setOpen(false);
onCancel();
}}
ref={cancelButtonRef}
>
Cancel
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
}
Loading