Skip to content

Commit

Permalink
Merge pull request #22 from SirSanctified/18-collect-additional-user-…
Browse files Browse the repository at this point in the history
…information

Update README.md, .env.example, images, and fix institution URL in MyDetails page
  • Loading branch information
SirSanctified committed Mar 11, 2024
2 parents 3bb1700 + f5629e2 commit 5f9594e
Show file tree
Hide file tree
Showing 14 changed files with 222 additions and 106 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,5 @@ For more information on running the project, you can refer to the [Makefile](htt

You can then access the backend at `http://localhost:8000` and the frontend at `http://localhost:3000`.

To view your database using pgAdmin, you can access it at `http://localhost:5050`. The default email and password are the ones set in your backend `.env` file as `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` respectively.

![Screenshot from 2024-02-27 17-10-22](https://github.com/SirSanctified/student-accommodation-api/assets/63302923/b5700327-e76f-4c67-8b16-c25ae20dfd89)
![Screenshot from 2024-02-27 17-09-42](https://github.com/SirSanctified/student-accommodation-api/assets/63302923/cd33b92f-48f5-4bbf-8da8-704c3408699e)
2 changes: 0 additions & 2 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
DEBUG=1
SECRET_KEY=<secret key>
DATABASE_URL=postgres://<username>:<password>@db:5432/<database>
PGADMIN_DEFAULT_EMAIL=<email>
PGADMIN_DEFAULT_PASSWORD=<password>
POSTGRES_PASSWORD=<password>
POSTGRES_USER=<username>
POSTGRES_DB=<database>
Expand Down
Binary file added client/public/auth-bg.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/auth-home.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/home.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 25 additions & 2 deletions client/src/app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Toaster } from "@/components/ui/sonner";
import "@/styles/globals.css";
import { Lato } from "next/font/google";
import Image from "next/image";

const lato = Lato({
weight: ["400", "700", "900"],
Expand All @@ -22,11 +23,33 @@ export default function RootLayout({
return (
<html lang="en">
<body
className={`${lato.className} flex min-h-screen items-center justify-center`}
className={`${lato.className} flex min-h-screen items-center justify-center bg-[url("/auth-home.jpg")] bg-cover bg-center bg-no-repeat`}
>
<div className="mx-auto my-auto flex min-h-[75vh] w-full max-w-xl flex-col items-center justify-center rounded-xl shadow-md shadow-indigo-500">
<div className="mx-auto my-auto flex min-h-screen w-full max-w-7xl flex-col items-center justify-center gap-8 md:flex-row ">
<Toaster position="top-right" />
{children}
<div className="mx-auto mr-4 hidden flex-1 rounded-lg bg-blue-200/45 p-6 md:block md:w-1/2">
<Image
src="/logo.png"
alt="logo"
width={100}
height={100}
className="mx-auto mb-4"
/>
<h1 className="text-4xl font-bold text-indigo-950">
Home is Where Your Story Begins
</h1>
<p className="mt-4 text-lg text-indigo-950">
Your home is more than just a place to live. It&rsquo;s where you
create memories, build relationships, and grow as a person.
It&rsquo;s where you feel safe, comfortable, and loved. Finding
the perfect home can be a challenge, but it&rsquo;s also an
exciting opportunity to start a new chapter in your life. Whether
you&rsquo;re a student looking for off-campus housing or a
landlord looking to rent out your property, Roomio can help you
find the perfect place to call home.
</p>
</div>
</div>
</body>
</html>
Expand Down
221 changes: 171 additions & 50 deletions client/src/app/(auth)/my-details/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { AddCity } from "@/components/shared/addCityDialog";
import { AddInstitution } from "@/components/shared/addInstitution";
import { ComboBoxResponsive } from "@/components/shared/combobox";
import { Button } from "@/components/ui/button";
Expand All @@ -12,9 +13,12 @@ import {
SelectValue,
} from "@/components/ui/select";
import { useAuthStore } from "@/store/store";
import type { ComboboxOption, InstitutionAndCityResponse } from "@/types";
import type {
ComboboxOption,
InstitutionAndCityResponse,
PreferredPaymentMethods,
} from "@/types";
import axios from "axios";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { type FormEvent, useEffect, useState } from "react";
import { toast } from "sonner";
Expand All @@ -24,24 +28,40 @@ axios.defaults.xsrfHeaderName = "X-CSRFToken";

const MyDetails = () => {
const [institutions, setInstitutions] = useState<ComboboxOption[]>([]);
const [cities, setCities] = useState<ComboboxOption[]>([]);
const [selectedInstitution, setSelectedInstitution] =
useState<ComboboxOption | null>(null);
const [phone, setPhone] = useState<string>("");
const [selectedCity, setSelectedCity] = useState<ComboboxOption | null>(null);
const [registrationNumber, setRegistrationNumber] = useState<string>("");
const [level, setLevel] = useState<string>("");
const [address, setAddress] = useState<string>("");
const [ecocashNumber, setEcocashNumber] = useState<string>("");
const [accountNmber, setAccountnumber] = useState<string>("");
const [accountName, setAccountName] = useState<string>("");
const [bankName, setBankName] = useState<string>("");
const [preferredPaymentMethod, setPreferredPaymentMethod] =
useState<PreferredPaymentMethods>("ecocash usd");
const [avatar, setAvatar] = useState<string>("");
const [role, setRole] = useState<string>("");
const { isAuthenticated, user } = useAuthStore();
const router = useRouter();

useEffect(() => {
async function fetchInstitutions() {
async function fetchInstitutionsAndCities() {
try {
const institutionsResponse = await axios.get<
InstitutionAndCityResponse[]
>("http://localhost:8000/api/institutions/");
const [institutionsResponse, citiesResponse] = await Promise.all([
axios.get<InstitutionAndCityResponse[]>(
"http://localhost:8080/api/institutions/",
),
axios.get<InstitutionAndCityResponse[]>(
"http://localhost:8080/api/cities/",
),
]);

institutionsResponse.data.forEach((institution) => {
const newOption: ComboboxOption = {
label: institution.name,
value: institution.id.toString(),
value: institution.url ?? institution.id.toString(),
};
setInstitutions((prev) => {
if (!prev.some((option) => option.value === newOption.value)) {
Expand All @@ -50,11 +70,25 @@ const MyDetails = () => {
return prev;
});
});

citiesResponse.data.forEach((city) => {
console.log(city);
const newOption: ComboboxOption = {
label: city.name,
value: city.url ?? city.id.toString(),
};
setCities((prev) => {
if (!prev.some((option) => option.value === newOption.value)) {
return [...prev, newOption];
}
return prev;
});
});
} catch (error) {
setInstitutions([]);
}
}
fetchInstitutions().catch(() => {
fetchInstitutionsAndCities().catch(() => {
/* do nothing */
});
}, []);
Expand All @@ -65,20 +99,30 @@ const MyDetails = () => {
async function submitForm(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const userRoleData = {
user: `${process.env.NEXT_PUBLIC_API_URL}/auth/users/${user?.id}/`,
institution: `${process.env.NEXT_PUBLIC_API_URL}/institutions/${selectedInstitution?.value}/`,
user: `http://localhost/api/auth/users/${user?.id}/`,
institution: selectedInstitution?.value,
city: selectedCity?.value,
registration_number: registrationNumber,
address: address,
level: level,
ecocash_number: ecocashNumber,
account_number: accountNmber,
account_name: accountName,
bank_name: bankName,
preferred_payment_method: preferredPaymentMethod,
is_student: role === "student",
is_landlord: role === "landlord",
};

const userData = avatar !== "" ? { phone, avatar } : { phone };
try {
if (userRoleData.is_student) {
await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/students/`,
{
user: userRoleData.user,
institution: userRoleData.institution,
registrationNumber: userRoleData.registration_number,
level: userRoleData.level,
},
{
withCredentials: true,
Expand All @@ -89,21 +133,20 @@ const MyDetails = () => {
`${process.env.NEXT_PUBLIC_API_URL}/landlords/`,
{
user: userRoleData.user,
address: userRoleData.address,
city: userRoleData.city,
preferred_payment_method: userRoleData.preferred_payment_method,
bank_name: userRoleData.bank_name,
account_name: userRoleData.account_name,
account_number: userRoleData.account_number,
ecocash_number: userRoleData.ecocash_number,
},
{
withCredentials: true,
},
);
}

await axios.patch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/users/${user?.id}/`,
userData,
{
withCredentials: true,
},
);

toast.success("Successfully updated");

router.push("/");
Expand All @@ -114,17 +157,10 @@ const MyDetails = () => {
}
}
return (
<main className="w-full p-4 pb-8 text-indigo-950">
<main className="flex w-full flex-1 flex-col items-center justify-center px-4 py-8 text-indigo-950">
<div className="flex w-full flex-col items-center justify-center gap-4">
<Image
src="/logo.png"
alt="Roomio logo"
width={200}
height={200}
className="mx-auto block"
/>
<h1 className="text-3xl font-bold">My Details</h1>
<p className="-mt-4 text-sm opacity-50">
<h1 className="w-full text-start text-3xl font-bold">My Details</h1>
<p className="-mt-4 w-full text-start text-sm opacity-80">
Let&apos;s get to know you better..
</p>
</div>
Expand All @@ -138,26 +174,111 @@ const MyDetails = () => {
<SelectItem value="landlord">I am a landlord</SelectItem>
</SelectContent>
</Select>
{role === "student" && (
<ComboBoxResponsive
options={institutions}
commandEmpty={
<AddInstitution setSelectedOption={setSelectedInstitution} />
}
selectedOption={selectedInstitution}
setSelectedOption={setSelectedInstitution}
placeholder="Which institution do you belong to?"
/>
)}
<Input
type="tel"
name="phone"
value={phone}
onChange={(event) => setPhone(event.target.value)}
placeholder="Phone number"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
{role === "student" ? (
<>
<ComboBoxResponsive
options={institutions}
commandEmpty={
<AddInstitution setSelectedOption={setSelectedInstitution} />
}
selectedOption={selectedInstitution}
setSelectedOption={setSelectedInstitution}
placeholder="Which institution do you belong to?"
/>
<Input
type="text"
name="registrationNumber"
value={registrationNumber}
onChange={(event) => setRegistrationNumber(event.target.value)}
placeholder="Registration number"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
<Input
type="text"
name="level"
value={level}
onChange={(event) => setLevel(event.target.value)}
placeholder="Level"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
</>
) : role === "landlord" ? (
<>
<ComboBoxResponsive
options={cities}
commandEmpty={<AddCity setSelectedOption={setSelectedCity} />}
selectedOption={selectedCity}
setSelectedOption={setSelectedCity}
placeholder="Where do you live?"
/>
<Input
type="text"
name="address"
value={address}
onChange={(event) => setAddress(event.target.value)}
placeholder="Address"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
<Input
type="text"
name="ecocashNumber"
value={ecocashNumber}
onChange={(event) => setEcocashNumber(event.target.value)}
placeholder="Ecocash number"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
<Input
type="text"
name="accountNumber"
value={accountNmber}
onChange={(event) => setAccountnumber(event.target.value)}
placeholder="Account number"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
<Input
type="text"
name="accountName"
value={accountName}
onChange={(event) => setAccountName(event.target.value)}
placeholder="Account name"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
<Input
type="text"
name="bankName"
value={bankName}
onChange={(event) => setBankName(event.target.value)}
placeholder="Bank name"
required
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
<Select
value={preferredPaymentMethod}
onValueChange={(value) =>
setPreferredPaymentMethod(value as PreferredPaymentMethods)
}
>
<SelectTrigger className="w-full bg-indigo-200 text-indigo-950 focus:outline-none">
<SelectValue
placeholder="Preferred payment method"
className="w-full bg-indigo-200 text-indigo-950 focus:outline-none"
/>
</SelectTrigger>
<SelectContent className="w-full bg-indigo-200 text-indigo-950 focus:outline-none">
<SelectItem value="bank transfer">Bank Transfer</SelectItem>
<SelectItem value="ecocash usd">Ecocash USD</SelectItem>
<SelectItem value="cash usd">Cash USD</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</>
) : null}
<Input
type="file"
name="avatar"
Expand Down
Loading

0 comments on commit 5f9594e

Please sign in to comment.