Skip to content

Commit

Permalink
chore: add Tip Calculator #1277 (#1306)
Browse files Browse the repository at this point in the history
* feat(play/tip-calculator): add Tip Calculator #1277

* renamed extensions - js to jsx

* sytle.css mod; root:selector removed

---------

Co-authored-by: Tapas Adhikary <tapas.adhikary@gmail.com>
  • Loading branch information
ayushsgithub and atapas committed Oct 19, 2023
1 parent fd84dd6 commit 6fef24b
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/plays/tip-calculator/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Tip Calculator

An app to calculate tip evenly where user can divide the total bill amount into sharing number of people and the tip, and distribute the bill for each. Split your bill with friends and family and calculate tip easily and no more hassle of calculating tip and bill amount.

## Play Demographic

- Language: js
- Level: Beginner

## Creator Information

- User: ayushsgithub
- Gihub Link: https://github.com/ayushsgithub
79 changes: 79 additions & 0 deletions src/plays/tip-calculator/TipCalculator.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client';
import React, { useState, useEffect } from 'react';
import PlayHeader from 'common/playlists/PlayHeader';
import './styles.css';
import BillAmount from './components/BillAmount';
import TipAmount from './components/TipAmount';
import PeopleAmount from './components/PeopleAmount';
import DisplayCard from './components/DisplayCard';

// WARNING: Do not change the entry componenet name

function TipCalculator(props) {
// Your Code Start below.

const [bill, setBill] = useState();
const [tip, setTip] = useState(0);
const [people, setPeople] = useState(0);

const [tipPerPerson, setTipPerPerson] = useState(0);
const [totalPerPerson, setTotalPerPerson] = useState(0);
const [total, setTotal] = useState(0);

const handleResetTip = (e) => {
e.preventDefault();
setBill(0);
setPeople(0);
setTipPerPerson(0);
setTotalPerPerson(0);
setTotal(0);
};

useEffect(() => {
if (bill && tip && people) {
const tipAmount = Number(bill) * Number(tip);
const totalAmount = Number(bill) + tipAmount;

setTipPerPerson(tipAmount / people);
setTotalPerPerson(totalAmount / people);
setTotal(totalAmount);
}
}, [bill, tip, people]);

return (
<>
<div className="play-details">
<PlayHeader play={props} />
<div className="play-details-body">
{/* Your Code Starts Here */}
<main className="flex h-fit flex-col items-center justify-center p-7 lg:p-5">
<h1 className="mb-3 text-center text-3xl font-bold uppercase tracking-widest text-cyan-800">
Tip Splitter
</h1>
<div className="w-full max-w-3xl overflow-hidden rounded-xl bg-white shadow-xl">
<div className="px-4 py-5 sm:p-6">
<form className="mx-auto grid max-w-6xl gap-y-5 lg:grid-cols-2 lg:gap-x-8">
<div className="flex flex-col gap-y-8 py-5 lg:px-5 lg:py-6">
<BillAmount bill={bill} setBill={setBill} />
<TipAmount setTip={setTip} />
<PeopleAmount people={people} setPeople={setPeople} />
</div>

<DisplayCard
reset={handleResetTip}
tipPerPerson={tipPerPerson}
total={total}
totalPerPerson={totalPerPerson}
/>
</form>
</div>
</div>
</main>
{/* Your Code Ends Here */}
</div>
</div>
</>
);
}

export default TipCalculator;
32 changes: 32 additions & 0 deletions src/plays/tip-calculator/components/BillAmount.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';

const BillAmount = (props) => {
const { bill, setBill } = props;

return (
<div>
<label className="block font-serif text-sm font-light leading-6 text-gray-600" htmlFor="bill">
Bill
</label>

<div className="relative mt-2 rounded-md shadow-sm">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span className="font-serif text-gray-500 sm:text-sm"></span>
</div>

<input
aria-describedby="bill-currency"
className="block w-full rounded-md border-0 py-1.5 pl-7 pr-2 font-serif text-gray-900 outline-none ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-cyan-500 sm:text-sm sm:leading-6"
id="bill"
name="bill"
placeholder="0.00"
type="number"
value={bill}
onChange={(e) => setBill(e.target.value)}
/>
</div>
</div>
);
};

export default BillAmount;
58 changes: 58 additions & 0 deletions src/plays/tip-calculator/components/DisplayCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';

