diff --git a/components/common/ErrorBoundary.js b/components/common/ErrorBoundary.js new file mode 100644 index 00000000..5bb11a9e --- /dev/null +++ b/components/common/ErrorBoundary.js @@ -0,0 +1,33 @@ +/* eslint-disable no-console */ +import { ErrorBoundary as Boundary } from 'react-error-boundary'; +import { Button } from 'react-basics'; +import useMessages from 'hooks/useMessages'; +import styles from './ErrorBoundry.module.css'; + +const logError = (error, info) => { + console.error(error, info.componentStack); +}; + +export function ErrorBoundary({ children }) { + const { formatMessage, messages } = useMessages(); + + const fallbackRender = ({ error, resetErrorBoundary }) => { + console.log({ error }); + return ( +
+

{formatMessage(messages.error)}

+

{error.message}

+
{error.stack}
+ +
+ ); + }; + + return ( + + {children} + + ); +} + +export default ErrorBoundary; diff --git a/components/common/ErrorBoundry.module.css b/components/common/ErrorBoundry.module.css new file mode 100644 index 00000000..915022c4 --- /dev/null +++ b/components/common/ErrorBoundry.module.css @@ -0,0 +1,19 @@ +.error { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + z-index: var(--z-index-overlay); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 600px; + gap: 20px; +} + +.error button { + align-self: center; +} diff --git a/components/common/FilterLink.js b/components/common/FilterLink.js index d0c4f1c1..30cdc025 100644 --- a/components/common/FilterLink.js +++ b/components/common/FilterLink.js @@ -13,18 +13,16 @@ export function FilterLink({ id, value, label, externalUrl, children, className const selected = query[id] === value; return ( -
+
{children} {!value && `(${label || formatMessage(labels.unknown)})`} {value && ( - + {safeDecodeURI(label || value)} )} diff --git a/components/common/FilterLink.module.css b/components/common/FilterLink.module.css index 6736db6c..894d6e00 100644 --- a/components/common/FilterLink.module.css +++ b/components/common/FilterLink.module.css @@ -4,11 +4,15 @@ gap: 10px; } -.row .inactive { +.row.inactive { color: var(--base500); } -.row .active { +.row.inactive img { + opacity: 0.35; +} + +.row.active { color: var(--base900); font-weight: 600; } diff --git a/components/input/DateFilter.js b/components/input/DateFilter.js index 68dcd417..b6c1ee72 100644 --- a/components/input/DateFilter.js +++ b/components/input/DateFilter.js @@ -23,7 +23,7 @@ export function DateFilter({ websiteId, value, className }) { if (data) { setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) }); } - } else { + } else if (value !== 'all') { setDateRange(value); } } @@ -61,7 +61,7 @@ export function DateFilter({ websiteId, value, className }) { value: '90day', }, { label: formatMessage(labels.thisYear), value: '1year' }, - { + websiteId && { label: formatMessage(labels.allTime), value: 'all', divider: true, @@ -71,7 +71,7 @@ export function DateFilter({ websiteId, value, className }) { value: 'custom', divider: true, }, - ]; + ].filter(n => n); const renderValue = value => { return value === 'custom' ? ( diff --git a/components/metrics/CountriesTable.js b/components/metrics/CountriesTable.js index 6dfb9195..2d7b5ceb 100644 --- a/components/metrics/CountriesTable.js +++ b/components/metrics/CountriesTable.js @@ -17,7 +17,7 @@ export function CountriesTable({ websiteId, ...props }) { value={countryNames[code] && code} label={countryNames[code]} > - {code} + {code} ); } diff --git a/components/metrics/RegionsTable.js b/components/metrics/RegionsTable.js index 83296d97..c8e0b768 100644 --- a/components/metrics/RegionsTable.js +++ b/components/metrics/RegionsTable.js @@ -18,7 +18,7 @@ export function RegionsTable({ websiteId, ...props }) { const renderLink = ({ x: code }) => { return ( - {code} + {code} ); }; diff --git a/components/pages/settings/users/UsersList.js b/components/pages/settings/users/UsersList.js index 3274d7df..1605cb51 100644 --- a/components/pages/settings/users/UsersList.js +++ b/components/pages/settings/users/UsersList.js @@ -18,10 +18,15 @@ export function UsersList() { const { toast, showToast } = useToast(); const hasData = data && data.length !== 0; - const handleSave = () => refetch(); + const handleSave = () => { + refetch().then(() => showToast({ message: formatMessage(messages.saved), variant: 'success' })); + }; - const handleDelete = () => - showToast({ message: formatMessage(messages.userDeleted), variant: 'success' }); + const handleDelete = () => { + refetch().then(() => + showToast({ message: formatMessage(messages.userDeleted), variant: 'success' }), + ); + }; return ( diff --git a/lang/ar-SA.json b/lang/ar-SA.json index b8b53a5b..6bb39439 100644 --- a/lang/ar-SA.json +++ b/lang/ar-SA.json @@ -1,6 +1,6 @@ { - "label.access-code": "Access code", - "label.actions": "اجراءات", + "label.access-code": "كود الدعوة", + "label.actions": "الإجراءات", "label.activity-log": "سجل الأحداث", "label.add-website": "إضافة موقع", "label.admin": "مدير", @@ -59,7 +59,7 @@ "label.more": "المزيد", "label.name": "الإسم", "label.new-password": "كلمة مرور جديدة", - "label.none": "لا شيء", + "label.none": "غير معرف", "label.operating-systems": "نظام التشغيل", "label.owner": "المالك", "label.page-views": "مشاهدات الصفحة", @@ -109,8 +109,8 @@ "label.users": "المستخدمين", "label.view": "عرض", "label.view-details": "عرض التفاصيل", - "label.views": "مشاهدات", - "label.visitors": "زوار", + "label.views": "المشاهدات", + "label.visitors": "الزوار", "label.website-id": "معرف الموقع", "label.websites": "المواقع", "label.yesterday": "الأمس", @@ -136,7 +136,7 @@ "message.saved": "تم الحفظ بنجاح.", "message.share-url": "هذا الرابط الذي تم مشاركته بشكل عام لـ {target}.", "message.team-already-member": "أنت عضو في المجموعة", - "message.team-not-found": "لم يتم العثور على المجموعة found.", + "message.team-not-found": "لم يتم العثور على المجموعة", "message.tracking-code": "كود التتبع", "message.user-deleted": "تم حذف المستخدم.", "message.visitor-log": "زائر من {country} يستخدم {browser} على {os} {device}", diff --git a/lang/es-MX.json b/lang/es-MX.json index 5e828f7c..999c1d8f 100644 --- a/lang/es-MX.json +++ b/lang/es-MX.json @@ -1,39 +1,39 @@ { - "label.access-code": "Access code", + "label.access-code": "Código de acceso", "label.actions": "Acciones", - "label.activity-log": "Activity log", - "label.add-website": "Agregar sitio", + "label.activity-log": "Registro de actividad", + "label.add-website": "Nuevo sitio web", "label.admin": "Administrador", "label.all": "Todos", "label.all-time": "Todos los tiempos", - "label.analytics": "Analytics", + "label.analytics": "Analíticas", "label.average-visit-time": "Tiempo promedio de visita", "label.back": "Atrás", "label.bounce-rate": "Porcentaje de rebote", "label.browsers": "Navegadores", "label.cancel": "Cancelar", "label.change-password": "Cambiar contraseña", - "label.cities": "Cities", - "label.clear-all": "Clear all", - "label.confirm": "Confirm", + "label.cities": "Ciudades", + "label.clear-all": "Limpiar todo", + "label.confirm": "Confirmar", "label.confirm-password": "Confirmar contraseña", - "label.continue": "Continue", + "label.continue": "Continuar", "label.countries": "Países", - "label.create-team": "Create team", - "label.create-user": "Create user", - "label.created": "Created", + "label.create-team": "Crear equipo", + "label.create-user": "Crear usuario", + "label.created": "Creado", "label.current-password": "Contraseña actual", "label.custom-range": "Intervalo personalizado", "label.dashboard": "Panel de control", - "label.data": "Data", - "label.date-range": "Fechas", + "label.data": "Datos", + "label.date-range": "Intervalo de fechas", "label.default-date-range": "Intervalo por defecto", "label.delete": "Eliminar", - "label.delete-team": "Delete team", - "label.delete-user": "Delete user", + "label.delete-team": "Eliminar team", + "label.delete-user": "Eliminar usuario", "label.delete-website": "Eliminar sitio", "label.desktop": "Escritorio", - "label.details": "Details", + "label.details": "Detalles", "label.devices": "Dispositivos", "label.dismiss": "Ignorar", "label.domain": "Dominio", @@ -43,18 +43,18 @@ "label.events": "Eventos", "label.filter-combined": "Combinado", "label.filter-raw": "Personalizado", - "label.join": "Join", - "label.join-team": "Join team", + "label.join": "Unir", + "label.join-team": "Unir a equipo", "label.language": "Idioma", "label.languages": "Idiomas", "label.laptop": "Portátil", "label.last-days": "Últimos {x} días", "label.last-hours": "Últimas {x} horas", - "label.leave": "Leave", - "label.leave-team": "Leave team", + "label.leave": "Abandonar", + "label.leave-team": "Abandonar equipo", "label.login": "Iniciar sesión", "label.logout": "Cerrar sesión", - "label.members": "Members", + "label.members": "Miembros", "label.mobile": "Móvil", "label.more": "Más", "label.name": "Nombre", @@ -67,80 +67,80 @@ "label.password": "Contraseña", "label.powered-by": "Analíticas de {name}", "label.profile": "Perfil", - "label.queries": "Queries", + "label.queries": "Consultas", "label.query-parameters": "Parámetros de petición", "label.realtime": "Tiempo real", "label.referrers": "Referido desde", "label.refresh": "Actualizar", - "label.regenerate": "Regenerate", - "label.regions": "Regions", - "label.remove": "Remove", + "label.regenerate": "Regenerar", + "label.regions": "Regiones", + "label.remove": "Quitar", "label.required": "Obligatorio", "label.reset": "Reiniciar", "label.reset-website": "Reiniciar estadísticas", - "label.role": "Role", + "label.role": "Rol", "label.save": "Guardar", "label.screens": "Pantallas", - "label.select-website": "Select website", - "label.sessions": "Sessions", + "label.select-website": "Seleccionar sitio web", + "label.sessions": "Sesiones", "label.settings": "Configuraciones", "label.share-url": "Compartir URL", - "label.single-day": "Dia", + "label.single-day": "Día", "label.tablet": "Tableta", - "label.team": "Team", - "label.team-guest": "Team guest", - "label.team-id": "Team ID", - "label.team-member": "Team member", - "label.team-owner": "Team owner", - "label.teams": "Teams", + "label.team": "Equipo", + "label.team-guest": "Invitado de equipo", + "label.team-id": "ID de equipo", + "label.team-member": "Miembro de equipo", + "label.team-owner": "Admin. del equipo", + "label.teams": "Equipos", "label.theme": "Tema", "label.this-month": "Este mes", "label.this-week": "Esta semana", "label.this-year": "Este año", "label.timezone": "Zona horaria", - "label.title": "Title", + "label.title": "Título", "label.today": "Hoy", "label.toggle-charts": "Alternar gráficas", "label.tracking-code": "Código de rastreo", "label.unique-visitors": "Visitantes únicos", "label.unknown": "Desconocida", - "label.user": "User", + "label.user": "Usuario", "label.username": "Nombre de usuario", - "label.users": "Users", - "label.view": "View", + "label.users": "Usuarios", + "label.view": "Visualizar", "label.view-details": "Ver detalles", "label.views": "Vistas", "label.visitors": "Visitantes", - "label.website-id": "Website ID", + "label.website-id": "ID del sitio web", "label.websites": "Sitios", "label.yesterday": "Ayer", "message.active-users": "{x} {x, plural, one {activo} other {activos}}", - "message.confirm-delete": "¿Estás seguro(a) de querer eliminar {target}?", - "message.confirm-leave": "Are you sure you want to leave {target}?", - "message.confirm-reset": "¿Seguro que deseas restablecer las estadísticas de {target}?", - "message.delete-website": "Eliminar sitio", + "message.confirm-delete": "¿Seguro que quieres eliminar {target}?", + "message.confirm-leave": "¿Seguro que quieres abandonar {target}?", + "message.confirm-reset": "¿Seguro que quieres BORRAR las analíticas de {target}?", + "message.delete-website": "Eliminar sitio web", "message.delete-website-warning": "Toda la información relacionada será eliminada.", "message.error": "Algo falló.", - "message.event-log": "{event} on {url}", + "message.event-log": "{event} en {url}", "message.go-to-settings": "Ir a la configuración", "message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.", "message.invalid-domain": "Dominio inválido", - "message.min-password-length": "Minimum length of {n} characters", + "message.min-password-length": "Longitud mínima de {n} caracteres", "message.no-data-available": "No hay información disponible.", "message.no-match-password": "Las contraseñas no coinciden", - "message.no-teams": "You have not created any teams.", - "message.no-users": "There are no users.", + "message.no-teams": "No has creado ningún equipo.", + "message.no-users": "No hay usuarios.", "message.page-not-found": "Página no encontrada", "message.reset-website": "Reiniciar estadísticas", "message.reset-website-warning": "Todas las estadísticas de esta página serán eliminadas, pero el código de rastreo permanecerá intacto.", - "message.saved": "Guardado exitosamente.", + "message.saved": "Guardado.", "message.share-url": "Esta es la URL compartida públicamente para {target}.", - "message.team-already-member": "You are already a member of the team.", - "message.team-not-found": "Team not found.", + "message.team-already-member": "Ya eres miembro de este equipo.", + "message.team-not-found": "Equipo no encontrado.", "message.tracking-code": "Código de rastreo", - "message.user-deleted": "User deleted.", + "message.user-deleted": "Usuario eliminado.", "message.visitor-log": "Visitante desde {country} usando {browser} en {os} {device}", - "messages.no-team-websites": "This team does not have any websites.", + "messages.no-team-websites": "Este equipo no tiene ningún sitio web configurado.", "messages.no-websites-configured": "No tienes ningún sitio configurado.", - "messages.team-websites-info": "Websites can be viewed by anyone on the team." + "messages.team-websites-info": "Las analíticas de tus sitios pueden verse por cualquier miembro del equipo." } diff --git a/lang/pl-PL.json b/lang/pl-PL.json index c04a2830..919fd9b3 100644 --- a/lang/pl-PL.json +++ b/lang/pl-PL.json @@ -1,27 +1,27 @@ { - "label.access-code": "Access code", + "label.access-code": "Kod dostępu", "label.actions": "Działania", - "label.activity-log": "Activity log", + "label.activity-log": "Dziennik aktywności", "label.add-website": "Dodaj witrynę", "label.admin": "Administrator", "label.all": "Wszystkie", "label.all-time": "Cały czas", - "label.analytics": "Analytics", + "label.analytics": "Analityka", "label.average-visit-time": "Średni czas wizyty", "label.back": "Powrót", "label.bounce-rate": "Współczynnik odrzuceń", "label.browsers": "Przeglądarki", "label.cancel": "Anuluj", "label.change-password": "Zmień hasło", - "label.cities": "Cities", - "label.clear-all": "Clear all", - "label.confirm": "Confirm", + "label.cities": "Miasta", + "label.clear-all": "Wyczyść wszystko", + "label.confirm": "Potwierdź", "label.confirm-password": "Potwierdź hasło", - "label.continue": "Continue", + "label.continue": "Kontynuuj", "label.countries": "Kraje", - "label.create-team": "Create team", - "label.create-user": "Create user", - "label.created": "Created", + "label.create-team": "Utwórz zespół", + "label.create-user": "Utwórz użytkownika", + "label.created": "Utworzony", "label.current-password": "Aktualne hasło", "label.custom-range": "Zakres niestandardowy", "label.dashboard": "Panel", @@ -29,11 +29,11 @@ "label.date-range": "Zakres dat", "label.default-date-range": "Domyślny zakres dat", "label.delete": "Usuń", - "label.delete-team": "Delete team", - "label.delete-user": "Delete user", + "label.delete-team": "Usuń zespół", + "label.delete-user": "Usuń użytkownika", "label.delete-website": "Usuń witrynę", "label.desktop": "Komputer", - "label.details": "Details", + "label.details": "Szczegóły", "label.devices": "Urządzenia", "label.dismiss": "Odrzuć", "label.domain": "Domena", @@ -43,18 +43,18 @@ "label.events": "Zdarzenia", "label.filter-combined": "Połączone", "label.filter-raw": "Surowe dane", - "label.join": "Join", - "label.join-team": "Join team", + "label.join": "Dołącz", + "label.join-team": "Dołącz do zespołu", "label.language": "Język", "label.languages": "Języki", "label.laptop": "Laptop", "label.last-days": "Ostatnie {x} dni", "label.last-hours": "Ostatnie {x} godzin", - "label.leave": "Leave", - "label.leave-team": "Leave team", + "label.leave": "Opuść", + "label.leave-team": "Opuść zespół", "label.login": "Zaloguj się", "label.logout": "Wyloguj", - "label.members": "Members", + "label.members": "Członkowie", "label.mobile": "Smartfon", "label.more": "Więcej", "label.name": "Nazwa", @@ -67,80 +67,80 @@ "label.password": "Hasło", "label.powered-by": "Obsługiwane przez {name}", "label.profile": "Profil", - "label.queries": "Queries", + "label.queries": "Zapytania", "label.query-parameters": "Parametry query", "label.realtime": "Czas rzeczywisty", "label.referrers": "Źródła odsyłające", "label.refresh": "Odśwież", - "label.regenerate": "Regenerate", - "label.regions": "Regions", - "label.remove": "Remove", + "label.regenerate": "Wygeneruj ponownie", + "label.regions": "Regiony", + "label.remove": "Usuń", "label.required": "Wymagany", "label.reset": "Zresetuj", "label.reset-website": "Zresetuj statystyki", "label.role": "Role", "label.save": "Zapisz", "label.screens": "Ekrany", - "label.select-website": "Select website", - "label.sessions": "Sessions", + "label.select-website": "Wybierz witrynę", + "label.sessions": "Sesje", "label.settings": "Ustawienia", "label.share-url": "Udostępnij adres URL", "label.single-day": "W tym dniu", "label.tablet": "Tablet", - "label.team": "Team", - "label.team-guest": "Team guest", - "label.team-id": "Team ID", - "label.team-member": "Team member", - "label.team-owner": "Team owner", - "label.teams": "Teams", + "label.team": "Zespół", + "label.team-guest": "Gość zespołu", + "label.team-id": "ID zespołu", + "label.team-member": "Członek zespołu", + "label.team-owner": "Właściciel zespołu", + "label.teams": "Zespoły", "label.theme": "Motyw", "label.this-month": "W tym miesiącu", "label.this-week": "W tym tygodniu", "label.this-year": "W tym roku", "label.timezone": "Strefa czasowa", - "label.title": "Title", + "label.title": "Tytuł", "label.today": "Dzisiaj", "label.toggle-charts": "Przełącz wykresy", "label.tracking-code": "Kod śledzenia", "label.unique-visitors": "Unikalni odwiedzający", "label.unknown": "Nieznany", - "label.user": "User", + "label.user": "Użytkownik", "label.username": "Nazwa użytkownika", - "label.users": "Users", - "label.view": "View", + "label.users": "Użytkownicy", + "label.view": "Zobacz", "label.view-details": "Pokaż szczegóły", "label.views": "Wyświetlenia", "label.visitors": "Odwiedzający", - "label.website-id": "Website ID", + "label.website-id": "ID witryny", "label.websites": "Witryny", "label.yesterday": "Wczoraj", "message.active-users": "{x} aktualnie {x, plural, one {odwiedzający} other {odwiedzających}}", "message.confirm-delete": "Czy na pewno chcesz usunąć {target}?", - "message.confirm-leave": "Are you sure you want to leave {target}?", + "message.confirm-leave": "Czy na pewno chcesz opuścić {target}?", "message.confirm-reset": "Czy na pewno chcesz zresetować statystyki {target}?", "message.delete-website": "Usuń witrynę", "message.delete-website-warning": "Wszystkie powiązane dane również zostaną usunięte.", "message.error": "Coś poszło nie tak.", - "message.event-log": "{event} on {url}", + "message.event-log": "{event} na {url}", "message.go-to-settings": "Przejdź do ustawień", "message.incorrect-username-password": "Nieprawidłowa nazwa użytkownika/hasło.", "message.invalid-domain": "Nieprawidłowa witryna", - "message.min-password-length": "Minimum length of {n} characters", + "message.min-password-length": "Minimalna długość {n} znaków", "message.no-data-available": "Brak dostępnych danych.", "message.no-match-password": "Hasła się nie zgadzają", - "message.no-teams": "You have not created any teams.", - "message.no-users": "There are no users.", + "message.no-teams": "Nie stworzyłeś żadnych zespołów.", + "message.no-users": "Nie ma żadnych użytkowników.", "message.page-not-found": "Strona nie znaleziona.", "message.reset-website": "Zresetuj statystyki", "message.reset-website-warning": "Wszystkie statystyki tej witryny zostaną usunięte, ale kod śledzenia pozostanie nienaruszony.", "message.saved": "Zapisano pomyślnie.", "message.share-url": "To jest publicznie udostępniany adres URL dla {target}.", - "message.team-already-member": "You are already a member of the team.", - "message.team-not-found": "Team not found.", + "message.team-already-member": "Jesteś już członkiem zespołu.", + "message.team-not-found": "Nie znaleziono zespołu.", "message.tracking-code": "Kod śledzenia", - "message.user-deleted": "User deleted.", + "message.user-deleted": "Użytkownik usunięty.", "message.visitor-log": "Odwiedzający z {country} używa {browser} na {os} {device}", - "messages.no-team-websites": "This team does not have any websites.", + "messages.no-team-websites": "Ten zespół nie ma żadnych witryn internetowych.", "messages.no-websites-configured": "Nie masz skonfigurowanych żadnych witryn internetowych.", - "messages.team-websites-info": "Websites can be viewed by anyone on the team." + "messages.team-websites-info": "Strony internetowe mogą być przeglądane przez każdego członka zespołu." } diff --git a/lib/date.js b/lib/date.js index 41d01304..1cfca75d 100644 --- a/lib/date.js +++ b/lib/date.js @@ -53,7 +53,7 @@ export function parseDateRange(value, locale = 'en-US') { const match = value.match(/^(?[0-9-]+)(?hour|day|week|month|year)$/); - if (!match) return; + if (!match) return {}; const { num, unit } = match.groups; diff --git a/lib/detect.ts b/lib/detect.ts index 8b11cd79..2597739b 100644 --- a/lib/detect.ts +++ b/lib/detect.ts @@ -56,12 +56,24 @@ export function getDevice(screen, os) { } } -export async function getLocation(ip) { +export async function getLocation(ip, req) { // Ignore local ips if (await isLocalhost(ip)) { return; } + if (req.headers['x-vercel-ip-country']) { + const country = req.headers['x-vercel-ip-country']; + const region = req.headers['x-vercel-ip-country-region']; + const city = req.headers['x-vercel-ip-city']; + + return { + country, + subdivision1: region, + city: city ? decodeURIComponent(city) : undefined, + }; + } + // Database lookup if (!lookup) { const dir = path.join(process.cwd(), 'geo'); @@ -70,18 +82,21 @@ export async function getLocation(ip) { } const result = lookup.get(ip); - const country = result?.country?.iso_code ?? result?.registered_country?.iso_code; - const subdivision1 = result?.subdivisions?.[0]?.iso_code; - const subdivision2 = result?.subdivisions?.[1]?.names?.en; - const city = result?.city?.names?.en; - return { country, subdivision1, subdivision2, city }; + if (result) { + return { + country: result.country?.iso_code ?? result?.registered_country?.iso_code, + subdivision1: result.subdivisions?.[0]?.iso_code, + subdivision2: result.subdivisions?.[1]?.names?.en, + city: result.city?.names?.en, + }; + } } export async function getClientInfo(req: NextApiRequestCollect, { screen }) { const userAgent = req.headers['user-agent']; const ip = getIpAddress(req); - const location = await getLocation(ip); + const location = await getLocation(ip, req); const country = location?.country; const subdivision1 = location?.subdivision1; const subdivision2 = location?.subdivision2; diff --git a/next.config.js b/next.config.js index 58e0561f..8efb45bc 100644 --- a/next.config.js +++ b/next.config.js @@ -65,10 +65,10 @@ const redirects = [ }, ]; -if (process.env.CLOUD_MODE) { +if (process.env.CLOUD_MODE && process.env.DISABLE_LOGIN && process.env.CLOUD_URL) { redirects.push({ source: '/login', - destination: CLOUD_URL, + destination: process.env.CLOUD_URL, permanent: false, }); } diff --git a/package.json b/package.json index a586ebb0..c1981e7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "2.1.1", + "version": "2.2.0", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", @@ -97,6 +97,7 @@ "react-basics": "^0.77.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.4", "react-intl": "^5.24.7", "react-simple-maps": "^2.3.0", "react-spring": "^9.4.4", diff --git a/pages/_app.js b/pages/_app.js index f1a34521..22458215 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Head from 'next/head'; import Script from 'next/script'; import { useRouter } from 'next/router'; +import ErrorBoundary from 'components/common/ErrorBoundary'; import useLocale from 'hooks/useLocale'; import useConfig from 'hooks/useConfig'; import '@fontsource/inter/400.css'; @@ -41,21 +42,37 @@ export default function App({ Component, pageProps }) { textComponent={Wrapper} onError={() => null} > - - - - - - - - - - - - - - - {!pathname.includes('/share/') &&