Skip to content
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
5 changes: 4 additions & 1 deletion src/app/(account)/join/Join.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSession } from 'next-auth/react';
import Link from 'next/link';
import { useEffect } from 'react';
import ProgressBar from './ProgressBar';
import StepFive from './steps/StepFive';
import StepFour from './steps/StepFour';
import StepOne from './steps/StepOne';
import StepThree from './steps/StepThree';
Expand Down Expand Up @@ -62,7 +63,9 @@ export default function Join() {
<ProgressBar step={step} />
{
// eslint-disable-next-line react/jsx-key
[<StepTwo />, <StepThree />, <StepFour />][step - 2]
[<StepTwo />, <StepThree />, <StepFour />, <StepFive />][
step - 2
]
}
</>
)}
Expand Down
4 changes: 3 additions & 1 deletion src/app/(account)/join/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Duck from '@/components/Duck';

const MAX_STEP_COUNT = 5;

function Progress({ filled, index }: { filled?: boolean; index: number }) {
return (
<div className="flex items-center justify-center">
Expand All @@ -15,7 +17,7 @@ export default function ProgressBar({ step }: { step: number }) {
{new Array(step).fill(null).map((_, i) => (
<Progress filled index={i + 1} key={i} />
))}
{new Array(4 - step).fill(null).map((_, i) => (
{new Array(MAX_STEP_COUNT - step).fill(null).map((_, i) => (
<Progress index={step + i + 1} key={i} />
))}
</div>
Expand Down
84 changes: 84 additions & 0 deletions src/app/(account)/join/steps/StepFive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Button from '@/components/Button';
import Field from '@/components/Field';
import { fetcher } from '@/lib/fetcher';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import useSWRMutation from 'swr/mutation';
import {
useJoinUsStep,
useJoinUsStudentInfo,
useJoinUsNotifications,
useSetJoinUsHeading,
} from '../store';

export default function StepFive() {
useSetJoinUsHeading({
title: 'Confirm Terms',
description:
'You must read and agree to the terms and proceed to complete the membership payment.',
});

const [agreement, setAgreement] = useState(false);
const [agreementError, setAgreementError] = useState<string | null>(null);

const { prevStep } = useJoinUsStep();
const { studentInfo } = useJoinUsStudentInfo();
const { notifications } = useJoinUsNotifications();

const router = useRouter();
const createMember = useSWRMutation('member', fetcher.post.mutate, {
onError: () => {
setAgreementError('Server error.');
},
onSuccess: () => {
router.push('/settings');
router.refresh();
},
});

const handleSignUp = async () => {
// Combine student info and notifications
const memberData = {
...studentInfo,
notifications,
};

setAgreementError(null);
if (!agreement) {
setAgreementError('Please agree to the terms');
return;
}
createMember.trigger(memberData);
};

const toggleAgreement = () => setAgreement(!agreement);

return (
<div>
<div className="mb-4 mt-8">
{/* TODO: Add links to codes of conduct */}
<Field
label="By submitting this form, you agree to abide by the University Code of Conduct and Computer Science Club Code of Conduct. You acknowledge that failure to adhere to these rules may result in your membership being suspended or revoked following formal procedures outlined in the Code of Conduct. You also acknowledge that services and events offered by the Club may change at any time upon our discretion without notice."
value={agreement ? 'Yes' : 'No'}
onChange={toggleAgreement}
error={agreementError}
type="checkbox"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<Button onClick={prevStep} colour="orange" width="w-full" size="small">
Back
</Button>
<Button
onClick={handleSignUp}
colour="purple"
width="w-full"
size="small"
loading={createMember.isMutating}
>
Sign up
</Button>
</div>
</div>
);
}
132 changes: 91 additions & 41 deletions src/app/(account)/join/steps/StepFour.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,120 @@
import Button from '@/components/Button';
import Field from '@/components/Field';
import { fetcher } from '@/lib/fetcher';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import useSWRMutation from 'swr/mutation';
import { useJoinUsStep, useJoinUsStudentInfo, useSetJoinUsHeading } from '../store';
import {
useJoinUsStep,
useJoinUsStudentInfo,
useJoinUsNotifications,
useSetJoinUsHeading,
} from '../store';

type NotificationTypes = 'email' | 'sms';
type CategoryTypes = 'newsletters' | 'clubEventsAndAnnouncements' | 'sponsorNotifications';

export default function StepFour() {
useSetJoinUsHeading({
title: 'Confirm Terms',
title: 'Notification Preferences',
description:
'You must read and agree to the terms and proceed to complete the membership payment.',
'Let us know how you would like to be notified about upcoming events and opportunities.',
});

const [agreement, setAgreement] = useState(false);
const [agreementError, setAgreementError] = useState<string | null>(null);
const { prevStep, nextStep } = useJoinUsStep();

const { prevStep } = useJoinUsStep();
const { studentInfo } = useJoinUsStudentInfo();
const { notifications, setNotificationsData } = useJoinUsNotifications();
const phoneNumber = studentInfo.phoneNumber;

const router = useRouter();
const createMember = useSWRMutation('member', fetcher.post.mutate, {
onError: () => {
setAgreementError('Server error.');
},
onSuccess: () => {
router.push('/settings');
router.refresh();
},
});

const handleSignUp = async () => {
setAgreementError(null);
if (!agreement) {
setAgreementError('Please agree to the terms');
return;
}
createMember.trigger(studentInfo);
const toggleNotification = (type: NotificationTypes, category: CategoryTypes) => {
setNotificationsData({
...notifications,
[type]: {
...notifications[type],
[category]: !notifications[type][category],
},
});
};

const toggleAgreement = () => setAgreement(!agreement);
const handleContinue = () => {
setNotificationsData(notifications);
nextStep();
};

return (
<div>
<div className="mb-4 mt-8">
{/* TODO: Add links to codes of conduct */}
<Field
label="By submitting this form, you agree to abide by the University Code of Conduct and Computer Science Club Code of Conduct. You acknowledge that failure to adhere to these rules may result in your membership being suspended or revoked following formal procedures outlined in the Code of Conduct. You also acknowledge that services and events offered by the Club may change at any time upon our discretion without notice."
value={agreement ? 'Yes' : 'No'}
onChange={toggleAgreement}
error={agreementError}
type="checkbox"
/>
{['email'].map((type) => (
<div key={type} className="mb-6">
<h2 className="text-lg font-semibold capitalize">{type}</h2>
{['newsletters', 'clubEventsAndAnnouncements', 'sponsorNotifications'].map(
(category) => (
<div key={category} className="flex items-center justify-between">
<p className="capitalize">
{category.replace(/([A-Z])/g, ' $1')}
</p>

<div>
<Field
label=""
type="toggle"
value={
notifications[type as NotificationTypes][
category as CategoryTypes
]
}
onChange={() =>
toggleNotification(
type as NotificationTypes,
category as CategoryTypes
)
}
/>
</div>
</div>
)
)}
</div>
))}
</div>

{phoneNumber && (
<div className="mb-4 mt-8">
<div className="mb-6">
<h2 className="text-lg font-semibold">SMS</h2>
{['newsletters', 'clubEventsAndAnnouncements', 'sponsorNotifications'].map(
(category) => (
<div key={category} className="flex items-center justify-between">
<p className="capitalize">
{category.replace(/([A-Z])/g, ' $1')}
</p>

<div>
<Field
label=""
type="toggle"
value={notifications['sms'][category as CategoryTypes]}
onChange={() =>
toggleNotification('sms', category as CategoryTypes)
}
/>
</div>
</div>
)
)}
</div>
</div>
)}

<div className="grid grid-cols-2 gap-4">
<Button onClick={prevStep} colour="orange" width="w-full" size="small">
Back
</Button>
<Button
onClick={handleSignUp}
colour="purple"
type="button"
colour="orange"
width="w-full"
size="small"
loading={createMember.isMutating}
onClick={handleContinue}
>
Sign up
Continue
</Button>
</div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/app/(account)/join/steps/StepTwo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ import { useJoinUsStep, useJoinUsStudentInfo, useSetJoinUsHeading } from '../sto
export const stepTwoSchema = z.object({
firstName: firstNameSchema,
lastName: lastNameSchema,
phoneNumber: z
.string()
.regex(/^0\d{9}$/, {
message: 'Please enter a valid Australian phone number',
})
.optional()
.or(z.literal('')),
studentStatus: z.enum(STUDENT_STATUSES, {
errorMap: () => ({ message: 'Please select a valid status' }),
}),
Expand Down Expand Up @@ -63,6 +70,12 @@ export default function StepTwo() {
<form onSubmit={handleContinue}>
<ControlledField label="First Name" control={form.control} name="firstName" />
<ControlledField label="Last Name" control={form.control} name="lastName" />
<ControlledField
label="Phone Number (optional)"
control={form.control}
name="phoneNumber"
longLabel="Providing your phone number allows you to receive sms updates about internships, graduate opportunities, and club notifications."
/>
<ControlledField
label="Are you a university student?"
control={form.control}
Expand Down
37 changes: 37 additions & 0 deletions src/app/(account)/join/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const initialStepThreeData: Record<keyof StepThreeData, string> = {
const initialStepTwoData: Record<keyof StepTwoData, string> = {
firstName: '',
lastName: '',
phoneNumber: '',
studentStatus: 'At The University of Adelaide',
studentId: '',
};
Expand All @@ -65,3 +66,39 @@ export const useJoinUsStudentInfo = () => {
const { getStudentInfo, ...inner } = useJoinUsStudentInfoInner();
return { ...inner, studentInfo: getStudentInfo() };
};

// Notifications
type CategoryTypes = 'newsletters' | 'clubEventsAndAnnouncements' | 'sponsorNotifications';

interface NotificationsState {
email: Record<CategoryTypes, boolean>;
sms: Record<CategoryTypes, boolean>;
}

type JoinUsNotificationsState = {
notifications: NotificationsState;
setNotificationsData: (notifications: NotificationsState) => void;
};

const initialNotificationsState: NotificationsState = {
email: {
newsletters: false,
clubEventsAndAnnouncements: false,
sponsorNotifications: false,
},
sms: {
newsletters: false,
clubEventsAndAnnouncements: false,
sponsorNotifications: false,
},
};

const useJoinUsNotificationsInner = create<JoinUsNotificationsState>((set) => ({
notifications: initialNotificationsState,
setNotificationsData: (notifications) => set({ notifications }),
}));

export const useJoinUsNotifications = () => {
const { notifications, setNotificationsData } = useJoinUsNotificationsInner();
return { notifications, setNotificationsData };
};
3 changes: 2 additions & 1 deletion src/app/(account)/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
import Sidebar from './Sidebar';
import MembershipSettings from './tabs/MembershipSettings';
import Notifications from './tabs/NotificationsSettings';

export const TAB_NAMES = ['Account', 'Personal Info', 'Membership', 'Notifications'] as const;
export type TabNames = (typeof TAB_NAMES)[number];
Expand All @@ -16,7 +17,7 @@ const SETTING_TABS = {
Account: () => <div>Coming soon</div>,
'Personal Info': () => <div>Coming soon</div>,
Membership: MembershipSettings,
Notifications: () => <div>Coming soon</div>,
Notifications: Notifications,
} as const satisfies Record<TabNames, SettingTabComponent>;

export default function Settings({ settingData }: { settingData: SettingData }) {
Expand Down
1 change: 0 additions & 1 deletion src/app/(account)/settings/tabs/AccountSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export default function AccountSettings({
}: AccountSettingsProps) {
useMount(() => {
const verifyMembershipPayment = async () => {
console.log('Verifying membership payment');
try {
const response = await fetch('/api/verify-membership-payment', {
method: 'PUT',
Expand Down
Loading
Loading