Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/domains/recipe/components/details/BackBtn.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use client';
import Back from '@/shared/assets/icons/back_36.svg';
import { useRouter } from 'next/navigation';
import Link from 'next/link';

function BackBtn() {
const router = useRouter();

return (
<button type="button" className="z-1" onClick={router.back} aria-label="๋’ค๋กœ๊ฐ€๊ธฐ">
<Back />
<button type="button" className="z-1" aria-label="๋’ค๋กœ๊ฐ€๊ธฐ">
<Link href="/recipe">
<Back />
</Link>
</button>
);
}
Expand Down
40 changes: 32 additions & 8 deletions src/domains/recipe/details/DetailItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Image from 'next/image';
import Short from '@/shared/assets/icons/short_36.svg';
import Label from '@/domains/shared/components/label/Label';
import AbvGraph from '@/domains/shared/components/abv-graph/AbvGraph';
import { labelTitle } from '../utills/labelTitle';
import useGlass from './hook/useGlass';

interface Props {
name: string;
Expand All @@ -15,6 +15,19 @@ interface Props {

function DetailItem({ name, nameKo, story, src, abv, glassType }: Props) {
const alcoholTitle = labelTitle(abv);
const abvNum = abv
.replace(/\(([^)]*)\)/g, '$1')
.split(' ')
.reverse()
.slice(0, 1)
.toString();
const maxAbv = abvNum
.replace(/[~%]/g, ' ')
.split(' ')
.filter((str) => str.trim() !== '')
.map(Number);

const glassIcon = useGlass(glassType);

return (
<div className="flex flex-col items-center">
Expand All @@ -37,8 +50,15 @@ function DetailItem({ name, nameKo, story, src, abv, glassType }: Props) {
<span className="absolute w-3 h-3 rounded-full -bottom-38 z-2 left-1/2 -translate-x-1/2 bg-secondary md:bg-transparent"></span>
</div>

<div className="rounded-2xl overflow-hidden w-75 h-93.75 mt-32 md:mt-4 lg:mt-7 [filter:drop-shadow(0_0_20px_rgba(255,255,255,0.3))]">
<Image src={src} alt={`${nameKo}์‚ฌ์ง„`} fill className="object-cover" />
<div className="rounded-2xl overflow-hidden w-75 h-93.75 mt-32 md:mt-4 lg:mt-7 [filter:drop-shadow(0_0_20px_rgba(255,255,255,0.3))] relative">
<Image
src={src}
alt={`${nameKo}์‚ฌ์ง„`}
fill
className="object-cover"
sizes="300px"
priority
/>
</div>

<dl className="flex flex-col mt-5 gap-3 w-75">
Expand All @@ -48,18 +68,22 @@ function DetailItem({ name, nameKo, story, src, abv, glassType }: Props) {
<span>|</span>
</dt>
<dd className="flex gap-3 items-center">
<p className="text-xs">{abv}</p>
<AbvGraph />
<p className="text-xs">{abvNum}</p>
<AbvGraph abv={Math.max(...maxAbv)} max={40} />
</dd>
</div>
<div className="flex items-center gap-3">
<dt className="flex gap-2 items-center">
<p>๊ธ€๋ž˜์Šค ํƒ€์ž…</p>
<span>|</span>
</dt>
<dd className="flex items-center gap-2">
<Short />
<p>{glassType} ๋“œ๋งํฌ</p>
<dd className="flex items-center ">
{glassIcon}
<p>
{glassType == '์ˆ' || glassType == '๋กฑ'
? `${glassType} ๋“œ๋งํฌ`
: `${glassType} ์นตํ…Œ์ผ`}
</p>
</dd>
</div>
</dl>
Expand Down
41 changes: 31 additions & 10 deletions src/domains/recipe/details/DetailList.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
'use client';
import { useEffect, useState } from 'react';
import DetailRecommendList from './DetailRecommendList';
import { getApi } from '@/app/api/config/appConfig';
import { useParams } from 'next/navigation';
import { RecommendCocktail } from '../types/types';
import Link from 'next/link';

function DetailList() {
const { id } = useParams();
const url = new URL(`${getApi}/cocktails/recommend/related`);
url.searchParams.set('cocktailId', String(id));

const [recommendItem, setRecommendItem] = useState<RecommendCocktail[]>([]);

const recommentFetch = async () => {
const res = await fetch(url.toString());
const json = await res.json();
if (!res.ok) throw new Error('๋ฐ์ดํ„ฐ ์š”์ฒญ ์‹คํŒจ');

setRecommendItem(json.data);
};
useEffect(() => {
recommentFetch();
}, []);

return (
<ul className="flex justify-between gap-2">
<li>
<DetailRecommendList />
</li>
<li>
<DetailRecommendList />
</li>
<li>
<DetailRecommendList />
</li>
<ul className="grid place-content-between [grid-template-columns:repeat(3,minmax(0,250px))] gap-4 ">
{recommendItem.map(({ cocktailImgUrl, cocktailName, cocktailNameKo, id }) => (
<li key={id}>
<Link href={`/recipe/${String(id)}`}>
<DetailRecommendList src={cocktailImgUrl} name={cocktailName} nameKo={cocktailNameKo} />
</Link>
</li>
))}
</ul>
);
}
Expand Down
33 changes: 27 additions & 6 deletions src/domains/recipe/details/DetailRecipe.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
import { ozToMl } from './hook/ozToMl';

type Recipe = {
ingredientName: string;
amount: string;
unit: string;
};

interface Props {
ingredient: string;
ingredient: Recipe[];
recipe: string;
}

function DetailRecipe({ ingredient, recipe }: Props) {
const ingredients = ingredient.trim().split(',').filter(Boolean);
const recipes = recipe.trim().split('.').filter(Boolean);
const arr = ingredient.map((a) => ({
...a,
convert: ozToMl(a.amount),
}));

return (
<div className="flex flex-col md:flex-row px-5 gap-5">
<article className="flex flex-col gap-4 w-[50%]">
<h4 className="text-2xl font-bold">์žฌ๋ฃŒ</h4>
<ul className="flex flex-col gap-2">
{ingredients.map((v, i) => (
<li key={i}>{v}</li>
))}
{arr.map((v, i) => {
return (
<li key={i} className="flex gap-3">
<p>{v.ingredientName}</p>
<span className="text-white/80 text-sm">
{v.amount}
{v.unit}

{v.unit == 'oz' && `${' '}|${' '} ${v.convert} ml`}
</span>
</li>
);
})}
</ul>
</article>

<span className="border-t-1 w-1/2 pt-5 md:border-l-1 md:border-t-0 md:px-10 border-white">
<span className="border-t-1 w-full md:w-1/2 pt-5 md:border-l-1 md:border-t-0 md:px-10 border-white">
<article className="flex flex-col gap-4 ">
<h4 className="text-2xl font-bold">๋งŒ๋“œ๋Š” ๋ฒ•</h4>
<ol className="flex flex-col gap-2 pl-4 list-decimal">
Expand Down
24 changes: 19 additions & 5 deletions src/domains/recipe/details/DetailRecommendList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
// import Image from 'next/image';
function DetailRecommendList() {
import Image from 'next/image';

interface Props {
src: string;
name: string;
nameKo: string;
}

function DetailRecommendList({ src, name, nameKo }: Props) {
return (
<div className="flex flex-col gap-3">
<div className="max-h-75">{/* <Image src="" alt="" /> */}</div>
<div
className="
relative overflow-hidden rounded-2xl
w-full max-w-62.5 aspect-[5/6]
"
>
<Image src={src} alt={`${nameKo} ์‚ฌ์ง„`} fill className="object-cover" sizes="250px" />
</div>
<div className="flex flex-col gap-1">
<h4 className="font-serif text-base lg:text-lg">Old Fashioned</h4>
<p className="font-serif text-base">์˜ฌ๋“œํŒจ์…˜๋“œ</p>
<h4 className="font-serif text-base truncate lg:text-lg">{name}</h4>
<p className="font-serif text-sm sm:text-base">{nameKo}</p>
</div>
</div>
);
Expand Down
46 changes: 46 additions & 0 deletions src/domains/recipe/details/hook/ozToMl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ์œ ๋‹ˆ์ฝ”๋“œ ๋ถ„์ˆ˜ ๋งคํ•‘ (์†Œ์ˆ˜์  ๋Œ€์‹  ์ •์ˆ˜ ml๋กœ ๋ณ€ํ™˜)
const FRAC_MAP: Record<string, number> = {
'ยผ': 8, // 0.25 * 30
'ยฝ': 15, // 0.5 * 30
'ยพ': 23, // 0.75 * 30
'โ…“': 10, // 1/3 * 30 โ‰ˆ 10
'โ…”': 20, // 2/3 * 30 โ‰ˆ 20
'โ…•': 6, // 1/5 * 30
'โ…–': 12, // 2/5 * 30
'โ…—': 18, // 3/5 * 30
'โ…˜': 24, // 4/5 * 30
'โ…™': 5, // 1/6 * 30
'โ…š': 25, // 5/6 * 30
'โ…›': 4, // 1/8 * 30
'โ…œ': 11, // 3/8 * 30
'โ…': 19, // 5/8 * 30
'โ…ž': 26, // 7/8 * 30
};

const FRAC_CLASS = Object.keys(FRAC_MAP).join('');

export function ozToMl(input: string): number | '' {
if (!input) return '';

const trimmed = input.trim();

// ํ˜ผํ•ฉ ๋ถ„์ˆ˜: "1 โ…”", "2 ยฝ"
const mixed = trimmed.match(new RegExp(`^(\\d+)\\s*([${FRAC_CLASS}])$`));
if (mixed) {
const whole = Number(mixed[1]);
const frac = FRAC_MAP[mixed[2]] ?? 0;
return whole * 30 + frac;
}

// ๋ถ„์ˆ˜ ๋‹จ๋…: "โ…”", "ยฝ"
if (FRAC_MAP[trimmed] != null) {
return FRAC_MAP[trimmed];
}

// ์ˆœ์ˆ˜ ์ˆซ์ž: "1", "2"
if (!isNaN(Number(trimmed))) {
return Number(trimmed) * 30;
}

return '';
}
18 changes: 18 additions & 0 deletions src/domains/recipe/details/hook/useGlass.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Shooter from '@/shared/assets/icons/shooter_36.svg';
import Short from '@/shared/assets/icons/short_36.svg';
import Long from '@/shared/assets/icons/long_36.svg';
import Classic from '@/shared/assets/icons/classic_36.svg';

const useGlass = (glass: string) => {
switch (glass) {
case '์Šˆํ„ฐ':
return <Shooter />;
case '์ˆ':
return <Short />;
case '๋กฑ':
return <Long />;
case 'ํด๋ž˜์‹':
return <Classic />;
}
};
export default useGlass;
9 changes: 9 additions & 0 deletions src/domains/recipe/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ export interface Cocktail {
cocktailImgUrl: string;
cocktailNameKo: string;
}

export interface RecommendCocktail {
id: number;
cocktailNameKo: string;
cocktailName: string;
cocktailImgUrl: string;
alcoholStrength: string;
alcoholBaseType: string;
}
30 changes: 23 additions & 7 deletions src/domains/shared/components/abv-graph/AbvGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
function AbvGraph() {
import clsx from 'clsx';

interface Props {
max?: number;
abv?: number;
}

function AbvGraph({ max, abv }: Props) {
if (!abv) return;
const safeMax = Math.max(0, max || 0.0001);
const rawPct = (abv / safeMax) * 100;
const pct = Math.min(100, Math.max(0, Number.isFinite(rawPct) ? rawPct : 0));

const bandClass = clsx(
'h-full rounded-full transition-[width] duration-500',
'bg-gradient-to-r from-amber-300 to-red-500', // ๊ธฐ๋ณธ ๊ทธ๋ผ๋ฐ์ด์…˜
pct >= 80 && 'shadow-[0_0_12px_rgba(250,36,36,0.45)]'
);

return (
<div
className="w-full md:w-49 h-3 rounded-full overflow-hidden border-[0.5px] border-gray relative"
className="w-45 h-3 rounded-full overflow-hidden border-[0.5px] border-gray relative"
role="progressbar"
aria-label="๋‚˜์˜ ์•Œ์ฝ”์˜ฌ ๋„์ˆ˜"
aria-valuemin={0}
aria-valuemax={max}
>
<div
className="absolute top-0 left-0 w-10 h-3
bg-linear-to-r from-[#FFCA8D] to-[#FA2424]
"
></div>
<div className={bandClass} style={{ width: `${pct}%` }}></div>
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/domains/shared/components/cocktail-card/CocktailCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ function CocktailCard({
<div className="flex flex-col gap-4">
<div
className={tw(
`${!className && 'w-80 h-75 md:w-62.5 '} rounded-xl overflow-hidden relative`,
`${!className && 'w-80 h-75 md:w-62.5 '} rounded-xl overflow-hidden relative`,
className
)}
>
<Image src={src} alt={name} fill className="object-cover" />
<Image src={src} alt={name} fill className="object-cover" sizes="320px" priority />
{keep && (
<div className="flex w-full pl-4 px-3 py-2 items-center justify-between absolute left-0 top-0">
<div>{alcoholTitle && <Label title={alcoholTitle} />}</div>
Expand Down