Skip to content
Merged
17 changes: 17 additions & 0 deletions src/app/api/kakao/KaKaoScript.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client';

import Script from 'next/script';

declare global {
interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Kakao: any;
}
}
function KaKaoScript() {
const onLoad = () => {
window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_JAVASCRIPT_KEY);
};
return <Script src="https://developers.kakao.com/sdk/js/kakao.js" async onLoad={onLoad} />;
}
export default KaKaoScript;
6 changes: 5 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { Toaster } from 'react-hot-toast';
import Header from '@/shared/components/header/Header';
import FooterWrapper from '@/shared/components/footer/FooterWrapper';
import ScrollTopBtnWrapper from '@/shared/components/scroll-top/ScrollTopBtnWrapper';
import KaKaoScript from './api/kakao/KaKaoScript';

export const metadata: Metadata = {
title: 'SSOUL',
title: { default: 'SSOUL', template: 'SSOUL | %s' },
metadataBase: new URL('http://www.ssoul.life'),
description: '칵테일을 좋아하는 사람들을 위한 서비스',
};

Expand Down Expand Up @@ -35,6 +38,7 @@ export default function RootLayout({

<ScrollTopBtnWrapper />
</body>
<KaKaoScript />
</html>
);
}
35 changes: 33 additions & 2 deletions src/app/recipe/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
import { getApi } from '@/app/api/config/appConfig';
import DetailMain from '@/domains/recipe/details/DetailMain';
import StarBg from '@/domains/shared/components/star-bg/StarBg';
import { Metadata } from 'next';

type RouteParams = { id: number };

export async function generateMetadata({
params,
}: {
params: Promise<RouteParams>;
}): Promise<Metadata> {
const { id } = await params;
const res = await fetch(`${getApi}/cocktails/${id}`);
const recipe = await res.json();
return {
title: `${recipe.data.cocktailNameKo}`,
description: `${recipe.data.cocktailNameKo} 레시피`,
openGraph: {
title: recipe.title,
images: [recipe.data.cocktailImgUrl],
type: 'article',
},
twitter: {
card: 'summary_large_image',
description: `${recipe.data.cocktailNameKo} 레시피`,
title: `${recipe.data.cocktailNameKo}`,
images: [recipe.data.cocktailImgUrl],
},
};
}

