Skip to content

Commit

Permalink
finished basic functions to test
Browse files Browse the repository at this point in the history
  • Loading branch information
SeiKasahara committed Jul 25, 2024
1 parent 089fb12 commit 8b13a8e
Show file tree
Hide file tree
Showing 13 changed files with 736 additions and 66 deletions.
10 changes: 10 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-image-crop": "^8.1.6",
"@types/react-transition-group": "^4.4.10",
"@types/react-webcam": "^3.0.0",
"@typescript-eslint/parser": "^7.13.1",
"eslint": "^8.57.0",
Expand Down
1 change: 1 addition & 0 deletions client/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const buttonVariants = cva(
pay: `bg-penni-main text-white subheadline rounded-penni-special`,
finish: `bg-penni-grey-finished text-penni-text-finish body-medium rounded-penni-special`,
cutout: `bg-white text-penni-main headline border border-2 border-penni-main rounded-lg border-solid`,
filecard: `bg-penni-background-file-card text-primary opacity-60 rounded-penni-select body-medium `,
},
size: {
default: "h-10 px-4 py-2",
Expand Down
178 changes: 178 additions & 0 deletions client/src/components/ui/signup/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { profile } from "console";
import Image from "next/image";
import { ChangeEvent, useState } from "react";

import { Button } from "@/components/ui/button";
import { HTMLTextTargetElement, SingleLineInput } from "@/components/ui/inputs";

interface AvaProps {
profilePhoto: File | null;
currentStep: number;
setCurrentStep: React.Dispatch<React.SetStateAction<number>>;
setProfilePhoto: React.Dispatch<React.SetStateAction<string | undefined>>;
setSubmit: React.Dispatch<React.SetStateAction<boolean>>;
client: string;
setTitle: React.Dispatch<React.SetStateAction<string>>;
}

export const Ava: React.FC<AvaProps> = ({
currentStep,
setCurrentStep,
profilePhoto,
setProfilePhoto,
client,
setSubmit,
setTitle,
}) => {
const [buttonVariant, setButtonVariant] = useState("inactive");
const [showOptions, setShowOptions] = useState(false);
const [inputType, setInputType] = useState<"file" | "camera">("file");

const handleAvatarChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
setProfilePhoto(reader.result as string);
};
reader.readAsDataURL(file);
setButtonVariant("default");
}
};

const handleCameraCapture = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const video = document.createElement("video");
video.srcObject = stream;
video.play();

return new Promise<string>((resolve, reject) => {
video.onloadedmetadata = () => {
video.width = video.videoWidth;
video.height = video.videoHeight;

const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;

const context = canvas.getContext("2d");
context?.drawImage(video, 0, 0);

canvas.toDataURL(
"image/*",
(dataUrl: string | PromiseLike<string>) => {
resolve(dataUrl);
stream.getTracks().forEach((track) => track.stop());
},
);
};
});
} catch (error) {
console.error("Error accessing camera", error);
}
};

const handleChooseOption = (type: "file" | "camera") => {
setInputType(type);
if (type === "file") {
document.getElementById("fileInput")?.click();
} else if (type === "camera") {
handleCameraCapture().then((dataUrl) => {
setProfilePhoto(dataUrl);
setShowOptions(false);
});
}
};

const buttonInputVariant =
buttonVariant === "default" ? "default" : "inactive";

const buttonEffect = () => {
if (client === "Poster") {
setSubmit(true);
} else {
setCurrentStep(currentStep + 1);
setTitle("Your Bio");
}
};

