diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cdbdedb0..30dd84a1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,6 +21,10 @@ We want to leverage the powers of NextJS as much as possible. And one thing it d
One of our goals with this project is to keep it as componentized as we can. Any element that's used in more than one place should be created as a separate component. This will help us create consistency and avoid creating the same or very similar components in multiple files.
+## Restricted Pages
+
+Some pages we only want users to be able to access after they've successfully authenticated. We gatekeep these pages using the `RestrictedPage` server component. When you create a new page that unathenticated users should not be able to access, wrap it in this component.
+
## Types
This project uses and relies on TypeScript. Meaning every variable, function, and component is required to have proper type definitions.
@@ -60,5 +64,3 @@ Note that end-to-end tests have not yet been set up. If you're passionate about
## Pull Requests
The pull request flow in this project isn't anything special. We require a pull request to be created before anything is merged into `main`. At least one person must approve the pull request.
-
-
diff --git a/next.config.js b/next.config.js
index 7521d057..8c2cc8c8 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,5 +1,10 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
+ experimental: {
+ serverActions: {
+ allowedOrigins: ['localhost'],
+ }
+ },
images: {
remotePatterns: [
{
diff --git a/package-lock.json b/package-lock.json
index 5f946ce6..7741b0e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,14 +12,14 @@
"@material-tailwind/react": "^2.1.9",
"@uiw/react-md-editor": "^4.0.4",
"classnames": "^2.5.1",
- "cross-fetch": "3.1.8",
+ "cross-fetch": "^4.0.0",
"js-cookie": "^3.0.5",
"next": "14.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",
"remark-gfm": "^4.0.0",
- "sublinks-js-client": "^0.0.13",
+ "sublinks-js-client": "^0.0.17",
"sublinks-markdown": "^0.1.4"
},
"devDependencies": {
@@ -3968,9 +3968,9 @@
}
},
"node_modules/cross-fetch": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
- "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
+ "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"dependencies": {
"node-fetch": "^2.6.12"
}
@@ -11251,9 +11251,9 @@
}
},
"node_modules/sublinks-js-client": {
- "version": "0.0.13",
- "resolved": "https://registry.npmjs.org/sublinks-js-client/-/sublinks-js-client-0.0.13.tgz",
- "integrity": "sha512-qMqPhVvln0N+lUew/fMQUur2DB3R3yr0fbTxmW2JrNftYL80cRCCapF1L+WfMTDe8agtS4+vaK6VG5M+q6HztQ=="
+ "version": "0.0.17",
+ "resolved": "https://registry.npmjs.org/sublinks-js-client/-/sublinks-js-client-0.0.17.tgz",
+ "integrity": "sha512-OPfvvqdDWD/CGO12bdBcEHUGTIj7Taqu3aV4gOpd9kjV7i/xjoRKLtxyHj/wGfkKIWlwBwOygtOMScIem86r1Q=="
},
"node_modules/sublinks-markdown": {
"version": "0.1.4",
diff --git a/package.json b/package.json
index 7f848ffb..afad3b0b 100644
--- a/package.json
+++ b/package.json
@@ -21,14 +21,14 @@
"@material-tailwind/react": "^2.1.9",
"@uiw/react-md-editor": "^4.0.4",
"classnames": "^2.5.1",
- "cross-fetch": "3.1.8",
+ "cross-fetch": "^4.0.0",
"js-cookie": "^3.0.5",
"next": "14.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",
"remark-gfm": "^4.0.0",
- "sublinks-js-client": "^0.0.13",
+ "sublinks-js-client": "^0.0.17",
"sublinks-markdown": "^0.1.4"
},
"devDependencies": {
diff --git a/src/app/c/create/page.tsx b/src/app/c/create/page.tsx
index 446e7f5b..686e731d 100644
--- a/src/app/c/create/page.tsx
+++ b/src/app/c/create/page.tsx
@@ -1,5 +1,6 @@
import React from 'react';
+import RestrictedPage from '@/components/auth/restricted-page';
import { H1 } from '@/components/text';
import CommunityForm from '@/components/form/community';
@@ -14,4 +15,4 @@ const Communities = () => (
);
-export default Communities;
+export default () => RestrictedPage();
diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx
index f9dd3e55..c64799e0 100644
--- a/src/app/logout/page.tsx
+++ b/src/app/logout/page.tsx
@@ -2,11 +2,12 @@
import React, { useContext, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
+import { Spinner } from '@material-tailwind/react';
import SublinksApi from '@/utils/api-client/client';
import { UserContext } from '@/context/user';
-import { Spinner } from '@material-tailwind/react';
import { BodyTitle } from '@/components/text';
+import { revalidateAll } from '@/utils/server';
const Logout = () => {
const router = useRouter();
@@ -17,6 +18,7 @@ const Logout = () => {
const logout = async () => {
await SublinksApi.Instance().logout();
clearMyUser();
+ revalidateAll();
router.replace('/');
};
diff --git a/src/app/p/page.tsx b/src/app/p/page.tsx
index b7046ffd..23336651 100644
--- a/src/app/p/page.tsx
+++ b/src/app/p/page.tsx
@@ -1,8 +1,9 @@
import React from 'react';
-import SublinksApi from '@/utils/api-client/server';
+import RestrictedPage from '@/components/auth/restricted-page';
import { ErrorText, H1 } from '@/components/text';
import PostForm from '@/components/form/post';
+import SublinksApi from '@/utils/api-client/server';
import logger from '@/utils/logger';
const getCommunities = async () => {
@@ -39,4 +40,4 @@ const PostCreate = async () => {
);
};
-export default PostCreate;
+export default () => RestrictedPage();
diff --git a/src/components/auth/restricted-page.tsx b/src/components/auth/restricted-page.tsx
new file mode 100644
index 00000000..e3267918
--- /dev/null
+++ b/src/components/auth/restricted-page.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { redirect } from 'next/navigation';
+import { cookies } from 'next/headers';
+
+import SublinksApi from '@/utils/api-client/server';
+import { AUTH_COOKIE_NAME } from '@/utils/api-client/base';
+
+const RestrictedPage = async (page: React.JSX.Element) => {
+ const authCookie = cookies().get(AUTH_COOKIE_NAME);
+ if (!authCookie?.value) {
+ redirect('/login');
+ }
+
+ const validation = await SublinksApi.Instance().Client().validateAuth();
+ if (!validation.success) {
+ redirect('/login');
+ }
+
+ return page;
+};
+
+export default RestrictedPage;
diff --git a/src/components/form/community.tsx b/src/components/form/community.tsx
index c1f3e1c6..1be4d9aa 100644
--- a/src/components/form/community.tsx
+++ b/src/components/form/community.tsx
@@ -1,8 +1,6 @@
'use client';
-import React, {
- FormEvent, useContext, useEffect, useState
-} from 'react';
+import React, { FormEvent, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Checkbox, InputField, MarkdownTextarea } from '@/components/input';
@@ -11,7 +9,6 @@ import { BodyTitleInverse, ErrorText, PaleBodyText } from '@/components/text';
import SublinksApi from '@/utils/api-client/client';
import logger from '@/utils/logger';
import { Spinner } from '@material-tailwind/react';
-import { UserContext } from '@/context/user';
const INPUT_IDS = {
NAME: 'name',
@@ -30,17 +27,10 @@ const REQUIRED_FIELDS = [
const CommunityForm = () => {
const router = useRouter();
- const { userData } = useContext(UserContext);
const [erroneousFields, setErroneousFields] = useState([]);
const [errorMessage, setErrorMessage] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
- useEffect(() => {
- if (userData.auth === false) {
- router.push('/login');
- }
- }, [userData]); // eslint-disable-line react-hooks/exhaustive-deps
-
const validateRequiredFields = (fieldValues: Record) => {
const emptyFields: string[] = [];
@@ -68,13 +58,11 @@ const CommunityForm = () => {
return undefined;
};
- const handleCreationAttempt = async (event: FormEvent) => {
- event.preventDefault();
+ const communityCreateAction = async (formData: FormData) => {
setIsSubmitting(true);
setErrorMessage('');
setErroneousFields([]);
- const formData = new FormData(event.currentTarget);
const fieldValues = {
name: formData.get(INPUT_IDS.NAME) as string,
title: formData.get(INPUT_IDS.TITLE) as string,
@@ -138,7 +126,7 @@ const CommunityForm = () => {
};
return (
-