From 22e1f7cb1cbf532070584263ffa1036fd498bab0 Mon Sep 17 00:00:00 2001 From: rhea-so Date: Sun, 5 May 2024 00:54:10 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EB=82=A0=EC=A7=9C=20=ED=91=9C?= =?UTF-8?q?=EA=B8=B0=EB=B2=95=20=EB=B0=94=EA=BF=80=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/package.json | 2 +- web/src/core/photo.ts | 48 +++++++++++++++++-- web/src/icons/time.icon.tsx | 12 +++++ web/src/locales/translations/en.json | 1 + web/src/locales/translations/ja.json | 1 + web/src/locales/translations/ko.json | 1 + .../components/date-notation.list-item.tsx | 17 +++++++ .../components/date-notation.popover.tsx | 38 +++++++++++++++ web/src/pages/setting/page.tsx | 4 ++ web/src/store.ts | 16 +++++++ 10 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 web/src/icons/time.icon.tsx create mode 100644 web/src/pages/setting/components/date-notation.list-item.tsx create mode 100644 web/src/pages/setting/components/date-notation.popover.tsx diff --git a/web/package.json b/web/package.json index 513b159..cee463b 100644 --- a/web/package.json +++ b/web/package.json @@ -1,5 +1,5 @@ { - "version": "0.4.80", + "version": "0.4.81", "type": "module", "scripts": { "dev": "vite", diff --git a/web/src/core/photo.ts b/web/src/core/photo.ts index c8c5268..382b66a 100644 --- a/web/src/core/photo.ts +++ b/web/src/core/photo.ts @@ -102,11 +102,51 @@ class Photo { */ public get takenAt(): string { if (!this.metadata.takenAt) return ''; + const takenAt = new Date(this.metadata.takenAt); - return `${takenAt.getFullYear()}/${(takenAt.getMonth() + 1).toString().padStart(2, '0')}/${takenAt.getDate().toString().padStart(2, '0')} ${takenAt - .getHours() - .toString() - .padStart(2, '0')}:${takenAt.getMinutes().toString().padStart(2, '0')}:${takenAt.getSeconds().toString().padStart(2, '0')}`; + switch (localStorage.getItem('dateNotation') || '2001/01/01 01:01:01') { + case '2001/01/01 01:01:01': + return `${takenAt.getFullYear()}/${(takenAt.getMonth() + 1).toString().padStart(2, '0')}/${takenAt.getDate().toString().padStart(2, '0')} ${takenAt + .getHours() + .toString() + .padStart(2, '0')}:${takenAt.getMinutes().toString().padStart(2, '0')}:${takenAt.getSeconds().toString().padStart(2, '0')}`; + + case '2001-01-01 01:01:01': + return `${takenAt.getFullYear()}-${(takenAt.getMonth() + 1).toString().padStart(2, '0')}-${takenAt.getDate().toString().padStart(2, '0')} ${takenAt + .getHours() + .toString() + .padStart(2, '0')}:${takenAt.getMinutes().toString().padStart(2, '0')}:${takenAt.getSeconds().toString().padStart(2, '0')}`; + + case '2001年01月01日 01時01分': + return `${takenAt.getFullYear()}年${(takenAt.getMonth() + 1).toString().padStart(2, '0')}月${takenAt.getDate().toString().padStart(2, '0')}日 ${takenAt + .getHours() + .toString() + .padStart(2, '0')}時${takenAt.getMinutes().toString().padStart(2, '0')}分`; + + case '2001년 01월 01일 01시 01분': + return `${takenAt.getFullYear()}년 ${(takenAt.getMonth() + 1).toString().padStart(2, '0')}월 ${takenAt.getDate().toString().padStart(2, '0')}일 ${takenAt + .getHours() + .toString() + .padStart(2, '0')}시 ${takenAt.getMinutes().toString().padStart(2, '0')}분`; + + case '2001/01/01': + return `${takenAt.getFullYear()}/${(takenAt.getMonth() + 1).toString().padStart(2, '0')}/${takenAt.getDate().toString().padStart(2, '0')}`; + + case '2001-01-01': + return `${takenAt.getFullYear()}-${(takenAt.getMonth() + 1).toString().padStart(2, '0')}-${takenAt.getDate().toString().padStart(2, '0')}`; + + case '2001年01月01日': + return `${takenAt.getFullYear()}年${(takenAt.getMonth() + 1).toString().padStart(2, '0')}月${takenAt.getDate().toString().padStart(2, '0')}日`; + + case '2001년 01월 01일': + return `${takenAt.getFullYear()}년 ${(takenAt.getMonth() + 1).toString().padStart(2, '0')}월 ${takenAt.getDate().toString().padStart(2, '0')}일`; + + case 'Jan 1, 2001': + return `${['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][takenAt.getMonth()]} ${takenAt.getDate().toString().padStart(2, '0')}, ${takenAt.getFullYear()}`; + + default: + return ''; + } } } diff --git a/web/src/icons/time.icon.tsx b/web/src/icons/time.icon.tsx new file mode 100644 index 0000000..4872cc7 --- /dev/null +++ b/web/src/icons/time.icon.tsx @@ -0,0 +1,12 @@ +import { Icon } from 'konsta/react'; +import { IoMdTime } from 'react-icons/io'; + +interface TimeIconProps { + size?: number; +} + +const TimeIcon = ({ size }: TimeIconProps) => { + return } />; +}; + +export default TimeIcon; diff --git a/web/src/locales/translations/en.json b/web/src/locales/translations/en.json index 9286dac..0b49b55 100644 --- a/web/src/locales/translations/en.json +++ b/web/src/locales/translations/en.json @@ -58,6 +58,7 @@ "root.settings.export-to-jpeg": "Export to JPEG", "root.settings.quality": "Quality", "root.settings.fix-image-width": "Fix Image Width", + "root.settings.date-notation": "Date Notation", "root.settings.focal-length-35mm-mode": "Focal Length 35mm Mode", "root.settings.focal-length-ratio": "Crop Factor", "root.settings.focal-length-ratio-mode": "Fixed Crop Factor", diff --git a/web/src/locales/translations/ja.json b/web/src/locales/translations/ja.json index c05eaf7..b92db69 100644 --- a/web/src/locales/translations/ja.json +++ b/web/src/locales/translations/ja.json @@ -58,6 +58,7 @@ "root.settings.export-to-jpeg": "JPEGにエクスポート", "root.settings.quality": "画質", "root.settings.fix-image-width": "画像幅を固定", + "root.settings.date-notation": "日付表記", "root.settings.focal-length-35mm-mode": "35mm換算焦点距離", "root.settings.focal-length-ratio": "焦点距離比率", "root.settings.focal-length-ratio-mode": "焦点距離比率モード", diff --git a/web/src/locales/translations/ko.json b/web/src/locales/translations/ko.json index 68a1e97..9eacdfc 100644 --- a/web/src/locales/translations/ko.json +++ b/web/src/locales/translations/ko.json @@ -59,6 +59,7 @@ "root.settings.export-to-jpeg": "JPEG로 내보내기", "root.settings.quality": "품질", "root.settings.fix-image-width": "사진 너비 고정", + "root.settings.date-notation": "날짜 표기법", "root.settings.focal-length-35mm-mode": "35mm 환산 화각 표시", "root.settings.focal-length-ratio": "크롭 팩터", "root.settings.focal-length-ratio-mode": "크롭 팩터 강제 변경", diff --git a/web/src/pages/setting/components/date-notation.list-item.tsx b/web/src/pages/setting/components/date-notation.list-item.tsx new file mode 100644 index 0000000..b1491e7 --- /dev/null +++ b/web/src/pages/setting/components/date-notation.list-item.tsx @@ -0,0 +1,17 @@ +import { ListItem } from 'konsta/react'; +import { useStore } from '../../../store'; +import { useTranslation } from 'react-i18next'; +import TimeIcon from '../../../icons/time.icon'; + +const DateNotationListItem = () => { + const { t } = useTranslation(); + const { dateNotation, setDateNotationPopover } = useStore(); + + return ( + <> + } after={
{dateNotation}
} onClick={() => setDateNotationPopover(true)} link /> + + ); +}; + +export default DateNotationListItem; diff --git a/web/src/pages/setting/components/date-notation.popover.tsx b/web/src/pages/setting/components/date-notation.popover.tsx new file mode 100644 index 0000000..173d336 --- /dev/null +++ b/web/src/pages/setting/components/date-notation.popover.tsx @@ -0,0 +1,38 @@ +import { List, ListItem, Popover } from 'konsta/react'; +import { useStore } from '../../../store'; + +const DateNotationPopover = () => { + const { dateNotationPopover, setDateNotationPopover, setDateNotation } = useStore(); + + return ( + setDateNotationPopover(false)}> + + {[ + 'empty', + 'Jan 1, 2001', + '2001/01/01 01:01:01', + '2001-01-01 01:01:01', + '2001年01月01日 01時01分', + '2001년 01월 01일 01시 01분', + '2001/01/01', + '2001-01-01', + '2001年01月01日', + '2001년 01월 01일', + ].map((ratio) => ( + { + setDateNotation(ratio as never); + setDateNotationPopover(false); + }} + /> + ))} + + + ); +}; + +export default DateNotationPopover; diff --git a/web/src/pages/setting/page.tsx b/web/src/pages/setting/page.tsx index 48854dd..fa7ff3e 100644 --- a/web/src/pages/setting/page.tsx +++ b/web/src/pages/setting/page.tsx @@ -31,6 +31,8 @@ import SponsorKakaopayListItem from './components/sponsor-kakaopay.list-item'; import SponsorsListItem from './components/sponsors.list-item'; import FocalLengthRatioModeListItem from './components/focal-length-ratio-mode.list-item'; import LabListItem from './components/lab.list-item'; +import DateNotationListItem from './components/date-notation.list-item'; +import DateNotationPopover from './components/date-notation.popover'; const ExportSettingsPage = () => { const { t } = useTranslation(); @@ -53,6 +55,7 @@ const ExportSettingsPage = () => { + @@ -97,6 +100,7 @@ const ExportSettingsPage = () => { + ); }; diff --git a/web/src/store.ts b/web/src/store.ts index fe7e257..67843ac 100644 --- a/web/src/store.ts +++ b/web/src/store.ts @@ -24,6 +24,12 @@ type Store = { quality: number; setQuality: (quality: number) => void; + dateNotationPopover: boolean; + setDateNotationPopover: (opened: boolean) => void; + + dateNotation: string; + setDateNotation: (dateNotation: string) => void; + fixImageWidth: boolean; setFixImageWidth: (fixImageWidth: boolean) => void; @@ -135,6 +141,16 @@ const useStore = create((set) => ({ return { quality }; }), + dateNotationPopover: false, + setDateNotationPopover: (opened: boolean) => set({ dateNotationPopover: opened }), + + dateNotation: localStorage.getItem('dateNotation') || '2001/01/01 01:01:01', + setDateNotation: (dateNotation: string) => + set(() => { + localStorage.setItem('dateNotation', dateNotation); + return { dateNotation }; + }), + fixImageWidth: localStorage.getItem('fixImageWidth') === 'true', setFixImageWidth: (fixImageWidth: boolean) => set(() => { From 859767cbeb9458d6f760c77c56af678454b68e43 Mon Sep 17 00:00:00 2001 From: rhea-so Date: Sun, 5 May 2024 00:55:52 +0900 Subject: [PATCH 2/4] feat: Add artist in film theme --- web/src/themes/08_FILM/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/themes/08_FILM/index.ts b/web/src/themes/08_FILM/index.ts index 0cc9e57..4619b71 100644 --- a/web/src/themes/08_FILM/index.ts +++ b/web/src/themes/08_FILM/index.ts @@ -6,6 +6,7 @@ import { ThemeOption, ThemeOptionInput } from '../../pages/theme/types/theme-opt import Font from '../../fonts'; const FILM_OPTIONS: ThemeOption[] = [ + { id: 'ARTIST', type: 'string', default: '', description: 'your name' }, { id: 'FONT_FAMILY', type: 'select', options: ['Barlow', ...Object.values(Font)], default: 'digital-7', description: 'ex. din-alternate-bold, digital-7, Barlow, Arial, sans-serif' }, { id: 'TEXT_COLOR', type: 'color', default: '#FFA500', description: 'default is orange hex code' }, { id: 'BACKGROUND_COLOR', type: 'color', default: '#000000', description: '#ffffff is white, #000000 is black' }, @@ -16,6 +17,7 @@ const FILM_OPTIONS: ThemeOption[] = [ ]; const FILM_FUNC: ThemeFunc = (photo: Photo, input: ThemeOptionInput, store: Store) => { + const ARTIST = (input.get('ARTIST') as string).trim(); const FONT_FAMILY = (input.get('FONT_FAMILY') as string).trim(); const TEXT_COLOR = input.get('TEXT_COLOR') as string; const BACKGROUND_COLOR = (input.get('BACKGROUND_COLOR') as string).trim(); @@ -73,7 +75,7 @@ const FILM_FUNC: ThemeFunc = (photo: Photo, input: ThemeOptionInput, store: Stor canvas.height - 205 ); context.font = `50px ${FONT_FAMILY}`; - context.fillText(photo.takenAt, 100, canvas.height - 305); + context.fillText(ARTIST ? ARTIST : photo.takenAt, 100, canvas.height - 305); return canvas; }; From 44ee06fe86916d4e939380e411297727db36d4bb Mon Sep 17 00:00:00 2001 From: rhea-so Date: Sun, 5 May 2024 01:05:16 +0900 Subject: [PATCH 3/4] feat: Error dialog --- web/src/locales/translations/en.json | 5 +++- web/src/locales/translations/ja.json | 5 +++- web/src/locales/translations/ko.json | 5 +++- .../components/add-photo-error.dialog.tsx | 20 ++++++++++++++++ .../convert/components/add-photo.button.tsx | 24 +++++++++++++------ web/src/pages/convert/page.tsx | 3 +++ .../collage/components/add-photo.button.tsx | 24 ++++++++++++++----- web/src/pages/lab/page.tsx | 3 +++ web/src/store.ts | 6 +++++ 9 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 web/src/pages/convert/components/add-photo-error.dialog.tsx diff --git a/web/src/locales/translations/en.json b/web/src/locales/translations/en.json index 0b49b55..d5e8f89 100644 --- a/web/src/locales/translations/en.json +++ b/web/src/locales/translations/en.json @@ -80,5 +80,8 @@ "lab.description": "The experimental function may appear like the wind and disappear without a sound.", "lab.collage": "Collage", "lab.collage-description": "Create a collage with multiple photos.", - "lab.collage-options": "You can set the top, bottom, left, and right margins and the space between photos." + "lab.collage-options": "You can set the top, bottom, left, and right margins and the space between photos.", + + "fail-to-load-photos": "Failed to load photos.", + "please-check-your-photo-extension": "Please check your photo file extension." } diff --git a/web/src/locales/translations/ja.json b/web/src/locales/translations/ja.json index b92db69..706dd72 100644 --- a/web/src/locales/translations/ja.json +++ b/web/src/locales/translations/ja.json @@ -80,5 +80,8 @@ "lab.description": "実験室機能は風のように現れ、音もなく消えることがあります。", "lab.collage": "コラージュ", "lab.collage-description": "写真をコラージュします。", - "lab.collage-options": "上下左右の余白と写真間の余白を設定できます。" + "lab.collage-options": "上下左右の余白と写真間の余白を設定できます。", + + "fail-to-load-photos": "写真の読み込みに失敗しました。", + "please-check-your-photo-extension": "写真の拡張子を確認してください。" } diff --git a/web/src/locales/translations/ko.json b/web/src/locales/translations/ko.json index 9eacdfc..c5019ad 100644 --- a/web/src/locales/translations/ko.json +++ b/web/src/locales/translations/ko.json @@ -81,5 +81,8 @@ "lab.description": "실험실 기능은 바람처럼 나타났다 소리없이 사라질 수 있습니다.", "lab.collage": "콜라주", "lab.collage-description": "여러 장의 사진을 한 장으로 합치는 기능입니다.", - "lab.collage-options": "상하좌우 여백과 사진 간 여백을 설정할 수 있습니다." + "lab.collage-options": "상하좌우 여백과 사진 간 여백을 설정할 수 있습니다.", + + "fail-to-load-photos": "사진을 불러오는데 실패했습니다", + "please-check-your-photo-extension": "사진 확장자를 확인해주세요" } diff --git a/web/src/pages/convert/components/add-photo-error.dialog.tsx b/web/src/pages/convert/components/add-photo-error.dialog.tsx new file mode 100644 index 0000000..3c79770 --- /dev/null +++ b/web/src/pages/convert/components/add-photo-error.dialog.tsx @@ -0,0 +1,20 @@ +import { Dialog, DialogButton } from 'konsta/react'; +import { useStore } from '../../../store'; +import { useTranslation } from 'react-i18next'; + +const AddPhotoErrorDialog = () => { + const { t } = useTranslation(); + const { openedAddPhotoErrorDialog, setOpenedAddPhotoErrorDialog } = useStore(); + + return ( + setOpenedAddPhotoErrorDialog(false)} + title={t('fail-to-load-photos')} + content={t('please-check-your-photo-extension')} + buttons={ setOpenedAddPhotoErrorDialog(false)}>{t('close')}} + /> + ); +}; + +export default AddPhotoErrorDialog; diff --git a/web/src/pages/convert/components/add-photo.button.tsx b/web/src/pages/convert/components/add-photo.button.tsx index 63b0b7c..2833a8e 100644 --- a/web/src/pages/convert/components/add-photo.button.tsx +++ b/web/src/pages/convert/components/add-photo.button.tsx @@ -7,7 +7,7 @@ import AddIcon from '../../../icons/add.icon'; const AddPhotoButton = () => { const { t } = useTranslation(); - const { photos, setPhotos, setLoading } = useStore(); + const { photos, setPhotos, setLoading, setOpenedAddPhotoErrorDialog } = useStore(); const onDragEnter = (e: React.DragEvent) => { e.preventDefault(); @@ -30,9 +30,14 @@ const AddPhotoButton = () => { e.stopPropagation(); const { files } = e.dataTransfer; if (!files) return; - await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { - setPhotos([...photos, ...newPhotos]); - }); + try { + await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { + setPhotos([...photos, ...newPhotos]); + }); + } catch (e) { + console.error(e); + setOpenedAddPhotoErrorDialog(true); + } setLoading(false); }; @@ -41,9 +46,14 @@ const AddPhotoButton = () => { await new Promise((resolve) => setTimeout(resolve, 100)); const { files } = event.target; if (!files) return; - await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { - setPhotos([...photos, ...newPhotos]); - }); + try { + await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { + setPhotos([...photos, ...newPhotos]); + }); + } catch (e) { + console.error(e); + setOpenedAddPhotoErrorDialog(true); + } setLoading(false); }; diff --git a/web/src/pages/convert/page.tsx b/web/src/pages/convert/page.tsx index c232056..af45948 100644 --- a/web/src/pages/convert/page.tsx +++ b/web/src/pages/convert/page.tsx @@ -12,6 +12,7 @@ import RemoveAllPhotoButton from './components/remove-all-photo.button'; import SettingsIcon from '../../icons/settings.icon'; import ImageIcon from '../../icons/image.icon'; import GenerateIcon from '../../icons/generate.icon'; +import AddPhotoErrorDialog from './components/add-photo-error.dialog'; const FramePage = () => { const { t } = useTranslation(); @@ -61,6 +62,8 @@ const FramePage = () => { + + ); }; diff --git a/web/src/pages/lab/collage/components/add-photo.button.tsx b/web/src/pages/lab/collage/components/add-photo.button.tsx index 68fd7a7..847cee9 100644 --- a/web/src/pages/lab/collage/components/add-photo.button.tsx +++ b/web/src/pages/lab/collage/components/add-photo.button.tsx @@ -4,10 +4,12 @@ import { useTranslation } from 'react-i18next'; import Photo from '../../../../core/photo'; import AddIcon from '../../../../icons/add.icon'; import { useStore } from '../store'; +import * as Root from '../../../../store'; const AddPhotoButton = () => { const { t } = useTranslation(); const { photos, setPhotos, setLoading } = useStore(); + const { setOpenedAddPhotoErrorDialog } = Root.useStore(); const onDragEnter = (e: React.DragEvent) => { e.preventDefault(); @@ -30,9 +32,14 @@ const AddPhotoButton = () => { e.stopPropagation(); const { files } = e.dataTransfer; if (!files) return; - await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { - setPhotos([...photos, ...newPhotos]); - }); + try { + await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { + setPhotos([...photos, ...newPhotos]); + }); + } catch (e) { + console.error(e); + setOpenedAddPhotoErrorDialog(true); + } setLoading(false); }; @@ -41,9 +48,14 @@ const AddPhotoButton = () => { await new Promise((resolve) => setTimeout(resolve, 100)); const { files } = event.target; if (!files) return; - await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { - setPhotos([...photos, ...newPhotos]); - }); + try { + await Promise.all(Array.from(files).map(Photo.create)).then((newPhotos) => { + setPhotos([...photos, ...newPhotos]); + }); + } catch (e) { + console.error(e); + setOpenedAddPhotoErrorDialog(true); + } setLoading(false); }; diff --git a/web/src/pages/lab/page.tsx b/web/src/pages/lab/page.tsx index 215ff77..afe49fe 100644 --- a/web/src/pages/lab/page.tsx +++ b/web/src/pages/lab/page.tsx @@ -2,6 +2,7 @@ import { Navbar, NavbarBackLink, Page } from 'konsta/react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import Collage from './collage/main'; +import AddPhotoErrorDialog from '../convert/components/add-photo-error.dialog'; const LabPage = () => { const navigator = useNavigate(); @@ -13,6 +14,8 @@ const LabPage = () => { navigator(-1)} />} /> + + ); diff --git a/web/src/store.ts b/web/src/store.ts index 67843ac..8ac297a 100644 --- a/web/src/store.ts +++ b/web/src/store.ts @@ -18,6 +18,9 @@ type Store = { openedPanel: 'left' | 'right' | null; setOpenedPanel: (panel: 'left' | 'right' | null) => void; + openedAddPhotoErrorDialog: boolean; + setOpenedAddPhotoErrorDialog: (opened: boolean) => void; + languagePopover: boolean; setLanguagePopover: (opened: boolean) => void; @@ -131,6 +134,9 @@ const useStore = create((set) => ({ openedPanel: null, setOpenedPanel: (panel: 'left' | 'right' | null) => set({ openedPanel: panel }), + openedAddPhotoErrorDialog: false, + setOpenedAddPhotoErrorDialog: (opened: boolean) => set({ openedAddPhotoErrorDialog: opened }), + languagePopover: false, setLanguagePopover: (opened: boolean) => set({ languagePopover: opened }), From 824e8498888805dee3f27ecb4a813e067700ba0e Mon Sep 17 00:00:00 2001 From: rhea-so Date: Sun, 5 May 2024 01:05:44 +0900 Subject: [PATCH 4/4] chore: Add sponsors --- web/src/pages/sponsors.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/pages/sponsors.tsx b/web/src/pages/sponsors.tsx index 4573069..4f87383 100644 --- a/web/src/pages/sponsors.tsx +++ b/web/src/pages/sponsors.tsx @@ -23,6 +23,7 @@ const SponsorsPage = () => {
  • FEMMK
  • {/* 2024-05-02 KakaoPay */}
  • 잠실귀요밍
  • {/* 2024-05-02 KakaoPay */}
  • 장비그래퍼
  • {/* 2024-05-03 KakaoPay */} +
  • iPhone_
  • {/* 2024-05-04 KakaoPay */}