const DisplayCard = (props) => {
const { reset, tipPerPerson, totalPerPerson, total } = props;

const data = [
{
label: 'Tip Amount',
value: tipPerPerson.toFixed(2)
},
{
label: 'Total',
value: totalPerPerson.toFixed(2)
}
];

return (
<div className="flex flex-col justify-between rounded-xl bg-cyan-700 p-5 lg:py-10">
<div className="flex flex-col gap-y-8">
{data.map((item, i) => (
<div className="flex items-end justify-between" key={i}>
<div>
<p className="font-serif text-white lg:text-lg">{item.label}</p>
<p className="font-serif text-xs font-light text-gray-300 lg:text-sm">/ person</p>
</div>
<div className="flex items-baseline gap-x-2">
<span className="text-xl font-extralight text-white lg:text-xl"></span>
<span className="font-serif text-3xl font-medium text-white lg:text-4xl">
{item.value}
</span>
</div>
</div>
))}

<div className="flex items-end justify-between">
<p className="font-serif font-medium text-white lg:text-lg">Total Bill</p>

<div className="flex items-baseline gap-x-2">
<span className="text-xl font-extralight text-white lg:text-2xl"></span>
<span className="font-serif text-4xl font-medium text-white lg:text-5xl">
{total?.toFixed(2)}
</span>
</div>
</div>
</div>

<button
className="mt-14 w-full rounded-md bg-cyan-200 px-3.5 py-2.5 text-lg font-semibold uppercase tracking-wide text-cyan-700 shadow-sm hover:bg-cyan-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-white"
type="submit"
onClick={(e) => reset(e)}
>
Reset Tip
</button>
</div>
);
};

export default DisplayCard;
35 changes: 35 additions & 0 deletions src/plays/tip-calculator/components/PeopleAmount.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// import { UsersIcon } from '@heroicons/react/20/solid';
import PersonIcon from '@mui/icons-material/Person';

const PeopleAmount = ({ people, setPeople }) => {
return (
<div>
<label
className="block font-serif text-sm font-light leading-6 text-gray-600"
htmlFor="people"
>
Number of People
</label>

<div className="relative mt-2 flex flex-grow items-stretch focus-within:z-10">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
{/* <UsersIcon className="h-5 w-5 text-gray-400" aria-hidden="true" /> */}
<PersonIcon aria-hidden="true" className="h-5 w-5 text-gray-400" />
</div>

<input
aria-describedby="number-of-people"
className="block w-full rounded-md border-0 py-1.5 pl-10 pr-3 font-serif text-gray-900 outline-none ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-cyan-500 sm:text-sm sm:leading-6"
id="people"
name="people"
placeholder="0"
type="number"
value={people}
onChange={(e) => setPeople(e.target.value)}
/>
</div>
</div>
);
};

export default PeopleAmount;
95 changes: 95 additions & 0 deletions src/plays/tip-calculator/components/TipAmount.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useState } from 'react';

const tips = [
{ tip: 5, isCustom: false },
{ tip: 10, isCustom: false },
{ tip: 15, isCustom: false },
{ tip: 20, isCustom: false },
{ tip: 30, isCustom: false },
{ tip: 0, isCustom: true }
];

const TipAmount = ({ setTip }) => {
const [customSelected, setCustomSelected] = useState(false);
const [activeTip, setActiveTip] = useState(null);
const [customTip, setCustomTip] = useState(0);

const handleTipClick = (index) => {
setCustomSelected(false);

if (activeTip === index) {
setActiveTip(null);

return;
}

setActiveTip(index);
setTip(tips[index].tip / 100);
};

const handleCustomTip = () => {
setActiveTip(null);
setCustomSelected(true);
setTip(customTip / 100);
};

const handleCustomTipBlur = () => {
setTip(customTip / 100);
if (customTip > 0) return;
setCustomSelected(false);
};

return (
<div>
<label className="block font-serif text-sm font-light leading-6 text-gray-600" htmlFor="tip">
Select Tip in %
</label>

<div className="mt-2 grid grid-cols-3 gap-3">
{tips.map((tip, index) => (
<div key={index}>
{tip.isCustom ? (
<>
{customSelected ? (
<input
aria-describedby="tip-amount"
className="block h-full w-full rounded-md border-0 px-2 py-1.5 text-gray-900 outline-none ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-cyan-500 sm:text-sm sm:leading-6"
id="tip"
name="tip"
placeholder="0.00"
type="number"
value={customTip}
onBlur={handleCustomTipBlur}
onChange={(e) => setCustomTip(e.target.value)}
/>
) : (
<button
className="w-full rounded-md bg-gray-100 px-3.5 py-2.5 font-medium text-cyan-700 shadow-sm hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-600"
type="button"
onClick={handleCustomTip}
>
Custom
</button>
)}
</>
) : (
<button
className={`${
index === activeTip
? 'bg-cyan-200 text-cyan-700 hover:bg-cyan-100'
: 'bg-cyan-600 text-white hover:bg-cyan-500'
} w-full rounded-md px-3.5 py-2.5 shadow-sm file:font-medium focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-600`}
type="button"
onClick={() => handleTipClick(index)}
>
{tip.tip}%
</button>
)}
</div>
))}
</div>
</div>
);
};

export default TipAmount;
1 change: 1 addition & 0 deletions src/plays/tip-calculator/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* enter stlyes here */

0 comments on commit 6fef24b

Please sign in to comment.