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
-[](#)
+[](https://www.gnu.org/licenses/agpl-3.0)
[](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