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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export const placesApi = {
},
importGoogleList: (tripId: number | string, url: string) =>
apiClient.post(`/trips/${tripId}/places/import/google-list`, { url }).then(r => r.data),
importNaverList: (tripId: number | string, url: string) =>
apiClient.post(`/trips/${tripId}/places/import/naver-list`, { url }).then(r => r.data),
}

export const assignmentsApi = {
Expand Down
95 changes: 64 additions & 31 deletions client/src/components/Planner/PlacesSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { useState, useRef, useMemo, useCallback } from 'react'
import { useState, useRef, useMemo, useCallback, useEffect } from 'react'
import DOM from 'react-dom'
import { Search, Plus, X, CalendarDays, Pencil, Trash2, ExternalLink, Navigation, Upload, ChevronDown, Check, MapPin, Eye } from 'lucide-react'
import PlaceAvatar from '../shared/PlaceAvatar'
Expand All @@ -12,6 +12,7 @@ import { useContextMenu, ContextMenu } from '../shared/ContextMenu'
import { placesApi } from '../../api/client'
import { useTripStore } from '../../store/tripStore'
import { useCanDo } from '../../store/permissionsStore'
import { useAddonStore } from '../../store/addonStore'
import type { Place, Category, Day, AssignmentsMap } from '../../types'

interface PlacesSidebarProps {
Expand Down Expand Up @@ -45,6 +46,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
const loadTrip = useTripStore((s) => s.loadTrip)
const can = useCanDo()
const canEditPlaces = can('place_edit', trip)
const isNaverListImportEnabled = useAddonStore((s) => s.isEnabled('naver_list_import'))

const handleGpxImport = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
Expand All @@ -68,32 +70,45 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
}
}

const [googleListOpen, setGoogleListOpen] = useState(false)
const [googleListUrl, setGoogleListUrl] = useState('')
const [googleListLoading, setGoogleListLoading] = useState(false)
const [listImportOpen, setListImportOpen] = useState(false)
const [listImportUrl, setListImportUrl] = useState('')
const [listImportLoading, setListImportLoading] = useState(false)
const [listImportProvider, setListImportProvider] = useState<'google' | 'naver'>('google')
const availableListImportProviders: Array<'google' | 'naver'> = isNaverListImportEnabled ? ['google', 'naver'] : ['google']
const hasMultipleListImportProviders = availableListImportProviders.length > 1