return (
<div>
<div className="absolute left-0 right-0 top-[150px] flex h-[120px] flex-col gap-3 p-[1px_0] px-4">
<span className="body text-primary">
Upload a profile photo so others can recognise you!
</span>
{/*Avatar Uploader*/}
<div className="flex items-center justify-center">
<div className="relative h-[136px] w-[136px]">
{profilePhoto ? (
<Image
src={profilePhoto || "/default-profile.svg"}
alt="Avatar"
layout="fill"
objectFit="cover"
className="rounded-full border-2 border-penni-grey-inactive"
/>
) : (
<div className="flex h-full w-full items-center justify-center rounded-full border-2 border-penni-grey-inactive bg-penni-grey-inactive">
<span className="bold text-penni-grey-inactive">No image</span>
</div>
)}
<input
type="file"
id="fileInput"
accept="image/*"
onChange={handleAvatarChange}
className="absolute inset-0 cursor-pointer opacity-0"
/>
<button
className="absolute inset-0 cursor-pointer opacity-0"
onClick={() => setShowOptions(true)}
>
Upload
</button>
</div>

{showOptions && (
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black bg-opacity-50 transition-transform">
<div className="duration-800 animate-slide-up w-full space-y-1 rounded-t-lg p-4 ease-in-out">
<Button
className="flex h-[56px] w-full px-4"
variant="filecard"
onClick={() => handleChooseOption("file")}
>
Choose from Gallery
</Button>
<Button
className="flex h-[56px] w-full px-4"
variant="filecard"
onClick={() => handleChooseOption("camera")}
>
Take a Photo
</Button>
<Button
className="mt-1 flex h-[56px] w-full px-4"
variant="filecard"
onClick={() => setShowOptions(false)}
>
Cancel
</Button>
</div>
</div>
)}
</div>
</div>
<div className="absolute left-0 right-0 top-[574px] flex flex-col gap-3 p-[1px_0] px-4">
<Button
className="flex h-[56px] w-full px-4 pb-4"
variant={buttonInputVariant}
onClick={buttonEffect}
disabled={buttonInputVariant === "inactive"}
>
Continue
</Button>
</div>
</div>
);
};
56 changes: 56 additions & 0 deletions client/src/components/ui/signup/bio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ChangeEvent, useState } from "react";

import { Button } from "@/components/ui/button";
import {
HTMLTextTargetElement,
ParagraphInput,
SingleLineInput,
} from "@/components/ui/inputs";

interface BioProps {
setSubmit: React.Dispatch<React.SetStateAction<boolean>>;
bio: string;
setBio: React.Dispatch<React.SetStateAction<string>>;
}

export const Bio: React.FC<BioProps> = ({ setBio, setSubmit, bio }) => {
const [buttonVariant, setButtonVariant] = useState("inactive");

const handleBioChange = (e: ChangeEvent<HTMLTextTargetElement>) => {
const value = e.target.value;
setBio(value);
setButtonVariant(value ? "default" : "inactive");
};

const buttonInputVariant =
buttonVariant === "default" ? "default" : "inactive";
return (
<div>
<div className="absolute left-0 right-0 top-[150px] flex h-[120px] flex-col gap-3 p-[1px_0] px-4">
<span className="body text-primary">
Tell us a bit about yourself, like what you are best at and your
background
</span>
<div className="w-full">
<ParagraphInput
name="Bio"
onChange={handleBioChange}
value={bio}
label="What are you best at?"
placeholder="Ever since I was little, I've always been passionate about not starving to death."
/>
</div>
</div>
<div className="absolute left-0 right-0 top-[574px] flex flex-col gap-3 p-[1px_0] px-4">
<Button
className="flex h-[56px] w-full px-4 pb-4"
variant={buttonInputVariant}
onClick={() => setSubmit(true)}
disabled={buttonInputVariant === "inactive"}
>
Continue
</Button>
</div>
</div>
);
};
37 changes: 37 additions & 0 deletions client/src/components/ui/signup/check-unique.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { AxiosError } from "axios";

import api from "@/lib/api";

type AxiosCustomError = AxiosError<{
message: string;
}>;

export const checkUnique = async (input: string): Promise<boolean> => {
try {
// this check logic should be revised, it will be a mess as data amount grows.
const response = await api.get("app/users/");
// axios automatically parses the response as JSON if the content type is application/json
const data = response.data;
for (const profile of data) {
if (profile.full_name === input) {
return true;
}
}
return false;
} catch (error) {
const axiosError = error as AxiosCustomError;
if (axiosError.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
throw new Error(
`Server responded with status: ${axiosError.response.status}`,
);
} else if (axiosError.request) {
// The request was made but no response was received
throw new Error("No response received from server");
} else {
// Something happened in setting up the request that triggered an Error
throw new Error(`Error in setting up request: ${axiosError.message}`);
}
}
};
Loading

0 comments on commit 8b13a8e

Please sign in to comment.