From b34be78798a92fcb02cea63a38bc4f84502e505d Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 19 Jan 2026 09:47:03 +0100 Subject: [PATCH] docs: Add AGPL-3.0 license and update README for open source release --- LICENSE | 17 +++++ README.md | 4 +- components/dashboard/Countries.tsx | 114 +++++++++++++++++++++++++++++ components/dashboard/TopPages.tsx | 40 ++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 components/dashboard/Countries.tsx create mode 100644 components/dashboard/TopPages.tsx diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bc0f19d --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2024-2026 Ciphera + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/README.md b/README.md index ce69191..e039df0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Analytics Frontend -[![License: Proprietary](https://img.shields.io/badge/License-Proprietary-red.svg)](#) +[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-green.svg)](https://www.gnu.org/licenses/agpl-3.0) [![Built with Next.js](https://img.shields.io/badge/Built%20with-Next.js-blue.svg?logo=next.js&logoColor=white)](https://nextjs.org/) Analytics Frontend is the dashboard interface for Ciphera Analytics. It provides a simple, intuitive interface for managing sites and viewing analytics data. @@ -73,4 +73,4 @@ The frontend follows the Ciphera design language: ## License -Proprietary - All rights reserved +AGPL-3.0 diff --git a/components/dashboard/Countries.tsx b/components/dashboard/Countries.tsx new file mode 100644 index 0000000..10ba72d --- /dev/null +++ b/components/dashboard/Countries.tsx @@ -0,0 +1,114 @@ +'use client' + +import { useState } from 'react' +import { formatNumber } from '@/lib/utils/format' +import * as Flags from 'country-flag-icons/react/3x2' +import WorldMap from './WorldMap' + +interface LocationProps { + countries: Array<{ country: string; pageviews: number }> + cities: Array<{ city: string; country: string; pageviews: number }> +} + +type Tab = 'countries' | 'cities' + +export default function Locations({ countries, cities }: LocationProps) { + const [activeTab, setActiveTab] = useState('countries') + + const getFlagComponent = (countryCode: string) => { + if (!countryCode || countryCode === 'Unknown') return null + // * The API returns 2-letter country codes (e.g. US, DE) + // * We cast it to the flag component name + const FlagComponent = (Flags as any)[countryCode] + return FlagComponent ? : null + } + + const getCountryName = (code: string) => { + if (!code || code === 'Unknown') return 'Unknown' + try { + const regionNames = new Intl.DisplayNames(['en'], { type: 'region' }) + return regionNames.of(code) || code + } catch (e) { + return code + } + } + + const renderContent = () => { + if (activeTab === 'countries') { + if (!countries || countries.length === 0) { + return

No data available

+ } + return ( +
+ +
+ {countries.map((country, index) => ( +
+
+ {getFlagComponent(country.country)} + {getCountryName(country.country)} +
+
+ {formatNumber(country.pageviews)} +
+
+ ))} +
+ ) + } + + if (activeTab === 'cities') { + if (!cities || cities.length === 0) { + return

No data available

+ } + return ( +
+ {cities.map((city, index) => ( +
+
+ {getFlagComponent(city.country)} + {city.city === 'Unknown' ? 'Unknown' : city.city} +
+
+ {formatNumber(city.pageviews)} +
+
+ ))} +
+ ) + } + } + + return ( +
+
+

+ Locations +

+
+ + +
+
+ {renderContent()} +
+ ) +} diff --git a/components/dashboard/TopPages.tsx b/components/dashboard/TopPages.tsx new file mode 100644 index 0000000..84c3e92 --- /dev/null +++ b/components/dashboard/TopPages.tsx @@ -0,0 +1,40 @@ +'use client' + +import { formatNumber } from '@/lib/utils/format' + +interface TopPagesProps { + pages: Array<{ path: string; pageviews: number }> +} + +export default function TopPages({ pages }: TopPagesProps) { + if (!pages || pages.length === 0) { + return ( +
+

+ Top Pages +

+

No data available

+
+ ) + } + + return ( +
+

+ Top Pages +

+
+ {pages.map((page, index) => ( +
+
+ {page.path} +
+
+ {formatNumber(page.pageviews)} +
+
+ ))} +
+
+ ) +}