Skip to content

Commit

Permalink
Merge pull request #237 from thanhdanh27600/uat
Browse files Browse the repository at this point in the history
Uat
  • Loading branch information
thanhdanh27600 authored Oct 16, 2023
2 parents 51e2ce9 + 768dbec commit 8597489
Show file tree
Hide file tree
Showing 24 changed files with 601 additions and 214 deletions.
4 changes: 2 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { i18n } = require('./next-i18next.config');
const { cronJob } = require('./src/services/crons');
const { queueReceiver } = require('./src/services/queue');
const { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_SERVER } = require('next/constants');
const isLocal = process.env.NEXT_PUBLIC_BUILD_ENV === 'local';
const isProduction = process.env.NEXT_PUBLIC_BUILD_ENV === 'production';

const nextConfig = {
reactStrictMode: true,
Expand All @@ -15,7 +15,7 @@ module.exports = async (phase, { defaultConfig }) => {
console.log('Quickshare is starting...');

let shouldRunQueue = phase === PHASE_DEVELOPMENT_SERVER || phase === PHASE_PRODUCTION_SERVER;
if (!isLocal) shouldRunQueue = false;
if (isProduction) shouldRunQueue = false;
if (process.env.NEXT_PUBLIC_SHORT_DOMAIN === 'true') shouldRunQueue = false;
if (shouldRunQueue) {
queueReceiver();
Expand Down
5 changes: 4 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,8 @@
"p2-head": "Shortening, tracking and sharing"
},
"signInLink": "Or click here to continue.",
"usePasswordToShortenedLink": "Also use this password for shortened URL"
"usePasswordToShortenedLink": "Also use this password for shortened URL",
"clicks": "Clicks",
"clicksByCountry": "Clicks by Country",
"otherCountry": "Others"
}
5 changes: 4 additions & 1 deletion public/locales/fr/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@
"card3-content": "Tous les accès aux liens raccourcis, y compris les bots et les utilisateurs, sont enregistrés. Les détails de suivi incluent la chaîne complète de l'agent utilisateur, l'adresse IP et les informations sur l'appareil."
},
"signInLink": "Ou cliquez ici pour continuer.",
"usePasswordToShortenedLink": "Utilisez également ce mot de passe pour l'URL raccourcie"
"usePasswordToShortenedLink": "Utilisez également ce mot de passe pour l'URL raccourcie",
"clicks": "Clics",
"clicksByCountry": "Clics par pays",
"otherCountry": "Autres"
}
11 changes: 7 additions & 4 deletions public/locales/hi/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,11 @@
"card2": "आपका डेटा सुरक्षित है",
"card2-content": "आपके मूल लिंक क्विकशेयर के माध्यम से एनोनाइमाइज़ किए जाते हैं। शॉर्ट URL सर्च इंजन परिणामों में नहीं दिखाई देंगे। आप लिंक ट्रैकिंग सेक्शन में अपने शॉर्ट URL को पासवर्ड प्रोटेक्ट भी कर सकते हैं।",
"card3": "लिंक ट्रैकिंग पारदर्शी है",
"card3-content": "बॉट्स और यूज़र्स सहित शॉर्ट URL तक सभी एक्सेस रिकॉर्ड किए जाते हैं। ट्रैकिंग डिटेल में पूरा यूज़र एजेंट स्ट्रिंग, आईपी एड्रेस और डिवाइस की जानकारी शामिल होती है।",
"signInLink": "या जारी रखने के लिए यहां क्लिक करें।",
"usePasswordToShortenedLink": "इस पासवर्ड का उपयोग शॉर्ट URL के लिए भी करें"
}
"card3-content": "बॉट्स और यूज़र्स सहित शॉर्ट URL तक सभी एक्सेस रिकॉर्ड किए जाते हैं। ट्रैकिंग डिटेल में पूरा यूज़र एजेंट स्ट्रिंग, आईपी एड्रेस और डिवाइस की जानकारी शामिल होती है।"
},
"signInLink": "या जारी रखने के लिए यहां क्लिक करें।",
"usePasswordToShortenedLink": "इस पासवर्ड का उपयोग शॉर्ट URL के लिए भी करें",
"clicks": "क्लिक्स",
"clicksByCountry": "देश के हिसाब से क्लिक्स",
"otherCountry": "अन्य"
}
5 changes: 4 additions & 1 deletion public/locales/ja/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@
"card3-content": "ボットやユーザーを含む、短縮URLへの全アクセスが記録されます。トラッキング情報にはユーザーエージェント文字列、IPアドレス、デバイス情報が全て含まれます。"
},
"signInLink": "またはここをクリックして続行。",
"usePasswordToShortenedLink": "このパスワードを短縮URLにも使用する"
"usePasswordToShortenedLink": "このパスワードを短縮URLにも使用する",
"clicks": "クリック数",
"clicksByCountry": "国別クリック数",
"otherCountry": "その他"
}
5 changes: 4 additions & 1 deletion public/locales/vi/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@
"card3-content": "Tất cả truy cập vào link rút gọn, kể cả bot hay người dùng đều được ghi lại. Thông tin theo dõi bao gồm đầy đủ User Agent, địa chỉ IP và thông tin thiết bị."
},
"signInLink": "Hoặc bạn có thể click vào đây để tiếp tục.",
"usePasswordToShortenedLink": "Áp dụng mật khẩu cho link rút gọn"
"usePasswordToShortenedLink": "Áp dụng mật khẩu cho link rút gọn",
"clicks": "Clicks",
"clicksByCountry": "Clicks theo quốc gia",
"otherCountry": "Khác"
}
5 changes: 4 additions & 1 deletion public/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@
"card3-content": "所有对缩短链接的访问,包括机器人和用户,都会被记录。跟踪信息包括完整的用户代理字符串、IP 地址和设备信息。"
},
"signInLink": "或者点击这里继续。",
"usePasswordToShortenedLink": "也为缩短网址使用这个密码"
"usePasswordToShortenedLink": "也为缩短网址使用这个密码",
"clicks": "点击次数",
"clicksByCountry": "各国点击量",
"otherCountry": "其他"
}
62 changes: 62 additions & 0 deletions src/components/atoms/BarChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Script from 'next/script';
import { useEffect, useState } from 'react';
import { Window } from 'types/constants';

