Skip to content

Commit

Permalink
Add server actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucieo committed Sep 21, 2023
1 parent 8da3197 commit 54d4c49
Show file tree
Hide file tree
Showing 19 changed files with 751 additions and 322 deletions.
27 changes: 7 additions & 20 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,25 @@
// https://nextjs.org/docs/api-reference/next.config.js/introduction
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
const { withSentryConfig } = require('@sentry/nextjs');
const { withContentlayer } = require('next-contentlayer')

const { withContentlayer } = require('next-contentlayer');

/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
serverActions: true,
serverComponentsExternalPackages: ['mjml', 'mjml-react'],
},
reactStrictMode: false,
};

module.exports = nextConfig;


const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer(withContentlayer(withSentryConfig(
module.exports,
{
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options

// Suppresses source map uploading logs during build
silent: true,
});

org: 'premier-octet-z6',
project: 'digestclub',
},
{
module.exports = withBundleAnalyzer(
withContentlayer(module.exports, {
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/

Expand All @@ -49,5 +36,5 @@ module.exports = withBundleAnalyzer(withContentlayer(withSentryConfig(

// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
}
)));
})
);
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"metascraper-twitter": "^5.33.7",
"mjml": "^4.13.0",
"mjml-react": "^2.0.8",
"next": "^13.3.0",
"next": "^13.5.2",
"next-auth": "^4.20.1",
"next-connect": "^1.0.0-next.3",
"next-contentlayer": "^0.3.2",
Expand Down Expand Up @@ -82,25 +82,25 @@
"@tailwindcss/typography": "^0.5.9",
"@types/jest": "^29.5.2",
"@types/lodash": "^4.14.194",
"@types/mjml": "^4.7.0",
"@types/mjml": "^4.7.1",
"@types/mjml-react": "^2.0.6",
"@types/node": "^18.14.5",
"@types/nodemailer": "^6.4.7",
"@types/react": "18.0.28",
"@types/react-beautiful-dnd": "^13.1.3",
"@types/react-dom": "18.0.11",
"@types/react": "^18.2.22",
"@types/react-beautiful-dnd": "^13.1.4",
"@types/react-dom": "^18.2.7",
"autoprefixer": "^10.4.14",
"clsx": "^1.2.1",
"cross-env": "^7.0.3",
"eslint": "8.35.0",
"eslint-config-next": "^13.3.0",
"eslint": "^8.49.0",
"eslint-config-next": "^13.5.2",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.7",
"tailwindcss-animate": "^1.0.5",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
"typescript": "^5.2.2"
}
}
53 changes: 53 additions & 0 deletions src/actions/generate-api-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use server';
import db from '@/lib/db';
import * as Sentry from '@sentry/nextjs';
import jwt from 'jsonwebtoken';

interface APIKeyGenerationResult {
error?: {
message: string;
};
data?: {
key: string;
};
}

/**
* @description Generates an write to the database a new API key for the team
* @param teamId a string representing the team id
* @returns
*/
export default async function generateAPIKey(
teamId: string
): Promise<APIKeyGenerationResult> {
if (!process.env.JWT_SECRET) {
return {
error: {
message: 'Internal server error',
},
};
}
try {
const token = jwt.sign({ teamId }, process.env.JWT_SECRET);

await db.team.update({
where: { id: teamId },
data: {
apiKey: token,
},
});

return {
data: {
key: token,
},
};
} catch (err) {
Sentry.captureException(err);
return {
error: {
message: 'Internal server error',
},
};
}
}
2 changes: 1 addition & 1 deletion src/app/(routes)/teams/[teamSlug]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export default async function Layout({
redirect(authOptions.pages!.signIn!);
}

return children;
return <>{children}</>;
}
2 changes: 1 addition & 1 deletion src/app/(routes)/teams/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export default async function Layout({
redirect(authOptions.pages!.signIn!);
}

return children;
return <>{children}</>;
}
1 change: 1 addition & 0 deletions src/components/Droppable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dynamic from 'next/dynamic';

