diff --git a/manifest.json b/manifest.json index f52fb2f..f3d2c6f 100644 --- a/manifest.json +++ b/manifest.json @@ -10,6 +10,9 @@ "default_popup": "index.html", "default_title": "Kleo Network: Extension" }, + "side_panel": { + "default_path": "index.html" + }, "icons":{ "128" : "logo/128.png", "64": "logo/64.png", @@ -19,7 +22,8 @@ "permissions": [ "tabs", "history", - "storage" + "storage", + "sidePanel" ], "content_scripts": [ { @@ -34,4 +38,3 @@ } ] } - \ No newline at end of file diff --git a/src/App.css b/src/App.css index 393e380..f03b5e2 100644 --- a/src/App.css +++ b/src/App.css @@ -45,6 +45,28 @@ font-style: normal; } +html, +body { + margin: 0; + min-width: 400px; + background: #eaecf5; + font-family: 'inter', sans-serif; +} + +body { + overflow: hidden; +} + +button, +input { + font: inherit; +} + +.container { + width: 400px; + min-height: 640px; +} + .color-empty { fill: #ebedf0; /* Light gray, for empty tiles */ } diff --git a/src/App.test.tsx b/src/App.test.tsx index 2a68616..aa8620a 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import App from './App'; -test('renders learn react link', () => { +test('renders the Kleo account screen', () => { render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); + expect(screen.getByText(/create an account/i)).toBeInTheDocument(); }); diff --git a/src/App.tsx b/src/App.tsx index e0dfdc4..bc7b99d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,108 +1,8 @@ -import { ReactElement, useEffect, useState } from 'react'; -import { Route, Routes, useNavigate } from 'react-router-dom'; -import { UserData } from './common/interface'; -import Navbar from './navbar/Navbar'; -import useFetch from './common/hooks/useFetch'; -import { HomeComponent } from './pages/home/HomeComponent'; -import Processing from './pages/ProfileCards/Processing' +import { ReactElement } from 'react'; +import SidePanel from './pages/sidepanel/SidePanel'; function App(): ReactElement { - const emptyStringArray: string[] = []; - const [slug, setSlug] = useState(''); - const [isUserLoading, setIsUserLoading] = useState(false); - - /* -{ - "about": "", - "badges": [], - "content_tags": [], - "first_time_user": true, - "identity_tags": [], - "kleo_points": 52, - "last_attested": 0, - "last_cards_marked": 0, - "milestones": { - "data_owned": 23000, - "followed_on_twitter": true, - "referred_count": 0, - "tweet_activity_graph": false - }, - "name": "", - "pfp": "", - "pii_removed_count": 3, - "profile_metadata": {}, - "referee": null, - "referrals": [], - "settings": {}, - "slug": "0x9bdcAeb9443316BbA3998a600Cc30888846A1C45", - "stage": 1, - "total_data_quantity": 23000, - "verified": false -} - */ - const [user, setUser] = useState({ - badges: emptyStringArray, - content_tags: emptyStringArray, - first_time_user: false, - identity_tags: emptyStringArray, - kleo_points: 88, - data_quality: 87, - last_minted: Math.floor(Date.now() / 1000), - address: '', - verified: false, - total_data_quantity: 2, // this would be in MegaBytes, this is 34MB - }); - const navigate = useNavigate(); - - useEffect(() => { - chrome.storage.local.get('user', (storageData) => { - if (storageData.user) { - setSlug(storageData.user.id); - } - }); - - }, []); - - const GET_USER_DETAIL = 'user/get-user/{slug}'; - const { fetchData: fetchUserData } = useFetch(); - - function getUserDetails(slug: string) { - return GET_USER_DETAIL.replace('{slug}', slug); - } - - - useEffect(() => { - try { - setIsUserLoading(true); - fetchUserData(getUserDetails(slug), { - onSuccessfulFetch(data) { - setIsUserLoading(false); - if (data) { - setUser(data) - navigate('/home') - } - }, - }); - } catch (error) { - console.error('Error fetching data:', error); - } - }, [slug]); - - return ( -
-
-
- -
- {isUserLoading &&
} - {!isUserLoading && user && ( - - } /> - - )} -
-
- ); + return ; } export default App; diff --git a/src/pages/sidepanel/SidePanel.tsx b/src/pages/sidepanel/SidePanel.tsx new file mode 100644 index 0000000..d7ac222 --- /dev/null +++ b/src/pages/sidepanel/SidePanel.tsx @@ -0,0 +1,579 @@ +import { FormEvent, ReactElement, useEffect, useState } from 'react'; + +type TabKey = 'tasks' | 'wallet' | 'settings'; + +interface StoredAccount { + name: string; + createdAt: number; +} + +interface TaskCardData { + title: string; + author: string; + description: string; + used: string; + sponsor: string; + earn: string; +} + +interface TransactionData { + title: string; + date: string; + amount: string; + positive: boolean; +} + +const ACCOUNT_STORAGE_KEY = 'kleo-sidepanel-account'; +const kleoLogoPath = '../assets/images/kleoLogo.svg'; +const kleoTokenPath = '../assets/images/KleoToken.svg'; + +const taskCards: TaskCardData[] = [ + { + title: 'Amazon Buy Ps5', + author: 'cyg123', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut ullamco', + used: '10.2K times', + sponsor: 'Amazon', + earn: '+5.5K', + }, + { + title: 'Amazon Buy Ps5', + author: 'cyg123', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut ullamco', + used: '10.2K times', + sponsor: 'Amazon', + earn: '+5.5K', + }, + { + title: 'Amazon Buy Ps5', + author: 'cyg123', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut ullamco', + used: '10.2K times', + sponsor: 'Amazon', + earn: '+5.5K', + }, +]; + +const transactions: TransactionData[] = [ + { title: 'Spent on Amazon buy Ps5', date: '06 Dec 2024', amount: '- 5 XP', positive: false }, + { title: 'Spent on Amazon buy Ps5', date: '06 Dec 2024', amount: '- 5 XP', positive: false }, + { title: 'Spent on Amazon buy Ps5', date: '06 Dec 2024', amount: '- 5 XP', positive: false }, + { title: 'Transferred from wallet', date: '06 Dec 2024', amount: '+ 142 XP', positive: true }, + { title: 'Completed Task successfully', date: '06 Dec 2024', amount: '+ 142 XP', positive: true }, + { title: 'Spent on Amazon buy Ps5', date: '06 Dec 2024', amount: '- 140 XP', positive: false }, +]; + +const generalSettings = [ + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'text' }, + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'field' }, + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'toggle-on' }, + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'toggle-off' }, + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'text' }, +]; + +const advancedSettings = [ + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'copy' }, + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'text' }, + { title: 'Lorem Ipsum dolor', subtitle: 'Lorem ipsum dolor sit amet', type: 'text' }, +]; + +function readStoredAccount(callback: (account: StoredAccount | null) => void) { + if (typeof chrome !== 'undefined' && chrome.storage?.local) { + chrome.storage.local.get([ACCOUNT_STORAGE_KEY, 'user'], (result) => { + const savedAccount = result[ACCOUNT_STORAGE_KEY] as StoredAccount | undefined; + const legacyUser = result.user as { name?: string; id?: string; address?: string } | undefined; + + if (savedAccount?.name) { + callback(savedAccount); + return; + } + + if (legacyUser) { + callback({ + name: legacyUser.name || legacyUser.id || legacyUser.address || 'Kleo User', + createdAt: Date.now(), + }); + return; + } + + callback(null); + }); + return; + } + + const rawAccount = localStorage.getItem(ACCOUNT_STORAGE_KEY); + callback(rawAccount ? JSON.parse(rawAccount) : null); +} + +function saveStoredAccount(account: StoredAccount) { + if (typeof chrome !== 'undefined' && chrome.storage?.local) { + chrome.storage.local.set({ [ACCOUNT_STORAGE_KEY]: account }); + return; + } + + localStorage.setItem(ACCOUNT_STORAGE_KEY, JSON.stringify(account)); +} + +function LogoHeader(): ReactElement { + return ( +
+
+ Kleo + KLEO +
+
+ + 1784 + Kleo +
+
+ ); +} + +function AccountScreen({ onAccountReady }: { onAccountReady: (name: string) => void }): ReactElement { + const [name, setName] = useState('Alice Watson'); + const [privateKey, setPrivateKey] = useState(''); + + const handleCreateAccount = (event: FormEvent) => { + event.preventDefault(); + onAccountReady(name.trim() || 'Kleo User'); + }; + + const handleImportAccount = (event: FormEvent) => { + event.preventDefault(); + onAccountReady(name.trim() || 'Imported Account'); + setPrivateKey(''); + }; + + return ( +
+
+ +
+ +

Create an Account

+

Enter your details to proceed further

+ +
+ + + +
+ +
+ + Or + +
+ +
+

Import your Account

+

Already have an account? Import by using your private key

+ + +
+
+ ); +} + +function TasksScreen(): ReactElement { + return ( +
+

Your Personal AI Assistant

+

+ Select your task and sit back as your personal ai assistant take over. +

+ + +
+ {taskCards.map((task, index) => ( + + ))} +
+
+ ); +} + +function TaskCard({ task }: { task: TaskCardData }): ReactElement { + return ( +
+
+
+
+ a +
+
+

{task.title}

+

Created by {task.author}

+
+
+
+ + 3.5 Rating +
+
+ +

{task.description}

+ +
+ } label="Used:" value={task.used} /> + a} label="Sponsored:" value={task.sponsor} /> + } label="Earn:" value={task.earn} /> +
+ + +
+ ); +} + +function WalletScreen(): ReactElement { + return ( +
+
+

Your balance is

+
+ + 1,454 +
+

XP Points

+ +
+ + +
+ + +
+ +
+

Previous Transactions

+

+ Select your task and sit back as your personal ai assistant take over. +

+
+ {transactions.map((transaction, index) => ( + + ))} +
+
+
+ ); +} + +function WalletStat({ label, value }: { label: string; value: string }): ReactElement { + return ( +
+
+ +
+
+

{label}

+

{value}

+
+
+ ); +} + +function TransactionItem({ transaction }: { transaction: TransactionData }): ReactElement { + return ( +
+
+

{transaction.title}

+

{transaction.date}

+
+ + {transaction.amount} + +
+ ); +} + +function SettingsScreen(): ReactElement { + return ( +
+

General Setting

+
+
+ V +
+ {generalSettings.map((setting, index) => ( + + ))} +
+ +

Advanced Settings

+

Adviced not to change these

+
+ {advancedSettings.map((setting, index) => ( + + ))} +
+
+ ); +} + +function SettingsRow({ + setting, + isLast, +}: { + setting: { title: string; subtitle: string; type: string }; + isLast: boolean; +}): ReactElement { + return ( +
+
+

{setting.title}

+ {setting.type === 'field' ? ( +
+ {setting.subtitle} +
+ ) : ( +

{setting.subtitle}

+ )} +
+ + {setting.type === 'toggle-on' && } + {setting.type === 'toggle-off' && } + {setting.type === 'copy' && } +
+ ); +} + +function Toggle({ enabled = false }: { enabled?: boolean }): ReactElement { + return ( + + + + ); +} + +function BottomNav({ activeTab, onChange }: { activeTab: TabKey; onChange: (tab: TabKey) => void }): ReactElement { + const items: Array<{ key: TabKey; label: string; icon: ReactElement }> = [ + { key: 'tasks', label: 'Tasks', icon: }, + { key: 'wallet', label: 'Wallet', icon: }, + { key: 'settings', label: 'Settings', icon: }, + ]; + + return ( + + ); +} + +function TaskChip({ icon, label, value }: { icon: ReactElement; label: string; value: string }): ReactElement { + return ( + + {icon} + {label} + {value} + + ); +} + +function SidePanel(): ReactElement { + const [account, setAccount] = useState(null); + const [isAccountLoaded, setIsAccountLoaded] = useState(false); + const [activeTab, setActiveTab] = useState('tasks'); + + useEffect(() => { + readStoredAccount((storedAccount) => { + setAccount(storedAccount); + setIsAccountLoaded(true); + }); + }, []); + + const handleAccountReady = (name: string) => { + const nextAccount = { name, createdAt: Date.now() }; + saveStoredAccount(nextAccount); + setAccount(nextAccount); + }; + + if (!isAccountLoaded) { + return ( +
+
+
+ ); + } + + if (!account) { + return ( +
+ +
+ ); + } + + return ( +
+
+ + {activeTab === 'tasks' && } + {activeTab === 'wallet' && } + {activeTab === 'settings' && } +
+ +
+ ); +} + +function UserIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function KeyIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function SearchIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function StarIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function CoinIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function PlayIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function SyncIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function TasksIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function WalletIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function SettingsIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +function CopyIcon({ className }: { className?: string }): ReactElement { + return ( + + ); +} + +export default SidePanel;