interface Props {
label: any[];
value: (string | number | null)[][];
title?: string;
className?: string;
}

export const BarChart = (props: Props) => {
const { className, title, label, value } = props;
const [loaded, setLoaded] = useState(false);
const google = Window().google;

useEffect(() => {
if (!google) return;

google.charts.load('current', { packages: ['corechart', 'bar'] });
google.charts.setOnLoadCallback(drawBasic);

function drawBasic() {
var data = google.visualization.arrayToDataTable([label, ...value]);

var options = {
title,
backgroundColor: '#fcfcfd',
hAxis: {
minValue: 0,
},
fontName: 'Roboto Slab',
fontSize: 12,
legend: 'none',
};

var chart = new google.visualization.BarChart(document.getElementById('bar-div'));

chart.draw(data, options);

return () => {
chart.clearChart();
};
}
}, [loaded, google]);

if (value.length === 0) return null;

return (
<>
<Script
onLoad={() => {
setLoaded(true);
}}
type="text/javascript"
src="https://www.gstatic.com/charts/loader.js"
/>

<div id="bar-div" className={className}></div>
</>
);
};
63 changes: 63 additions & 0 deletions src/components/atoms/GeoChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Script from 'next/script';
import { useCallback, useEffect, useState } from 'react';
import { Window } from 'types/constants';

interface Props {
label: (string | number | null)[];
value: (string | number | null)[][];
className?: string;
}