export default dynamic(
// @ts-expect-error
() => import('react-beautiful-dnd').then((res) => res.Droppable),
{ ssr: false }
);
2 changes: 2 additions & 0 deletions src/components/bookmark/BookmarkListDnd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type BookmarkListDndProps = {
const BookmarkListDnd = ({ teamLinks, team, digest }: BookmarkListDndProps) => {
return (
<Droppable droppableId="bookmark" type="bookmark">
{/* @ts-expect-error */}
{(provided) => (
<ul
className="flex flex-col gap-2"
Expand All @@ -24,6 +25,7 @@ const BookmarkListDnd = ({ teamLinks, team, digest }: BookmarkListDndProps) => {
draggableId={teamLink.bookmark[0].id}
index={index}
>
{/* @ts-expect-error */}
{(provided) => (
<li
{...provided.draggableProps}
Expand Down
2 changes: 2 additions & 0 deletions src/components/digests/BlockListDnd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type BlockListDndProps = {
export const BlockListDnd = ({ digest }: BlockListDndProps) => {
return (
<Droppable droppableId="block" type="bookmark">
{/* @ts-expect-error */}
{(provided, snapshot) => (
<ul
{...provided.droppableProps}
Expand All @@ -30,6 +31,7 @@ export const BlockListDnd = ({ digest }: BlockListDndProps) => {
return (
<div className="group/wrapper" key={block.id}>
<Draggable draggableId={block.id} index={index}>
{/* @ts-expect-error */}
{(provided) => (
<li
{...provided.draggableProps}
Expand Down
1 change: 1 addition & 0 deletions src/components/pages/DigestEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export const DigestEditPage = ({
]}
/>
<div className="flex flex-col gap-5 items-stretch justify-start md:flex-row">
{/* @ts-expect-error */}
<DragDropContext onDragEnd={onDragEnd}>
<div className="flex flex-col gap-5 w-full md:w-1/2">
<div className="flex gap-4 items-stretch w-full">
Expand Down
1 change: 0 additions & 1 deletion src/components/pages/Homepage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const Homepage = ({ user }: { user?: Session['user'] }) => {
<HomeSteps />
</section>
<section className="bg-white flex flex-1 justify-center">
{/* @ts-expect-error */}
<HomeDigests />
</section>
<HomeFooter />
Expand Down
4 changes: 1 addition & 3 deletions src/components/pages/Invitation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ const Invitation = ({
{
onSuccess: () => {
refresh();
router.push(`${routes.TEAMS}/${team.slug}`, {
forceOptimisticNavigation: true,
});
router.push(`${routes.TEAMS}/${team.slug}`);
},
}
);
Expand Down
67 changes: 31 additions & 36 deletions src/components/teams/form/TeamAPIKey.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';
import Button from '@/components/Button';
import { Team } from '@prisma/client';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useTransition } from 'react';
import {
ClipboardIcon,
ClipboardDocumentCheckIcon,
Expand All @@ -9,36 +10,21 @@ import {
import { copyToClipboard, makeHidden } from '@/utils/string';
import clsx from 'clsx';
import useCustomToast from '@/hooks/useCustomToast';
import api from '@/lib/api';
import { useMutation } from 'react-query';
import { ApiTeamResponseSuccess } from '@/pages/api/teams';
import { AxiosError, AxiosResponse } from 'axios';
import useTransitionRefresh from '@/hooks/useTransitionRefresh';
import { useRouter } from 'next/navigation';
import * as Popover from '@radix-ui/react-popover';
import generateAPIKey from '@/actions/generate-api-key';
interface Props {
team: Team;
}

export default function TeamAPIKey({ team }: Props) {
const id = team.apiKey;
const displayedId = id ? makeHidden(id) : '';
export default function TeamAPIKeyServer({ team }: Props) {
const key = team.apiKey;
const router = useRouter();

const displayedKey = key ? makeHidden(key) : '';
const [isAnimating, setIsAnimating] = useState(false);
const { isRefreshing, refresh } = useTransitionRefresh();
const { successToast, errorToast } = useCustomToast();
const { mutate: updateKey, isLoading } = useMutation<
AxiosResponse<ApiTeamResponseSuccess>,
AxiosError<ErrorResponse>,
void
>('update-team-key', () => api.get(`/teams/${team.id}/key/new`), {
onSuccess: () => {
successToast('Team API Key updated successfully');
refresh();
},
onError: (error) => {
errorToast('An error occurred while updating the API Key');
console.log(error);
},
});
let [isPending, startTransition] = useTransition();

useEffect(() => {
if (isAnimating) {
Expand All @@ -48,6 +34,19 @@ export default function TeamAPIKey({ team }: Props) {
}
}, [isAnimating]);

function handleClick() {
startTransition(async () => {
const { error } = await generateAPIKey(team.id);
if (error) {
errorToast(error.message);
return;
} else {
successToast('Team API Key updated successfully');
router.refresh();
}
});
}

return (
<div className=" w-full flex flex-col gap-2 items-stretch">
<span className="font-semibold mt-4 mb-4">API Key</span>
Expand All @@ -57,15 +56,15 @@ export default function TeamAPIKey({ team }: Props) {
<div
className="w-full group cursor-pointer"
onClick={() => {
if (!id) return;
if (!key) return;
setIsAnimating(true);
successToast('Copied to clipboard ✅');
copyToClipboard(id);
copyToClipboard(key);
}}
>
<div className="px-3 py-2 flex justify-between items-center rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 sm:max-w-md">
<span className="w-[40ch] overflow-hidden text-ellipsis block select-none items-center text-gray-500 sm:text-base">
{displayedId}
{displayedKey}
</span>

{isAnimating ? (
Expand All @@ -84,7 +83,7 @@ export default function TeamAPIKey({ team }: Props) {
<Button
className="whitespace-nowrap"
icon={<ArrowPathIcon />}
disabled={isLoading || isRefreshing}
disabled={isPending}
variant="outline"
>
Create new
Expand All @@ -101,10 +100,8 @@ export default function TeamAPIKey({ team }: Props) {
<Button
variant="destructiveOutline"
size="sm"
onClick={() => {
updateKey();
}}
isLoading={isLoading}
onClick={handleClick}
isLoading={isPending}
>
Yes, regenerate it
</Button>
Expand All @@ -116,11 +113,9 @@ export default function TeamAPIKey({ team }: Props) {
</>
) : (
<Button
onClick={() => {
updateKey();
}}
onClick={handleClick}
className="whitespace-nowrap"
disabled={isLoading || isRefreshing}
disabled={isPending}
variant="outline"
>
Create
Expand Down
1 change: 1 addition & 0 deletions src/emails/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const sendEmail = async ({
},
});

// @ts-expect-error
const { html } = render(component);

await transporter.sendMail({
Expand Down
5 changes: 5 additions & 0 deletions src/emails/templates/InvitationEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ const InvitationEmail = ({
teamName: string;
}) => (
<Mjml>
{/* @ts-expect-error */}
<MjmlBody width={500}>
{/* @ts-expect-error */}
<MjmlWrapper>
{/* @ts-expect-error */}
<MjmlSection>
{/* @ts-expect-error */}
<MjmlColumn>
<MjmlImage
width="600px"
Expand All @@ -44,6 +48,7 @@ const InvitationEmail = ({
and paste it into the browser of your choice.`}
</MjmlText>
<MjmlText>
{/* @ts-expect-error */}
<a
rel="nofollow"
style={{
Expand Down
Loading

0 comments on commit 54d4c49

Please sign in to comment.