Skip to content

Commit

Permalink
Merge pull request #202 from novaforgood/Max/email-whitelist
Browse files Browse the repository at this point in the history
Max/email whitelist
  • Loading branch information
legitmaxwu authored Jul 7, 2024
2 parents 7865468 + 8ddcf98 commit 64a7ce6
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- INSERT INTO profile_role (value, description) VALUES
-- ('Archived', 'Profile cannot access the space, but can modify profile settings, and regain access via invite link.');
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INSERT INTO profile_role (value, description) VALUES
('Archived', 'Profile cannot access the space, but can modify profile settings, and regain access via invite link.');
62 changes: 62 additions & 0 deletions web/components/ArchivedModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ReactNode, useState } from "react";

import Link from "next/link";
import { useRouter } from "next/router";

import { Profile_Role_Enum, useUserQuery } from "../generated/graphql";
import { useCurrentProfile } from "../hooks/useCurrentProfile";
import { useCurrentSpace } from "../hooks/useCurrentSpace";

import { Button, Modal, Text } from "./atomic";

const SAFE_LINKS = ["join", "create-profile"];

export function ArchivedModal() {
const router = useRouter();
const { currentProfile, currentProfileHasRole } = useCurrentProfile();
const { currentSpace } = useCurrentSpace();

const [{ data: ownerUserData, fetching }, refetchUserData] = useUserQuery({
variables: { id: currentSpace?.owner_id ?? "" },
});

// http://localhost:3000/space/uchicago-buddy-up/join/6d59aa5a-7b58-4ff3-a810-23602d571e80

const isArchived = currentProfileHasRole(Profile_Role_Enum.Archived);

const pathSegment = router.asPath.split("/")[3];
const showModal = !!isArchived && !SAFE_LINKS.includes(pathSegment);

return (
<Modal isOpen={showModal} onClose={() => {}}>
<div className="rounded-md bg-white">
<div className="rounded-md bg-white px-8 pt-16 pb-8">
<div className="flex w-96 flex-col items-center">
<Text variant="heading4">Your profile has been archived.</Text>
<div className="h-8"></div>
<Text>
Your admin has archived your profile, meaning you can no longer
access or participate in this space, <i>unless</i> you join again
with a new invite link.
</Text>
<div className="h-4"></div>
<Text>
If you believe that this was a mistake, please contact the space
owner at {ownerUserData?.user_by_pk?.email}.
</Text>
<div className="h-12"></div>
<Link href="/" passHref>
<Button rounded>Back to home</Button>
</Link>
<div className="h-16"></div>
<img
draggable={false}
src="/assets/create-profile/many_trees.svg"
alt="many trees"
/>
</div>
</div>
</div>
</Modal>
);
}
8 changes: 4 additions & 4 deletions web/components/EditProfileListing.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { useDisclosure } from "@mantine/hooks";
import toast from "react-hot-toast";

import { Profile_Role_Enum, useProfileImageQuery } from "../generated/graphql";
import { BxMessageDetail } from "../generated/icons/regular";
import { BxsPencil } from "../generated/icons/solid";
import { useCurrentProfile } from "../hooks/useCurrentProfile";
import { useCurrentSpace } from "../hooks/useCurrentSpace";
import { useSpaceAttributes } from "../hooks/useSpaceAttributes";
import { useUserData } from "../hooks/useUserData";
import { handleError } from "../lib/error";

import { Button, Text } from "./atomic";
import { CheckBox } from "./atomic/CheckBox";
import { EditButton } from "./common/EditButton";
import { ProfileImage } from "./common/ProfileImage";
import { EditHeadline } from "./edit-profile/EditHeadline";
Expand All @@ -18,10 +22,6 @@ import { EditResponse } from "./edit-profile/EditResponse";
import { ProfileSocialsDisplay } from "./edit-socials-info/ProfileSocialsDisplay";
import { ProfileSocialsModal } from "./edit-socials-info/ProfileSocialsModal";
import PublishedToggleSwitch from "./PublishedToggleSwitch";
import { useSpaceAttributes } from "../hooks/useSpaceAttributes";
import { CheckBox } from "./atomic/CheckBox";
import { handleError } from "../lib/error";
import toast from "react-hot-toast";

