From f8cb180903b44b1d8b2ac220ab1f578b99dda170 Mon Sep 17 00:00:00 2001 From: junseublim Date: Sat, 21 Dec 2024 17:15:07 +0900 Subject: [PATCH 01/19] feat: add getBoardStyle --- .../_components/Header/DefaultHeader.tsx | 6 ++++-- .../_components/Header/SelectModeHeader.tsx | 8 +++++--- .../[boardId]/_components/Header/index.tsx | 9 ++------- src/app/board/[boardId]/page.tsx | 6 +++--- src/lib/utils/board.ts | 17 +++++++++++++++++ 5 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 src/lib/utils/board.ts diff --git a/src/app/board/[boardId]/_components/Header/DefaultHeader.tsx b/src/app/board/[boardId]/_components/Header/DefaultHeader.tsx index 9a0afdf..9f408f4 100644 --- a/src/app/board/[boardId]/_components/Header/DefaultHeader.tsx +++ b/src/app/board/[boardId]/_components/Header/DefaultHeader.tsx @@ -6,13 +6,15 @@ import { BoardTutorial } from '@/components/Tutorial' import { useSession } from 'next-auth/react' import PinIcon from 'public/icons/pinFilled.svg' import { twMerge } from 'tailwind-merge' +import { getBoardStyle } from '@/lib/utils/board' import { useBoard } from '../../_contexts/BoardContext' import ShareBtn from '../Share' import { Step1Tooltip } from '../Tooltips' -const DefaultHeader = ({ className }: { className: string }) => { +const DefaultHeader = () => { const { data: session } = useSession() const { board } = useBoard() + const { titleClassName } = getBoardStyle(board) return (
{ ) } - className={twMerge('bg-transparent', className)} + className={twMerge('bg-transparent', titleClassName)} shadow={false} /> ) diff --git a/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx b/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx index 3a382de..e5d14d9 100644 --- a/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx +++ b/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx @@ -3,11 +3,13 @@ import Header from '@/components/Header' import { useSelect } from '@/app/board/[boardId]/_contexts/SelectModeContext' import { useBoard } from '@/app/board/[boardId]/_contexts/BoardContext' import { twMerge } from 'tailwind-merge' +import { getBoardStyle } from '@/lib/utils/board' -const SelectModeHeader = ({ className }: { className: string }) => { +const SelectModeHeader = () => { const { selectedIds, MAX_SELECT_COUNT } = useSelect() const { board } = useBoard() const maxLength = Math.min(board.items.length, MAX_SELECT_COUNT) + const { selectCountClassName, titleClassName } = getBoardStyle(board) return (
{

꾸미고 싶은 폴라로이드를 골라주세요

- {selectedIds.length} + {selectedIds.length} / {maxLength}

} - className={twMerge('bg-transparent', className)} + className={twMerge('bg-transparent', titleClassName)} shadow={false} /> ) diff --git a/src/app/board/[boardId]/_components/Header/index.tsx b/src/app/board/[boardId]/_components/Header/index.tsx index d6db6b5..965b3d3 100644 --- a/src/app/board/[boardId]/_components/Header/index.tsx +++ b/src/app/board/[boardId]/_components/Header/index.tsx @@ -4,19 +4,14 @@ import React from 'react' import { useSelect } from '@/app/board/[boardId]/_contexts/SelectModeContext' import DefaultHeader from '@/app/board/[boardId]/_components/Header/DefaultHeader' import SelectModeHeader from '@/app/board/[boardId]/_components/Header/SelectModeHeader' -import { useBoard } from '@/app/board/[boardId]/_contexts/BoardContext' -import { BOARDTHEMAS } from '@/lib/constants' const Header = () => { const { isSelectMode } = useSelect() - const { board } = useBoard() - const boardTheme = BOARDTHEMAS[board.options.THEMA].theme - const className = boardTheme === 'LIGHT' ? 'text-gray-900' : 'text-gray-0' if (isSelectMode) { - return + return } - return + return } export default Header diff --git a/src/app/board/[boardId]/page.tsx b/src/app/board/[boardId]/page.tsx index 38c8c53..6356a8f 100644 --- a/src/app/board/[boardId]/page.tsx +++ b/src/app/board/[boardId]/page.tsx @@ -5,6 +5,7 @@ import { SelectContextProvider } from '@/app/board/[boardId]/_contexts/SelectMod import { BoardTutorialProvider } from '@/components/Tutorial' import { getBoard } from '@/lib' import { Metadata } from 'next' +import { getBoardStyle } from '@/lib/utils/board' import CreatePolaroid from './_components/CreatePolaroidModal' import { ModalProvider } from './_components/CreatePolaroidModal/ModalContext' import Empty from './_components/Empty' @@ -49,8 +50,7 @@ interface BoardPageProps { const BoardPage = async ({ params }: BoardPageProps) => { const { boardId } = params const board = await getBoard(boardId) - - const background = `/images/boardThemas/${board.options.THEMA}.png` + const { backgroundImage } = getBoardStyle(board) return ( @@ -58,7 +58,7 @@ const BoardPage = async ({ params }: BoardPageProps) => {
{board.items.length === 0 ? : } diff --git a/src/lib/utils/board.ts b/src/lib/utils/board.ts new file mode 100644 index 0000000..abcf173 --- /dev/null +++ b/src/lib/utils/board.ts @@ -0,0 +1,17 @@ +import { Board } from '@/types' +import { BOARDTHEMAS } from '@/lib' + +export const getBoardStyle = (board: Board) => { + const boardTheme = BOARDTHEMAS[board.options.THEMA].theme + const titleClassName = + boardTheme === 'LIGHT' ? 'text-gray-900' : 'text-gray-0' + const selectCountClassName = + boardTheme === 'LIGHT' ? 'text-gray-900/50' : 'text-gray-0/50' + const backgroundImage = `/images/boardThemas/${board.options.THEMA}.png` + + return { + titleClassName, + selectCountClassName, + backgroundImage, + } +} From 0e0bf38d16012d7e0b4732acae86baefee8462b0 Mon Sep 17 00:00:00 2001 From: junseublim Date: Sat, 21 Dec 2024 17:18:38 +0900 Subject: [PATCH 02/19] style: add board style to screenshot --- public/icons/{PinIcon.svg => BigPinIcon.svg} | 4 +--- src/app/board/[boardId]/screenshot/page.tsx | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) rename public/icons/{PinIcon.svg => BigPinIcon.svg} (92%) diff --git a/public/icons/PinIcon.svg b/public/icons/BigPinIcon.svg similarity index 92% rename from public/icons/PinIcon.svg rename to public/icons/BigPinIcon.svg index d045a47..c1d2bca 100644 --- a/public/icons/PinIcon.svg +++ b/public/icons/BigPinIcon.svg @@ -1,5 +1,3 @@ - - - + diff --git a/src/app/board/[boardId]/screenshot/page.tsx b/src/app/board/[boardId]/screenshot/page.tsx index 5397e70..ed3f4cb 100644 --- a/src/app/board/[boardId]/screenshot/page.tsx +++ b/src/app/board/[boardId]/screenshot/page.tsx @@ -1,10 +1,12 @@ import React from 'react' import { getBoard } from '@/lib' -import PinIcon from 'public/icons/PinIcon.svg' +import BigPinIcon from 'public/icons/BigPinIcon.svg' import PolaboLogo from 'public/images/polabo-logo.png' import Image from 'next/image' import { ensureArray } from '@/lib/utils/array' import PolaroidList from '@/app/board/[boardId]/screenshot/_components/PolaroidList' +import { twMerge } from 'tailwind-merge' +import { getBoardStyle } from '@/lib/utils/board' interface BoardScreenshotPageProps { params: { @@ -25,6 +27,7 @@ const BoardScreenshotPage = async ({ const selectedPolaroids = board.items.filter((item) => polaroidIds.includes(String(item.id)), ) + const { titleClassName, backgroundImage } = getBoardStyle(board) if (selectedPolaroids.length === 0) { throw new Error('No polaroids found') @@ -34,9 +37,15 @@ const BoardScreenshotPage = async ({
-
- +
+ {board.title} From 9835e45d1514e0373033e50c41ef7695dcb3cd81 Mon Sep 17 00:00:00 2001 From: junseublim Date: Sat, 21 Dec 2024 22:35:29 +0900 Subject: [PATCH 03/19] feat: fix ensureArray to return empty array when null or undefined --- src/lib/utils/array.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/utils/array.ts b/src/lib/utils/array.ts index 19fadeb..46d52d3 100644 --- a/src/lib/utils/array.ts +++ b/src/lib/utils/array.ts @@ -3,5 +3,9 @@ export const ensureArray = (value: T | T[]) => { return value } + if (value === null || value === undefined) { + return [] + } + return [value] } From 87e6f0d8c7a08ab5e8e23ffff06d28c090282b98 Mon Sep 17 00:00:00 2001 From: junseublim Date: Sat, 21 Dec 2024 22:39:33 +0900 Subject: [PATCH 04/19] style: fix decorate page style --- .../[boardId]/decorate/_components/Sticker/index.tsx | 9 +++++++-- .../board/[boardId]/decorate/_components/SubmitBtn.tsx | 9 +++++++-- src/app/board/[boardId]/decorate/page.tsx | 8 ++++++-- tailwind.config.ts | 1 + 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/app/board/[boardId]/decorate/_components/Sticker/index.tsx b/src/app/board/[boardId]/decorate/_components/Sticker/index.tsx index 9c9607d..e3cd88c 100644 --- a/src/app/board/[boardId]/decorate/_components/Sticker/index.tsx +++ b/src/app/board/[boardId]/decorate/_components/Sticker/index.tsx @@ -19,7 +19,7 @@ const Sticker = () => { useEffect(() => { const firstTarget = targets[0] - setTargetedStickerId(firstTarget?.dataset.file ?? undefined) + setTargetedStickerId(firstTarget?.dataset.id ?? undefined) }, [targets]) useEffect(() => { @@ -80,7 +80,12 @@ const Sticker = () => { />
{selectedStickers.map(({ id, file }) => ( -
+
Sticker { +const SubmitBtn = ({ onClick }: { onClick: () => void }) => { return (
{ targetStyle="FIT" targetStyleProperites={{ borderRadius: '12px' }} > - diff --git a/src/app/board/[boardId]/decorate/page.tsx b/src/app/board/[boardId]/decorate/page.tsx index fbafdca..5823a0b 100644 --- a/src/app/board/[boardId]/decorate/page.tsx +++ b/src/app/board/[boardId]/decorate/page.tsx @@ -28,13 +28,17 @@ const BoardDecoratePage = async ({ searchParams }: BoardDecoratePageProps) => { -
+
screenshot
diff --git a/tailwind.config.ts b/tailwind.config.ts index d14052f..1eb1594 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -121,6 +121,7 @@ const config: Config = { '0px 1px 1px 0px rgba(0, 0, 0, 0.25), 0px 2px 4px 0px rgba(0, 0, 0, 0.25)', polaroid: '0px 0.597px 0.597px 0px rgba(0, 0, 0, 0.25), 0px 1.194px 2.389px 0px rgba(0, 0, 0, 0.25)', + screenshot: '0px 2.143px 12.857px 0px rgba(0, 0, 0, 0.16);', }, aspectRatio: { vertical: '10 / 13', From 58c44a75a078038d09b859ea0aef3a0a541ab0fa Mon Sep 17 00:00:00 2001 From: junseublim Date: Sat, 21 Dec 2024 22:57:42 +0900 Subject: [PATCH 05/19] feat: fix screenshot api to show stickers --- .../_components/PolaroidList/index.tsx | 7 ++- src/app/board/[boardId]/screenshot/page.tsx | 37 ++++++++++++ src/app/board/api/screenshot/route.ts | 56 ++++++++++++++----- src/lib/utils/query.ts | 15 +++++ src/types/sticker.ts | 9 +++ 5 files changed, 109 insertions(+), 15 deletions(-) diff --git a/src/app/board/[boardId]/_components/PolaroidList/index.tsx b/src/app/board/[boardId]/_components/PolaroidList/index.tsx index e84a806..27d0853 100644 --- a/src/app/board/[boardId]/_components/PolaroidList/index.tsx +++ b/src/app/board/[boardId]/_components/PolaroidList/index.tsx @@ -7,6 +7,7 @@ import Button from '@/components/Button' import PolaroidDetailModal from '@/components/Polaroid/PolaroidDetail' import { useRouter } from 'next/navigation' import { useState } from 'react' +import { createPolaroidSearchParams } from '@/lib/utils/query' const PolaroidList = () => { const { board, boardId } = useBoard() @@ -56,7 +57,11 @@ const PolaroidList = () => { const imageUrl = URL.createObjectURL(blob) setIsLoading(false) - router.push(`/board/${boardId}/decorate?imageUrl=${imageUrl}`) + const polaroidIdsSearchParam = createPolaroidSearchParams(selectedIds) + + router.push( + `/board/${boardId}/decorate?imageUrl=${imageUrl}&boardId=${boardId}&${polaroidIdsSearchParam}`, + ) } return ( diff --git a/src/app/board/[boardId]/screenshot/page.tsx b/src/app/board/[boardId]/screenshot/page.tsx index ed3f4cb..19e043f 100644 --- a/src/app/board/[boardId]/screenshot/page.tsx +++ b/src/app/board/[boardId]/screenshot/page.tsx @@ -14,6 +14,7 @@ interface BoardScreenshotPageProps { } searchParams: { polaroidIds: string | string[] + stickers: string | string[] } } @@ -27,6 +28,20 @@ const BoardScreenshotPage = async ({ const selectedPolaroids = board.items.filter((item) => polaroidIds.includes(String(item.id)), ) + const stickers = ensureArray(searchParams.stickers) + + const parsedStickers = stickers.map((sticker) => { + const stickerStyle = sticker.split(',') + return { + width: stickerStyle[0], + height: stickerStyle[1], + x: stickerStyle[2], + y: stickerStyle[3], + angle: stickerStyle[4], + file: stickerStyle[5], + } + }) + const { titleClassName, backgroundImage } = getBoardStyle(board) if (selectedPolaroids.length === 0) { @@ -50,6 +65,28 @@ const BoardScreenshotPage = async ({ {board.title}
+
+ {parsedStickers.map((sticker) => ( +
+ Sticker +
+ ))} +
`polaroidIds=${id}`) - .join('&') - - const url = `${process.env.URL}/board/${boardId}/screenshot?${polaroidIdsSearchParam}` +type RequestBodyType = { + boardId: string + polaroids: string[] + stickers: StickerStyle[] +} +const initializeBrowser = async () => { const browser = await puppeteer.launch() const page = await browser.newPage() await page.setViewport({ width: 1080, height: 1920, }) + return { browser, page } +} + +export async function POST(request: Request) { + let browser: Browser | null = null try { + const { + boardId, + polaroids, + stickers = [], + }: RequestBodyType = await request.json() + + const polaroidParams = createPolaroidSearchParams(polaroids) + const stickerParams = createStickerSearchParams(stickers) + const url = `${process.env.URL}/board/${encodeURIComponent( + boardId, + )}/screenshot?${polaroidParams}&${stickerParams}` + + const { browser: initializedBrowser, page } = await initializeBrowser() + browser = initializedBrowser + await page.goto(url, { waitUntil: 'networkidle2' }) const element = await page.$('div#screenshot_target') - const screenshotBuffer = await element?.screenshot() - await browser.close() + if (!element) { + throw new Error('Screenshot target element not found.') + } + + const screenshotBuffer = await element.screenshot() return new NextResponse(screenshotBuffer, { headers: { @@ -32,8 +57,11 @@ export async function POST(request: Request) { }, }) } catch (error) { - console.log(error) - await browser.close() + console.error('Error taking screenshot:', error) return new NextResponse('Error taking screenshot', { status: 500 }) + } finally { + if (browser) { + await browser.close() + } } } diff --git a/src/lib/utils/query.ts b/src/lib/utils/query.ts index 9008bdf..5479861 100644 --- a/src/lib/utils/query.ts +++ b/src/lib/utils/query.ts @@ -1,3 +1,5 @@ +import { StickerStyle } from '@/types' + export const createQueryString = ( searchParams: URLSearchParams, name: string, @@ -8,3 +10,16 @@ export const createQueryString = ( return params.toString() } + +export const createPolaroidSearchParams = ( + polaroids: string[] | number[], +): string => + polaroids.map((id) => `polaroidIds=${encodeURIComponent(id)}`).join('&') + +export const createStickerSearchParams = (stickers: StickerStyle[]): string => + stickers + .map( + ({ width, height, x, y, angle, file }) => + `stickers=${encodeURIComponent([width, height, x, y, angle, file].join(','))}`, + ) + .join('&') diff --git a/src/types/sticker.ts b/src/types/sticker.ts index d406290..70fa2d9 100644 --- a/src/types/sticker.ts +++ b/src/types/sticker.ts @@ -4,3 +4,12 @@ export interface SendRecentStickersPayload { stickerIds: string[] boardId: string } + +export interface StickerStyle { + width: string + height: string + x: string + y: string + angle: string + file: string +} From 977dae1c76f031582f19336232c116cdb3064da6 Mon Sep 17 00:00:00 2001 From: junseublim Date: Mon, 23 Dec 2024 00:00:18 +0900 Subject: [PATCH 06/19] feat: take final screenshot --- .../decorate/_utils/getStickerStyles.ts | 38 ++++++++++++++++++ src/app/board/[boardId]/decorate/page.tsx | 40 +++++++++++++++++-- src/lib/utils/image.ts | 10 +++++ 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/app/board/[boardId]/decorate/_utils/getStickerStyles.ts diff --git a/src/app/board/[boardId]/decorate/_utils/getStickerStyles.ts b/src/app/board/[boardId]/decorate/_utils/getStickerStyles.ts new file mode 100644 index 0000000..60d6766 --- /dev/null +++ b/src/app/board/[boardId]/decorate/_utils/getStickerStyles.ts @@ -0,0 +1,38 @@ +export const getStickerStyles = () => { + const preview = document.querySelector('#preview') + const stickers = Array.from(document.querySelectorAll('.sticker')) + const previewRect = preview!.getBoundingClientRect() + + const ratio = 1080 / previewRect.width + + return stickers.map((sticker) => { + const stickerStyle = sticker.getBoundingClientRect() + const { width } = stickerStyle + const { height } = stickerStyle + const x = stickerStyle.left - previewRect.left + const y = stickerStyle.top - previewRect.top + + const computedStyle = window.getComputedStyle(sticker) + const { transform } = computedStyle + let angle: number + + if (transform !== 'none') { + const values = transform.match(/matrix\(([^)]+)\)/)?.[1].split(', ') + const a = parseFloat(values![0]) + const b = parseFloat(values![1]) + + angle = Math.round(Math.atan2(b, a) * (180 / Math.PI)) + } else { + angle = 0 + } + + return { + width: Math.round(width * ratio), + height: Math.round(height * ratio), + x: Math.round(x * ratio), + y: Math.round(y * ratio), + angle: Math.round(angle), + file: (sticker as HTMLElement).dataset.file, + } + }) +} diff --git a/src/app/board/[boardId]/decorate/page.tsx b/src/app/board/[boardId]/decorate/page.tsx index 5823a0b..081540d 100644 --- a/src/app/board/[boardId]/decorate/page.tsx +++ b/src/app/board/[boardId]/decorate/page.tsx @@ -1,6 +1,13 @@ +'use client' + import Header from '@/components/Header' import { DecorateTutorialProvider } from '@/components/Tutorial' import Image from 'next/image' +import { ensureArray } from '@/lib/utils/array' +import { getStickerStyles } from '@/app/board/[boardId]/decorate/_utils/getStickerStyles' +import { useEffect, useState } from 'react' +import { getBoard } from '@/lib' +import { downloadImage } from '@/lib/utils/image' import OpenStickerModalBtn from './_components/OpenStickerModalBtn' import SelectSticker from './_components/SelectStickerModal' import Sticker from './_components/Sticker' @@ -10,12 +17,37 @@ import { StickerProvider } from './_contexts/StickerContext' interface BoardDecoratePageProps { searchParams: { + boardId: string imageUrl: string + polaroidIds: string | string[] } } -const BoardDecoratePage = async ({ searchParams }: BoardDecoratePageProps) => { - const { imageUrl } = searchParams +const BoardDecoratePage = ({ searchParams }: BoardDecoratePageProps) => { + const { imageUrl, boardId, polaroidIds } = searchParams + const [boardName, setBoardName] = useState('') + + useEffect(() => { + getBoard(boardId).then((board) => { + setBoardName(board.title) + }) + }) + + const takeScreenshot = () => { + fetch(`/board/api/screenshot`, { + method: 'POST', + body: JSON.stringify({ + polaroids: ensureArray(polaroidIds), + boardId, + stickers: getStickerStyles(), + }), + }) + .then((res) => res.blob()) + .then((blob) => { + const screenshotUrl = URL.createObjectURL(blob) + downloadImage(screenshotUrl, boardName) + }) + } return ( @@ -29,7 +61,7 @@ const BoardDecoratePage = async ({ searchParams }: BoardDecoratePageProps) => {
@@ -44,7 +76,7 @@ const BoardDecoratePage = async ({ searchParams }: BoardDecoratePageProps) => {
- +
diff --git a/src/lib/utils/image.ts b/src/lib/utils/image.ts index 0361bf0..98e8230 100644 --- a/src/lib/utils/image.ts +++ b/src/lib/utils/image.ts @@ -16,3 +16,13 @@ export const getImageWidthHeight = ( } }) } + +export const downloadImage = (imageUrl: string, imageName = 'file') => { + const a = document.createElement('a') + a.href = imageUrl + a.download = `${imageName}.png` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(imageUrl) +} From 903b259268d48ac9737e0e82182370c22a397b99 Mon Sep 17 00:00:00 2001 From: junseublim Date: Mon, 23 Dec 2024 21:52:23 +0900 Subject: [PATCH 07/19] feat: take final screenshot --- .../_components/DecorateScreenshot.tsx | 64 +++++++++++++++++++ .../SelectStickerModal/Contents.tsx | 8 ++- .../_components/SelectStickerModal/index.tsx | 8 ++- .../decorate/_components/SubmitBtn.tsx | 2 +- .../decorate/_contexts/StickerContext.tsx | 9 +++ src/app/board/[boardId]/decorate/page.tsx | 63 +----------------- src/lib/constants/stickerConfig.ts | 2 + 7 files changed, 91 insertions(+), 65 deletions(-) create mode 100644 src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx diff --git a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx new file mode 100644 index 0000000..f3529c8 --- /dev/null +++ b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx @@ -0,0 +1,64 @@ +'use client' + +import React, { useEffect, useState } from 'react' +import Sticker from '@/app/board/[boardId]/decorate/_components/Sticker' +import Image from 'next/image' +import SubmitBtn from '@/app/board/[boardId]/decorate/_components/SubmitBtn' +import { ensureArray } from '@/lib/utils/array' +import { getStickerStyles } from '@/app/board/[boardId]/decorate/_utils/getStickerStyles' +import { downloadImage } from '@/lib/utils/image' +import { useParams, useSearchParams } from 'next/navigation' +import { getBoard } from '@/lib' + +const DecorateScreenshot = () => { + const { boardId } = useParams<{ boardId: string }>() + const searchParams = useSearchParams() + const polaroidIds = searchParams.getAll('polaroidIds') + const imageUrl = searchParams.get('imageUrl')! + const [boardName, setBoardName] = useState('') + + useEffect(() => { + getBoard(boardId).then((board) => { + setBoardName(board.title) + }) + }) + + const takeScreenshot = () => { + fetch(`/board/api/screenshot`, { + method: 'POST', + body: JSON.stringify({ + polaroids: ensureArray(polaroidIds), + boardId, + stickers: getStickerStyles(), + }), + }) + .then((res) => res.blob()) + .then((blob) => { + const screenshotUrl = URL.createObjectURL(blob) + downloadImage(screenshotUrl, boardName) + }) + } + + return ( +
+
+ + screenshot +
+
+ +
+
+ ) +} + +export default DecorateScreenshot diff --git a/src/app/board/[boardId]/decorate/_components/SelectStickerModal/Contents.tsx b/src/app/board/[boardId]/decorate/_components/SelectStickerModal/Contents.tsx index 7c1701b..5786369 100644 --- a/src/app/board/[boardId]/decorate/_components/SelectStickerModal/Contents.tsx +++ b/src/app/board/[boardId]/decorate/_components/SelectStickerModal/Contents.tsx @@ -2,6 +2,7 @@ import Image from 'next/image' import { useEffect, useState } from 'react' import { getRecentStickers, postStickers } from '@/lib/api/sticker' import { useParams } from 'next/navigation' +import { useSession } from 'next-auth/react' import { useSticker } from '../../_contexts/StickerContext' import { useStickerModal } from '../../_contexts/ModalContext' import { getStickerFile } from '../../_utils/getStickerFile' @@ -11,6 +12,7 @@ const Contents = () => { const { closeModal } = useStickerModal() const [stickerFiles, setStickerFiles] = useState([]) const { boardId } = useParams<{ boardId: string }>() + const { status } = useSession() useEffect(() => { const fetchStickers = async () => { @@ -25,8 +27,10 @@ const Contents = () => { fetchStickers() }, [selectedMenu]) - const handleClickSticker = async (file: string) => { - await postStickers({ stickerIds: [file], boardId }) + const handleClickSticker = (file: string) => { + if (status === 'authenticated') { + postStickers({ stickerIds: [file], boardId }) + } addSticker(file) closeModal() } diff --git a/src/app/board/[boardId]/decorate/_components/SelectStickerModal/index.tsx b/src/app/board/[boardId]/decorate/_components/SelectStickerModal/index.tsx index 44ed141..c1554e0 100644 --- a/src/app/board/[boardId]/decorate/_components/SelectStickerModal/index.tsx +++ b/src/app/board/[boardId]/decorate/_components/SelectStickerModal/index.tsx @@ -1,8 +1,9 @@ 'use client' -import { STICKER_MENU } from '@/lib/constants/stickerConfig' +import { GUEST_STICKER_MENU, STICKER_MENU } from '@/lib/constants/stickerConfig' import { StickerMenu } from '@/types' import Image from 'next/image' +import { useSession } from 'next-auth/react' import Contents from './Contents' import Header from './Header' import Menu from './Menu' @@ -23,11 +24,14 @@ const StickerIcon = ({ num }: StickerIconProps) => { } const SelectSticker = () => { + const { status } = useSession() + const stickerMenus = status ? STICKER_MENU : GUEST_STICKER_MENU + return (
- {STICKER_MENU.map((sticker) => ( + {stickerMenus.map((sticker) => ( } menuNum={sticker} diff --git a/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx b/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx index dc7c887..e5a0e4b 100644 --- a/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx +++ b/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx @@ -6,7 +6,7 @@ import { Step2Tooltip } from './Tooltips' const SubmitBtn = ({ onClick }: { onClick: () => void }) => { return ( -
+
{ + const { status } = useSession() const [selectedMenu, setSelectedMenu] = useState(1) const [selectedStickers, setSelectedStickers] = useState([]) + useEffect(() => { + if (status !== 'authenticated' && selectedMenu === 0) { + setSelectedMenu(1) + } + }, [status]) + const addSticker = (file: string) => { const newSticker = { id: uuidv4(), file } setSelectedStickers((prev) => [...prev, newSticker]) diff --git a/src/app/board/[boardId]/decorate/page.tsx b/src/app/board/[boardId]/decorate/page.tsx index 081540d..17a9d2b 100644 --- a/src/app/board/[boardId]/decorate/page.tsx +++ b/src/app/board/[boardId]/decorate/page.tsx @@ -1,54 +1,12 @@ -'use client' - import Header from '@/components/Header' import { DecorateTutorialProvider } from '@/components/Tutorial' -import Image from 'next/image' -import { ensureArray } from '@/lib/utils/array' -import { getStickerStyles } from '@/app/board/[boardId]/decorate/_utils/getStickerStyles' -import { useEffect, useState } from 'react' -import { getBoard } from '@/lib' -import { downloadImage } from '@/lib/utils/image' +import DecorateScreenshot from '@/app/board/[boardId]/decorate/_components/DecorateScreenshot' import OpenStickerModalBtn from './_components/OpenStickerModalBtn' import SelectSticker from './_components/SelectStickerModal' -import Sticker from './_components/Sticker' -import SubmitBtn from './_components/SubmitBtn' import { StickerModalProvider } from './_contexts/ModalContext' import { StickerProvider } from './_contexts/StickerContext' -interface BoardDecoratePageProps { - searchParams: { - boardId: string - imageUrl: string - polaroidIds: string | string[] - } -} - -const BoardDecoratePage = ({ searchParams }: BoardDecoratePageProps) => { - const { imageUrl, boardId, polaroidIds } = searchParams - const [boardName, setBoardName] = useState('') - - useEffect(() => { - getBoard(boardId).then((board) => { - setBoardName(board.title) - }) - }) - - const takeScreenshot = () => { - fetch(`/board/api/screenshot`, { - method: 'POST', - body: JSON.stringify({ - polaroids: ensureArray(polaroidIds), - boardId, - stickers: getStickerStyles(), - }), - }) - .then((res) => res.blob()) - .then((blob) => { - const screenshotUrl = URL.createObjectURL(blob) - downloadImage(screenshotUrl, boardName) - }) - } - +const BoardDecoratePage = () => { return (
@@ -60,24 +18,9 @@ const BoardDecoratePage = ({ searchParams }: BoardDecoratePageProps) => { -
- - screenshot -
+
-
- -
) diff --git a/src/lib/constants/stickerConfig.ts b/src/lib/constants/stickerConfig.ts index e543c74..d39a4a1 100644 --- a/src/lib/constants/stickerConfig.ts +++ b/src/lib/constants/stickerConfig.ts @@ -1,3 +1,5 @@ import { StickerMenu } from '@/types' export const STICKER_MENU: StickerMenu[] = [0, 1, 2, 3] + +export const GUEST_STICKER_MENU: StickerMenu[] = [1, 2, 3] From c185db88d400e2c7f259ada96af22c3f237a16cd Mon Sep 17 00:00:00 2001 From: junseublim Date: Mon, 23 Dec 2024 21:58:54 +0900 Subject: [PATCH 08/19] style: header height --- src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx b/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx index e5d14d9..7577c62 100644 --- a/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx +++ b/src/app/board/[boardId]/_components/Header/SelectModeHeader.tsx @@ -23,7 +23,7 @@ const SelectModeHeader = () => {
} - className={twMerge('bg-transparent', titleClassName)} + className={twMerge('h-20 bg-transparent p-3', titleClassName)} shadow={false} /> ) From dcc21e37463af7455f1464b158cf4a44f3ce4523 Mon Sep 17 00:00:00 2001 From: junseublim Date: Mon, 23 Dec 2024 23:11:47 +0900 Subject: [PATCH 09/19] style: decorate page --- .../_components/DecorateScreenshot.tsx | 20 +++++++++++++++---- .../_components/OpenStickerModalBtn.tsx | 2 +- src/app/board/[boardId]/decorate/page.tsx | 15 +++----------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx index f3529c8..9364089 100644 --- a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx +++ b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx @@ -9,6 +9,8 @@ import { getStickerStyles } from '@/app/board/[boardId]/decorate/_utils/getStick import { downloadImage } from '@/lib/utils/image' import { useParams, useSearchParams } from 'next/navigation' import { getBoard } from '@/lib' +import OpenStickerModalBtn from '@/app/board/[boardId]/decorate/_components/OpenStickerModalBtn' +import SelectSticker from '@/app/board/[boardId]/decorate/_components/SelectStickerModal' const DecorateScreenshot = () => { const { boardId } = useParams<{ boardId: string }>() @@ -40,21 +42,31 @@ const DecorateScreenshot = () => { } return ( -
+
+
+
+
+ 보드 꾸미기 +
+
+
+ + + screenshot
-
+
diff --git a/src/app/board/[boardId]/decorate/_components/OpenStickerModalBtn.tsx b/src/app/board/[boardId]/decorate/_components/OpenStickerModalBtn.tsx index 3f23aa7..1a617a7 100644 --- a/src/app/board/[boardId]/decorate/_components/OpenStickerModalBtn.tsx +++ b/src/app/board/[boardId]/decorate/_components/OpenStickerModalBtn.tsx @@ -22,7 +22,7 @@ const OpenStickerModalBtn = ({ children }: OpenModalBtnProps) => { } return ( -
+
} hasNext> diff --git a/src/app/board/[boardId]/decorate/page.tsx b/src/app/board/[boardId]/decorate/page.tsx index 17a9d2b..1d3ac47 100644 --- a/src/app/board/[boardId]/decorate/page.tsx +++ b/src/app/board/[boardId]/decorate/page.tsx @@ -1,25 +1,16 @@ -import Header from '@/components/Header' import { DecorateTutorialProvider } from '@/components/Tutorial' import DecorateScreenshot from '@/app/board/[boardId]/decorate/_components/DecorateScreenshot' -import OpenStickerModalBtn from './_components/OpenStickerModalBtn' -import SelectSticker from './_components/SelectStickerModal' import { StickerModalProvider } from './_contexts/ModalContext' import { StickerProvider } from './_contexts/StickerContext' const BoardDecoratePage = () => { return ( -
-
+
-
- - - - - + -
+
From 05c4219cd7b7d5ae1d280b9611f2c274d79aebb3 Mon Sep 17 00:00:00 2001 From: junseublim Date: Mon, 23 Dec 2024 23:25:29 +0900 Subject: [PATCH 10/19] feat: take preview screenshot at decorate page --- .../_components/PolaroidList/index.tsx | 27 +-------- .../_components/DecorateScreenshot.tsx | 58 +++++++++++++------ 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/app/board/[boardId]/_components/PolaroidList/index.tsx b/src/app/board/[boardId]/_components/PolaroidList/index.tsx index 27d0853..8eea445 100644 --- a/src/app/board/[boardId]/_components/PolaroidList/index.tsx +++ b/src/app/board/[boardId]/_components/PolaroidList/index.tsx @@ -14,7 +14,6 @@ const PolaroidList = () => { const { isSelectMode, selectedIds, toggleSelectedId } = useSelect() const [isModalOpen, setIsModalOpen] = useState(false) const [selectedIdx, setSelectedIdx] = useState(0) - const [isLoading, setIsLoading] = useState(false) const router = useRouter() const openDetailModal = (idx: number) => { @@ -39,29 +38,9 @@ const PolaroidList = () => { return '' } - const onSelectComplete = async () => { - setIsLoading(true) - const res = await fetch(`/board/api/screenshot`, { - method: 'POST', - body: JSON.stringify({ - polaroids: selectedIds, - boardId, - }), - }) - - if (!res.ok) { - throw new Error('Failed to take screenshot') - } - - const blob = await res.blob() - const imageUrl = URL.createObjectURL(blob) - setIsLoading(false) - + const onSelectComplete = () => { const polaroidIdsSearchParam = createPolaroidSearchParams(selectedIds) - - router.push( - `/board/${boardId}/decorate?imageUrl=${imageUrl}&boardId=${boardId}&${polaroidIdsSearchParam}`, - ) + router.push(`/board/${boardId}/decorate?${polaroidIdsSearchParam}`) } return ( @@ -84,7 +63,7 @@ const PolaroidList = () => {
-
- - - - - screenshot -
+ {previewUrl && ( +
+ + + + + screenshot +
+ )}
From 3aab708360bb97715f5e851ab18c914ecc55a44d Mon Sep 17 00:00:00 2001 From: junseublim Date: Mon, 23 Dec 2024 23:52:11 +0900 Subject: [PATCH 11/19] feat: add loading page --- public/images/screenshot_loading.png | Bin 0 -> 27088 bytes .../_components/DecorateScreenshot.tsx | 43 ++++++++++++++++-- .../decorate/_components/SubmitBtn.tsx | 14 +++++- 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 public/images/screenshot_loading.png diff --git a/public/images/screenshot_loading.png b/public/images/screenshot_loading.png new file mode 100644 index 0000000000000000000000000000000000000000..c999f3666dcbcfa1dd72f01d3fca2a3cae468c32 GIT binary patch literal 27088 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 literal 0 HcmV?d00001 diff --git a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx index b344de1..3a5b58a 100644 --- a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx +++ b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx @@ -7,10 +7,12 @@ import SubmitBtn from '@/app/board/[boardId]/decorate/_components/SubmitBtn' import { ensureArray } from '@/lib/utils/array' import { getStickerStyles } from '@/app/board/[boardId]/decorate/_utils/getStickerStyles' import { downloadImage } from '@/lib/utils/image' -import { useParams, useSearchParams } from 'next/navigation' +import { useParams, useRouter, useSearchParams } from 'next/navigation' import { getBoard } from '@/lib' import OpenStickerModalBtn from '@/app/board/[boardId]/decorate/_components/OpenStickerModalBtn' import SelectSticker from '@/app/board/[boardId]/decorate/_components/SelectStickerModal' +import ScreenshotLoading from 'public/images/screenshot_loading.png' +import Button from '@/components/Button' const DecorateScreenshot = () => { const { boardId } = useParams<{ boardId: string }>() @@ -18,6 +20,10 @@ const DecorateScreenshot = () => { const polaroidIds = searchParams.getAll('polaroidIds') const [boardName, setBoardName] = useState('') const [previewUrl, setPreviewUrl] = useState('') + const [isLoadingPreview, setIsLoadingPreview] = useState(true) + const [isLoadingDownload, setIsLoadingDownload] = useState(false) + const [isDownloaded, setIsDownloaded] = useState(false) + const router = useRouter() useEffect(() => { getBoard(boardId).then((board) => { @@ -42,12 +48,14 @@ const DecorateScreenshot = () => { const blob = await res.blob() const previewURl = URL.createObjectURL(blob) setPreviewUrl(previewURl) + setIsLoadingPreview(false) } takePreview() }, []) const takeScreenshot = () => { + setIsLoadingDownload(true) fetch(`/board/api/screenshot`, { method: 'POST', body: JSON.stringify({ @@ -60,9 +68,23 @@ const DecorateScreenshot = () => { .then((blob) => { const screenshotUrl = URL.createObjectURL(blob) downloadImage(screenshotUrl, boardName) + setIsLoadingDownload(false) + setIsDownloaded(true) }) } + const routeToHome = () => { + router.push('/') + } + + if (isLoadingPreview) { + return ( +
+ loading +
+ ) + } + return (
@@ -75,7 +97,7 @@ const DecorateScreenshot = () => { {previewUrl && (
@@ -86,12 +108,25 @@ const DecorateScreenshot = () => { alt="screenshot" width={1080} height={1920} - className="max-h-full w-auto object-contain" + className="aspect-[9/16] max-h-full w-auto object-contain" />
)}
- + {isDownloaded ? ( + + ) : ( + + {isLoadingDownload ? '다운로드 중...' : '꾸미기 완료'} + + )}
) diff --git a/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx b/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx index e5a0e4b..822743c 100644 --- a/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx +++ b/src/app/board/[boardId]/decorate/_components/SubmitBtn.tsx @@ -2,9 +2,18 @@ import Button from '@/components/Button' import { DecorateTutorial } from '@/components/Tutorial' +import { ReactNode } from 'react' import { Step2Tooltip } from './Tooltips' -const SubmitBtn = ({ onClick }: { onClick: () => void }) => { +const SubmitBtn = ({ + onClick, + children, + disabled, +}: { + onClick: () => void + children: ReactNode + disabled: boolean +}) => { return (
void }) => { size="lg" variant="secondary" className="w-full" + disabled={disabled} onClick={onClick} > - 꾸미기 완료 + {children}
From 8ac3bead113e4625054050585bc4ba9b4d2db769 Mon Sep 17 00:00:00 2001 From: junseublim Date: Sat, 28 Dec 2024 16:41:22 +0900 Subject: [PATCH 12/19] feat: add download steps --- .../_components/DecorateScreenshot.tsx | 26 ++++++++++++++++--- .../decorate/_contexts/StickerContext.tsx | 10 ++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx index 3a5b58a..df55abe 100644 --- a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx +++ b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx @@ -13,6 +13,8 @@ import OpenStickerModalBtn from '@/app/board/[boardId]/decorate/_components/Open import SelectSticker from '@/app/board/[boardId]/decorate/_components/SelectStickerModal' import ScreenshotLoading from 'public/images/screenshot_loading.png' import Button from '@/components/Button' +import { useSticker } from '@/app/board/[boardId]/decorate/_contexts/StickerContext' +import DownloadIcon from 'public/icons/download.svg' const DecorateScreenshot = () => { const { boardId } = useParams<{ boardId: string }>() @@ -23,6 +25,7 @@ const DecorateScreenshot = () => { const [isLoadingPreview, setIsLoadingPreview] = useState(true) const [isLoadingDownload, setIsLoadingDownload] = useState(false) const [isDownloaded, setIsDownloaded] = useState(false) + const { isDecorating, setIsDecorating } = useSticker() const router = useRouter() useEffect(() => { @@ -90,7 +93,7 @@ const DecorateScreenshot = () => {
- 보드 꾸미기 + {isDownloaded ? '앨범에 저장되었습니다!' : '보드 꾸미기'}
@@ -113,7 +116,7 @@ const DecorateScreenshot = () => {
)}
- {isDownloaded ? ( + {isDownloaded && ( - ) : ( + )} + {!isDownloaded && isDecorating && ( + + )} + {!isDownloaded && !isDecorating && ( - {isLoadingDownload ? '다운로드 중...' : '꾸미기 완료'} + + 내 보드 이미지 저장 + )}
diff --git a/src/app/board/[boardId]/decorate/_contexts/StickerContext.tsx b/src/app/board/[boardId]/decorate/_contexts/StickerContext.tsx index a43e130..91eea90 100644 --- a/src/app/board/[boardId]/decorate/_contexts/StickerContext.tsx +++ b/src/app/board/[boardId]/decorate/_contexts/StickerContext.tsx @@ -24,6 +24,8 @@ interface StickerContextProps { selectedStickers: Sticker[] addSticker: (sticker: string) => void deleteSticker: (stickerIdx: string) => void + isDecorating: boolean + setIsDecorating: Dispatch> } const StickerContext = createContext({ @@ -32,6 +34,8 @@ const StickerContext = createContext({ selectedStickers: [], addSticker: () => {}, deleteSticker: () => {}, + isDecorating: false, + setIsDecorating: () => {}, }) export const StickerProvider = ({ @@ -42,6 +46,7 @@ export const StickerProvider = ({ const { status } = useSession() const [selectedMenu, setSelectedMenu] = useState(1) const [selectedStickers, setSelectedStickers] = useState([]) + const [isDecorating, setIsDecorating] = useState(false) useEffect(() => { if (status !== 'authenticated' && selectedMenu === 0) { @@ -52,6 +57,7 @@ export const StickerProvider = ({ const addSticker = (file: string) => { const newSticker = { id: uuidv4(), file } setSelectedStickers((prev) => [...prev, newSticker]) + setIsDecorating(true) } const deleteSticker = (stickerId: string) => { @@ -65,8 +71,10 @@ export const StickerProvider = ({ selectedStickers, addSticker, deleteSticker, + isDecorating, + setIsDecorating, }), - [selectedMenu, selectedStickers], + [selectedMenu, selectedStickers, isDecorating], ) return ( From aed4c51a0383025a83d046a12e002660c112ec1f Mon Sep 17 00:00:00 2001 From: junseublim Date: Sun, 29 Dec 2024 00:38:53 +0900 Subject: [PATCH 13/19] style: fix screenshot page background size to cover --- src/app/board/[boardId]/screenshot/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/board/[boardId]/screenshot/page.tsx b/src/app/board/[boardId]/screenshot/page.tsx index 19e043f..e3e3b2f 100644 --- a/src/app/board/[boardId]/screenshot/page.tsx +++ b/src/app/board/[boardId]/screenshot/page.tsx @@ -51,7 +51,7 @@ const BoardScreenshotPage = async ({ return (
Date: Sun, 29 Dec 2024 00:39:38 +0900 Subject: [PATCH 14/19] style: add dependencies to DecorateScreenshot getBoard useEffect --- .../board/[boardId]/decorate/_components/DecorateScreenshot.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx index df55abe..a70f99d 100644 --- a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx +++ b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx @@ -32,7 +32,7 @@ const DecorateScreenshot = () => { getBoard(boardId).then((board) => { setBoardName(board.title) }) - }) + }, []) useEffect(() => { const takePreview = async () => { From 2ea7610cf7082ac972585b2e33aa8cf536f464f9 Mon Sep 17 00:00:00 2001 From: junseublim Date: Sun, 29 Dec 2024 00:50:10 +0900 Subject: [PATCH 15/19] style: apply text style to Empty board component --- src/app/board/[boardId]/_components/Empty.tsx | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app/board/[boardId]/_components/Empty.tsx b/src/app/board/[boardId]/_components/Empty.tsx index f936c16..38c1b93 100644 --- a/src/app/board/[boardId]/_components/Empty.tsx +++ b/src/app/board/[boardId]/_components/Empty.tsx @@ -1,10 +1,21 @@ +'use client' + import Icon from 'public/icons/pinned.svg' +import { getBoardStyle } from '@/lib/utils/board' +import { useBoard } from '@/app/board/[boardId]/_contexts/BoardContext' + +const Empty = () => { + const { board } = useBoard() + const { titleClassName } = getBoardStyle(board) -const Empty = () => ( -
- - 보드를 꾸며주세요! -
-) + return ( +
+ + + 보드를 꾸며주세요! + +
+ ) +} export default Empty From e7e3be27a4a41b4c2f56989623cf74f534b7a20d Mon Sep 17 00:00:00 2001 From: junseublim Date: Sun, 29 Dec 2024 00:54:56 +0900 Subject: [PATCH 16/19] style: fix Polaroid detail message height --- src/components/Polaroid/PolaroidDetail/PolaroidItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Polaroid/PolaroidDetail/PolaroidItem.tsx b/src/components/Polaroid/PolaroidDetail/PolaroidItem.tsx index c5cc5fe..534ac00 100644 --- a/src/components/Polaroid/PolaroidDetail/PolaroidItem.tsx +++ b/src/components/Polaroid/PolaroidDetail/PolaroidItem.tsx @@ -25,7 +25,7 @@ const PolaroidItem = ({ polaroid }: PolaroidItemProps) => {
Date: Sun, 29 Dec 2024 01:07:16 +0900 Subject: [PATCH 17/19] feat: add chromium install to dockerfile --- .github/workflows/cicd.yml | 2 +- Dockerfile | 15 ++++++++++++++- src/app/board/api/screenshot/route.ts | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index e802824..46dfb9f 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -3,7 +3,7 @@ name: Deploy to AWS EC2 on: push: branches: - - develop + - feature/puppeteer release: types: [published] diff --git a/Dockerfile b/Dockerfile index ce62b98..b5b91ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,22 @@ FROM node:20-alpine3.19 as builder +# Install necessary dependencies for Puppeteer and Chromium +RUN apk add --no-cache \ + chromium \ + nss \ + freetype \ + harfbuzz \ + ca-certificates \ + ttf-freefont + WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build + +# Set environment variable for Puppeteer to use installed Chromium +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser + EXPOSE 3000 -CMD ["npm", "run" , "start"] \ No newline at end of file +CMD ["npm", "run" , "start"] diff --git a/src/app/board/api/screenshot/route.ts b/src/app/board/api/screenshot/route.ts index a817bb5..3a960ec 100644 --- a/src/app/board/api/screenshot/route.ts +++ b/src/app/board/api/screenshot/route.ts @@ -11,7 +11,7 @@ export async function POST(request: Request) { const url = `${process.env.URL}/board/${boardId}/screenshot?${polaroidIdsSearchParam}` - const browser = await puppeteer.launch() + const browser = await puppeteer.launch({ args: ['--no-sandbox'] }) const page = await browser.newPage() await page.setViewport({ width: 1080, From c1447ac18ed6edcc9e0450aed0552396b962d2af Mon Sep 17 00:00:00 2001 From: junseublim Date: Sun, 29 Dec 2024 01:33:28 +0900 Subject: [PATCH 18/19] feat: reset branch to develop --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 46dfb9f..e802824 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -3,7 +3,7 @@ name: Deploy to AWS EC2 on: push: branches: - - feature/puppeteer + - develop release: types: [published] From b24dd8c52e4a2922363941d03f997edb4bafc2de Mon Sep 17 00:00:00 2001 From: junseublim Date: Sun, 29 Dec 2024 13:32:12 +0900 Subject: [PATCH 19/19] feat: change loading image to gif --- public/images/screenshot_loading.gif | Bin 0 -> 44942 bytes public/images/screenshot_loading.png | Bin 27088 -> 0 bytes .../decorate/_components/DecorateScreenshot.tsx | 8 ++++---- 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 public/images/screenshot_loading.gif delete mode 100644 public/images/screenshot_loading.png diff --git a/public/images/screenshot_loading.gif b/public/images/screenshot_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..7bda4619e45872c50039ff5377b601ce90d9de36 GIT binary patch literal 44942 zcmeFZXH?YLw)R_99c;3Mg_80wOtQNdh9F$OQF(WU-#f#w1uE_d`A7G*<>Mf~G0 zo*gPZH6ue6C4IGn5>iA+_%Y82@NJvO{dwW}2TBfC?>FDUx*Vot8)ddCy9y)aB z;lqc0eSJrd9{u?7FHTtUq5;B)UP zXlRIyjZIHakC&I%&d%=0kt4;$#Yss?&!0bET3T9OURF?0P*PIz@bHL;h&X)s@P!K( zR8>`P-@d(X-#!%;6$=XsO-)TXIXPWj-2(>>+`4s3TwI)!laq&s=gyrwr%s(ZcI=qG zzW(a!YC=N7&!0cDva(WAQp(HA7Zw)Q*4CPvn=>*pzI^#oSXekWH`mtI_U+rZkdP1! z4GmLM(}M>OUc7j*x3{;Up~2nV{rmUtTU%S5ot?YK@xOolcl;k9-$5YBIW-FsYdy#r zL@yBvlIp$DeA=;^g~^TntcNUz356-m!Q3i-oLWVxE#bn3*_T?1(%Pb=ZnVZ~6{oj9 zmwPxn+)|wJHbK#Yl1sZJvnxe2T=a5lNmfsWeu{RS_RH+P9FtX^d zDqiPR(Z{yvLw82syej_uHc`cYpKe9TOi!j^&Xx9xmvaLJH`?NLUzaYtFMl{U+Wxw1 zX$%2z9&J5x?2=v7sGn;R^?GuBb{dTnXE-hXHR>FUajmFb?G zt8c5Tw$_$Ev?ZLbsovgNUzrMgCG?i#}YGJjJuHwW{$g4ADJHaK&uN+;26(Y zOn9=IXHIx=+D%V*b9)I-`tV0uO!^9^XHNQwmrhUmOEn392$1Qw_z)=fDf2^+!uRP9 z!HT3JQz5F%x2Hli1+%8YbdJnSh3l(}e2g$Ucl+a06Z5Q(k(cadK1P{(iF}GSi@g2m znMHcmrLqUwe7$CInLoz*5`QV?=znhu%x2Xi8yA<=_DV)?CIoyBeT;f zA?l(tsS)QaXVRk0vuDy{?Pg~(61_xcGgBiiXR|WXvuCq&OJ`?u3YvCA=WE$8ye zKV{G5SAL(Jdx0kvn=fc!zB6A)5X_k`dUIrMzPM9eY@ww0+?|D&gXTF4rNee}3uWV8 zVvFTdk#`nfO{eE9R?L^qExulE5?iWV?Z2~Bwf-q*srtwFxuqHenfNlEnC0$rExAzc zavino{Bk{7L;Onvqw(D@jjXqFzcg{$&wpv=_7?w2;E%fdwM951_iL+o+5Fcwsb=w& zH!=fvSK8%1=dN@ptj({yRU|vG+NsK7wc4dAl(*WgBfGHLqpxw`Td$F^)we#ATY2C5 zFWE1A8!+`g@O{uM%If<&i;TSQLwCy-zQ4C=KCm`yJ7Bdo;_x|dZPa;fXJKs&OD3^C zj$^T2pYRdNU!M$+U0nYVq9L&{6=7_>@loQ6jXMReb7w6v8!`9hbZV6K=1gWr{^o3M z+2ZCjn@(pf8E>>r9SPzgR+)TX<>J;7o=nntc^?joBHIWy%#DJtmXbh=#i*F^Bcq^1#%RUg`oJkiko!AvJS(pSxdP|OL};anM9mWOPg;^GT4ovSJ14Z z57Q%I&x}M1n$|H#+MqqJtyoTH5Yo_6emoBeAIP0z-^VL?M^C_)fOKaClZexCnG;t#Rs*7%>k$J?( z{Alks!wci*IHzxlg&K=7l7~MPE;vgbcTVA z=SeciJXM6Az9077e)8*N{CGXP*@;ItErgEMk5>o~3{hv6FQXTa?vV1iV9#+OCW!;| zLljjLA7Mh2l>7+t(w&Li3UU^6mFPw{{56`{R3?T;(?qz{YmY1x38W@bL^x{fqm#mQ zO1*eew{B5ed7Qg)W{5o1DmTxgb>-Q8F=)Ze#itav>*?&Gkrx)0iCm*^&-xUEa3n5L z52}*WmW0TAit>lc%Vg6&&P4N#PuQ9!su&u`5_`=jW>eK#(!9K&k}t|nm+_iNCDy5; zNMb5Cv)EEO9^dw|!_wPYm;92htY?@gEi~V zFY=%;!YC(!)(YX=HJa(m?sNIr(S5o0$QLOHr;pex(Wo!6#hafn8Bz*OKZB6%d{-G7 zpS3@*E_z;^o$evVcJ};&U7b&?d0wRNgsOaTG?$*)!8=T&XsWR2m$Ny(GwY`=<~sye zRGt;Ts?&U7)Us6OL<+6#*;dX3>8GzYQ-kZ}v~_45{o{uF_=9yCPkqV`)v>VD1|o%&;>Ie}(Ju5o&wZITJs-wABoOn!b$uIc*C`yEX4 zlZEL%w@9x@pFcIf!XV}+?nUY3s&%QKj_yDW&k-^20I#d2&tFb!M!oIoypWXkimcw! z*QTdSg{PF8%5r>$?IOMS;bp18JmT!lM|8w2h^u+>L_u>dB_}TLyz%rnFm=Ip^+tmG zsk{^Rm+w6dutu(bN>gBRb#yvX8nx)qJT=KhXBzV%_OoJH`(S&%U0Kd~RD2Yom?ja? zE`}IFcPbF4(u~v|!Vc-NS0Zx*5qLhtdr}+h*ek2y7h+}a$J^x})dY^_K9n7%#>+c4 z(@bO)mklF=P`So^q&sMCycm;AblY>5ecSDIWAH^7W68Qo*m@ z+|pZ#dH>^++}l?j_8lwnTR%Q4(p7YN>#ZhpY)@;RuIP^HSWQ2+J)<97(VL<7E&Jm3 ztjXJo{<4m5`H!mvDfJOFP5FqPCK?p2g(49%H@5V3)dCF`iyDlyE3MgHba2HFx1Y1G z+}d7pV!1fBqvwEMio0`LQem6}k8L`HU%Yn<-_N;>XsmgH46w&fP@#~7vL`ENI4VDK zj3dYt8&<;TwudxMJ9Q?EE>PJ#>83yF)N`u=d9SRhTlnhZcSPLtXbWeeeal!9)~gF> zMe?bP*pgP36N_X9iXYcmzY$-tK0476Lr7uRep8vj61b|cCT?Fd>lJA5XSqFlD4!=lk4u8Uq=Dogm}VD*5rwd9J*K5U4+MNbIopt1;?t1o-xMZ>~W|s z?niy-DVN=G%?Q;&J8f2}V{+)LCJt1IZq#^B&t{KxA;dLS`V&4Lr2}3lyay4VE71ft z(%#dW%*}AxULn()F4GRD=|hKic;Jn>Km9~u+VR|urc_&$w>rVwstkeF_qr^H6LIjm zB5Z$+#g6)mgWmum(b(P8N2<5k+1A9lyF8{N8BpHq<$xE6bX`<;>DCcsX z98o3g{U1>MFrGdeEDu+zVA3VkPTpm5IsG^rJ`me-oCv8tuWbfz=dt zm(O%3=J9z&K;JY8q*d}hFe4c?5P&OlA>Pq)VcKwxVfAD~I@YYCTLz`#?H!hc5l5rL z8AZ_Az8=Ka{CGB;Y&0;otWQyVF6vofI?wDFeF8Kn9+TX6Jd}l&w0PQT98Od3iEMGB zNJ3l{!HK-{_O-y=Jc{9-k+RBsEFcm&?j7nP8{D{#2>gtQk_%tI71{4#Psiu;hRpvg z!Qo@M8^tr%Sn_b9`_cQ)yV4yBBc2HGlXWGV@vHWB-HJvJtV4ta(YA z#E>TiVGJXpW}e|>UGty0vQ67X-VV0+jwbSWTIuuLMb`E4`6s7YU8~tJm&${vQX-Gs zjuyVRJNhrQB)*>UBMrsTC!0 zeYoX+@0K?$`kCYf=|#4rLWd_b1~@wlzZMqkhPO0PMYycPlbYM;WncTm=IFCd@r?DJ z3YjT>LeUJNX&1DUYJH@+M19q#J#8I5$YNYm$&hr8(jphoJWfw6V=&~|u6#p}MMV)e z9Hov>x?FnaV0Zz;vzbP!Akfb4$Gxe^Z2y3 z1}$%7a3p1YV~Y}G#cY#@zPaE#7(+jsork*ig4V!`qCQWr+{HmIn=;x{Uj%d0Cx5jL z_qNPY(ju2nDcfbu^({N5z@lK;JvX5x_*51>t-t;9KoEml2(tdglk(uJ1o}QF`xUYz z%!FgEyd-%}rY9vLM5BYHl8FmBYrI8tgGE2t91}7TlbSER9A27SOq?dTzSfpZ zw(!!@F1`7>`0Pbz%g1GfnJ+I*=gfAOs7IGNaFn;S2JU<3<@{K}TBO|XwQrM#v!`5i znsIdEdPJX4w8e}B$LsPc3+G!7K9-*y(lnyuHH)oc+?P@1BtzvvTdzXIDiX*Uo^9Eu z`X5jTsfe3<6~b1Lf9#d&{SfNqlwrBo0|&`A?O)>tD_)CLs)Pn83_7;YO5Da4Rn9S= zbwqdg+e?c{kcZf}K4uC!S~|XSrzE;nf@9e`Mx&~bT#|YN@otN$@M8S(*P^k(D*gLK zX`2$hTlSytFfYANSb0$8I8bd;SpCVz0~ITgvsSR`&p7bzDS|>e;0kb8C1j=}&PU)ck=x8$hq` ze2{y-K`qopFRVSy?JkP;^Y&~>u-Zd;2t+H{* zOrasp8U5_XA*XIXl{2Dvr>R$r!HVxz?Z_o^fa%lTaz+ z8CyTi)}~iXupGwJ&dazegmBch>E38*a7LF=A12JVvLM^_iCU5`VfqvfN8T+M(m9;R zS>n3ce(q3Pfp^^p=fkgJ{jZB3`S_!ag+0a0kg&!2_U1|lX;a z$90QVb&HR5OKf*barGQL-XpVfx#!4}9=W(4`Klg;k)9LVJ&Iht%ExD7$u z)voHDl17k+ASe_OXLJ#y`hDjv_l-*<^tO8qbo)dy9*BPynrF6QRYH*2^zYMPBiBce zm?G#kFc(MA+)?*Ml@L~O{VF_tr~&$UyZ&44{i0CN5 z<$3KfVY=^fCwlb`$n$F4dmM)$QXDMZre|CmQs^3T;KJ~04AUyzZ>GT@%7*y9yx+DP zV(uEMI!;f~c@K#m7M4a(>|hb&+r2|vi1y={raTO}V&BJh*46&O)zVQW9{FJ!%!PIg z{y3wsko@updNqBx?ST9M7Y0W&!tZvk_cEsD$-9vQV=~BlWCdfZR`1Y@=onqL?c?ZG z^>LQ2d&O2~^hWwbWJjmkG1Mm*Z*Yy%Cyg`TpP*0bqu7UFm!LeNw~vg8O^h?p zVs2D@AX7p-4z!W3W)-DHuvMdL4)ht*43fK9b6KM!tK?6Nq6H)vg@Q1$K^V0lw0XO{ z3N8JOJoG3{pV_`qGJ_8^N_|(W(dTIyJ|B^nyMi%3fmtnmcl;+>SoY%sgDH)CbfK<% z`@$5Jl8vhs!jR^k(@w`H-QzR0XbYqMoF5Z~TE z#5L_7G$pM!cx-gQZ*&%g`ItjANAGs;r`4RW#=OhIKswiW)CqL@l|j<3dnG~Ebc>T0 z;}BOD-e-Jx=chY+Yv15I+RtQ3v)50Ih994CU!+f?nHtI)qE%X;yf#OIoT9w{Sv`Mj z=gI>z?4<3)$eFzNiFqTWoqcw3%Tg}r4LuBz-bBI`G-p0KNkYDUbcRcMWUl=^F>)bu zWojgTj?r~`l@>9%b7H!A--zqJ2PD${cjCX2FU`CkL8o0FJa!t*Rzv^UYAQ>24#o3T zh;BU1dg%J-N<{}+RPw9#TjsG*`C~i7tXEgqCFPIpUtJsh)*-QiII+rxTo5B05k85= z(@X_d_vyZmm#;d(Oy0TVdf@AG+NEP1-$;2j zXp{OLxPH;Ra=&otn^`dZ`I_axeVYix{K*q*4lmXzlm=hKtr5MIr<0(!Pna{UnM=GU z->tVuinO5)Mu%;$1?|xF8CvyOJ^4nH^sasTQ|99LIhPTu)99ir4_;$6|C!4qCf zEL@V~T`$%?FMYqt^)=0fQ(KxFWMBLp2~G{HdmyFU&~vD#zajsadODhR$8xQTIzst z^^@(NB=c*Dm+(ZS)0GyF?llyhKl+m~n`u%fgMR;L6=(Fl_nGn(Il8Ur>_!69)t^(& zUsiiiB-Ci^WqrFHrS?pLa2>6)k~fHNAUShm9WvE&F1pQDIw?BN_20X?l}}@eFsaF6 zn_Z=le_TI&uu3_~dTrd!|Gw{7Q2dqc%^{Ch^m5T>*RMyQt}#SgCE}4^9lce=X?QE8 zibUnUx2n87NYrka{o03@hz{E)^_BY22U;DvEb@9Pe8!BhZ71*e^)oszX*YN?xrv?X zrTFTv$lPrVR&-xD@QHGrI*Cf)O+fuy=LTId)T;kdgXeIYrpAul-9jy)o*s<42n|Df z;STTD0BpYx;4gba<9uD`=s zbV{0wOr8efQX{iojk=+o(5Azsa~F=@U`OPm807=_ju?_;A25!(vv^?8`P30jb64>W z-jYimy1L$(3#D)4`frw*ephH`&M6Gx4NgpyJ3~7@RuXnb-JeaDymn!>PxhMTtI22{ zy*bNB6~$M9{i;FyKOYrsk5@^5I{)!9(-HSa#idFych9lu9q_+9n_(ON`uvbcaK3|D z;bx3pQ_A~I*5r^K7WR#yuoarQ)@LWSSc-D7hvn{mYkibIKX5Kiw~;All}R{aJ7-nw zHbWXa&vJVH+l%LKRY_WAODr&UsNO=AWa4X^Bu)q-(MI#pynH4Da*o-zMdI;6hpLIl zm!B`-L=HzB$L}wD&6~@ny=t39a}?@3$EavnG0M88TB`urqjN?*fWel{>-92F^i z!CYGK68H8Amip-FREuW+tGBLrifkSi_Eq`OmPf5$GB^Lmceaiv((V)2p>H<#zYz72 zPRJ3Im?+Jq{ev-i-i55Zf5jy*nGUh(wy%tG38CHyZWN@9T$VF3+ml1ERCI^I)&Gt zzE)n7t71i*iOS{ENI%MQbzpfsn0JO@%Um$fJekyCB51gxBc0Z3{370jd7*thrcJA zv-DkVa7A)i=-<4aA@cHtanLn-6{Sy|%xA4!5BuM`BX5xwt=^pALLt&K$WmzZyw1tqA8<dpDmyd)F2pBE^Q^bUyeD8;~TT(o=z|Dtf;-JhW#0Eac&*wy2B5C3oFc z3Q^a(z89U3lSrAqRR2Ni!3WN0C+0TwB&)y}!=r>S&6p6o?Ld6vUC|@k@o>vz)&TCV zv?7I@T=UCkwzN`*FIL9P#aLc^|Grqv*hlwC>Ak4O&pxT!SM*g`+m*c^S@N>4{CNDH z)4==DwI=)OZ&mkP*4~fpkUHRZNKV+evkZ@uX*kp|Roi&U4o}c|J2VKKxbJ5?Jjv4R z&~&)^evtj}hy7%Z1ho?n!lH(!gftvm&sIN(EF1nP>FxOD=81bh z8~%Kf%&F7!gl#g*$h3xrQ}@$q+jQBHnbY1*y=f;NWgCyo8aF%jzpQ?gZ$C13mF)3g z;|aT>sFC?w8jpwis_jb4Mi%aQKOX*g!oFf)WYNC)@#wc|`|7okC08=%agvh`bu6RH z-Wtx6Of?QovZG&uy`857PCB+4kA976cK&p@#<9bGbR~i8$+X%@r|ziH)eMa%vuA6Z z`pZVYz3_fAfAi$yp@GrwWzA0(AJyzU9$gztf5Z4x9-4Hoy2OL zByqhIW4$zg{Xv=fL#p*MM)f;~&FYWX*307R#Vph-h|9ZFVYYe%#RP+}r$Qs@Y|=*_D`pWhA)q6WnD89;yVK z5y8`p;AKnj#u0oX2)?O=`+g+^{{})pFClP>5VT4NCTk9XnB^}5>wLhyrCtwwC4RLffw(o1u{DXmHCd)LMYT26s5Q;3HQlx~ z1J{}v(VCUonqAVG)6kmR+nP7knorz-g@v3AVv=85IIKuFqy-X#@P^HY--|d-PEN2@ z^Uv!zum=MhBzsFau(|SkFGosB3YJ=aZ|A@Y4s6xHJ`HTiz&ZK=)Yql<^PX=CTWc#R?0}2`B+`*gOLSwzWeSH1=4FrVD zPTgeN7w|MPD*D-Z>JVKsnwX^IlvJ-+ePm)AyJ0qVCe|vNP*!YBYI|uQn&m)twb3G%AOY>j8Rxd5C ztgYvN8{OFcnf&7?1*d~B`5&2f2Bzu%%rq?20lu)J2CHa)BpbHu0A*OK+rt_b-e9{7 zD4(95{>?X#4NG@GB!CEebg)DRc)^++Y`DS38tkXRju{kyGiS~yD=RN9E_QWw!Cu+; z_&8JoSV$8V76!O~lMQ=oP+fj6?Cq`U{U&=4>wmGN2OE07*YjXE?=L{_E$01&Y1qmG zpnosp?QP=yGs^$GdiS>|!|L51_wIf}{FlAEy`{VVCHTLwn2^3z#G=QKveJbm{T43l>15R--d!OK4ren^Ngxhln zJO`%JeB+RM<_zh1`7a7C#+}Y7EG;W9EI!Sp^{NJ68zgv8p9WRe(%SYA#dDf(KjBSp zU;lX&3L~b2dDk;AG5JAxjFyxXAv!%aKDD%5xA2aadu8#<#%96V+SU%@;TThjk_&-OR(e_68q zU()|uNGI(E#!Y!yDYE~T@td^|f$`9SJ>M>O?vQ_PnEdLOZ)30W+2Pp4+;)9CAkctc z7>DBt`sLdf$ii?8`+xbiPwXviJNh*6?H3ngPn+iy0OK!;PIIyUVqBuGv8mY~#c>+L zjUse*bzeZC4oK-7=zH5UGCHR6UVu(gnSt*8@c7((;|z)ujiz2&_`X)QwAr`5{WEQ= zdz(WOZK(K&gh5oboHA8jLf5hDO7v`Su{}z_yA)$l!0o|}w4hRF7xw*MO zpSHF(L~Mx0AaAGuSFT(Ec8(uEZftBUBqRhm6yi3ld>0iJfw7jAl|jz^^yw3%Z-BbB zwH3C+`S|!?WBfnw1WDWBnDp6L$pZiOn9`IXUMc;KR~N76Kb~6u6|ZWz9+`8XUH=iU zSUGQM-y{o&*gd8T#HiCK3c6>%;??sw6zQ*cbul9@dpBN{>9SFmS5{T~F!1P;A#0kN z30BBPJ(ji>IHq%LNG}BqT$lGqWv2NGe9!h4*u--(G%Q_`bQ7 zzrOMVL3F)U=pW13#LK(KWa9p(25R?&_91pph>3{_P6#UO*|TTikYMj0YMivRG#nK~ zC^#*s#jvjrRT#pDi;D}?M`)Cwoq^H`hXjcV9y!3{2Xk|CDBqBi;E*6%#mC3PYX|5Q z;N3!1RTTuRl9Cd55dnoB$~5FQFE1~6&j1k(P8W{Z)6)}P8pz7ZLZ1R^0DVBAhe!lP z6W%%O-@pHFkC}7_P6#c`?oIfc036zcRdfCk^LF}I03K+(AN?c${SuteSX}T&HD`AZ zoTr!f>H81;kUl}dA)#R=>a27Kx+uD9;c@W^iSBA_IvBU4%&hF32r^wk`oP@cl9xFW zQMyz`rB&56UL+_z4n}-4p~bpR7loqGlOyHr;3g$)eK+)8xvoRI6w?K z6Nrx}T4~*VOA~bYvm2WU2RXidq>2%?SbqD=;|V##iGv3E_y9UxRvFtPib;+B&hm`Z zUHr;a?lfZ8VSQeg2ED}SV^(*aw=qoWMof}YqIo;VY10x5Z~kHJYtIxtN<#@#X`LyW z?ZP&bSmW*t{TyP4+Nt=SOp{_!=`+_`aE2G^y##I`-TZh&&=HunBeThBH-=h0wM!)u zUpWL<(=f=s7>l->yRv z7?PkC$yPVFeq8vz) z|AE`Hrf^}kE_By@jGw^OeK@%@JbJCq<#@I&pVhUA9@aVPLAgMb{1GR3Pn@knt|ZIw zpalJVL}X{nX{po2mVI^ZSYyU- zP|xA?9UUD(4^TE>Z~}=1>Neyt2xAb{w6wI~;Gl{?!G~8!>gwu>ii+@Ns=B%wGyo+5 zYCF^nc)J9r57Dc#vJwggs0BhANF_Bj6{H2)0AWC-ffNUlgVP5o!khzi0;T{Wflvq9 zi;j-|e^G*srZ~HAo_kTJ{9haB@x>f|VJaD)e>37s9UIY0@&7PlQ(Ai*uiLK%I^c{j zE`%sBe7C}%XJosnZXOoBs{}XlSUpri>TUzAdKhV#uAlXyto)T9vJhEaRZ&}4A6Zr1 zTv?=t6h1A@g>LBX={?t6*wozCb(*TRZ+xP!g-}z|hGf+fKQK1Av|QfIK&{G5m}U_d zSEd!m3{6sSYWhXF^D*N zBM8rLj*DEk;jyPt42+U{n9svKuEl0-ea4zrE2}uOOGM%EzK!#q(_I<*!TiSsrf548 z4T>!fA7nc7j6pe#@Sx)5T|M-bwsO3KK<>LTwH`|DP*3sU*NhBO*a!r?zd@LD4ss(J z-H5OZQ8a%F{r~PYdud=VYX8mz5ZR$R>?H%pqo5>6=TIX+Oz_+bssKFw+6(TG6+k*L zXM=(QAs$+EP#7cz;2-Lan3xz?*j{LW5&$XzDRgvnfT|$=LPY=#!2>yn*PvAB^Wps% z^d6ujK|w)iAs|eD{rVNm3v3R=^v|OCcdwZWF|=Sl(fX%h%XopqkHay+z2`OY)>P(G z<<5F9z-ye>-@G_CY<+;mY1eBYw4HT+a)wp(uBMeFoBwWO_Bq*1T%pcT{C<^~M14Q+4Q+nOx2kmMbG{R8QpY6d+<2lov^ zWA=FH3{u=s3^}#1xHSApTU}UNLg4-K=GMf}{EC_)r<^Gf-j|3Y`O8IuezXGCT%O+s zp95a=@<*J97d+L{W#?hv(kzk@}Pq7rROr-Q66VtHfdR?3&PIB#0*yTIj_!lCm z&LfXnOY=3#Y-+3-@08uE&q@}gFyWTT8LQvoFGnJLmPgO|@U8G6Gj)l@LIm!(WVj)@ zS&8yHM|5I1X;7I4$HpJLEV*+>>7NqJeuNjC)1IoJ48RB&L<1cmJa&Yk5qJw|3(gok z2Q&x5f_wv$Bp4S#$N<5?se{76RUoNA6oujhjT+P~C{Ca=7!*ORf_erq6GlYfb?}@K zf)SJ}c+LnF3ls*i2Fevw8gQgNg@IE+>4Ha!P_&?=!Bhz(1#Si<4KxS!2_g<;ERZG) zphZPRA<4q58T1Mw9vd42xr6>di+}`_%>ViQ85t~s{Yp|eq_3Yhj2(V?;;*a=>W+EY+rhk!a^S3JW%(mir1B~zEl*RA}Lkii7-kGM2ZDSzifj_*w-J~ z6>tbS04>RQBa(6gJaIZpNp%L)y&OP=`tt4j+UQCUigx|y4#H3*vDxP;9v&i|W7iA` zK_CwZ3d2K0UM#i5z7xcWp=wBfZk3-ZNrkCcWT-r=NlUjkAxi4$Ggif7GF_Tbm*`$O z{`9vARabQ63k_3fF^b`$fenFYamnmF;&IwcidYT=JNagaWj!{KcK;;8;N|>j9#5Ri zVNXPEk~dkPOWJtQ<74SE_xGtx}T&_Yplt3psZ;p2C|U zM63~%=voY}IZ8!H&&Oa>3cGSL9F4&EHBkc!*u;ORX!Y8m zjO(?k2$@yo(U-${kqq{FPh$F&J0DBF=-7_&^QMp(zCK!2d615pRa5HiJ~Qq=#hV`6kF2$|H~j-V9|H#*1LCkm{$V zruK%gd-ocU*!LbPM@B}1{6YILVF9HB2hbOQwF6ra_@F*RzX0d}Nnp=#=>SmxrZX^g zg(43T5bPhyJHQ0m9~~V9h{5%t{DSs@BuIEb19&-vJg@-Z5{5O9@Bogzt^{!T=R*BI z?I(Xi1nK?Hh$KAsRYRtvL@-cfQ7|U%PUZ@CV`GGWc|eGo1{nM0TOKEvHrEpc(;LX4 zRYN?8r0A_KxAn6kS;BjQEGnhzyLefMNFz*oOJ9tgJG@V!51}Z<|B9VV<_xiJ>QGGx zpE2s1CgP?Bu`*T@p5N9z3sdpWZ%9%}A!k;$Inhv_kkwV7@;Gxy6Hlh$vtQJF;ThNT zAbwjJy{w7!P=EFLzGT@X{O5O4;x0b9TaRC9m;XaFWaxnN6B*1-dS zBuJ{@BETO21c-obKu89^0Tx02;0gc@7!?o(j%8+M2E@R46Iv~>C14Av1sWbdehdr& zWZ)=(HNXmR06LI*AnyQA(1k;;20G!|V$X`+zkd(L11TL0X)iy6M*?aPg@FD&vcX4z zx&LV}{fmE}-<(0va;GE>B<1si_UsQ^;1}lQ5 z9v^wIG=uTb?yVLCEu8RybX+=`yWr(=Gog|EQb z7J{7u#x9gUJZsf?Zy-tGO}xGl79oSa?J!u7tmYk@Grs&#il;utP{V3Dv9AfICY=)> zXr$<^!fn$lreE>l_7FaF^hk-y7an9K!^@XHj+C!FKkmZmK2e*Wbf$&lxl708L=tX4 zyF})x$q4R??=V&7qg79;5wQ%8>PGR=B4oTyvi5JU?jzEr&^Ns*BEEWST#cNy$c^j+ z(?M=xGW9`ygouo3k|&dl2|0?{nj24*t0l~N^&T+K0NMb)yu3VsZ*Qal zX%L1YrKP1XED&xE z0be{G55e;D=g%+#fx-YBLk-w#pTG{Gia@=ALIO1Z3s(Mb{jEPShYSex`)mA5hzb-! zqcK5=yMwe}%smg_qdn{{N1Ki;{KZ^tT?A56#!KSR>-snC9dA3kJOX?fd%JhNZ8$hU z+)trx($z0wlt0F)y~?<93wS3pVc?@9n^ZVt}Y%+YV=l?zn*#J zZNf7P)iat{0zEUHIYHLsTcZy#oy?#qx$`$6o+IZ{?Jp^jB^M1`!Rk&=NM2zL8|7x@ z=0)7kk-B~vA?7m8D}_|Nro||TRHXOIyDK;8ub@ms%piHZt1aj_^F{#Xw7^j}l2Dyb z41XF8{kt3PHD-I)_rM`A3B5aXR$z{BB?m--_d&A+gaCmcWe9!10Q6m;eh~4V^g-?b zE%avKO5lKdJ2H?WVM@K%_d`Dhwgn%VftxRIK(IgP_+iEjAr~5YX!gKTVWJ3~7~luS z2l#=t!3qyR4(%T#T*#HbTSVv>p-bGu2}~J?f(8=m0wm%+N&zsKJwW+@zze+`loFs6 zY#piw+%N*D&=c+@S%46V2;AiXw9xwjkl_1(>;EnG{`K$k|Aoxtpu~S`%<^z40U-qi z`MYC>R~2Q&$fBy$*LC$UtSrS>`?e%E?~>W=`KIknVb3m^X#=fZ$wK5)cJB+*J7-mlp5|c5x#$n5MPS^03v9MOD90cWWD0i- zF>a>U&LNO$q>L2HqGbiwlnfCBO|;ghBqDPw8-#fD@RSk7+TjZ(@VUp#G!3i z{?hVO;>Q{?&wKkNX0(b+d=jP(iZ*o%2vYp6a)nom`1X|HpS^PT)}8YYqY~f`<{^6) z2}A;U;9rS}iNFfTAF?D!9^40z-9r!vhO7#65W*`_m6jCri z47YzU=mjpJ3x$~pI41O;5Kf_N0K`B%4E2Ciz!c~NG=XNQ9{@4%4S4`K-eVC8%zuq7 z{wHEJQXmh6%oEK`HXq z{UWlyu`ht6wS^3>1xLrm`X-V7gYU}Uj{}hl$sa!WeL{JbbI~mVkv}|^mwgKa=tJ@o z;G0be+y35}SYCGu_-503HajH=E0axumjw}crAj{UxjGMwvXTO;imF5?9UCuvvq?l) zF;JE7T!{iBw`!OQ)wym??TDwH=a9@6yv$1OI3+oL&I9nxrgWsqwZp7Q!YVk`Not?C z+OF((#r_g^yA>0y-ZS{W6pU52r9=&S8g|Y0JyvCKzkB~aTiwmI#kwk=Z;O?y*ijzj z0s5BX8YY<0hUoU|!eLrxlc_n;w-Xi5<7rp}5aw6f*~1Y;zRVG%2EHb9@5_<%iitBs zH|cpEQ0@PjO*~_E)JN><31LzSJrR0-bf=Qjp-+Pts!V+MLm~Ek#r|Wb8kBjReO)S9 z-@3@%YtuvAmJVlLDOx1Cn>*#z?j|~CqoHFbE!}sku&^Lr}qd*+V_Wv z3J48C93)3z1<-+h49EaJfG~)kkmev@!k`02%^-hJH<%e@Qcy0y1S;N(c(7aw5f3N? z!GeJSm%te$S>Op`DGVmr+1WwY5JH`uo#CbxED&4=GA86r91aJ>fc(K@;N}e61#%?} zDZw%ULCBJjg(3gKT|2-5rVF7J0xYl#*R?Qpf%FQFm7kvv%^_U0L&gN)Agh8+f-3_n zKp13Rh^v4#%-ex2NWQ=vj6#7|C=~$f--`V|^@o2U$?!<o#7{uyFvI)u3pbf;{ey=8HMTny9Mg5vH9er;18x2;w}^S^V0i|C`HY_##s=VDdW9Ai@H>9;t>jHrP^g@{Xl{4 z`xu|=Rh36+y{sRrVO{i@7)GcBC{E}3Y;1iJB`-%}@%$dtXhkfQyMVHygT{EqxSX(f zr-^{V48Inpo~dBm6V+(%!+5COmc)zu;2+Z?2l5f}=AUMk5Er4_0ha=yfoXxjL60Cn z00_(jn1kjAzyfuHr~x&A2I3rqLl}|(G?35$8ekN#fT0Q?5)cpo=)mj~$bg&)qyi{l zA)s`)E(Fs8rh!H9yuGm~zzRU`EkuBw0M@`XAO#2lrhpVc22=sczz5I?&_f^uyZ{b> z2AmI=2511aJhLf`JV|xAcKMe{yIzX z3#LV;B7ZGN{OapMgVKXT3irnPRbDSsYfGzlueu3UDKFo&{PHUQG$cLI;9%V^uPO{; zW%uL=9^WM_49Q6D&A^l~4}^^%1${YJ*LPr+vip5OB^5(P3Spbw?+f~+lPmHoD*6zk z4_PV*-%n2R+oyMzQ;Ys;v!93PIWrTu;M7H`HlUJ<%ylCrAI=8IOx7NFeuPGGP^)(W zDHQo*WD{AE98RT{qJE%oPL(WCIMvcq*c2&9%zBYcD&$(uyC@`k?B%vdCL($(#a|y7 zl&D~`TNyeVM*d>728&#$(J>M8(jlW1>&P@Myy4+Xq84wPSVW?AaNnJuc7ASxX?~pg z(lQRkGgtl`z?{G~*#2M}AUcpFbOuoC!7!jBf$jk!5a=2-3sZOyAcQfPAcC%8;0Hkn z0t}b|XdG%Zlx7ehRA&%7#2XkX00$sCP#kCt2KO*b*qcs(NI~OpEefp?1TA0%vKA;5 z{05)_qyPn&B!J3*2H0wZaUcXa@BqkrAb&s&Vio8bkODYhmI)3Ast3t~-XX#Pa6lpO z0oDcT28=-6Knx%S>sr5648;6LLi?}$`JYAYfkcW0BTw$_W$oQn!GJJ1B_K2?BlX{g z6@^hDK_R8VMZ0%ZFd+1OnNg3#|GKMcNe?XxY3tg(t9sfn7}WnB0@&-&{Ujegh@_A0 ziaILjz)?@h`uWvw-^Y+_zO0RFFs#5}9^OXdFI%L`V zNKsLk^Qk(TRqFv6AJINKit_Tpv{Tlcn-o8iuBGWBsq}_qN*<8rIXyL(|u4i>UqV?sS7xYIM4Strenm>n9bA|HgBndtWCgVz+6c(+Kmf=Pk`>fq5IEFUXh-0(6k2_l zXzv9ifB-bRH?iB}2FQSh126)?LT?Xg2Z}FTGlB472_8^@rCv}y3&ZAYN$Z zfeEO~(6Iw302y!zwSJGk-%1Dh10NuBu%Er|WRFYGJ2WW33P>KBr~gV(|DXI>e+B~g zcU3+#I0c#XkIUP<{D{=l7jQGRJ0tKbEUJKsd2JoOvao*l^0qCkvFZ)X2zrV;QhIl% zcTf9+hsP%-2S>*~K#r>Tm^KSJDs%qJ>bKRVfcb^*Kepd@Y<$|_&M|LX=q2GL zHsw2XJO^Vn4GaXW*KOcU|MD9>5r5c5!z6{c>uXy{q zDfzQ39I2Xkk)oMAwp8Lc@__PT&Dp0)$&puPhPZ~8G}B1~e(<6KH1Q<4IQWdL5!Re1 z#$D)+I!nJkjVhglaChf~WP4A>p?g=hbBg)NqRT~5&oxoA@3YU(b&zg^U|I5sLNsMO zJnkr!osJr5S1@)TM6ATYkanIudj=5&hykYw z4-bc+0^ET8Kyimu1^~fG4YUu&0ICNI0iNK}7(y3BHHcV{n!u32MSv2>LO={O^S~K& z55OfL4u%H=1N1;Junx2V<-j6<4p0JTKqR0BAcFY;nm`!f{(mC*|BPR|yBV~*Mdn}N z_CWvGU$@B4=wP)iC_3C3YuPPc*DmjE23=OWJA`2+;WM=MxZ$w3C+sKaqCvjjz)GAd zGLp$NV0U*S!Bjp*?GUBDwCrglgB~|?O5*OturD22k0w|TMV+lLQ1EhhM2A4qKHsM= zB-sr3J(r_uAgk+v5nVln!o?#a(8k2^yffmI`2=l@G0EwpjQXU@(8d_eAUL)Wi^nlG zrZ5ngLR~6Q=dX*ntcE5iY8FoNGNE&b(LDS_ioE=tI!wAVacPbmVr1 z6s5P{r;ikVcOO3DOeC!Ihs&9Lpv&w3(B+}uAw-C=AgoZu%H@4F^2#JX=P7Fpbzi}sNqod zfd|MzQ1c=BgK$B$;2IEfK+sUGA?ECbY)CiIL4#j_B0+^OSso$1GQ!Ud$Nup=&>>8pu{Sx$(||dIog9mmd$6~$ zb^&FKq}1enqDMiAij1O^_WPyFyE4}`kWN~BVVVIND89tqi`3h)Sw<+dCOerPCzF=A zp49&0-SGeawD;X{QQq0t^9;i<3`M#FBE5@%qDa&vBOnY&vttdSQAAXtXpAvt=tB`{ zN)aPchoaI1L^Pl%T~Sd{Q9w`-Q7LwfzGrw8u_T+%?q`3$yzl4zYqQyGHaqv6d(OSz zd+%c*!Lk{x5-jdWrPp9$Eon-2v!GFxPt{tatK9}!*e@cxh+fo#IoEmVIfoykyq;^O z;HVho9nt3BoJo5vV-S|nQDLS&Dp_qQC(kXBw}5gd$uxkfrcPVT5#_pBM~*gXTeP%h zqMg4r!Qp$#CI!S#G+14PiM27&A_u>pA-UR|9`SFo~HyRI*1vU<*sUKjQXH}@Hnf$e zfX$8L79JH8rb|`qWcFV`>&Jl*wL5dYtv#f5&Mqf{X)Qu@-hTLKDIst1(|l=FiqL3h zf+qC#tzn^`O>WyBA7?ACh>3ywEw|D&?F#1$vCo9antI5MM4J{e;nH`R5K9&S9Poi0 z2%G_MKo6ow7EcgbwBP_H;13~3>;Vx>cpxJI3J_5kOMncC2+c-B8t{T5BAWpmP*YTs zu*3isbRD<^wg6(l3z!B{VS|BDWJtgoR(2RMe%gAEm8DA%83bpzVVE5mC=xT4 z2q-NOjD}zaSLO6j*A{VK;9n$lpT4FfZ#eOYRQYFzg@5RxFDb z8+`F{keM(_Vjrz{3@10Se1Ybq^*m>^Gqws#sV7ycCnvd$GWF1UCd8)DV`<7-YMWAO zqOWLCOd{H%rdk-!u1!4VND7@S3Lyp#hY>}B7ABI!6~6X?XHnQV;1=k@mp{noondr=2ml!14U9p5F_%c@HN2aW zFhlP9XCh6cXBb)mL}*wrPt}h}gnvABl$^3JI%L1N&Cn`{c;-xQRm;QEkOYB!X`3^xZ`htGXq=$oODh&0a zD|HUs!KG zP)i{0fZVY3A}a#Gfa|aZ1I&OfvJ?5u7*dIm{QwmJ9tinQ^hr2eO)Y%Y+TYx#(haju zdKsSgUf;Uy@S`1%o)JN==AN&^-|Yw$5NUEg!>1M6a?KYsYs}kS`F;)CF;< zY{ix1*3*NoP^#ZqfXiQtkN4anxL$#U3j23!Sc2;n?-nY^>lN5^UrjLmkj#xOjcA4; z5s`DlmMzqYiHc8fr_rVoggE85eQD_{gl2t3rHW-7$u%AEyS~336M%nk(gFf(NH6$l z0a~aYfQN=9WE3?jEFe&ap9->wdI7&Z7K$Lm5NQ15D4~(@Q1K#FB10k>BICj8K^9^4 z5DhFT0pWNdCts?8VZ53lp&^(E5eR~3LBtT0L97rcXf#O{Bxi&d;R0ol*ufgONs=`P z7ZNr8;pq?9TxUx?1=BBI|7f?ydaBd#dg_>!hgA*VU;mKZvPEUPIKds-B3`1RhWj7# zazBX(&)aJ+5su}@gD$F34yzak#(m?UlMD-WAk~@5AL{LU6*wI|E)c`nqxGCsr3DKq zd4#O93Qw?*vXdaR9M!KkBV(<-F0E`o_IgtXUe9Km5Okr(PkKtF=#O5#UeZJKQ>jaT zoJFhHiRDMd=Z}`~<-dxvng@F?yPmqD`-j}La-KcYFhp6uGbvHzjY1r*3N0Ivd{~6w zR*3wF^)u)fR0C1N!XbW7fE@!v_|5SnBTV?+Ko|VH2sGk_V8N;5w?z=~t3sH_+aF{H z55@++JbrMry%8y(0>MJufeqw0Fb}~3HV`ia3kpj%GJq4Z-b5pV%v1mpVuxS>Jiq{n zA0PpFj|`t6f`eFJrC`CB_r(R{I~I}Bal)f#zE3hnk8tauKIr|e2mIfmzrXb$tMbvU z2MOtSw;rgDt3^M(^`Kz8VZt#)aFLOkGo9GTS~9Nkd}9hR%1Mc+y#PD2Y!tx~w`W~# zZo&JfeWyG=eWv{2>aBZ^pWL|ANyv?RarNn2Nbeyh%xU)1W67f9^ejIKWGxc zEg+OgSNP@OB9QiBXp!m=Sp*1uE4UE^6h%5bEqX?Hi$FlpFhXuahJ!SKr{FbSNDxQp z3ls{BLxLouMh>Tt!GeB4h7k1OU_o3Etl>!gb&?Yl+ET9SEYUCD?9ld)MjVIN2zTr} zyl?U#naREzyLF4oXs*DPhK9`^HO6m?*sdY(uvjQ^i>O?@pqL5Tx5jRZ@Q`;{vd?LC z))Dky59c|FOO(Kt?vk7xT>)FViC{W0uK9pG*wS@62LN+N_g*%r5<$`hlNx+-i&b^%_%H z`gbS9ok^M@!%2DBpP8dX$(mCT_;J()6N1tsq7JIyHUMIbF+Fnl4zWQ1$O{3WE8_jh zc>r?M7WQ%YJOIikBwU0X#R+r`vqnh4c&kDb@u-W~Bk({H=t_nhfB>Y1pz>2T*u-9zZ0~aTSBS z;*jA?9lGLh%t29IP{Q(LHJpVB!JPVfqRMe}gJ5N9K?9v>Pj5JP`Bv+#7HOIa1;iCx zaj0uhCB#J~TP{3!_WanBSGc}(u|HH=a4| zBzYI0weQEI{KBQT$;%DLwHzeZ{7UX>IufHCOz32t=sDIucBu0uASAiX=1vvPf;8@& zM0K1vArC{7Ys2j|RE{ID&e5S3+EOJeF<3$utD;m<>4(lWUn><-uM!oyj$KV7m8L3L znE3;&t}eOWfx)P8kD4?L9>03MLsa!*gtE}VBk0JkW2w#EBu;>kM<=7 ze5~pi2rb{CY`e*pLf4+VSKRqj4b{P5|p#n>lt(XE~P` zA8x@gS;?Q9Y#PS-#cuw-NWM$9hlI%H?1hdayS6NhUI@knKiDF~5AlYm!Vp0KQSE_; zBUk&$+79U!#3Mfh2`dEZ!jeK85qlUS*dhP|u}AkFf{fvK1Qp>&)X9~9l7WA=^^b&& zRxb1w57@9y0+9n+@mAM5w?tHn=*FfhxfiY`**<~yj!21uHEgwZD4uGE!K zVM|L{oWgd$=ob6u{kAl{DRXKQ%*}6?Z16h3zR=_x+1Hjxk)1QCrcIB3 zDPxJjCO@fw3;LU!3q1DtTHFlC;-5;mW>?f3#bTr~)wt}C`Ab)KLi^IFl|UX$W%xpNIQRh6#!^@ z-#QPD_RTl>>XX*_?`;py;4pysu%a)0NW?!nFyb(P=KI3{`X3Gh$dCErVF1$+hXJq= zaZ+#?AogTgRh|=}?=-D|fCFN{&Xdt!PgktCEwj zVl{Uu8W~v{Nmunzgbw!{ZDY_y%g&8mSI(y8Z>4J5dCIw-kyQ|t-z#K>Wp7Kub@~r^ z6t~EAd13LdtMj#$}%?a|ISr#by#;SyV%h|sfk z545RK;$0?37j3QA9RQu=S5b*qzfz_paX#iXrM00JTUCY z4FCB(0QgEQ6hLxdDS(wPdcY3?)0vB70PHUx14s}Y1MnDm3}AW71|!9h#{iVZ;TV9) zm=DJQm>(Yl(9|7y3}E7iV*v6WjsY;|{w~;23mQ5HV5)i@PkwHVbBXbD4T-zhQJZu2 z-b{TvW1X7=um9-mz7^uni72O28LD7I{4R1-KC!ZH(rquJb9JLo^qY02)2~ zqMFWryDdB+v3Zj0nG^G~7N*j()RJ;9bzd!*aJtsb`HJ@SVm%3$XbtY6sKZ`^J7bot zH?%F-E;Oagz$1z)H1j5NdGAKwF%+)U+b5MJ`M=9>uXCL3uH|=anSY*0>G|7g#a$V3Kb(9Kj}8n^n_T!A|gcqXh0dl zj@lkc3FCXi2LT{ua3kobpwS6SGkgaC-FYmspiqRep$P*=gaidUgsPIvJSZsvZPXZ; zUB>`C5*@iXf?5&9Cz_skNJkqBa6@GRp~EgTOf0M{pa;~03PAclv->$xc*#E=o+$%^fbNHb0D57D9}WVHe}51_j64YN?F{lDfavf+0P&%N0OLP72%xX_ z?jV4i;2=Q1#n3^3aU%`__zMmK>|(K_sW=GW9@oSeItVb&UL$X=CMH>j4g&1)OI8{) zbP#}|LeXL5N0J8tPWCw;^}L*(F2$YvxQ~0ScvfgY(jTc?4=gF!CQoNw4&Gt1De^(VyolkW=2x+M#;1_Vvn}2jOBOi4;q)T`ndc+TV0CNrN#VX8#*r*e0Qne zxgu;y&C$?>3oL#886B7W#7FQ$k=RPCA(Tc8A!5!0TNLorpaR?&zzd@W!Gy;`VFa%R z0)Z>gOtRAjHsCED-W(YjITx)J5CUlzuS?Ks*nG$}>^Y`SAmA{3m`evIP@8Xx-{aQ(1@$m4iHKp6qZgNQJlP<{vi9zH=zPzS^XHQ^3M5{M)RXoOk> z3J=zjWCB;v%=sLf|Aap?%nv^t%=qFpfP`RGTmwiFoP!zK0y!L-AC^n*WgXw2oceo8 zVcF1oL3MaRUCq$OWNe73(3Pvq-!BTDqRM{P3N#uY94SY?M`yPRX4id#jdw}j4Hsib zQeRU!jDEZqR6np{*i3w>-&y5GufUhP@5*?UOZxKO})n5|NlK3nJ9eoplEpIr+MG+Im^+dnOpnirB?kS^}4uD{nU6)7iQ$l31~NRY&dmqvdyfZHR|v7OozLTYu*pNU^4~ zgi@`<>{)I}jNFc@A~{<<=OkhNGM=euUf*M1`dH_?P`irB4W&<3d{^=C z7(U(IxMpU;y8G!8$NL`&KHa@hWNXO}7kx%dql^>=vZe(p0VoWc09fIbKbfK2Y`1FC3Ep>zUE z$W?vt2K)f+;9gK$K{mhuG^hYxunljmAPPEO=o+F&2uT1-M(*i@g%~U`NkPV-+oxnpHft0UNJOmSnof6Ol8J2 z%?7lco3BL~(k~Q^7Sz_AWutCovfFm^L64<=sa_Ehp}$fBpR9LTn^ff!Or^|dYFDj_)|-8HiKz5)j)IhUJ0mVX-624A zbHXGoDY_=bLQd~FyAKa#9n_*##|j#2x#}z}R<{hCafA0xD=jIN`4KLsLxj!FcT7LL zJbvr?sYQpD*ooa^RPP^W+ZK9^HjjHJv#Ix1F;{4`fwYfHJ%^BLR;v`EHdw8#GmytO z>xGFxnmDhp;*`25@4(?-mLBuIp&Cqk&WR}}N>xd`6Sw z#S|H{TdE`7$`eaZ9h9EtJE~P-cEp3u<{M0}4b?VY?eWd4Rd-LSj3Eso5NSjl7$Gp96im@fqM6qOa7XDXxRe5Ksdk*VasGERw3=BS;Dgqbm&QKradM_3v%zV+bCKgf7Gv z7xj@7$fN-~h>jS!s1IJl3u5e^T#`gv3OYuv=)>?~(+gPzNdRd99Srz6a347YMi9mh z2?S{eIRvTZi}l6dj%{w4FU(o46%v(2Y0UO}&_(;p9%NVzE5Rd8wikXDy)g7p$Gjq%rJya^-gO zDGPG#N|~-#^K&Aik_+1lJ!k1CO_N&Zz02cDvDsVMucej@1kek%jnDeJ&^U5_QCPXo z?VkA&ZB`6B1%pKzM>_mv%oMV2k1zMrkDDMC9NKNr!U@*i?D@LH`=sh3xfP1jX%`}k z0vSI}78muYV0~+UKfKuLv6fZ{&&#M&@qXXUYGb1cxi+qbX!$h1@i~)^J-OMSs@?bW z$~G;TkWGo^J5Q9I2)i!tZ~(()TqT}di`vGMZ;CDlHIBRf@Z|CNS*+Z)8R}EZ@x4aI z#AqcACuc54MxIr9U`p@oGYsKTtK!rd8Cu;d%;raQDSs=QOl7$S{ldR(%&er_hDwbu zwsW0-`FU%I`5PIo>|VCdMO&qs=Gx%Lcigom&CpYyD!fnPk<5`~jazmU1M$Q0O5;{Cg|=U?h?Xc5}$04D5sp0Db>H)&7&e z^6${}AG@{Bj^t8qMS-@6w;t(1ZMAmCR*#j^;&6yI2oT z=F#k{3oOMhG$l;12(q%1{@&m9cJ__ph^IP~T;lxVqn2te5%TOLBXuFOW2eU2yF|!Q zCdybv**&0K8>9SMlIl6ntG&({Tl?xS=1*M5EOb2M_M;nj&YJi{=fR?;5XHhbk?s|dQoC5kex23Yr8&PVx1Z|HGNDky2=!zq z)$@n9^HoBU^j=h%|Ewu3XB~Oq(PYYY`>XE78RuKmA{-9f`*Go~v;oa)za~%y>$t*Z z&+RuZrZg_`Eihyy`LUEtSFC&1e#$jajk$T*MppNx6+FF%GIf%M9%ctM?q<$e5i%(& zu0`8LZEi>SZ0TnVnd$U<^FCUR`Kxt(03CutuIr=g1t$rtLueqKqzs{{ftPT&RxkmL zEO<`97g9BHS08Vr@Rnc^N-iiSEGue0SYH%^5MmGsdJ3icI~)5b;Gn;Mv9yn;SEwhn z8NCiww^U7VNRL!IH80$aQ^ftn2H|{AgWY>@U{!l_mexy8dFpy1r*_Am`n> zzIC#Yw8fOEf^~iAh-y@UC4zPR9)eug7qgJ1e*cSn&Hg1v4r`Q6duCv^uQ^I+dysz#XVUY; z_Tz6A)|vfG*y_v=Ht}S~`1pQLeC1|BEje~{ojp}tE6VG-mA2cF&G+ezLO$tId%xv1 zJCr>iCzBriz~o%n3pGVm^Gr@e8%?CAA#vS8!zAjg#{C}KS{!#CiIfiR(`Hgr%3j?* zIm&p3)xrK(uhu9^mB?|qmKNVM2eoqRg(<`WMj*S*_r`?EHa}D0&mJ^Czs-j74zLB8 z&`SnCz#cqVf=_Ux@S9kF!AJoJ1iy(gkz8^FT0t5J0VZ)VJqZ?~_yfH_K6pSpSfHN6 z^bh0%phlMjJrTeh(8kLcoGEHVun^5Xm|56Hv`f%!0W)A%!2oz+^jzQ{(L4l$;YML0 zQR=~vqDF*S=5o2{5Q49$0x>p+ivS=rSPp9m<4kHI1QC82ghMHbIug7A)j)2TP~-`a z4|W^;1I0fF`+xh-d`u=kHhuSQPS_{6*^ABya0^=(csW z&vbe+`Oehh{;e{rRh53xW4m21rnFpizEiSg)7Vt^mr1-A2OG{f&N%6~JZaSo$LWb8 zZR%y2V|r@q&OhB3F%bJiWc;FU-B0t}H0yO;&8f;nIb}tjNx83BEjw67sKu2zN+`#q zEudM-wNl%re+@@;!4_^i@9j3u?cmF_zoZ(nIr@YEGKd8{LYBY=kN`4+HVQiE=)$1a zN3sxf1kaHA!7#Gq1DrAB174zs0(k&kz#DR(A3a9s6MBpw2kI_>8e3|p(8y5&@DjWM zlTgbIZ~LQ@g1ve$0OAMkLXf~nXc8Wcz!JO)f{~cu0%@R&kVCKp?q08>Fh@EEBAc>-JnJOA6<`sDBb3Dt=3-Na|Q6&7ws;QH%_ zK1+1O(eMJyFbJr|OHDtOQ7riSL``i~QA3^J@YAKj%81G)!K{1fZdqD;`_Y?%;~0Cw zRrLb&ci+SI&K-g!A0(!f`S_1FZ-1w@a0-1@1s8dgRjxt3KxnEKkDAi+&jPd%dW3f~yPeg3r zjHaHO`}8P;l}5MDR4)px^|bpgir7ItKiA3#&C#2@*%y4PqD(EV`z$YnrzTjYh*OBU z6k77!aj#3W&dOTxMOP?mmFM={_{w&=waK)V>s!io-DW;kpDm;qOq*UUUG~!{2Ad%o z*_1p*T~;@7UF+FBdm}~Krnu@@tw^m7GAPP^X7l)>_`bZWbxxTn8GjJp+`Zo_|Iw|q zpD=PUvL_El0LJ8D0Eh!dNzs=CAc23#6l4df9q5H9K_$Q@bQnpW1j>L}03Ny+z%VEV zNddEl@%hPB0DvEC`isK=pIik1VWB+GAJ7Vvfn-5^z)%ne`vp)@^ioNw0WXHvrAY=s z6-jDhtaPm z1)mW)|46rn%iTG&j1=S@vV}Kv+1Je{hKLfJcK>PR4?f=e1S2E!SE&eRX6pN1zHIJ6x1)+S-fDFhr!N0$ zjs~s!o{l}e!-m?@HZT~Ze46u(Qr{~ov-g#CTaD|CBS{t3hAoN}6hqr-JEc0t;xhnF zH0?1l)5A)REWt&ms}cNzsTDz5s^$;({h*dw7wal_vLMePjVo@l`J{KB>+bONbY-=( z-AM&{#!RJi3~thD8kNTSE#1U!@&fMIH7tAf#bh5DmTvk|zFm=9z&OH4=bG1%oEvLs zdCv=ElDZC*9-9R}2q@zdy8-O@G^1!P>NdRpm?BRO=z&qJC$D|rG0XZIp z1L@Mh9%OOw14RvJ1i->AVG0n*{*$W!&?sOW-F{U5P$7^B?h0fA^Pm7E+lHYH$b${Q zH5ds_kSqkc$su9@9Cg|M3YP!9|IxoC8WE|;P*Rs1(yIa1eXAw?j3yg@>R>((B4Q~RN~iPG(Kx+hIE_IQ78jl*s^-JpXTBmgM$z>Y z*TQIPsj||A)9Q57pSB8&R_N9ss$Sd=-M-GrE-w_FCuz;*(r^fgvzX(rA;!$l3MgLT z!f304?^8P&cAJ%*Xgb|V<45-JMy|G!Jv zhGc*|SPz5*pwNMFB6s+aoZ*pRrAUH-FqnWKuLIzb7p(;HKmcjLVBYYKMcN(=ghI2x zz~KV{$nc;L6agH7;{unUS@40dTc};YaBv1J1qneL@DQDGOxs|p0&EBEL3NN8YzO5) za!{H)2LM)+P7K_J0D+lE2mc=2|EvG4_i7S)z-LpV`KPmEbDUgDHOpx3gukmqNo(7? zy_ScmW|Kw3sQPm^`mg+noCXga_p6QEsM#BgCd9@n1@01@14we6vTvrktn*}3CqmqD zl5*-n!JYUJQKq9pgdSo}0j4WD(+BWCHt#+~@(oe_%&Oc6dl?C0jqOU2Vsp32OLGMur3%yF zXpfR3BkwcM8k+OjzzzKE*r~ssaCCCGO5r9ep z1}Fly0T{dtkuXLz0q~F*hChdX07!r_`6vtE!7QPmKoTeMOiuKm0|m0+feJM`uu4AI zz}3LOpp-z#faz5604Ml`0t65R_)$mvb5Kh6eYbRd%7OCf zXzWD!N}J^N@$Y<&lj_-kpNBqSQ}#HwY3SRcYor)!wZ#1d-yPgcrG*RDXFbj6SZYh$ zzetAq8+^|2Em`=Sy!@Ra1w*%Fk8cr@FDtLuOUPpB`qvpBU@>xqb>z!DWFOLv7~{7? z_z`a;7eo#MPHr^=F3@nyk)kq02oWd568+1O>+(osSlj}P$lZN}3X>>EOvr$+tw=^t zZ-fUpfWJV}L9|e;qS^$)fJ+EFQWfke5Q%yc_7~MAV1yV0VF)wa0Wud^H^LO48w1dw z{sanPPe>#1KVSR(dwFey_+y1NG&r0xe)lKa(R*mR(V+@7K5aCy$Cyv&e|_-X8KBbr zLIKpTqUD8j{MCeVc54ewq@qF?dt9&`edTHaHJ)JdrbRIuDCatqRhx&-DIU<<&E^pX z;bPT6qPGK;P0b&mIm0Xq=v3j0)0H!M5#3x(LYR(*!W^P_4w<5-5W$n^6Ni*tVP4R(>_+S+cLQJ{BYmx?mnZ%#YMzk?3HB`ivg}oy9 z^mC~$1v|?YR|nD8`_bI>CRarGS}(qobz<_BIb1o)1bX#RxfzFftWD}MOlS7tRvGuJ zOqF_le;w=i4JBT6{2yFTMQ9F+JNx8+UlV7l$s93Q^T1exjdm`X_`2Mkk6{ zr@s5XNP%G0-!_2C4_C4;6zt7n(|>PSfH%E|N&-4KaFq~^Tt$m>L71AVae&;2w-CKNL#Qc$xuJ><}Cb*Wif>tyO-FP9x zA=GZa-6)TFSuRUPV(T^vq`K;eO5z#A!%OlS~!WV0_G zlz`g9iQ(Az)+c8Qbmm2zdA~tCe5PRRhcgA@ zBIKEZ(L-kn7y?JwLdeQvxuRsHo23`(Y zg>WKBu;~ae{1SPu0P=%q0Sa*T$WWy3f;ES^hl_*t|NKk=tQ2{pfc93Z+BW-y`Td6D zZ(a32c_WbyuKzWkj4%-*L>-w(`3cR~1B(S%ieZy|(F6bxr_Pzgk_c%qK@DQ9q zBtK$8`k$eXnBd-(Ksi1-C;d)21m~n*qhtQ=*ewpTV>YTvo*!%?>R0s}NRYiSMkaARSB5^W388}^yQ0M%eC;{e-_Io7U_e<6hvZLqAiF!NG| za|)P!H0yGu%K_E9g3(WEraBP+EBJ5`S*?@`lo* z_pj!ztiwod(SsW$oA2LhI9~inFp{h4h|hU-R7a2d?p)ci!LG)XSqB_Xls>uNT(GY7 z+==7Q9<`kgRdqU9_F}O6QucS}Po8-7tgoZ)fYa|M|9JI$@O~?fqrZJiq3gB1<a2U|iCcT_Wh4a}^zc*ndh}%S z)3bWAa*BF-vUATG+|Mbz;&J~_$=$5`hs&P!+&@xIAJ>~(sj#j$uUaR&H^1KOe(&3Y z#_PpgiX5Yjk}N@E&!CL*HsMl7i@WlL=u4e96n3X)KP>I-ku4|uJwgfM>T3N*5}k{^ zjwI5g*cKE*i^BcgTT<;IgUaBtkBd*~?K>@HW7uCQx6rfy3~NnJ|5@c9d;6=@f(!?$ zwPHO7YV;rb_+UDh0Wa%Jg{51w3g4I2Nnc~$KCr`Oyw4n4i@S^VJX4X>*4 z&sw~%u77rOTi2myw{|>x@T`?9V)VStfAogu?L6JX&u@oKdHB2|!p7)DXUxJ4FYd&x zIsD@7-NYXszUWE{GJ4sa8oS};J$}aFmpwVf4`1HTtulJmTX=QDs|O`rhhIG`d-m|v zqjC}B*L{_vH@@z#);;oipnl4u*Mp5V#(z9+TDb9#C(Ub){PDEq$47rWYYQ@d^Sm>5 z@9xu=2c&n@!QvruWo$%$MdcuZ{NIr2BD*fCs9N=FI=Z@? zKDEzBDlCbn%c)>4?(=2UC()-=S4eK}^HUQ)$gts@l1b?E*Rwy!Tv&Zdp|mf+BwSUd^#>(?tUj&svM-1&o-7r_snk&F=W*Dk!U&Pf=E;oGMfEUc+rP&%+DH!RI?jcwiH>jQgB>eGyVtf^c6av-)`Jl!P7w%$!? zFs|A@ePV1){pzWM@r_~WlQL`@JQfcoG}ot_71uOu*gm+gO+3TA%J$r*gu%pa`;4hq ZYtC&g9o*j+mNC7{_WX7a11e?O{{bv5ll1@q literal 0 HcmV?d00001 diff --git a/public/images/screenshot_loading.png b/public/images/screenshot_loading.png deleted file mode 100644 index c999f3666dcbcfa1dd72f01d3fca2a3cae468c32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27088 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 diff --git a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx index a70f99d..6c96b60 100644 --- a/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx +++ b/src/app/board/[boardId]/decorate/_components/DecorateScreenshot.tsx @@ -11,7 +11,7 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation' import { getBoard } from '@/lib' import OpenStickerModalBtn from '@/app/board/[boardId]/decorate/_components/OpenStickerModalBtn' import SelectSticker from '@/app/board/[boardId]/decorate/_components/SelectStickerModal' -import ScreenshotLoading from 'public/images/screenshot_loading.png' +import ScreenshotLoading from 'public/images/screenshot_loading.gif' import Button from '@/components/Button' import { useSticker } from '@/app/board/[boardId]/decorate/_contexts/StickerContext' import DownloadIcon from 'public/icons/download.svg' @@ -32,7 +32,7 @@ const DecorateScreenshot = () => { getBoard(boardId).then((board) => { setBoardName(board.title) }) - }, []) + }, [boardId]) useEffect(() => { const takePreview = async () => { @@ -55,7 +55,7 @@ const DecorateScreenshot = () => { } takePreview() - }, []) + }, [boardId, polaroidIds]) const takeScreenshot = () => { setIsLoadingDownload(true) @@ -82,7 +82,7 @@ const DecorateScreenshot = () => { if (isLoadingPreview) { return ( -
+
loading
)