Added the ability for Corporate accounts to register without codes

This commit is contained in:
Tiago Ribeiro
2023-10-29 14:38:46 +00:00
parent 6e31a05f21
commit a20b980adb
9 changed files with 98 additions and 23 deletions

View File

@@ -111,7 +111,7 @@ export default function OwnerDashboard({user}: Props) {
); );
const InactiveStudentsList = () => { const InactiveStudentsList = () => {
const filter = (x: User) => x.type === "student" && (x.isDisabled || moment().isAfter(x.subscriptionExpirationDate)); const filter = (x: User) => x.type === "student" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate));
return ( return (
<> <>
@@ -131,7 +131,7 @@ export default function OwnerDashboard({user}: Props) {
}; };
const InactiveCorporateList = () => { const InactiveCorporateList = () => {
const filter = (x: User) => x.type === "corporate" && (x.isDisabled || moment().isAfter(x.subscriptionExpirationDate)); const filter = (x: User) => x.type === "corporate" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate));
return ( return (
<> <>
@@ -196,7 +196,11 @@ export default function OwnerDashboard({user}: Props) {
<span className="flex flex-col gap-1 items-center text-xl"> <span className="flex flex-col gap-1 items-center text-xl">
<span className="text-lg">Inactive Students</span> <span className="text-lg">Inactive Students</span>
<span className="font-semibold text-mti-rose"> <span className="font-semibold text-mti-rose">
{users.filter((x) => x.type === "student" && (x.isDisabled || moment().isAfter(x.subscriptionExpirationDate))).length} {
users.filter(
(x) => x.type === "student" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate)),
).length
}
</span> </span>
</span> </span>
</div> </div>
@@ -207,7 +211,11 @@ export default function OwnerDashboard({user}: Props) {
<span className="flex flex-col gap-1 items-center text-xl"> <span className="flex flex-col gap-1 items-center text-xl">
<span className="text-lg text-center">Inactive Corporate</span> <span className="text-lg text-center">Inactive Corporate</span>
<span className="font-semibold text-mti-rose"> <span className="font-semibold text-mti-rose">
{users.filter((x) => x.type === "corporate" && (x.isDisabled || moment().isAfter(x.subscriptionExpirationDate))).length} {
users.filter(
(x) => x.type === "corporate" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate)),
).length
}
</span> </span>
</span> </span>
</div> </div>
@@ -219,7 +227,7 @@ export default function OwnerDashboard({user}: Props) {
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {users
.filter((x) => x.type === "student") .filter((x) => x.type === "student")
.sort((a, b) => dateSorter(a, b, "asc", "registrationDate")) .sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}
@@ -230,17 +238,17 @@ export default function OwnerDashboard({user}: Props) {
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {users
.filter((x) => x.type === "corporate") .filter((x) => x.type === "corporate")
.sort((a, b) => dateSorter(a, b, "asc", "registrationDate")) .sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}
</div> </div>
</div> </div>
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Disabled Corporate</span> <span className="p-4">Unpaid Corporate</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {users
.filter((x) => x.type === "corporate" && x.isDisabled) .filter((x) => x.type === "corporate" && x.status === "paymentDue")
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}

View File

@@ -16,8 +16,8 @@ export interface User {
demographicInformation?: DemographicInformation; demographicInformation?: DemographicInformation;
corporateInformation?: CorporateInformation; corporateInformation?: CorporateInformation;
subscriptionExpirationDate?: null | Date; subscriptionExpirationDate?: null | Date;
isDisabled?: boolean;
registrationDate?: Date; registrationDate?: Date;
status: "active" | "disabled" | "paymentDue";
} }
export interface CorporateInformation { export interface CorporateInformation {

View File

@@ -85,7 +85,7 @@ export default function UserList({user, filter}: {user: User; filter?: (user: Us
const toggleDisableAccount = (user: User) => { const toggleDisableAccount = (user: User) => {
if ( if (
!confirm( !confirm(
`Are you sure you want to ${user.isDisabled ? "enable" : "disable"} ${ `Are you sure you want to ${user.status === "disabled" ? "enable" : "disable"} ${
user.name user.name
}'s account? This change is usually related to their payment state.`, }'s account? This change is usually related to their payment state.`,
) )
@@ -93,9 +93,12 @@ export default function UserList({user, filter}: {user: User; filter?: (user: Us
return; return;
axios axios
.post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {...user, isDisabled: !user.isDisabled}) .post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {
...user,
status: user.status === "disabled" ? "active" : "disabled",
})
.then(() => { .then(() => {
toast.success(`User ${user.isDisabled ? "enabled" : "disabled"} successfully!`); toast.success(`User ${user.status === "disabled" ? "enabled" : "disabled"} successfully!`);
reload(); reload();
}) })
.catch(() => { .catch(() => {
@@ -166,10 +169,10 @@ export default function UserList({user, filter}: {user: User; filter?: (user: Us
)} )}
{PERMISSIONS.updateUser[row.original.type].includes(user.type) && ( {PERMISSIONS.updateUser[row.original.type].includes(user.type) && (
<div <div
data-tip={row.original.isDisabled ? "Enable User" : "Disable User"} data-tip={row.original.status === "disabled" ? "Enable User" : "Disable User"}
className="cursor-pointer tooltip" className="cursor-pointer tooltip"
onClick={() => toggleDisableAccount(row.original)}> onClick={() => toggleDisableAccount(row.original)}>
{row.original.isDisabled ? ( {row.original.status === "disabled" ? (
<BsCheckCircle className="hover:text-mti-purple-light transition ease-in-out duration-300" /> <BsCheckCircle className="hover:text-mti-purple-light transition ease-in-out duration-300" />
) : ( ) : (
<BsFillExclamationOctagonFill className="hover:text-mti-purple-light transition ease-in-out duration-300" /> <BsFillExclamationOctagonFill className="hover:text-mti-purple-light transition ease-in-out duration-300" />

View File

@@ -47,6 +47,13 @@ export default function RegisterCorporate({isLoading, setIsLoading, mutateUser,
password, password,
type: "corporate", type: "corporate",
profilePicture: "/defaultAvatar.png", profilePicture: "/defaultAvatar.png",
corporateInformation: {
companyInformation: {
name: companyName,
userAmount: companyUsers,
},
allowedUserAmount: companyUsers,
},
}) })
.then((response) => { .then((response) => {
mutateUser(response.data.user).then(() => sendEmailVerification(setIsLoading, onSuccess, onError)); mutateUser(response.data.user).then(() => sendEmailVerification(setIsLoading, onSuccess, onError));
@@ -114,7 +121,9 @@ export default function RegisterCorporate({isLoading, setIsLoading, mutateUser,
<Button <Button
className="lg:mt-8 w-full" className="lg:mt-8 w-full"
color="purple" color="purple"
disabled={isLoading || !email || !name || !password || !confirmPassword || password !== confirmPassword}> disabled={
isLoading || !email || !name || !password || !confirmPassword || password !== confirmPassword || !companyName || companyUsers <= 0
}>
Create account Create account
</Button> </Button>
</form> </form>

View File

@@ -43,6 +43,7 @@ export default function RegisterIndividual({queryCode, isLoading, setIsLoading,
name, name,
email, email,
password, password,
type: "individual",
code, code,
profilePicture: "/defaultAvatar.png", profilePicture: "/defaultAvatar.png",
}) })

View File

@@ -4,13 +4,13 @@ import {app} from "@/firebase";
import {sessionOptions} from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import {withIronSessionApiRoute} from "iron-session/next"; import {withIronSessionApiRoute} from "iron-session/next";
import {getFirestore, doc, setDoc, query, collection, where, getDocs} from "firebase/firestore"; import {getFirestore, doc, setDoc, query, collection, where, getDocs} from "firebase/firestore";
import {DemographicInformation, Type} from "@/interfaces/user"; import {CorporateInformation, DemographicInformation, Type} from "@/interfaces/user";
import {addUserToGroupOnCreation} from "@/utils/registration"; import {addUserToGroupOnCreation} from "@/utils/registration";
const auth = getAuth(app); const auth = getAuth(app);
const db = getFirestore(app); const db = getFirestore(app);
export default withIronSessionApiRoute(login, sessionOptions); export default withIronSessionApiRoute(register, sessionOptions);
const DEFAULT_DESIRED_LEVELS = { const DEFAULT_DESIRED_LEVELS = {
reading: 9, reading: 9,
@@ -26,8 +26,21 @@ const DEFAULT_LEVELS = {
speaking: 0, speaking: 0,
}; };
async function login(req: NextApiRequest, res: NextApiResponse) { async function register(req: NextApiRequest, res: NextApiResponse) {
const {email, password, code} = req.body as {email: string; password: string; code: string; demographicInformation: DemographicInformation}; const {type} = req.body as {
type: "individual" | "corporate";
};
if (type === "individual") return registerIndividual(req, res);
if (type === "corporate") return registerCorporate(req, res);
}
async function registerIndividual(req: NextApiRequest, res: NextApiResponse) {
const {email, password, code} = req.body as {
email: string;
password: string;
code?: string;
};
const codeQuery = query(collection(db, "codes"), where("code", "==", code)); const codeQuery = query(collection(db, "codes"), where("code", "==", code));
const codeDocs = (await getDocs(codeQuery)).docs.filter((x) => !Object.keys(x.data()).includes("userId")); const codeDocs = (await getDocs(codeQuery)).docs.filter((x) => !Object.keys(x.data()).includes("userId"));
@@ -70,3 +83,41 @@ async function login(req: NextApiRequest, res: NextApiResponse) {
res.status(401).json({error}); res.status(401).json({error});
}); });
} }
async function registerCorporate(req: NextApiRequest, res: NextApiResponse) {
const {email, password} = req.body as {
email: string;
password: string;
corporateInformation: CorporateInformation;
};
createUserWithEmailAndPassword(auth, email, password)
.then(async (userCredentials) => {
const userId = userCredentials.user.uid;
delete req.body.password;
const user = {
...req.body,
desiredLevels: DEFAULT_DESIRED_LEVELS,
levels: DEFAULT_LEVELS,
bio: "",
isFirstLogin: false,
focus: "academic",
type: "corporate",
subscriptionExpirationDate: null,
status: "paymentDue",
registrationDate: new Date().toISOString(),
};
await setDoc(doc(db, "users", userId), user);
req.session.user = {...user, id: userId};
await req.session.save();
res.status(200).json({user: {...user, id: userId}});
})
.catch((error) => {
console.log(error);
res.status(401).json({error});
});
}

View File

@@ -13,6 +13,8 @@ async function sendVerification(req: NextApiRequest, res: NextApiResponse) {
const short = new ShortUniqueId(); const short = new ShortUniqueId();
if (req.session.user) { if (req.session.user) {
console.log("ME HERE");
const transport = prepareMailer("verification"); const transport = prepareMailer("verification");
const mailOptions = prepareMailOptions( const mailOptions = prepareMailOptions(
{ {
@@ -25,7 +27,8 @@ async function sendVerification(req: NextApiRequest, res: NextApiResponse) {
"verification", "verification",
); );
await transport.sendMail(mailOptions); const result = await transport.sendMail(mailOptions);
console.log(result);
res.status(200).json({ok: true}); res.status(200).json({ok: true});
return; return;

View File

@@ -65,7 +65,7 @@ export default function Home() {
return true; return true;
}; };
if (user && (user.isDisabled || checkIfUserExpired())) { if (user && (user.status === "disabled" || checkIfUserExpired())) {
return ( return (
<> <>
<Head> <Head>
@@ -79,7 +79,7 @@ export default function Home() {
</Head> </Head>
<Layout user={user} navDisabled> <Layout user={user} navDisabled>
<div className="flex flex-col items-center justify-center text-center w-full gap-4"> <div className="flex flex-col items-center justify-center text-center w-full gap-4">
{user.isDisabled ? ( {user.status === "disabled" ? (
<> <>
<span className="font-bold text-lg">Your account has been disabled!</span> <span className="font-bold text-lg">Your account has been disabled!</span>
<span>Please contact an administrator if you believe this to be a mistake.</span> <span>Please contact an administrator if you believe this to be a mistake.</span>

View File

@@ -8,7 +8,7 @@ export const preventNavigation = (navDisabled: boolean, focusMode: boolean): boo
}; };
export const shouldRedirectHome = (user: User) => { export const shouldRedirectHome = (user: User) => {
if (user.isDisabled) return true; if (user.status === "disabled") return true;
if (user.isFirstLogin) return true; if (user.isFirstLogin) return true;
if (!user.demographicInformation) return true; if (!user.demographicInformation) return true;
if (user.subscriptionExpirationDate && moment(new Date()).isAfter(user.subscriptionExpirationDate)) return true; if (user.subscriptionExpirationDate && moment(new Date()).isAfter(user.subscriptionExpirationDate)) return true;