Skip to content

Commit

Permalink
feat: 링크로 공유하기 기능 구현 (#14)
Browse files Browse the repository at this point in the history
* feat: Add share dialog

* refactor: Fix folder structures

* fix: Fix tag error

* fix: Fix console error

* feat: Add copy url and toast

* feat: Add checking postposition
  • Loading branch information
Najeong-Kim authored Aug 17, 2024
1 parent 7c06735 commit fe504b3
Show file tree
Hide file tree
Showing 29 changed files with 180 additions and 23 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
"autoprefixer": "^10.4.19",
"class-variance-authority": "^0.7.0",
"next": "14.2.5",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-hls-player": "^3.0.7",
"sonner": "^1.5.0",
"tailwind-scrollbar-hide": "^1.1.7",
"tailwindcss-animate": "^1.0.7",
"ts-pattern": "^5.2.0",
Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
Binary file added public/shares/share_01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/shares/share_02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/shares/share_03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/shares/share_04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/app/(web)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { cn } from '@/shared/lib';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className={cn('flex size-full justify-center bg-main-5 antialiased')}>
<div className={cn('size-full max-w-[670px] bg-opacity-65 bg-[url("/background.png")]')}>
<div
className={cn('size-full max-w-[670px] bg-opacity-65 bg-[url("/assets/background.png")]')}
>
{children}
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '@/app/globals.css';

import type { Metadata } from 'next';
import { Toaster } from '@/shared/ui/toaster';
import Providers from './_providers';

export const metadata: Metadata = {
Expand All @@ -13,6 +14,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<html lang="en">
<body>
<Providers>{children}</Providers>
<Toaster />
</body>
</html>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Image from 'next/image';
import android from '@public/android.png';
import ios from '@public/ios.png';
import logo from '@public/logo.svg';
import snow from '@public/snow.png';
import logo from '@public/assets/logo.svg';
import snow from '@public/assets/snow.png';
import android from '@public/downloads/android.png';
import ios from '@public/downloads/ios.png';
import { CloseIcon } from '@/shared/icons';
import { cn } from '@/shared/lib';

Expand All @@ -22,13 +22,11 @@ const AppDownloadDialog = ({ className, onClose }: AppDownloadDialogProps) => {
<Image
src={snow}
alt="snow"
width={191}
height={196}
className={cn('absolute right-[-21px] top-0')}
className={cn('absolute right-[-21px] top-0 h-[196px] w-[191px]')}
/>
<CloseIcon className={cn('absolute right-6 top-6 size-6 cursor-pointer')} onClick={onClose} />
<Image src={logo} alt="logo" />
<p className={cn('title1 mb-[39px] mt-[35px] whitespace-pre-wrap font-bold')}>
<p className={cn('title1 mb-[39px] mt-[35px] whitespace-pre-wrap')}>
{'지금 앱 다운로드하고\n블라인드된 정보를\n매일 확인해보세요'}
</p>
<div className={cn('flex gap-3')}>
Expand Down
94 changes: 94 additions & 0 deletions src/features/discovery-detail/ui/share-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import Image from 'next/image';
import { useCallback } from 'react';
import { toast } from 'sonner';
import logo from '@public/assets/logo.svg';
import snow from '@public/assets/snow.png';
import share1 from '@public/shares/share_01.png';
import share2 from '@public/shares/share_02.png';
import share3 from '@public/shares/share_03.png';
import share4 from '@public/shares/share_04.png';
import { CloseIcon } from '@/shared/icons';
import { cn, getPostposition } from '@/shared/lib';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from '@/shared/ui/dialog';

interface ShareDialogProps {
trigger: React.ReactNode;
name?: string;
}

const ShareDialog = ({ trigger, name }: ShareDialogProps) => {
const handleCopyUrl = useCallback(() => {
const currentUrl = window.location.href;
navigator.clipboard
.writeText(currentUrl)
.then(() => {
toast('클립보드에 복사되었습니다');
})
.catch((error) => {
console.error('클립보드 복사 실패!', error);
});
}, []);

return (
<Dialog>
<DialogTrigger>{trigger}</DialogTrigger>

<DialogContent
className={cn(
'w-[321px] gap-0 overflow-hidden rounded-[7px] bg-white px-[30px] pb-[30px] pt-[27px]'
)}
>
<DialogTitle className={cn('hidden')} />
<DialogDescription className={cn('hidden')} />
<Image
src={snow}
alt="snow"
className={cn('absolute right-[-41px] top-0 h-[196px] w-[191px]')}
/>
<DialogClose>
<CloseIcon className={cn('absolute right-6 top-6 size-6 cursor-pointer text-gray-50')} />
</DialogClose>
<Image src={logo} alt="logo" />
<div className={cn('h3-semibold mb-[31px] mt-[38px] flex h-16 flex-col justify-evenly')}>
<p>{getPostposition(name || '')}</p>
<p>공유해보세요!</p>
</div>
<div className={cn('body3-medium z-10 flex justify-between gap-3 text-gray-70')}>
<div className={cn('flex flex-col items-center gap-[6px]')}>
<button>
<Image src={share1} alt="kakao-talk" width={46} height={46} />
</button>
<p>카카오톡</p>
</div>
<div className={cn('flex flex-col items-center gap-[6px]')}>
<button>
<Image src={share2} alt="instagram" width={46} height={46} />
</button>
<p>인스타그램</p>
</div>
<div className={cn('flex flex-col items-center gap-[6px]')}>
<button>
<Image src={share3} alt="thread" width={46} height={46} />
</button>
<p>스레드</p>
</div>
<div className={cn('flex flex-col items-center gap-[6px]')}>
<button onClick={handleCopyUrl}>
<Image src={share4} alt="link" width={46} height={46} />
</button>
<p>링크 공유</p>
</div>
</div>
</DialogContent>
</Dialog>
);
};

export default ShareDialog;
18 changes: 9 additions & 9 deletions src/pages/discovery-detail/ui/discovery-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import Image from 'next/image';
import { useState } from 'react';
import { useCallback } from 'react';
import blind1 from '@public/blind_01.png';
import blind1 from '@public/blinds/blind_01.png';
import { DiscoveryContentTabList } from '@/widgets/discovery-detail/model/constants';
import AppDownloadDialog from '@/widgets/discovery-detail/ui/app-download-dialog';
import DiscoverySummary from '@/widgets/discovery-detail/ui/discovery-summary';
import { Header } from '@/widgets/header/ui';
import { WebcamMap, WebcamSlopList } from '@/widgets/webcam/ui';
import AppDownloadDialog from '@/features/discovery-detail/ui/app-download-dialog';
import useMapPinch from '@/features/slop/hooks/useMapPinch';
import calculateWebcamPosition from '@/features/slop/lib/calculateWebcamPosition';
import { DiscoveryData } from '@/entities/discovery';
Expand Down Expand Up @@ -49,7 +49,7 @@ const DiscoveryDetailPage = ({ params }: { params: { resortId: number } }) => {

return (
<div className={cn('size-full')}>
<Header hasBackButton hasShareButton />
<Header resortName={discovery.name} hasBackButton hasShareButton />
<DiscoverySummary {...discovery} />
<ul className={cn('flex size-full h-[53px] bg-white')}>
{DiscoveryContentTabList.map((tab) => (
Expand Down Expand Up @@ -99,8 +99,8 @@ const DiscoveryDetailPage = ({ params }: { params: { resortId: number } }) => {
<p className={cn('title3-semibold mb-6 pl-6 pt-8 xs:pl-10')}>주간 예보</p>
<div
className={cn(
'h-[372px] bg-[url("/blind_02-1.png")] bg-contain bg-no-repeat',
'xs:h-[180px] xs:bg-[url("/blind_02.png")]'
'h-[372px] bg-[url("/blinds/blind_02-1.png")] bg-contain bg-no-repeat',
'xs:h-[180px] xs:bg-[url("/blinds/blind_02.png")]'
)}
/>
{showAppDownloadDialog && (
Expand All @@ -118,16 +118,16 @@ const DiscoveryDetailPage = ({ params }: { params: { resortId: number } }) => {
<p className={cn('title3-semibold mb-6 pl-6 pt-8 xs:pl-10 xs:pt-10')}>인기 시간대</p>
<div
className={cn(
'aspect-[2.86] w-full bg-[url("/blind_03-1.png")] bg-cover',
'xs:aspect-[2.62] xs:bg-[url("/blind_03.png")]'
'aspect-[2.86] w-full bg-[url("/blinds/blind_03-1.png")] bg-cover',
'xs:aspect-[2.62] xs:bg-[url("/blinds/blind_03.png")]'
)}
/>
<div className={cn('mt-10 h-[6px] w-full bg-gray-20')} />
<p className={cn('title3-semibold mb-6 pl-6 pt-8 xs:pl-10')}>슬로프 운행 현황</p>
<div
className={cn(
'aspect-[0.71] w-full bg-[url("/blind_04-1.png")] bg-cover',
'aspect-[0.88] xs:bg-[url("/blind_04.png")]'
'aspect-[0.71] w-full bg-[url("/blinds/blind_04-1.png")] bg-cover',
'aspect-[0.88] xs:bg-[url("/blinds/blind_04.png")]'
)}
/>
{showAppDownloadDialog && (
Expand Down
12 changes: 12 additions & 0 deletions src/shared/lib/getPostposition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const getPostposition = (name: string) => {
const charCode = name.charCodeAt(name.length - 1);

const consonantCode = (charCode - 44032) % 28;

if (consonantCode === 0) {
return `${name}를`;
}
return `${name}을`;
};

export default getPostposition;
1 change: 1 addition & 0 deletions src/shared/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as cn } from '@/shared/lib/cn';
export { default as getBoundedPositions } from '@/shared/lib/getBoundedPositions';
export { default as getPostposition } from '@/shared/lib/getPostposition';
export type { ExcludeArray } from '@/shared/lib/util.d.ts';
29 changes: 29 additions & 0 deletions src/shared/ui/toaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';

import { useTheme } from 'next-themes';
import { Toaster as Sonner } from 'sonner';

type ToasterProps = React.ComponentProps<typeof Sonner>;

const Toaster = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme();

return (
<Sonner
theme={theme as ToasterProps['theme']}
className="toaster group"
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'group-[.toast]:text-muted-foreground',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
},
}}
{...props}
/>
);
};

export { Toaster };
2 changes: 1 addition & 1 deletion src/widgets/discovery-detail/ui/discovery-summary.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useCallback } from 'react';
import WeatherIcon from '@/features/discovery/ui/weather-icon';
import VoteDialog from '@/features/discovery-detail/ui/vote-dialog';
import type { Discovery } from '@/entities/discovery';
import { BusIcon, LiftIcon, VoteIcon } from '@/shared/icons';
import { cn } from '@/shared/lib';
import Card from '@/shared/ui/card';
import { DiscoverySummaryActionList } from '../model/constants';
import DiscoverySummaryAction from './discovery-summary-action';
import VoteDialog from './vote-dialog';

const DiscoverySummary = ({ name, slope, weather }: Discovery) => {
const handleAction = useCallback(
Expand Down
15 changes: 11 additions & 4 deletions src/widgets/header/ui/header.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useRouter } from 'next/navigation';
import ShareDialog from '@/features/discovery-detail/ui/share-dialog';
import { ChevronLeftIcon, ShareIcon } from '@/shared/icons';
import { cn } from '@/shared/lib';

interface HeaderProps {
resortName?: string;
hasBackButton?: boolean;
hasShareButton?: boolean;
}

const Header = ({ hasBackButton, hasShareButton }: HeaderProps) => {
const Header = ({ resortName, hasBackButton, hasShareButton }: HeaderProps) => {
const router = useRouter();

return (
Expand All @@ -24,9 +26,14 @@ const Header = ({ hasBackButton, hasShareButton }: HeaderProps) => {
WeSki
</h2>
{hasShareButton && (
<button className={cn('absolute right-7 top-1/2 -translate-y-1/2')}>
<ShareIcon />
</button>
<ShareDialog
trigger={
<div className={cn('absolute right-7 top-1/2 -translate-y-1/2')}>
<ShareIcon />
</div>
}
name={resortName}
/>
)}
</div>
);
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,11 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==

next-themes@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.3.0.tgz#b4d2a866137a67d42564b07f3a3e720e2ff3871a"
integrity sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==

[email protected]:
version "14.2.5"
resolved "https://registry.yarnpkg.com/next/-/next-14.2.5.tgz#afe4022bb0b752962e2205836587a289270efbea"
Expand Down Expand Up @@ -2878,6 +2883,11 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==

sonner@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.5.0.tgz#af359f817063318415326b33aab54c5d17c747b7"
integrity sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==

source-map-js@^1.0.2, source-map-js@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
Expand Down

0 comments on commit fe504b3

Please sign in to comment.