Skip to content

Add Stripe deactivate link functionality to UI #170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 23, 2025
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
4 changes: 0 additions & 4 deletions src/api/functions/auditLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
entry,
}: AuditLogParams) {
if (process.env.DISABLE_AUDIT_LOG && process.env.RunEnvironment === "dev") {
console.log(`Audit log entry: ${JSON.stringify(entry)}`);

Check warning on line 35 in src/api/functions/auditLog.ts

View workflow job for this annotation

GitHub Actions / Run Unit Tests

Unexpected console statement
return;
}
const safeDynamoClient =
Expand All @@ -56,10 +56,6 @@
}: {
entry: AuditLogEntry;
}): TransactWriteItem {
if (process.env.DISABLE_AUDIT_LOG && process.env.RunEnvironment === "dev") {
console.log(`Audit log entry: ${JSON.stringify(entry)}`);
return {};
}
const item = buildMarshalledAuditLogItem(entry);
return {
Put: {
Expand Down
4 changes: 4 additions & 0 deletions src/ui/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type KnownGroups = {

export type ConfigType = {
AadValidClientId: string;
LinkryPublicUrl: string;
ServiceConfiguration: Record<ValidServices, ServiceConfiguration>;
KnownGroupMappings: KnownGroups;
};
Expand All @@ -43,6 +44,7 @@ type EnvironmentConfigType = {
const environmentConfig: EnvironmentConfigType = {
"local-dev": {
AadValidClientId: "d1978c23-6455-426a-be4d-528b2d2e4026",
LinkryPublicUrl: "go.aws.qa.acmuiuc.org",
ServiceConfiguration: {
core: {
friendlyName: "Core Management Service (NonProd)",
Expand Down Expand Up @@ -75,6 +77,7 @@ const environmentConfig: EnvironmentConfigType = {
},
dev: {
AadValidClientId: "d1978c23-6455-426a-be4d-528b2d2e4026",
LinkryPublicUrl: "go.aws.qa.acmuiuc.org",
ServiceConfiguration: {
core: {
friendlyName: "Core Management Service (NonProd)",
Expand Down Expand Up @@ -107,6 +110,7 @@ const environmentConfig: EnvironmentConfigType = {
},
prod: {
AadValidClientId: "43fee67e-e383-4071-9233-ef33110e9386",
LinkryPublicUrl: "go.acm.illinois.edu",
ServiceConfiguration: {
core: {
friendlyName: "Core Management Service",
Expand Down
15 changes: 3 additions & 12 deletions src/ui/pages/linkry/LinkShortener.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,16 @@ import {
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { notifications } from "@mantine/notifications";
import {
IconCancel,
IconCross,
IconEdit,
IconPlus,
IconTrash,
} from "@tabler/icons-react";
import dayjs from "dayjs";
import { IconCancel, IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { z } from "zod";

import { capitalizeFirstLetter } from "./ManageLink.page.js";
import FullScreenLoader from "@ui/components/AuthContext/LoadingScreen";
import { AuthGuard } from "@ui/components/AuthGuard";
import { useApi } from "@ui/util/api";
import { AppRoles } from "@common/roles.js";
import { wrap } from "module";
import { linkRecord } from "@common/types/linkry.js";
import { getRunEnvironmentConfig } from "@ui/config.js";

const wrapTextStyle: React.CSSProperties = {
wordWrap: "break-word",
Expand Down Expand Up @@ -82,7 +73,7 @@ export const LinkShortener: React.FC = () => {
}}
>
<Table.Td style={wrapTextStyle}>
https://go.acm.illinois.edu/{link.slug}
{getRunEnvironmentConfig().LinkryPublicUrl}/{link.slug}
</Table.Td>
<Table.Td style={wrapTextStyle}>
<Anchor href={link.redirect} target="_blank" size="sm">
Expand Down
7 changes: 4 additions & 3 deletions src/ui/pages/linkry/ManageLink.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import { IconDeviceFloppy } from "@tabler/icons-react";
import { LinkryGroupUUIDToGroupNameMap } from "@common/config";
import FullScreenLoader from "@ui/components/AuthContext/LoadingScreen";
import { LINKRY_MAX_SLUG_LENGTH } from "@common/types/linkry";
import { getRunEnvironmentConfig } from "@ui/config";

export function capitalizeFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}

const baseUrl = "go.acm.illinois.edu";
const baseUrl = getRunEnvironmentConfig().LinkryPublicUrl;
const slugRegex = new RegExp("^(https?://)?[a-zA-Z0-9-._/]*$");
const urlRegex = new RegExp("^https?://[a-zA-Z0-9-._/?=&+:]*$");

Expand Down Expand Up @@ -205,7 +206,7 @@ export const ManageLinkPage: React.FC = () => {
rightSectionWidth="150px"
leftSection={
<Button variant="outline" mr="auto" size="auto">
{baseUrl || "go.acm.illinois.edu"}
{baseUrl}
</Button>
}
rightSection={
Expand All @@ -216,7 +217,7 @@ export const ManageLinkPage: React.FC = () => {
color="blue"
onClick={generateRandomSlug}
>
Generate Random URL
Random
</Button>
)
}
Expand Down
75 changes: 64 additions & 11 deletions src/ui/pages/stripe/CurrentLinks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ vi.mock("@ui/components/AuthContext", async () => {

describe("StripeCurrentLinksPanel Tests", () => {
const getLinksMock = vi.fn();
const deactivateLinkMock = vi.fn();

const renderComponent = async () => {
await act(async () => {
Expand All @@ -26,7 +27,10 @@ describe("StripeCurrentLinksPanel Tests", () => {
withCssVariables
forceColorScheme="light"
>
<StripeCurrentLinksPanel getLinks={getLinksMock} />
<StripeCurrentLinksPanel
getLinks={getLinksMock}
deactivateLink={deactivateLinkMock}
/>
</MantineProvider>
</MemoryRouter>,
);
Expand Down Expand Up @@ -151,10 +155,10 @@ describe("StripeCurrentLinksPanel Tests", () => {
expect(checkbox).not.toBeChecked();
});

it("triggers deactivation when clicking deactivate button", async () => {
it("triggers deactivation when clicking deactivate button with all active", async () => {
getLinksMock.mockResolvedValue([
{
id: "1",
id: "abc123",
active: true,
invoiceId: "INV-001",
invoiceAmountUsd: 5000,
Expand All @@ -163,7 +167,6 @@ describe("StripeCurrentLinksPanel Tests", () => {
link: "http://example.com",
},
]);
const notificationsMock = vi.spyOn(notifications, "show");
await renderComponent();

const checkbox = screen.getByLabelText("Select row");
Expand All @@ -172,14 +175,64 @@ describe("StripeCurrentLinksPanel Tests", () => {
const deactivateButton = await screen.findByText(/Deactivate 1 link/);
await userEvent.click(deactivateButton);

expect(notificationsMock).toHaveBeenCalledWith(
expect.objectContaining({
title: "Feature not available",
message: "Coming soon!",
color: "yellow",
}),
expect(deactivateLinkMock).toHaveBeenCalledWith("abc123");
});

it("doesn't show deactivation when clicking deactivate button on non-active", async () => {
getLinksMock.mockResolvedValue([
{
id: "abc123",
active: false,
invoiceId: "INV-001",
invoiceAmountUsd: 5000,
userId: "user@example.com",
createdAt: "2024-02-01",
link: "http://example.com",
},
]);
await renderComponent();

const checkbox = screen.getByLabelText("Select row");
await userEvent.click(checkbox);

const deactivateButton = screen.queryByText(/Deactivate 1 link/);
expect(deactivateButton).not.toBeInTheDocument();

expect(deactivateLinkMock).not.toHaveBeenCalled();
});

it("only offers to deactivate one link when there is one active and one non-active", async () => {
getLinksMock.mockResolvedValue([
{
id: "abc123",
active: false,
invoiceId: "INV-001",
invoiceAmountUsd: 5000,
userId: "user@example.com",
createdAt: "2024-02-01",
link: "http://example.com",
},
{
id: "def456",
active: true,
invoiceId: "INV-002",
invoiceAmountUsd: 5000,
userId: "user@example.com",
createdAt: "2024-02-01",
link: "http://example.com",
},
]);
await renderComponent();

const checkboxes = screen.getAllByLabelText("Select row");
checkboxes.map(async (x) => await userEvent.click(x));

const deactivateButton = await screen.findByText(
/Deactivate 1 active link/,
);
await userEvent.click(deactivateButton);

notificationsMock.mockRestore();
expect(deactivateLinkMock).toHaveBeenCalledTimes(1);
expect(deactivateLinkMock).toHaveBeenNthCalledWith(1, "def456");
});
});
Loading
Loading