const handleGoogleListImport = async () => {
if (!googleListUrl.trim()) return
setGoogleListLoading(true)
useEffect(() => {
if (!isNaverListImportEnabled && listImportProvider === 'naver') {
setListImportProvider('google')
}
}, [isNaverListImportEnabled, listImportProvider])

const handleListImport = async () => {
if (!listImportUrl.trim()) return
setListImportLoading(true)
try {
const result = await placesApi.importGoogleList(tripId, googleListUrl.trim())
const provider = listImportProvider === 'naver' && isNaverListImportEnabled ? 'naver' : 'google'
const result = provider === 'google'
? await placesApi.importGoogleList(tripId, listImportUrl.trim())
: await placesApi.importNaverList(tripId, listImportUrl.trim())
await loadTrip(tripId)
toast.success(t('places.googleListImported', { count: result.count, list: result.listName }))
setGoogleListOpen(false)
setGoogleListUrl('')
toast.success(t(provider === 'google' ? 'places.googleListImported' : 'places.naverListImported', { count: result.count, list: result.listName }))
setListImportOpen(false)
setListImportUrl('')
if (result.places?.length > 0) {
const importedIds: number[] = result.places.map((p: { id: number }) => p.id)
pushUndo?.(t('undo.importGoogleList'), async () => {
pushUndo?.(t(provider === 'google' ? 'undo.importGoogleList' : 'undo.importNaverList'), async () => {
for (const id of importedIds) {
try { await placesApi.delete(tripId, id) } catch {}
}
await loadTrip(tripId)
})
}
} catch (err: any) {
toast.error(err?.response?.data?.error || t('places.googleListError'))
const provider = listImportProvider === 'naver' && isNaverListImportEnabled ? 'naver' : 'google'
toast.error(err?.response?.data?.error || t(provider === 'google' ? 'places.googleListError' : 'places.naverListError'))
} finally {
setGoogleListLoading(false)
setListImportLoading(false)
}
}

Expand Down Expand Up @@ -161,7 +176,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
<Upload size={11} strokeWidth={2} /> {t('places.importGpx')}
</button>
<button
onClick={() => setGoogleListOpen(true)}
onClick={() => setListImportOpen(true)}
style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
flex: 1, padding: '5px 12px', borderRadius: 8,
Expand All @@ -170,7 +185,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
cursor: 'pointer', fontFamily: 'inherit',
}}
>
<MapPin size={11} strokeWidth={2} /> {t('places.importGoogleList')}
<MapPin size={11} strokeWidth={2} /> {t(hasMultipleListImportProviders ? 'places.importList' : 'places.importGoogleList')}
</button>
</div>
</>}
Expand Down Expand Up @@ -448,27 +463,45 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
</div>,
document.body
)}
{googleListOpen && ReactDOM.createPortal(
{listImportOpen && ReactDOM.createPortal(
<div
onClick={() => { setGoogleListOpen(false); setGoogleListUrl('') }}
onClick={() => { setListImportOpen(false); setListImportUrl('') }}
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)', zIndex: 99999, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 }}
>
<div
onClick={e => e.stopPropagation()}
style={{ background: 'var(--bg-card)', borderRadius: 16, width: '100%', maxWidth: 440, padding: 24, boxShadow: '0 8px 32px rgba(0,0,0,0.2)' }}
>
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--text-primary)', marginBottom: 4 }}>
{t('places.importGoogleList')}
{t('places.importList')}
</div>
{hasMultipleListImportProviders && (
<div style={{ display: 'flex', gap: 6, marginBottom: 10 }}>
{availableListImportProviders.map(provider => (
<button
key={provider}
onClick={() => setListImportProvider(provider)}
style={{
padding: '6px 10px', borderRadius: 20, border: 'none', cursor: 'pointer',
fontSize: 11, fontWeight: 600, fontFamily: 'inherit',
background: listImportProvider === provider ? 'var(--accent)' : 'var(--bg-tertiary)',
color: listImportProvider === provider ? 'var(--accent-text)' : 'var(--text-muted)',
}}
>
{provider === 'google' ? t('places.importGoogleList') : t('places.importNaverList')}
</button>
))}
</div>
)}
<div style={{ fontSize: 12, color: 'var(--text-faint)', marginBottom: 16 }}>
{t('places.googleListHint')}
{t(listImportProvider === 'google' ? 'places.googleListHint' : 'places.naverListHint')}
</div>
<input
type="text"
value={googleListUrl}
onChange={e => setGoogleListUrl(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter' && !googleListLoading) handleGoogleListImport() }}
placeholder="https://maps.app.goo.gl/..."
value={listImportUrl}
onChange={e => setListImportUrl(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter' && !listImportLoading) handleListImport() }}
placeholder={listImportProvider === 'google' ? 'https://maps.app.goo.gl/...' : 'https://naver.me/...'}
autoFocus
style={{
width: '100%', padding: '10px 14px', borderRadius: 10,
Expand All @@ -479,7 +512,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
/>
<div style={{ display: 'flex', gap: 8, marginTop: 16, justifyContent: 'flex-end' }}>
<button
onClick={() => { setGoogleListOpen(false); setGoogleListUrl('') }}
onClick={() => { setListImportOpen(false); setListImportUrl('') }}
style={{
padding: '8px 16px', borderRadius: 10, border: '1px solid var(--border-primary)',
background: 'none', color: 'var(--text-primary)', fontSize: 13, fontWeight: 500,
Expand All @@ -489,17 +522,17 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
{t('common.cancel')}
</button>
<button
onClick={handleGoogleListImport}
disabled={!googleListUrl.trim() || googleListLoading}
onClick={handleListImport}
disabled={!listImportUrl.trim() || listImportLoading}
style={{
padding: '8px 16px', borderRadius: 10, border: 'none',
background: !googleListUrl.trim() || googleListLoading ? 'var(--bg-tertiary)' : 'var(--accent)',
color: !googleListUrl.trim() || googleListLoading ? 'var(--text-faint)' : 'var(--accent-text)',
fontSize: 13, fontWeight: 500, cursor: !googleListUrl.trim() || googleListLoading ? 'default' : 'pointer',
background: !listImportUrl.trim() || listImportLoading ? 'var(--bg-tertiary)' : 'var(--accent)',
color: !listImportUrl.trim() || listImportLoading ? 'var(--text-faint)' : 'var(--accent-text)',
fontSize: 13, fontWeight: 500, cursor: !listImportUrl.trim() || listImportLoading ? 'default' : 'pointer',
fontFamily: 'inherit',
}}
>
{googleListLoading ? t('common.loading') : t('common.import')}
{listImportLoading ? t('common.loading') : t('common.import')}
</button>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions client/src/i18n/translations/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,10 +815,15 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'places.importGpx': 'GPX',
'places.gpxImported': 'تم استيراد {count} مكان من GPX',
'places.gpxError': 'فشل استيراد GPX',
'places.importList': 'استيراد قائمة',
'places.importGoogleList': 'قائمة Google',
'places.importNaverList': 'قائمة Naver',
'places.googleListHint': 'الصق رابط قائمة Google Maps المشتركة لاستيراد جميع الأماكن.',
'places.googleListImported': 'تم استيراد {count} أماكن من "{list}"',
'places.googleListError': 'فشل استيراد قائمة Google Maps',
'places.naverListHint': 'الصق رابط قائمة Naver Maps مشتركة لاستيراد جميع الأماكن.',
'places.naverListImported': 'تم استيراد {count} مكان من "{list}"',
'places.naverListError': 'فشل استيراد قائمة Naver Maps',
'places.viewDetails': 'عرض التفاصيل',
'places.urlResolved': 'تم استيراد المكان من الرابط',
'places.assignToDay': 'إلى أي يوم تريد الإضافة؟',
Expand Down Expand Up @@ -1554,6 +1559,7 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'undo.lock': 'تم تبديل قفل المكان',
'undo.importGpx': 'استيراد GPX',
'undo.importGoogleList': 'استيراد خرائط Google',
'undo.importNaverList': 'استيراد خرائط Naver',

// Notifications
'notifications.title': 'الإشعارات',
Expand Down
6 changes: 6 additions & 0 deletions client/src/i18n/translations/br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,10 +797,15 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'places.importGpx': 'GPX',
'places.gpxImported': '{count} lugares importados do GPX',
'places.gpxError': 'Falha ao importar GPX',
'places.importList': 'Importar lista',
'places.importGoogleList': 'Lista Google',
'places.importNaverList': 'Lista Naver',
'places.googleListHint': 'Cole um link compartilhado de uma lista do Google Maps para importar todos os lugares.',
'places.googleListImported': '{count} lugares importados de "{list}"',
'places.googleListError': 'Falha ao importar lista do Google Maps',
'places.naverListHint': 'Cole um link compartilhado de uma lista do Naver Maps para importar todos os lugares.',
'places.naverListImported': '{count} lugares importados de "{list}"',
'places.naverListError': 'Falha ao importar lista do Naver Maps',
'places.viewDetails': 'Ver detalhes',
'places.urlResolved': 'Lugar importado da URL',
'places.assignToDay': 'Adicionar a qual dia?',
Expand Down Expand Up @@ -1549,6 +1554,7 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'undo.lock': 'Bloqueio do local alternado',
'undo.importGpx': 'Importação de GPX',
'undo.importGoogleList': 'Importação do Google Maps',
'undo.importNaverList': 'Importação do Naver Maps',

// Notifications
'notifications.title': 'Notificações',
Expand Down
6 changes: 6 additions & 0 deletions client/src/i18n/translations/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,10 +814,15 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'places.gpxImported': '{count} míst importováno z GPX',
'places.urlResolved': 'Místo importováno z URL',
'places.gpxError': 'Import GPX se nezdařil',
'places.importList': 'Import seznamu',
'places.importGoogleList': 'Google Seznam',
'places.importNaverList': 'Naver Seznam',
'places.googleListHint': 'Vložte sdílený odkaz na seznam Google Maps pro import všech míst.',
'places.googleListImported': '{count} míst importováno ze seznamu "{list}"',
'places.googleListError': 'Import seznamu Google Maps se nezdařil',
'places.naverListHint': 'Vložte sdílený odkaz na seznam Naver Maps pro import všech míst.',
'places.naverListImported': '{count} míst importováno ze seznamu "{list}"',
'places.naverListError': 'Import seznamu Naver Maps se nezdařil',
'places.viewDetails': 'Zobrazit detaily',
'places.assignToDay': 'Přidat do kterého dne?',
'places.all': 'Vše',
Expand Down Expand Up @@ -1552,6 +1557,7 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'undo.lock': 'Zámek místa přepnut',
'undo.importGpx': 'Import GPX',
'undo.importGoogleList': 'Import z Google Maps',
'undo.importNaverList': 'Import z Naver Maps',

// Notifications
'notifications.title': 'Oznámení',
Expand Down
6 changes: 5 additions & 1 deletion client/src/i18n/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,10 +814,14 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'places.gpxImported': '{count} Orte aus GPX importiert',
'places.urlResolved': 'Ort aus URL importiert',
'places.gpxError': 'GPX-Import fehlgeschlagen',
'places.importList': 'Listenimport',
'places.importGoogleList': 'Google Liste',
'places.googleListHint': 'Geteilten Google Maps Listen-Link einfügen, um alle Orte zu importieren.',
'places.googleListImported': '{count} Orte aus "{list}" importiert',
'places.googleListError': 'Google Maps Liste konnte nicht importiert werden',
'places.naverListHint': 'Geteilten Naver Maps Listen-Link einfügen, um alle Orte zu importieren.',
'places.naverListImported': '{count} Orte aus "{list}" importiert',
'places.naverListError': 'Naver Maps Liste konnte nicht importiert werden',
'places.viewDetails': 'Details anzeigen',
'places.assignToDay': 'Zu welchem Tag hinzufügen?',
'places.all': 'Alle',
Expand Down Expand Up @@ -1097,7 +1101,6 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'packing.menuCheckAll': 'Alle abhaken',
'packing.menuUncheckAll': 'Alle Haken entfernen',
'packing.menuDeleteCat': 'Kategorie löschen',
'packing.assignUser': 'Benutzer zuweisen',
'packing.noMembers': 'Keine Mitglieder',
'packing.addItem': 'Eintrag hinzufügen',
'packing.addItemPlaceholder': 'Artikelname...',
Expand Down Expand Up @@ -1556,6 +1559,7 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'undo.lock': 'Ortssperre umgeschaltet',
'undo.importGpx': 'GPX-Import',
'undo.importGoogleList': 'Google Maps-Import',
'undo.importNaverList': 'Naver Maps-Import',

// Notifications
'notifications.title': 'Benachrichtigungen',
Expand Down
7 changes: 6 additions & 1 deletion client/src/i18n/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,10 +833,15 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'places.gpxImported': '{count} places imported from GPX',
'places.urlResolved': 'Place imported from URL',
'places.gpxError': 'GPX import failed',
'places.importList': 'List Import',
'places.importGoogleList': 'Google List',
'places.importNaverList': 'Naver List',
'places.googleListHint': 'Paste a shared Google Maps list link to import all places.',
'places.googleListImported': '{count} places imported from "{list}"',
'places.googleListError': 'Failed to import Google Maps list',
'places.naverListHint': 'Paste a shared Naver Maps list link to import all places.',
'places.naverListImported': '{count} places imported from "{list}"',
'places.naverListError': 'Failed to import Naver Maps list',
'places.viewDetails': 'View Details',
'places.assignToDay': 'Add to which day?',
'places.all': 'All',
Expand Down Expand Up @@ -1116,7 +1121,6 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'packing.menuCheckAll': 'Check All',
'packing.menuUncheckAll': 'Uncheck All',
'packing.menuDeleteCat': 'Delete Category',
'packing.assignUser': 'Assign user',
'packing.noMembers': 'No trip members',
'packing.addItem': 'Add item',
'packing.addItemPlaceholder': 'Item name...',
Expand Down Expand Up @@ -1593,6 +1597,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'undo.lock': 'Place lock toggled',
'undo.importGpx': 'GPX import',
'undo.importGoogleList': 'Google Maps import',
'undo.importNaverList': 'Naver Maps import',
'undo.addPlace': 'Place added',
'undo.done': 'Undone: {action}',

Expand Down
5 changes: 5 additions & 0 deletions client/src/i18n/translations/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,10 +788,14 @@ const es: Record<string, string> = {
'places.importGpx': 'GPX',
'places.gpxImported': '{count} lugares importados desde GPX',
'places.gpxError': 'Error al importar GPX',
'places.importList': 'Importar lista',
'places.importGoogleList': 'Lista Google',
'places.googleListHint': 'Pega un enlace compartido de una lista de Google Maps para importar todos los lugares.',
'places.googleListImported': '{count} lugares importados de "{list}"',
'places.googleListError': 'Error al importar la lista de Google Maps',
'places.naverListHint': 'Pega un enlace compartido de una lista de Naver Maps para importar todos los lugares.',
'places.naverListImported': '{count} lugares importados de "{list}"',
'places.naverListError': 'Error al importar la lista de Naver Maps',
'places.viewDetails': 'Ver detalles',
'places.urlResolved': 'Lugar importado desde URL',
'places.assignToDay': '¿A qué día añadirlo?',
Expand Down Expand Up @@ -1556,6 +1560,7 @@ const es: Record<string, string> = {
'undo.lock': 'Bloqueo de lugar activado/desactivado',
'undo.importGpx': 'Importación GPX',
'undo.importGoogleList': 'Importación de Google Maps',
'undo.importNaverList': 'Importación de Naver Maps',

// Notifications
'notifications.title': 'Notificaciones',
Expand Down
Loading
Loading