function EditProfileImage() {
const { currentProfile } = useCurrentProfile();
Expand Down
1 change: 1 addition & 0 deletions web/components/admin/ChatIntroductions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { getCurrentUser } from "../../lib/firebase";
import { getFullNameOfUser } from "../../lib/user";
import { Button, Select, Text } from "../atomic";
import { Table } from "../common/Table";

import { ChatIntroResults } from "./ChatIntroResults";

type ChatIntro = ChatIntrosQuery["chat_intro"][number];
Expand Down
1 change: 1 addition & 0 deletions web/components/admin/CopyText.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useClipboard } from "@mantine/hooks";
import classNames from "classnames";
import toast from "react-hot-toast";

import { Button } from "../atomic";

export function CopyText({
Expand Down
4 changes: 2 additions & 2 deletions web/components/admin/SetPrivacySettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
useUpdateSpaceMutation,
} from "../../generated/graphql";
import { useCurrentSpace } from "../../hooks/useCurrentSpace";
import { useSpaceAttributes } from "../../hooks/useSpaceAttributes";
import { useSaveChangesState } from "../../hooks/useSaveChangesState";
import { useSpaceAttributes } from "../../hooks/useSpaceAttributes";
import { SpaceAttributes } from "../../lib/spaceAttributes";
import { CheckBox } from "../atomic/CheckBox";
import { TextInput } from "../inputs/TextInput";
import { SpaceAttributes } from "../../lib/spaceAttributes";

export function SetPrivacySettings() {
const { currentSpace } = useCurrentSpace();
Expand Down
1 change: 1 addition & 0 deletions web/components/admin/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const MAP_ROLE_TO_TITLE: Record<Profile_Role_Enum, string> = {
[Profile_Role_Enum.Member]: "View-only Member",
[Profile_Role_Enum.MemberWhoCanList]: "Full Member (can list profile)",
[Profile_Role_Enum.Banned]: "Banned",
[Profile_Role_Enum.Archived]: "Archived",
};

export const ROLE_SELECT_OPTIONS = Object.values(Profile_Role_Enum).map(
Expand Down
1 change: 1 addition & 0 deletions web/components/atomic/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReactNode } from "react";

import { Text } from ".";

export function CheckBox(
Expand Down
4 changes: 3 additions & 1 deletion web/components/atomic/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Popover as HeadlessPopover, Transition } from "@headlessui/react";
import { Fragment } from "react";

import { Popover as HeadlessPopover, Transition } from "@headlessui/react";

import { BxChevronDown } from "../../generated/icons/regular";

export function Popover(props: {
Expand Down
2 changes: 1 addition & 1 deletion web/components/space-homepage/SpaceLandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {
} from "../../generated/graphql";
import { useCurrentProfile } from "../../hooks/useCurrentProfile";
import { useCurrentSpace } from "../../hooks/useCurrentSpace";
import { useSpaceAttributes } from "../../hooks/useSpaceAttributes";
import { useQueryParam } from "../../hooks/useQueryParam";
import { useSpaceAttributes } from "../../hooks/useSpaceAttributes";
import { useUserData } from "../../hooks/useUserData";
import {
adminBypassAtom,
Expand Down
6 changes: 6 additions & 0 deletions web/graphql-admin.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -44555,6 +44555,12 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Archived",
"description": "Profile cannot access the space, but can modify profile settings, and regain access via invite link.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Banned",
"description": "Profile is banned from space",
Expand Down
6 changes: 6 additions & 0 deletions web/graphql.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -25264,6 +25264,12 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Archived",
"description": "Profile cannot access the space, but can modify profile settings, and regain access via invite link.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "Banned",
"description": "Profile is banned from space",
Expand Down
2 changes: 2 additions & 0 deletions web/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { CustomPage } from "../types";
import type { AppProps } from "next/app";

import "../styles/globals.css";
import { ArchivedModal } from "../components/ArchivedModal";

type CustomAppProps = AppProps & {
Component: CustomPage;
Expand Down Expand Up @@ -206,6 +207,7 @@ function App({ Component, pageProps }: CustomAppProps) {
<Metadata />
{getLayout(<Component {...pageProps} />)}
{Component.showFooter !== false && <Footer />}
<ArchivedModal />
</Suspense>
);
}
Expand Down
101 changes: 69 additions & 32 deletions web/pages/api/invite/joinProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { z } from "zod";

import {
executeGetInviteLinkQuery,
executeGetProfileQuery,
executeGetProfilesQuery,
executeGetSpaceQuery,
executeInsertProfileMutation,
executeUpdateProfileMutation,
executeUpdateProfileRoleRowMutation,
Profile_Constraint,
Profile_Role_Enum,
Profile_Update_Column,
Expand Down Expand Up @@ -71,45 +75,78 @@ export default applyMiddleware({
}
}

// Determine profile metadata
const shouldEnableChatIntros = !!attributes.optUsersInToMatchesByDefault;

// If not expired, accept invite link and add user to program
const listingEnabled =
inviteLink.type === Space_Invite_Link_Type_Enum.MemberListingEnabled
? true
: false;
const { error: insertError, data: insertData } =
await executeInsertProfileMutation({
data: {
user_id: req.token.uid,
space_id: inviteLink.space_id,
attributes: {
enableChatIntros: shouldEnableChatIntros,

const newProfileRole = listingEnabled
? Profile_Role_Enum.MemberWhoCanList
: Profile_Role_Enum.Member;

// Check for existing profile
const rawProfileData = await executeGetProfilesQuery({
where: {
user_id: { _eq: req.token.uid },
space_id: { _eq: inviteLink.space_id },
},
});
const existingProfile = rawProfileData.data?.profile[0];
if (existingProfile) {
// If user profile exists but is archived, unarchive it.
const archivedRoleRowEntry = existingProfile.profile_roles.find(
(role) => role.profile_role === Profile_Role_Enum.Archived
);
if (archivedRoleRowEntry) {
await executeUpdateProfileRoleRowMutation({
row_id: archivedRoleRowEntry.id,
profile_role: newProfileRole,
});

const response = makeApiSuccess({
newProfileId: existingProfile.id,
inviteLink: inviteLink,
});
res.status(response.code).json(response);
return;
} else {
throw makeApiFail("Profile already exists and is not archived.");
}
} else {
// If not expired, accept invite link and add user to program
const { error: insertError, data: insertData } =
await executeInsertProfileMutation({
data: {
user_id: req.token.uid,
space_id: inviteLink.space_id,
attributes: {
enableChatIntros: shouldEnableChatIntros,
},
profile_roles: {
data: [
{
profile_role: newProfileRole,
},
],
},
},
profile_roles: {
data: [
{
profile_role: listingEnabled
? Profile_Role_Enum.MemberWhoCanList
: Profile_Role_Enum.Member,
},
],
on_conflict: {
constraint: Profile_Constraint.ProfilesPkey,
update_columns: [Profile_Update_Column.Id],
},
},
on_conflict: {
constraint: Profile_Constraint.ProfilesPkey,
update_columns: [Profile_Update_Column.Id],
},
});
if (insertError) {
throw makeApiError(insertError.message);
}
});
if (insertError) {
throw makeApiError(insertError.message);
}

const newProfileId = insertData?.insert_profile_one?.id;
if (!newProfileId) {
throw makeApiError("Failed to insert new profile");
}
const newProfileId = insertData?.insert_profile_one?.id;
if (!newProfileId) {
throw makeApiError("Failed to insert new profile");
}

const response = makeApiSuccess({ newProfileId, inviteLink: inviteLink });
res.status(response.code).json(response);
const response = makeApiSuccess({ newProfileId, inviteLink: inviteLink });
res.status(response.code).json(response);
}
});
14 changes: 12 additions & 2 deletions web/pages/api/services/sendAnnouncement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,18 @@ export default applyMiddleware({
const { space, user, profile_listing } = callerProfile ?? {};

const { data: emailQueryData } = await executeEmailsBySpaceIdQuery({
_space_id: space?.id || "",
_user_attr_filter: { disableEmailNotifications: true },
space_id: space?.id || "",
where: {
space_id: { _eq: space?.id },
flattened_profile_roles: {
profile_role: { _eq: Profile_Role_Enum.Member },
},
user: {
_not: {
attributes: { _contains: { disableEmailNotifications: true } },
},
},
},
});
const emails = emailQueryData?.profile
// Filter out deleted profiles
Expand Down
1 change: 0 additions & 1 deletion web/pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Input } from "../components/atomic/Input";
import { TextInput } from "../components/inputs/TextInput";
import { ImageSidebar } from "../components/layout/ImageSidebar";
import { TwoThirdsPageLayout } from "../components/layout/TwoThirdsPageLayout";
import { useUserQuery } from "../generated/graphql";
import { BxlApple, BxlGoogle } from "../generated/icons/logos";
import { useIsLoggedIn } from "../hooks/useIsLoggedIn";
import { useRedirectUsingQueryParam } from "../hooks/useRedirectUsingQueryParam";
Expand Down
Loading

0 comments on commit 64a7ce6

Please sign in to comment.