export const GeoChart = (props: Props) => {
const { className, label, value } = props;
const [loaded, setLoaded] = useState(false);
const google = Window().google;
const id = 'regions_div';

const drawRegionsMap = useCallback(
function () {
const element = document.getElementById(id);

var data = google.visualization.arrayToDataTable([label, ...value]);

var options = {
legend: 'none',
backgroundColor: '#fcfcfd',
tooltip: {
textStyle: {
fontName: 'Roboto Slab',
fontSize: 12,
},
},
colorAxis: { minValue: 0, colors: ['#9d8ed1', '#6644de'] },
};

const chart = new google.visualization.GeoChart(element);

chart.draw(data, options);
},
[loaded, google],
);

useEffect(() => {
if (!google) return;
google.charts.load('current', {
packages: ['geochart'],
});
google.charts.setOnLoadCallback(drawRegionsMap);
}, [loaded, google]);

return (
<>
<Script
onLoad={() => {
setLoaded(true);
}}
type="text/javascript"
src="https://www.gstatic.com/charts/loader.js"
/>

<div id={id} className={className}></div>
</>
);
};
94 changes: 94 additions & 0 deletions src/components/screens/URLTracking/HistoryByCountry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import clsx from 'clsx';
import { BarChart } from 'components/atoms/BarChart';
import { GeoChart } from 'components/atoms/GeoChart';
import { useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import { useQuery } from 'react-query';
import { getStatsGeo } from 'requests';
import { HistoryGeoItem } from 'types/stats';
import { getCountryName } from 'utils/country';
import { useDimensionWindow } from 'utils/dom';
import { useTrans } from 'utils/i18next';
import { parseIntSafe } from 'utils/number';
import { QueryKey, strictRefetch } from 'utils/requests';

const color = '#7354e5';

interface Props {
hash: string;
className?: string;
}

export const HistoryByCountry = (props: Props) => {
const { hash } = props;
const { t, locale } = useTrans();
const [rerender, setRerender] = useState(0);
const { width } = useDimensionWindow();
const fetchStatsGeo = useQuery({
queryKey: QueryKey.STATS_GEO,
queryFn: async () => getStatsGeo({ hash }),
...strictRefetch,
onSuccess(data) {
if (data.history) setData(data.history);
},
onError(error) {
console.error(error);
toast.error(t('errorNoTracking'));
},
});

const [data, setData] = useState<HistoryGeoItem[]>(fetchStatsGeo.data?.history || []);

const chartDataGeo = useMemo(() => {
if (!data) return [];
return data.map((data) => [getCountryName(data.countryCode || '') || 'world', data._count.countryCode]);
}, [data]);

const chartDataBar = useMemo(() => {
if (!data) return [];
let total = data.map((data) => [
getCountryName(data.countryCode || '') || t('unknown'),
data._count.countryCode,
`color: ${color}`,
]);
if (total.length > 3) {
const others = total.slice(3);
total = [
...total.slice(0, 3),
others.reduce((prev, cur) => [
t('otherCountry'),
parseIntSafe(prev[1]) + parseIntSafe(cur[1]),
`color: ${color}`,
]),
];
}
return total;
}, [data]);

useEffect(() => {
setRerender((_) => _ + 1);
}, [width, locale]);

if (!fetchStatsGeo.data) return null;

return (
<div>
<div
className={clsx('flex h-full w-full flex-col items-center justify-center gap-4 lg:flex-row', props.className)}>
<GeoChart
key={`geo-${rerender}`}
label={['Country', t('totalClick')]}
value={chartDataGeo}
className="h-[240px] w-full sm:w-[400px] lg:h-[300px] lg:w-[500px]"
/>
<BarChart
key={`bar-${rerender}`}
label={['Country', t('totalClick'), { role: 'style' }]}
value={chartDataBar}
className="w-full sm:w-[400px] md:w-[600px]"
/>
</div>
{chartDataGeo.length === 0 && <p className="mt-4 text-center text-base text-gray-500">{t('noData')}</p>}
</div>
);
};
Loading

0 comments on commit 8597489

Please sign in to comment.