Skip to content

Commit

Permalink
Base commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Kahan committed May 20, 2023
1 parent 1d948e5 commit 499e164
Show file tree
Hide file tree
Showing 22 changed files with 1,409 additions and 101 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?

/node_modules/
570 changes: 529 additions & 41 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
"preview": "vite preview"
},
"dependencies": {
"@tabler/icons-react": "^2.19.0",
"axios": "^1.4.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-select": "^5.7.3"
},
"devDependencies": {
"@types/react": "^18.0.28",
Expand Down
42 changes: 0 additions & 42 deletions src/App.css

This file was deleted.

34 changes: 30 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
import { useEffect, useState } from "react"
import Login from "./app/pages/Login"
import Home from "./app/pages/Home"
import { User } from "./app/utils/requests/types"
import { Toaster } from "react-hot-toast"

function App() {
const USER_KEY = "user"
const [user, setUser] = useState<User>()

useEffect(() => {
const userJson = localStorage.getItem("user")
if (userJson) {
setUser(JSON.parse(userJson))
}
}, [])

useEffect(() => {
if (user != undefined) {
localStorage.setItem(USER_KEY, JSON.stringify(user))
}
}, [user])

return (
<div className="bg-primary">
<p className="font-body font-md">Welcome,</p>
<h1 className="font-title font-xl">Let’s find a lucky dog for you.</h1>
</div>
<>
{user == undefined ? (
<Login setUser={setUser} />
) : (
<Home setUser={setUser} />
)}
<Toaster />
</>
)
}

Expand Down
25 changes: 25 additions & 0 deletions src/app/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { FC, MouseEventHandler } from "react"

interface ButtonProps {
text: string
type: "button" | "submit" | "reset" | undefined
variant?: string
onClick?: MouseEventHandler<HTMLButtonElement> | undefined
}

const Button: FC<ButtonProps> = ({ text, type, variant, onClick }) => {
return (
<button
type={type}
onClick={onClick}
className={`
flex justify-center align-center transition duration-200 ease-linear hover:opacity-80 rounded-md px-12 py-3 min-w-full sm:min-w-max
${variant === "primary" ? "text-title bg-primary" : "text-white bg-accent"}
`}
>
{text}
</button>
)
}

export default Button
57 changes: 57 additions & 0 deletions src/app/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { FC } from "react"
import dogIcon from "../../assets/dogIcon.svg"
import locationIcon from "../../assets/locationIcon.svg"
import { Dog } from "../utils/requests/types"

interface CardProps {
dog: Dog
selected: string[]
setSelectedDogs: React.Dispatch<React.SetStateAction<string[]>>
}

const Card: FC<CardProps> = ({ dog, selected, setSelectedDogs }) => {
const handleSelect = (id: string) => {
if (selected.includes(id)) {
setSelectedDogs((prevSelectedDogs) =>
prevSelectedDogs.filter((dogId) => dogId !== id)
)
} else {
setSelectedDogs((prevSelectedDogs) => [...prevSelectedDogs, id])
}
}

return (
<button
onClick={() => handleSelect(dog.id)}
className={`
p-4 flex flex-col gap-3 shadow-md bg-white rounded-lg cursor-pointer lg:flex-row border-2 border-solid border-transparent ${
selected.includes(dog.id) && "active-card"
}
`}
>
<img
className="w-[100%] aspect-square object-cover rounded-sm md:rounded-md lg:w-2/5 lg:self-center"
src={dog.img}
alt="dog photo"
/>
<div className="w-full flex flex-col gap-2">
<p className="card-font ">
{dog.name}, {dog.age}
</p>

<div className="card-grid gap-1">
<img className="sm:w-6 " src={dogIcon} alt="Dog icon" />
<p className="card-font whitespace-nowrap text-ellipsis overflow-hidden">
{dog.breed}
</p>
</div>
<div className="flex gap-1">
<img className="sm:w-6" src={locationIcon} alt="Location icon" />
<p className="card-font">{dog.zip_code}</p>
</div>
</div>
</button>
)
}

export default Card
78 changes: 78 additions & 0 deletions src/app/components/CustomSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { FC, useEffect, useState } from "react"
import Select, {
ActionMeta,
CSSObjectWithLabel,
GroupBase,
MultiValue,
OptionProps,
} from "react-select"
import api from "../utils/requests/api"
import { StylesConfig } from "react-select"

interface CustomSelectProps {
setSelectedBreeds: React.Dispatch<React.SetStateAction<string[]>>
}

const CustomSelect: FC<CustomSelectProps> = ({ setSelectedBreeds }) => {
// Select options for react-select
const [selectOptions, setSelectOptions] = useState<
{ value: String; label: String }[]
>([])

// Styles for react-select
const customStyles: StylesConfig<any> = {
control: (base: CSSObjectWithLabel) => ({
...base,
padding: [12, 10],
fontSize: 16,
outline: "none",
background: "#ffffff",
height: "100%",
minWidth: "250px",
borderWidth: "2px",
borderColor: "rgb(209 213 219)",
borderStyle: "solid",
borderRadius: 6,
}),
option: (
base: CSSObjectWithLabel,
{ isFocused }: OptionProps<any, boolean, GroupBase<any>>
) => ({
...base,
backgroundColor: isFocused ? "rgb(233 213 255 / 1)" : "white",
}),
}

const handleSelect: (
newValue: MultiValue<any>,
actionMeta: ActionMeta<any>
) => void = (option) => {
const breeds = option.map((o) => o.label)
setSelectedBreeds(breeds)
}

const getBreeds = async () => {
try {
const res = await api.fetchBreeds()
const options = res.map((r) => ({ value: r, label: r }))
setSelectOptions(options)
} catch (msg) {
console.log(msg)
}
}

useEffect(() => {
getBreeds()
}, [])

return (
<Select
isMulti
styles={customStyles}
options={selectOptions}
onChange={handleSelect}
/>
)
}

export default CustomSelect
37 changes: 37 additions & 0 deletions src/app/components/InputComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FC } from "react"