async function Page({ params }: { params: Promise<RouteParams> }) {
const { id } = await params;

function Page() {
return (
<div className="w-full relative">
<StarBg className="absolute top-0 left-0 h-200 lg:h-200" />
<DetailMain />
<DetailMain id={id} />
</div>
);
}
Expand Down
12 changes: 7 additions & 5 deletions src/domains/recipe/details/DetailItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ function DetailItem({ name, nameKo, story, src, abv, glassType }: Props) {
<div className="flex flex-col w-full gap-3 relative md:flex-row md:justify-between md:w-150 md:gap-20 lg:w-187.5 h-50">
<div className="flex flex-col gap-1 items-center md:items-end md:w-1/2">
<span>{alcoholTitle && <Label title={alcoholTitle} />}</span>
<h2 className="w-fit font-serif font-bold text-right text-3xl lg:text-4xl text-secondary ">
{name}
</h2>
<h2 className="font-serif font-bold text-right text-xl lg:text-4xl text-secondary">
{nameKo}
<h2 className="flex flex-col gap-2">
<span className="w-fit font-serif font-bold text-right text-3xl lg:text-4xl text-secondary ">
{name}
</span>
<span className="font-serif font-bold text-right text-xl lg:whitespace-nowrap lg:text-4xl text-secondary">
{nameKo}
</span>
</h2>
</div>

Expand Down
33 changes: 13 additions & 20 deletions src/domains/recipe/details/DetailMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ import DetailList from './DetailList';
import { Suspense, useEffect, useState } from 'react';
import SkeletonDetail from '../skeleton/SkeletonDetail';
import RecipeComment from '../components/details/RecipeComment';
import { useParams } from 'next/navigation';
import { getApi } from '@/app/api/config/appConfig';

function DetailMain() {
const { id } = useParams();

function DetailMain({ id }: { id: number }) {
const [cocktail, setCocktail] = useState();

const fetchData = async () => {
Expand Down Expand Up @@ -57,12 +54,13 @@ function DetailMain() {

return (
<Suspense fallback={<SkeletonDetail />}>
<div className="max-w-1024 page-layout py-12">
<DetailsHeader />
<h1 className="sr-only">${cocktailNameKo} 상세정보</h1>
<div className="max-w-1024 page-layout pt-4 pb-6 sm:pb-12">
<DetailsHeader id={id} />

<article className="flex flex-col items-center mt-4 lg:mt-0">
<span className="md:bg-secondary w-1 h-100 -translate-y-19 absolute top-0 left-1/2 -translate-x-1/2 md: z-2"></span>
<span className="h-3 w-3 rounded-full absolute top-80 left-1/2 -translate-x-1/2 z-99 md:bg-secondary"></span>
<span className="md:bg-secondary w-1 h-104 -translate-y-19 absolute top-0 left-1/2 -translate-x-1/2 md: z-2"></span>
<span className="h-3 w-3 rounded-full absolute top-82 left-1/2 -translate-x-1/2 z-2 md:bg-secondary"></span>
<DetailItem
name={cocktailName}
nameKo={cocktailNameKo}
Expand All @@ -74,22 +72,17 @@ function DetailMain() {
</article>

<section className="mt-20 flex flex-col gap-5">
<div className="border-b-1 h-18 border-white">
<div className="flex items-center gap-3">
<Image src={SsuryShake} alt="" width="48" height="48" />
<h3 className="text-3xl font-bold">레시피</h3>
</div>
<div className="border-b-1 h-18 border-white flex gap-3 items-center">
<Image src={SsuryShake} alt="" width="48" height="48" />
<h3 className="text-2xl font-bold">레시피</h3>
</div>
<DetailRecipe ingredient={ingredient} recipe={recipe} />
</section>

<section className="mt-20" aria-labelledby="옆으로 슬라이드되는 리스트">
<h2 className="sr-only">추천 칵테일 리스트</h2>
<div className="border-b-1 h-18 border-white">
<div className="flex items-center gap-3">
<Image src={SsuryDrink} alt="" width="48" height="48" />
<h3 className="text-3xl font-bold">추천리스트</h3>
</div>
<section className="mt-20">
<div className="border-b-1 h-18 border-white flex items-center gap-3">
<Image src={SsuryDrink} alt="" width="48" height="48" />
<h3 className="text-2xl font-bold">추천리스트</h3>
</div>

<div className="mt-5">
Expand Down
8 changes: 4 additions & 4 deletions src/domains/recipe/details/DetailRecipe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ function DetailRecipe({ ingredient, recipe }: Props) {

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>
<article className="flex flex-col gap-4 w-full md:w-[50%]">
<h4 className="text-xl font-bold">재료</h4>
<ul className="flex flex-col gap-2">
{arr.map((v, i) => {
return (
Expand All @@ -39,9 +39,9 @@ function DetailRecipe({ ingredient, recipe }: Props) {
</ul>
</article>

<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">
<span className="border-t-1 w-full md:w-1/2 pt-5 md:pt-0 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>
<h4 className="text-xl font-bold">만드는 법</h4>
<ol className="flex flex-col gap-2 pl-4 list-decimal">
{recipes.map((v, i) => (
<li key={i}>{v}</li>
Expand Down
38 changes: 34 additions & 4 deletions src/domains/recipe/details/DetailsHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
'use client';
import Share from '@/domains/shared/components/share/Share';
import BackBtn from '../components/details/BackBtn';
import Keep from '@/domains/shared/components/keep/Keep';
import { useEffect, useState } from 'react';
import ShareModal from '@/domains/shared/components/share/ShareModal';
import { getApi } from '@/app/api/config/appConfig';

interface Meta {
title: string;
imageUrl: string;
url: string;
}

function DetailsHeader({ id }: { id: number }) {
const [isShare, setIsShare] = useState(false);
const [meta, setMeta] = useState<Meta | null>(null);
const url = async () => {
const res = await fetch(`${getApi}/cocktails/${id}/share`);
const json = await res.json();
setMeta(json.data);
};
useEffect(() => {
url();
}, []);

function DetailsHeader() {
return (
<div className="flex items-center justify-between ">
<div className="flex items-center justify-between pb-5 sm:pb-12">
{isShare && meta && (
<ShareModal
open={isShare}
onClose={() => setIsShare(!isShare)}
src={meta.imageUrl}
title={meta.title}
url={meta.url}
/>
)}
<BackBtn />
<div className="flex items-center gap-3">
<Share size="sm" />
<Keep />
<Share size="sm" onClick={() => setIsShare(true)} />
<Keep cocktailId={id} />
</div>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion src/domains/recipe/main/CocktailList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ function CocktailList({ cocktails, RecipeFetch, hasNextPage, lastId, onItemClick
({ cocktailImgUrl, cocktailId, cocktailName, cocktailNameKo, alcoholStrength }) => (
<li key={cocktailId} onClick={onItemClick}>
<Link
href={`/recipe/${String(cocktailId)}`}
href={`/recipe/${cocktailId}`}
onClick={() => {
sessionStorage.setItem('listScrollY', String(window.scrollY));
}}
>
<CocktailCard
id={cocktailId}
src={cocktailImgUrl}
name={cocktailName}
nameKo={cocktailNameKo}
Expand Down
29 changes: 29 additions & 0 deletions src/domains/shared/api/keep/keep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getApi } from '@/app/api/config/appConfig';

export async function postKeep(CocktailId: number) {
const res = await fetch(`${getApi}/me/bar/${CocktailId}/keep`, {
method: 'POST',
credentials: 'include',
});

let payload = null;
try {
payload = await res.json();
} catch {
console.error();
}
return payload;
}

export async function deleteKeep(CocktailId: number) {
const res = await fetch(`${getApi}/me/bar/${CocktailId}/keep`, {
method: 'DELETE',
credentials: 'include',
});
let payload = null;
try {
payload = await res.json();
} catch {
console.error();
}
}
4 changes: 3 additions & 1 deletion src/domains/shared/components/cocktail-card/CocktailCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Keep from '../keep/Keep';
import { labelTitle } from '@/domains/recipe/utills/labelTitle';

interface Props {
id?: number;
src: string;
name: string;
nameKo?: string;
Expand All @@ -27,6 +28,7 @@ function CocktailCard({
textSize1,
textSize2,
alcohol,
id,
}: Props) {
const alcoholTitle = labelTitle(alcohol);

Expand All @@ -42,7 +44,7 @@ function CocktailCard({
{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>
<Keep />
{id && <Keep cocktailId={id} />}
</div>
)}
</div>
Expand Down
22 changes: 20 additions & 2 deletions src/domains/shared/components/keep/Keep.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
'use client';

import KeepIcon from '@/shared/assets/icons/keep_36.svg';
import KeepIconActive from '@/shared/assets/icons/keep_active_36.svg';
import { useState } from 'react';
import { deleteKeep, postKeep } from '../../api/keep/keep';

interface Props {
className?: string;
cocktailId?: number;
}
// ID는 커뮤니티 공유할때 id 타입보고 옵셔널 체크 풀어주세요!
// 만약 타입 안맞는다면 그냥 두셔도 됩니다.

function Keep({ className }: Props) {
function Keep({ className, cocktailId }: Props) {
const [isClick, setIsClick] = useState(false);
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();

setIsClick(!isClick);

try {
if (!cocktailId) return;
if (!isClick) {
await postKeep(cocktailId);
} else {
await deleteKeep(cocktailId);
}
} catch (err) {
console.error(err);
}
};

return (
Expand Down
10 changes: 3 additions & 7 deletions src/domains/shared/components/share/Share.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,24 @@ import ShareBtn from '@/shared/assets/icons/share_28.svg';
interface Props {
onClick?: () => void;
variants?: 'default' | 'community';
title?: string;
content?: string;
size: 'sm' | 'md';
}

function Share({ onClick, variants = 'default', title, content, size }: Props) {
// title과 content는 추후 API가 들어오면 사용예정 API가 들어오면 타입 옵셔널 체크 해제헤 주세요

function Share({ onClick, variants = 'default', size }: Props) {
return (
<button
type="button"
className={`${
variants == 'community' && size === 'md'
? 'w-13.75 h-13.75 flex-center border-1 border-white rounded-full'
: ''
: 'z-1'
} bg-primary`}
aria-label="공유하기"
onClick={onClick}
>
<ShareBtn
fill="transparent"
className="duration-100 object-contain hover:[&_*]:fill-white/50 [&_*]:duration-200"
className="duration-100 object-contain hover:[&_*]:fill-white/50 [&_*]:duration-200"
aria-hidden
/>
</button>
Expand Down
Loading