diff --git a/README.md b/README.md
index 63f97939..d349b8cd 100644
--- a/README.md
+++ b/README.md
@@ -4,35 +4,35 @@ This is the official repository for the University of Adelaide Computer Science
## Getting Started
-To get started, please follow these steps:
+To get started, please follow these steps:
+
1. Install the dependencies.
+
```bash
pnpm install
```
-2. Set up the required keys:
-- Copy `.env.local.example` to a new file `.env.local`.
-- Create a [Clerk](https://clerk.com) account and make a new application within the Clerk dashboard.
-- Configure the settings to require a name, reject compromised passwords, and enforce average strength passwords.
-- Copy the keys to `.env.local`.
+2. Copy `.env.local.example` to a new file `.env.local` and set required environment variables (check `/docs` folder if you don't know how to edit it)
3. Initialise the database.
+
```bash
pnpm run db:push
```
-4. Then run the development server.
+4. Run the development server.
```bash
pnpm run dev
```
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+5. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Contributing
We welcome contributions to enhance the CS Club Website! If you find any issues, have suggestions, or want to request a feature, please follow our [Contributing Guidelines](CONTRIBUTING.md).
## License
-This project is licensed under the MIT License.
-See [LICENSE](LICENSE) for details.
\ No newline at end of file
+
+This project is licensed under the MIT License.
+See [LICENSE](LICENSE) for details.
diff --git a/docs/clerk.md b/docs/clerk.md
new file mode 100644
index 00000000..0b5e608e
--- /dev/null
+++ b/docs/clerk.md
@@ -0,0 +1,11 @@
+# Clerk
+
+## Getting started
+
+1. Create a [Clerk](https://clerk.com) account and make a new application within the Clerk dashboard.
+2. Configure the settings to require a name, reject compromised passwords, and enforce average strength passwords.
+
+## Admin account
+
+1. Go to user profile in clerk dashboard
+2. Set `{ "isAdmin": true }` in public metadata
diff --git a/package.json b/package.json
index 85a37bea..548b55ed 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"dependencies": {
"@clerk/clerk-react": "^4.30.3",
"@clerk/nextjs": "^4.29.3",
+ "@headlessui/react": "^1.7.18",
"@hookform/resolvers": "^3.3.4",
"@libsql/client": "0.4.0-pre.7",
"@t3-oss/env-nextjs": "^0.7.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2aacd516..aa25db9c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,9 @@ dependencies:
'@clerk/nextjs':
specifier: ^4.29.3
version: 4.29.3(next@14.0.4)(react-dom@18.2.0)(react@18.2.0)
+ '@headlessui/react':
+ specifier: ^1.7.18
+ version: 1.7.18(react-dom@18.2.0)(react@18.2.0)
'@hookform/resolvers':
specifier: ^3.3.4
version: 3.3.4(react-hook-form@7.49.3)
@@ -970,6 +973,19 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
+ /@headlessui/react@1.7.18(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16 || ^17 || ^18
+ react-dom: ^16 || ^17 || ^18
+ dependencies:
+ '@tanstack/react-virtual': 3.0.4(react-dom@18.2.0)(react@18.2.0)
+ client-only: 0.0.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@hookform/resolvers@3.3.4(react-hook-form@7.49.3):
resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==}
peerDependencies:
@@ -1431,6 +1447,21 @@ packages:
zod: 3.22.4
dev: false
+ /@tanstack/react-virtual@3.0.4(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-tiqKW/e2MJVCr7/pRUXulpkyxllaOclkHNfhKTo4pmHjJIqnhMfwIjc1Q1R0Un3PI3kQywywu/791c8z9u0qeA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ '@tanstack/virtual-core': 3.0.0
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /@tanstack/virtual-core@3.0.0:
+ resolution: {integrity: sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==}
+ dev: false
+
/@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.2.1):
resolution: {integrity: sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==}
peerDependencies:
diff --git a/src/app/(account)/settings/page.tsx b/src/app/(account)/settings/page.tsx
index f1bb3067..4008b05f 100644
--- a/src/app/(account)/settings/page.tsx
+++ b/src/app/(account)/settings/page.tsx
@@ -1,7 +1,7 @@
import FancyRectangle from '@/components/FancyRectangle';
import Title from '@/components/Title';
import { db } from '@/db';
-import { checkUserExists } from '@/db/queries';
+import { checkUserExists, updateMemberExpiryDate } from '@/db/queries';
import { memberTable } from '@/db/schema';
import { redisClient } from '@/lib/redis';
import { squareClient } from '@/lib/square';
@@ -39,12 +39,7 @@ const verifyMembershipPayment = async (clerkId: string) => {
}
// Set expiry date to be the January 1st of the following year
- const now = new Date();
- const expiryDate = new Date(`${now.getFullYear() + 1}-01-01`);
- await db
- .update(memberTable)
- .set({ membershipExpiresAt: expiryDate })
- .where(eq(memberTable.clerkId, clerkId));
+ const expiryDate = updateMemberExpiryDate(clerkId, 'clerkId');
// Delete key from Redis since it is no longer needed
await redisClient.del(`payment:membership:${clerkId}`);
diff --git a/src/app/admin/MemberForm.tsx b/src/app/admin/MemberForm.tsx
new file mode 100644
index 00000000..891c8539
--- /dev/null
+++ b/src/app/admin/MemberForm.tsx
@@ -0,0 +1,74 @@
+'use client';
+
+import Autocomplete from '@/components/Autocomplete';
+import { fetcher } from '@/lib/fetcher';
+import { useState } from 'react';
+import useSWRMutation from 'swr/mutation';
+import type { Member } from './page';
+
+const getMemberStr = (member: Member) => `${member.email} - ${member.firstName} ${member.lastName}`;
+
+function MemberDetail({ member }: { member: Member }) {
+ const { paid, ...details } = member;
+
+ const [payment, setPayment] = useState(member.paid);
+ const updatePayment = useSWRMutation('payment', fetcher.put.mutate, {
+ onSuccess: () => {
+ setPayment(!payment);
+ },
+ });
+ const handlePaymentChange = () => {
+ updatePayment.trigger({ id: member.id, paid: !member.paid });
+ };
+
+ return (
+