interface InputComponentProps {
label: string
value: string
type: React.HTMLInputTypeAttribute
handleChange: (value: React.ChangeEvent<HTMLInputElement>) => void
}

const InputComponent: FC<InputComponentProps> = ({
label,
value,
type,
handleChange,
}) => {
return (
<div className="flex flex-col gap-2 sm:gap-4">
<label htmlFor={label}>
{label.charAt(0).toUpperCase() + label.slice(1).toLowerCase()}
</label>
<input
required
className="flex items-center p-2 bg-white rounded-md border-2 border-md"
name={label}
id={label}
type={type}
value={value}
onChange={handleChange}
placeholder={
label.charAt(0).toUpperCase() + label.slice(1).toLowerCase()
}
/>
</div>
)
}

export default InputComponent
63 changes: 63 additions & 0 deletions src/app/components/MatchCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { FC } from "react"
import { Dog } from "../utils/requests/types"
import Button from "./Button"

interface MatchCardProps {
match: Dog
resetMatch: Function
}

const MatchCard: FC<MatchCardProps> = ({ match, resetMatch }) => {
const handleClick = () => {
resetMatch()
}

return (
<div className="bg-white rounded-3xl flex flex-col items-center gap-5 px-12 py-12 max-w-md md:px-20">
<img
className=" w-2/5 rounded-full aspect-square object-cover"
src={match.img}
alt="Dog image"
/>
<div className="w-8 h-1 bg-title rounded-sm" />
<div className="flex flex-col gap-3 items-center">
<h1 className="font-title font-md text-title">Meet {match.name}</h1>
<p className="text-center text-body text-xs">
{generateDogParagraph(match.name, match.age)}
</p>
</div>
<Button
text="Adopt me :)"
type="button"
variant="primary"
onClick={handleClick}
/>
</div>
)
}

export default MatchCard

const generateDogParagraph = (name: string, age: number) => {
// Array of possible sentences with placeholders for the name
const sentences = [
"Hello, my name is [name], and I am a loving and friendly companion looking for my forever home. At [age] years old, I have the perfect balance of playfulness and maturity.",
"Meet [name], the adorable dog ready to bring joy and loyalty into your life. With [age] years of experience, I've learned to be the most loyal and trustworthy companion you could ask for.",
"Hello there! I'm [name], a [age]-year-old furry bundle of happiness eager to join your family. I have a heart full of love to give and an energetic spirit that will keep you entertained and active.",
"Say hello to [name], a [age]-year-old dog with a heart of gold. I'm ready to shower you with unconditional love, cuddles, and loyalty. I'll be your steadfast companion through thick and thin.",
"Greetings! My name is [name], and at [age] years old, I've mastered the art of being a perfect cuddle buddy. With my charming personality and gentle nature, I'll bring warmth and happiness to your home.",
"Introducing [name], a [age]-year-old canine treasure seeking a loving family. I come with a winning smile, a wagging tail, and a heart full of affection. Let's embark on countless adventures together!",
]

// Choose a random sentence
const randomIndex = Math.floor(Math.random() * sentences.length)
let paragraph = sentences[randomIndex]

// Replace the placeholder [name] with the provided name
paragraph = paragraph.replace(/\[name\]/g, name)

// Replace the placeholder [age] with the provided age
paragraph = paragraph.replace(/\[age\]/g, age.toString())

return paragraph
}
Loading

1 comment on commit 499e164

@vercel
Copy link

@vercel vercel bot commented on 499e164 May 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

dog-matcher – ./

dog-matcher-git-main-kahan.vercel.app
dog-matcher.vercel.app
dog-matcher-kahan.vercel.app

Please sign in to comment.