first: commit
22
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: "18.x"
|
||||||
|
- uses: c-hive/gha-yarn-cache@v2
|
||||||
|
|
||||||
|
- run: yarn install
|
||||||
|
- run: yarn build
|
||||||
|
- run: yarn run check
|
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
releases
|
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode"]
|
||||||
|
}
|
8
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": ["manifest.json"],
|
||||||
|
"url": "https://json.schemastore.org/chrome-manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
6
CHANGELOG.MD
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
## [Version 1.0.1]
|
||||||
|
|
||||||
|
- first release
|
||||||
|
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Andrei O.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
15
PRIVACY_POLICY.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# 𝐏𝐫𝐢𝐯𝐚𝐜𝐲 𝐏𝐨𝐥𝐢𝐜𝐲:
|
||||||
|
|
||||||
|
## Privacy Points:
|
||||||
|
|
||||||
|
- This extension does not collect any data form your device.
|
||||||
|
- All storage uses chrome.storage.local
|
||||||
|
- This extension does not use external scripts, everything is packed into the extension.
|
||||||
|
- This extension uses the manifest V3 which does not allow any third party scripts to be injected.
|
||||||
|
- This extension is completely open source, the source is available on Github - [https://github.com/andrei0x309/yup-live-chrome-extension](https://github.com/andrei0x309/yup-live-chrome-extension).
|
||||||
|
- Extension has no production dependecies
|
||||||
|
- Only third party APIs used is YUP API and CoinGecko for the price of YUP.
|
||||||
|
|
||||||
|
### 𝐂𝐨𝐧𝐭𝐚𝐜𝐭:
|
||||||
|
|
||||||
|
Discord: andrei0x309#6562
|
47
README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# YUP Live Chrome Extension
|
||||||
|
|
||||||
|
> Svelte open source helper extension for YUP protocol, a decentralized social media platform.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Extremely light-weight size less than 1080p picture.
|
||||||
|
- No external dependencies.
|
||||||
|
- No production dependencies.
|
||||||
|
- No external components.
|
||||||
|
- Made with Svelte & WindiCSS & TypeScript & Vite.
|
||||||
|
- Everything loaded asynchronously.
|
||||||
|
- Inhouse router system.
|
||||||
|
- Automatically detects and disables other YUP chrome extensions.
|
||||||
|
|
||||||
|
## Pages
|
||||||
|
|
||||||
|
- Main page - profile, balance, and website rateing system.
|
||||||
|
- Optional Notifications page - shows notifications.
|
||||||
|
- Settings page - change settings.
|
||||||
|
- Login page - login or create account using YUP Live website.
|
||||||
|
- Usage page - show how many likes you have left and a counter for the next refill.
|
||||||
|
- Info page - show info about the extension.
|
||||||
|
|
||||||
|
## Additional features
|
||||||
|
|
||||||
|
- Chrome notification at login.
|
||||||
|
- Light and dark theme.
|
||||||
|
- Optinally enable or disable notification system.
|
||||||
|
- Opt-in notification for reward.
|
||||||
|
- Opt-in notification for refill.
|
||||||
|
- Optinally enable a overlay for all websites to easely rate on mobile.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![Screenshot 1](/misc/screen_1.png?raw=true "Screenshot 1")
|
||||||
|
![Screenshot 2](/misc/screen_2.png?raw=true "Screenshot 2")
|
||||||
|
|
||||||
|
## Possible ways to contribute
|
||||||
|
|
||||||
|
- Install and use the extension.
|
||||||
|
- Fork the repository.
|
||||||
|
- Open a pull request.
|
||||||
|
- Open an issue.
|
||||||
|
- Rate the extension.
|
||||||
|
- Star the repository.
|
||||||
|
- Share the extension.
|
58
manifest.json
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "yup live",
|
||||||
|
"description": "Light alternative extension for yup protocol",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"manifest_version": 3,
|
||||||
|
"icons": {
|
||||||
|
"16": "src/assets/icons/yup_ext_16.png",
|
||||||
|
"32": "src/assets/icons/yup_ext_32.png",
|
||||||
|
"48": "src/assets/icons/yup_ext_48.png",
|
||||||
|
"64": "src/assets/icons/yup_ext_64.png",
|
||||||
|
"128": "src/assets/icons/yup_ext_128.png",
|
||||||
|
"258": "src/assets/icons/yup_ext_256.png"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "src/background/index.ts"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_popup": "src/popup/popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "src/assets/icons/yup_ext_16.png",
|
||||||
|
"32": "src/assets/icons/yup_ext_32.png",
|
||||||
|
"48": "src/assets/icons/yup_ext_48.png",
|
||||||
|
"64": "src/assets/icons/yup_ext_64.png",
|
||||||
|
"128": "src/assets/icons/yup_ext_128.png",
|
||||||
|
"258": "src/assets/icons/yup_ext_256.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
"all_frames": true,
|
||||||
|
"run_at": "document_start",
|
||||||
|
"js": ["src/client/content.ts"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web_accessible_resources": [{
|
||||||
|
"resources": [
|
||||||
|
"src/client/inject.js",
|
||||||
|
"src/assets/icons/yup_ext_32.png",
|
||||||
|
"src/assets/icons/yup_ext_48.png",
|
||||||
|
"src/assets/icons/yup_ext_64.png",
|
||||||
|
"src/assets/icons/yup_ext_128.png",
|
||||||
|
"src/assets/res/reward_optimized.png"
|
||||||
|
],
|
||||||
|
"matches": ["<all_urls>"]
|
||||||
|
}],
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"management",
|
||||||
|
"notifications",
|
||||||
|
"tabs",
|
||||||
|
"clipboardWrite",
|
||||||
|
"alarms"
|
||||||
|
]
|
||||||
|
}
|
BIN
misc/screen_1.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
misc/screen_2.png
Normal file
After Width: | Height: | Size: 15 KiB |
39
package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "yup-live-browser-extension",
|
||||||
|
"description": "Yup Live Browser Extension",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"author": "andrei0x309",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/nekitcorp/chrome-extension-svelte-typescript-boilerplate.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"inject": "tsc --out src/client/inject.js src/client/inject.ts",
|
||||||
|
"build": "yarn inject && vite build",
|
||||||
|
"upgrade": "yarn-upgrade-all",
|
||||||
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"release": "yarn config set version-tag-prefix yup-live-browser-extension@v && yarn config set version-git-message 'yup-live-browser-extension@v%s' && yarn version --patch && yarn postversion",
|
||||||
|
"postversion": "git push",
|
||||||
|
"pub": "yarn build && yarn release && ts-node --esm ./scripts/create-release.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@crxjs/vite-plugin": "^1.0.14",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
||||||
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
|
"@types/chrome": "^0.0.212",
|
||||||
|
"archiver": "^5.3.1",
|
||||||
|
"sass": "^1.58.0",
|
||||||
|
"svelte": "^3.55.1",
|
||||||
|
"svelte-check": "^3.0.3",
|
||||||
|
"svelte-preprocess": "^5.0.1",
|
||||||
|
"svelte-windicss-preprocess": "~4.1.0",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tslib": "^2.5.0",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"vite": "^4.1.1",
|
||||||
|
"yarn-upgrade-all": "^0.7.2"
|
||||||
|
}
|
||||||
|
}
|
69
scripts/create-release.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
const pFs = import('fs')
|
||||||
|
const pCps = import ('child_process')
|
||||||
|
|
||||||
|
async function ghRelease(changes: string[]) {
|
||||||
|
const fs = (await pFs).default
|
||||||
|
|
||||||
|
if (!fs.existsSync('releases')){
|
||||||
|
fs.mkdirSync('releases');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync('package.json').toString());
|
||||||
|
|
||||||
|
const archiver = (await import('archiver')).default
|
||||||
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||||
|
const dirPipes = ['dist'];
|
||||||
|
|
||||||
|
const filePipes = ['LICENSE', 'README.md', 'PRIVACY_POLICY.md'];
|
||||||
|
const outputPath = `releases/${pkg.version}.zip`;
|
||||||
|
const outputZip = fs.createWriteStream(outputPath);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
let arch = archive;
|
||||||
|
dirPipes.forEach((dir) => {
|
||||||
|
arch = arch.directory(dir, false);
|
||||||
|
});
|
||||||
|
filePipes.forEach((file) => {
|
||||||
|
arch = arch.file(file, { name: file });
|
||||||
|
});
|
||||||
|
arch.on('error', (err: unknown) => reject(err)).pipe(outputZip);
|
||||||
|
|
||||||
|
outputZip.on('close', () => resolve(true));
|
||||||
|
arch.finalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
const changeLogPath = `releases/${pkg.version}.changelog.md`;
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
changeLogPath,
|
||||||
|
`# ${pkg.version} \n
|
||||||
|
${changes.reduce((acc: string, change: string) => {
|
||||||
|
return acc + `- ${change}\n`;
|
||||||
|
}, '')}`,
|
||||||
|
);
|
||||||
|
const cps = (await pCps)
|
||||||
|
console.log(
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const p = cps.spawn('gh', ['release', 'create', `v${pkg.version}`, `./${outputPath}`, '-F', `./${changeLogPath}`], {
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
// const p = spawn('pwd');
|
||||||
|
let result = '';
|
||||||
|
p.stdout.on('data', (data) => (result += data.toString()));
|
||||||
|
p.stderr.on('data', (data) => (result += data.toString()));
|
||||||
|
p.on('close', () => {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
if (!process.argv[2]) {
|
||||||
|
console.log('No changes provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const changes = process.argv[2].split(',');
|
||||||
|
await ghRelease(changes);
|
||||||
|
console.log('Release created', changes);
|
||||||
|
})();
|
BIN
src/assets/fonts/55xoey1sJNPjPiv1ZZZrxK110b3wKg.woff2
Normal file
BIN
src/assets/icons/yup-icon.ext_2.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
src/assets/icons/yup_ext_128.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/icons/yup_ext_16.png
Normal file
After Width: | Height: | Size: 934 B |
BIN
src/assets/icons/yup_ext_256.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
src/assets/icons/yup_ext_32.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
src/assets/icons/yup_ext_48.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/icons/yup_ext_64.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
src/assets/res/reward_optimized.png
Normal file
After Width: | Height: | Size: 12 KiB |
144
src/background/index.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { SEND_AUTH_NOTIF } from '@/constants/messeges';
|
||||||
|
import { initStorage } from '@/utils/storage'
|
||||||
|
import { getStore, setProfile, setNotifStorageNotifs, setSettings, getNotifStorage, setNotifStorageLastRewardNotif } from '@/utils/storage'
|
||||||
|
import type { Notification } from '@/utils/types';
|
||||||
|
import { API_BASE } from '@/constants/config';
|
||||||
|
import { getNotifications } from '@/utils/notifications';
|
||||||
|
import { setBadge } from '@/utils/chrome-misc'
|
||||||
|
import { closeTo, getTimeRemaining } from '@/utils/time';
|
||||||
|
import { getActionUsage } from '@/utils/user';
|
||||||
|
// Disable conflict with yup extension
|
||||||
|
const yupExtensionId = 'nhmeoaahigiljjdkoagafdccikgojjoi'
|
||||||
|
|
||||||
|
console.info('Service worker started')
|
||||||
|
|
||||||
|
const alarmHandler = async () => {
|
||||||
|
const store = await getStore()
|
||||||
|
const requests = {} as Record<string, Promise<Response | Notification[]>>
|
||||||
|
if (store?.user?.auth?.authToken) {
|
||||||
|
requests.profile = fetch(`${API_BASE}/web3-profiles/` + store.user.auth.address)
|
||||||
|
if (store?.settings.notificationsEnabled) {
|
||||||
|
requests.notifications = getNotifications({
|
||||||
|
type: 'all',
|
||||||
|
limit: '15',
|
||||||
|
skip: '0',
|
||||||
|
userId: store.user.auth.userId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
requests.coinGecko = fetch('https://api.coingecko.com/api/v3/simple/price?ids=yup&vs_currencies=usd')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const profile = await requests.profile as Response
|
||||||
|
const profileJson = await profile.json()
|
||||||
|
setProfile(profileJson).catch(console.error)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching profile', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const coinGecko = await requests.coinGecko as Response
|
||||||
|
const coinGeckoJson = await coinGecko.json()
|
||||||
|
const coinGeckoPrice = coinGeckoJson.yup.usd
|
||||||
|
const store = await getStore()
|
||||||
|
if (store.settings.coinGeckoPrice !== coinGeckoPrice) {
|
||||||
|
await chrome.storage.local.set({ store: { ...store, settings: { ...store.settings, coinGeckoPrice } } })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching coinGecko', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store?.settings.notificationsEnabled) {
|
||||||
|
try {
|
||||||
|
const notifications = await requests.notifications as Notification[]
|
||||||
|
const notSeen = notifications.reverse().filter(notif => !notif.seen)
|
||||||
|
const updateSettings = {} as Record<string, boolean>
|
||||||
|
if (notSeen.length > 0) {
|
||||||
|
setBadge(String(notSeen.length))
|
||||||
|
setNotifStorageNotifs(notSeen).catch(console.error)
|
||||||
|
updateSettings.hasNewNotifications = true
|
||||||
|
} else {
|
||||||
|
setBadge('')
|
||||||
|
updateSettings.hasNewNotifications = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store.settings?.chromeNotifWhenReward && notSeen.some(notif => notif.action === 'reward')) {
|
||||||
|
const rewardNotif = notSeen.find(notif => notif.action === 'reward')
|
||||||
|
if (rewardNotif) {
|
||||||
|
const storeReward = (await getNotifStorage()).lastRewardNotif
|
||||||
|
if (!storeReward || (storeReward.id !== rewardNotif._id &&
|
||||||
|
!closeTo(new Date(storeReward.createdAt), new Date(rewardNotif.createdAt), 2e4))) {
|
||||||
|
{
|
||||||
|
setNotifStorageLastRewardNotif({ createdAt: rewardNotif.createdAt, id: rewardNotif._id }).then(() => {
|
||||||
|
chrome.notifications.create({
|
||||||
|
type: 'basic',
|
||||||
|
iconUrl: chrome.runtime.getURL('src/assets/icons/yup_ext_128.png'),
|
||||||
|
title: 'Yup Live Extension',
|
||||||
|
message: `You have been alocated a future reward of ${rewardNotif.quantity} YUP`,
|
||||||
|
})
|
||||||
|
}).catch(console.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store.settings?.chromeNotifWhenAbleToVote) {
|
||||||
|
const lastReset = (await getActionUsage(store?.user?.auth?.userId))?.data?.lastReset
|
||||||
|
if (lastReset) {
|
||||||
|
const isReset = getTimeRemaining(lastReset).total <= 0;
|
||||||
|
if (!closeTo(new Date(lastReset), new Date(store?.settings?.refilNotifTimestamp), 3.6e6) && isReset) {
|
||||||
|
updateSettings.refilNotifTimestamp = lastReset
|
||||||
|
chrome.notifications.create({
|
||||||
|
type: 'basic',
|
||||||
|
iconUrl: chrome.runtime.getURL('src/assets/icons/yup_ext_128.png'),
|
||||||
|
title: 'Yup Live Extension',
|
||||||
|
message: `You can curate now again`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSettings(updateSettings).catch(console.error)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching notifications', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.alarms.create(
|
||||||
|
'alarm',
|
||||||
|
{
|
||||||
|
periodInMinutes: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
chrome.alarms.onAlarm.addListener(alarmHandler)
|
||||||
|
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
initStorage()
|
||||||
|
chrome.management.setEnabled(yupExtensionId, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.runtime.onStartup.addListener(() => {
|
||||||
|
initStorage()
|
||||||
|
chrome.management.setEnabled(yupExtensionId, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
|
||||||
|
try {
|
||||||
|
if (request.type === SEND_AUTH_NOTIF) {
|
||||||
|
chrome.notifications.create({
|
||||||
|
type: 'basic',
|
||||||
|
iconUrl: chrome.runtime.getURL('src/assets/icons/yup_ext_128.png'),
|
||||||
|
title: 'Yup Live Extension',
|
||||||
|
message: 'You have been logged in.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in message listener', error)
|
||||||
|
sendResponse({ error })
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
51
src/client/content.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
(() =>{
|
||||||
|
try {
|
||||||
|
const container = document.documentElement;
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.setAttribute('async', "false")
|
||||||
|
script.setAttribute('fetchpriority', "high")
|
||||||
|
script.src = chrome.runtime.getURL('src/client/inject.js')
|
||||||
|
container.prepend(script)
|
||||||
|
script.addEventListener('load', () => { container.removeChild(script) } )
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Yup Live Extension inject failed.', error);
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
import { SEND_VOTE, SET_AUTH } from '@/constants/messeges'
|
||||||
|
import { setAuth } from '@/utils/storage'
|
||||||
|
|
||||||
|
const allowedEvents = [SEND_VOTE, SET_AUTH]
|
||||||
|
|
||||||
|
window.addEventListener("message", (event) => {
|
||||||
|
if (event.source != window)
|
||||||
|
return;
|
||||||
|
if(allowedEvents.includes(event?.data?.type ?? '')){
|
||||||
|
console.log('Yup Live Extension received message:', event.data);
|
||||||
|
switch (event.data.type) {
|
||||||
|
case SEND_VOTE:
|
||||||
|
console.log('SEND_VOTE', event.data.payload)
|
||||||
|
break;
|
||||||
|
case SET_AUTH:
|
||||||
|
console.log('SET_AUTH', event.data.payload)
|
||||||
|
setAuth(event.data.payload).catch(console.error)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
import Overlay from '@/overlay/Overlay.svelte'
|
||||||
|
|
||||||
|
import('@/utils/storage').then(({ getStore }) => {
|
||||||
|
getStore().then(async (store) => {
|
||||||
|
if (store.settings.injectEmbed) {
|
||||||
|
setTimeout(() => {
|
||||||
|
//@ts-ignore
|
||||||
|
new Overlay({ target: document.body });
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
42
src/client/inject.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
var SEND_VOTE = 'SEND_VOTE';
|
||||||
|
var SET_AUTH = 'SET_AUTH';
|
||||||
|
var WebCommunicator = /** @class */ (function () {
|
||||||
|
function WebCommunicator(injectAuthMethod) {
|
||||||
|
if (injectAuthMethod === void 0) { injectAuthMethod = false; }
|
||||||
|
var _this = this;
|
||||||
|
this._send = function (data) {
|
||||||
|
window.postMessage(data, "*");
|
||||||
|
};
|
||||||
|
this.submitVote = function (vote) {
|
||||||
|
return _this._send({
|
||||||
|
type: SEND_VOTE,
|
||||||
|
payload: vote
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.setAuth = function (authData) {
|
||||||
|
return _this._send({
|
||||||
|
type: SET_AUTH,
|
||||||
|
payload: authData
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (injectAuthMethod) {
|
||||||
|
;
|
||||||
|
window.yupSetAuth = this.setAuth;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
;
|
||||||
|
window.yupSetAuth = function () { return Promise.resolve(null); };
|
||||||
|
}
|
||||||
|
;
|
||||||
|
window.yupSubmitVote = this.submitVote;
|
||||||
|
}
|
||||||
|
return WebCommunicator;
|
||||||
|
}());
|
||||||
|
var allowRegex = /^((http:|https:))?([/][/])?(www.)?[a-zA-Z\-_0-9]{0,}\.?[a-zA-Z\-_0-9]{0,}(yup.info.gf|yup-live.pages.dev|.yup.io|yup-team.vercel.app|localhost\/|localhost:)(.*)/gm;
|
||||||
|
var isAllowed = allowRegex.test(window.location.href);
|
||||||
|
if (isAllowed) {
|
||||||
|
new WebCommunicator(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new WebCommunicator();
|
||||||
|
}
|
43
src/client/inject.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const SEND_VOTE = 'SEND_VOTE'
|
||||||
|
const SET_AUTH = 'SET_AUTH'
|
||||||
|
|
||||||
|
class WebCommunicator {
|
||||||
|
_send = (data) => {
|
||||||
|
window.postMessage(data, "*")
|
||||||
|
}
|
||||||
|
|
||||||
|
submitVote = (vote) => {
|
||||||
|
return this._send({
|
||||||
|
type: SEND_VOTE,
|
||||||
|
payload: vote
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setAuth = (authData) => {
|
||||||
|
return this._send({
|
||||||
|
type: SET_AUTH,
|
||||||
|
payload: authData
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor (injectAuthMethod = false) {
|
||||||
|
if (injectAuthMethod) {
|
||||||
|
;(<any>window).yupSetAuth = this.setAuth
|
||||||
|
} else {
|
||||||
|
;(<any>window).yupSetAuth = () => Promise.resolve(null)
|
||||||
|
}
|
||||||
|
;(<any>window).yupSubmitVote = this.submitVote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowRegex = /^((http:|https:))?([/][/])?(www.)?[a-zA-Z\-_0-9]{0,}\.?[a-zA-Z\-_0-9]{0,}(yup.info.gf|yup-live.pages.dev|.yup.io|yup-team.vercel.app|localhost\/|localhost:)(.*)/gm
|
||||||
|
const isAllowed = allowRegex.test(window.location.href)
|
||||||
|
|
||||||
|
if(isAllowed) {
|
||||||
|
new WebCommunicator(true)
|
||||||
|
} else {
|
||||||
|
new WebCommunicator()
|
||||||
|
}
|
||||||
|
|
91
src/components/Alert.svelte
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from 'svelte/transition'
|
||||||
|
import type { FlyParams } from 'svelte/transition'
|
||||||
|
|
||||||
|
let hidden = true
|
||||||
|
let timer: undefined | number | string = undefined
|
||||||
|
let alertTimeout = 5000
|
||||||
|
let alretType = 'success'
|
||||||
|
let alertMsg = ''
|
||||||
|
|
||||||
|
export const show = (message: string, type: string = 'success', timeout: number = 5000) => {
|
||||||
|
hidden = false
|
||||||
|
alertMsg = message
|
||||||
|
alretType = type
|
||||||
|
alertTimeout = timeout
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
hidden = true
|
||||||
|
}, alertTimeout) as unknown as number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const close = () => {
|
||||||
|
hidden = true
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tryFly = (node: Element, params?: FlyParams) => {
|
||||||
|
try{
|
||||||
|
return fly(node, params)
|
||||||
|
}catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{#if !hidden}
|
||||||
|
<div transition:tryFly="{{ y: 200, duration: 500 }}" class="shadow-md flex flex-row rounded-lg alertCmp">
|
||||||
|
<div
|
||||||
|
class="{`${
|
||||||
|
alretType === 'warning' ? 'yellow' : alretType === 'error' ? 'red' : 'green'
|
||||||
|
} inline-block rounded-lg p-1 mr-1`}"
|
||||||
|
></div>
|
||||||
|
<p class="p-1 flex items-center">{ alertMsg }</p>
|
||||||
|
<span class="h-5 w-5 text-gray-500 inline-block p-1" on:click={() => close()} aria-hidden="true">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 absolute"
|
||||||
|
style="right: 0.4rem; top: 0.4rem"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.alertCmp {
|
||||||
|
background-color: #0e0e0efc;
|
||||||
|
box-shadow: 0 1px 3px #0000001a, 0 1px 2px #0000000f;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
width: 230px;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
border: 1px solid rgba(0,0,0,.1);
|
||||||
|
color: #fff;
|
||||||
|
font-size: .675rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: .025em;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
bottom: 0.8rem;
|
||||||
|
}
|
||||||
|
.green {
|
||||||
|
background-color: #48bb78;
|
||||||
|
}
|
||||||
|
.yellow {
|
||||||
|
background-color: #f6e05e;
|
||||||
|
}
|
||||||
|
.red {
|
||||||
|
background-color: #e53e3e;
|
||||||
|
}
|
||||||
|
</style>
|
43
src/components/ImgLoader.svelte
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { isUrlInvalid } from '@/utils/misc';
|
||||||
|
|
||||||
|
export let source
|
||||||
|
|
||||||
|
let loading = true
|
||||||
|
let loaded = false
|
||||||
|
let error = false
|
||||||
|
|
||||||
|
export const onLoad = () => {
|
||||||
|
loading = false
|
||||||
|
loaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onError = () => {
|
||||||
|
loading = false
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if(isUrlInvalid(source)) {
|
||||||
|
loading = false
|
||||||
|
error = true
|
||||||
|
} else {
|
||||||
|
loading = true
|
||||||
|
error = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="inline-flex {`${loading ? 'animate-pulse' :''}`}">
|
||||||
|
{#if loading || loaded}
|
||||||
|
<slot name="img">
|
||||||
|
</slot>
|
||||||
|
{:else if error}
|
||||||
|
<slot name="error">
|
||||||
|
</slot>
|
||||||
|
{/if}
|
||||||
|
<slot name="after">
|
||||||
|
</slot>
|
||||||
|
</div>
|
93
src/components/Notification.svelte
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { timeSince } from '@/utils/time';
|
||||||
|
import ImgLoader from './ImgLoader.svelte';
|
||||||
|
import type { Notification } from '@/utils/types';
|
||||||
|
import { mainStore } from '@/utils/store';
|
||||||
|
import { chromeUrl } from '@/utils/chrome-misc';
|
||||||
|
import { extrenalNavigate } from "@/utils/chrome-misc";
|
||||||
|
|
||||||
|
let loader
|
||||||
|
|
||||||
|
export let notif: Notification
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if notif.action === 'vote'}
|
||||||
|
{@const url = notif.post.url }
|
||||||
|
{@const length = url.length}
|
||||||
|
{@const shortUrl = url.slice(0, 10) + '...' + url.slice(length - 10, length) }
|
||||||
|
{@const finalUrl = length > 24 ? shortUrl : url }
|
||||||
|
<div class="flex flex-row notifBody">
|
||||||
|
<ImgLoader bind:this={loader} source="{notif.image} ">
|
||||||
|
<img class="notificationImage" on:load="{() => loader.onLoad()}" on:error={() => loader.onError()} style="{ $mainStore.settings.theme === 'light'? 'filter: invert(0.9);' : '' }" slot="img" src="{notif.image}" alt="preview">
|
||||||
|
<svg class="notificationImage" style="{ $mainStore.settings.theme === 'light'? 'filter: invert(0.9);' : '' }" slot="error" viewBox="0 0 512 512"><g><polygon points="40,38.999 40,468.998 215,293.998 270,348.998 360,258.998 470,358.998 470,38.999 " style="fill:#EFF3F6;"/>
|
||||||
|
<g><circle cx="150" cy="158.999" r="40" style="fill:#FCD884;"/></g><g><polygon points="470,358.998 470,468.998 385,468.998 385,463.998 270,348.998 360,258.998 " style="fill:#70993F;"/></g><g><polygon points="385,463.998 385,468.998 40,468.998 215,293.998 270,348.998" style="fill:#80AF52;"/></g></g><g/></svg>
|
||||||
|
</ImgLoader>
|
||||||
|
<div class="ml-4 text-left" style="width: 97%">
|
||||||
|
<p class="text-xs text-gray-200 my-0 mt-1">
|
||||||
|
{#if notif.like}
|
||||||
|
<svg class="w-4 like-dsilike" viewBox="0 0 24 24">
|
||||||
|
<path fill="#fff" d="M12,3.172L5.586,9.586c-0.781,0.781-0.781,2.047,0,2.828s2.047,0.781,2.828,0L10,10.828v7.242c0,1.104,0.895,2,2,2 c1.104,0,2-0.896,2-2v-7.242l1.586,1.586C15.977,12.805,16.488,13,17,13s1.023-0.195,1.414-0.586c0.781-0.781,0.781-2.047,0-2.828 L12,3.172z"/>
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg class="w-4 down like-dsilike" viewBox="0 0 24 24">
|
||||||
|
<path fill="#fff" d="M12,3.172L5.586,9.586c-0.781,0.781-0.781,2.047,0,2.828s2.047,0.781,2.828,0L10,10.828v7.242c0,1.104,0.895,2,2,2 c1.104,0,2-0.896,2-2v-7.242l1.586,1.586C15.977,12.805,16.488,13,17,13s1.023-0.195,1.414-0.586c0.781-0.781,0.781-2.047,0-2.828 L12,3.172z"/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
by {notif.voter.length > 12 ? notif.voter.slice(0, 12) + '...' : notif.voter}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-200 my-0 mt-1">
|
||||||
|
<span on:click={() => extrenalNavigate(`https://yup-live.pages.dev/post/${notif.postid}`)}
|
||||||
|
aria-hidden
|
||||||
|
class="text-blue-200 interactive-svg">{finalUrl}</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-200 my-0 my-1 text-right mr-2">
|
||||||
|
<svg class="w-3 inline" viewBox="0 0 20 20" ><path fill="#fff" d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-1-7.59V4h2v5.59l3.95 3.95-1.41 1.41L9 10.41z"/></svg>
|
||||||
|
{timeSince(new Date(notif.createdAt))}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else if notif.action === 'reward'}
|
||||||
|
<div class="flex flex-row notifBody">
|
||||||
|
<img class="notificationImage" src="{chromeUrl('src/assets/res/reward_optimized.png')}" alt="reward">
|
||||||
|
<div class="ml-4 text-left" style="width: 97%">
|
||||||
|
<p class="text-xs text-gray-200 my-0 mt-1">You were alocated a future reward of {notif?.quantity ?? 'unknown'} amount of YUP.
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500 my-0 my-1 text-right mr-2">
|
||||||
|
<svg class="w-3 inline" viewBox="0 0 20 20" ><path fill="#fff" d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-1-7.59V4h2v5.59l3.95 3.95-1.41 1.41L9 10.41z"/></svg>
|
||||||
|
{timeSince(new Date(notif.createdAt))}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else }
|
||||||
|
<div class="flex flex-row items-center notifBody">
|
||||||
|
<div class="ml-4 text-left" style="width: 97%">
|
||||||
|
<p class="text-xs text-gray-200 my-0 mt-1">{notif?.message ?? 'unknown notification type'}</p>
|
||||||
|
<p class="text-xs text-gray-500 my-0 my-1 text-right mr-2">
|
||||||
|
<svg viewBox="0 0 20 20" ><path fill="#fff" d="M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-1-7.59V4h2v5.59l3.95 3.95-1.41 1.41L9 10.41z"/></svg>
|
||||||
|
{timeSince(new Date(notif.createdAt))}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style class="scss">
|
||||||
|
.notificationImage {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifBody {
|
||||||
|
background: #00000061;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.938);
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-dsilike {
|
||||||
|
position: relative;
|
||||||
|
top: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
63
src/components/PageLoader.svelte
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="page-loader-wrapper">
|
||||||
|
<div class="page-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
.page-loader-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 3rem;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 10;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-loader {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
top: 40%;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
margin: -75px 0 0 -75px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
border-top-color: #16a085;
|
||||||
|
animation: spin 1.7s linear infinite;
|
||||||
|
z-index: 11;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-loader:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: 5px;
|
||||||
|
right: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
border-top-color: #e74c3c;
|
||||||
|
animation: spin-reverse .6s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-loader:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 15px;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
border-top-color: #f9c922;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
247
src/components/RateSingle.svelte
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { mainStore } from '@/utils/store';
|
||||||
|
import { hasVote, getPost } from '@/utils/votes';
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import { formatNumber } from '@/utils/misc'
|
||||||
|
import { fetchWAuth } from '@/utils/auth'
|
||||||
|
import { alertStore } from '@/utils/store';
|
||||||
|
import { API_BASE } from '@/constants/config';
|
||||||
|
import { executeVote } from '@/utils/votes';
|
||||||
|
|
||||||
|
export let url = ''
|
||||||
|
export let disabled = false
|
||||||
|
let loading = true
|
||||||
|
const defaultUserVote = {
|
||||||
|
rating: 0,
|
||||||
|
like: null,
|
||||||
|
_id: null
|
||||||
|
}
|
||||||
|
let userVote = defaultUserVote
|
||||||
|
let saveUserVote = defaultUserVote
|
||||||
|
let positiveWeight = 0
|
||||||
|
let negativeWeight = 0
|
||||||
|
let savePositiveWeight = 0
|
||||||
|
let saveNegativeWeight = 0
|
||||||
|
let timer = 0
|
||||||
|
let doingVote = false
|
||||||
|
let post = null
|
||||||
|
let delLoading = false
|
||||||
|
|
||||||
|
|
||||||
|
const getPostIntial = async (onlyPost = false) => {
|
||||||
|
post = await getPost(url)
|
||||||
|
if(!post) {
|
||||||
|
loading = false
|
||||||
|
} else {
|
||||||
|
positiveWeight = Math.trunc(post.rawPositiveWeight ?? 0 as number)
|
||||||
|
negativeWeight = Math.trunc(post.rawNegativeWeight ?? 0 as number)
|
||||||
|
savePositiveWeight = positiveWeight
|
||||||
|
saveNegativeWeight = negativeWeight
|
||||||
|
if(!onlyPost) {
|
||||||
|
const has = (await hasVote(post._id.postid, $mainStore.user.auth.userId))?.[0] ?? null
|
||||||
|
|
||||||
|
if(has){
|
||||||
|
userVote = has
|
||||||
|
saveUserVote = has
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userVote = defaultUserVote
|
||||||
|
saveUserVote = defaultUserVote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if(disabled) {
|
||||||
|
loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(!url) {
|
||||||
|
loading = false
|
||||||
|
disabled = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await getPostIntial()
|
||||||
|
loading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const doVote = (type = true) => {
|
||||||
|
if(disabled) {
|
||||||
|
$alertStore?.show('You shall not pass', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(timer && !doingVote) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
let noVoteAlert = false
|
||||||
|
if(!doingVote) {
|
||||||
|
const lastRating = userVote?.rating ?? 1
|
||||||
|
let sameType = true
|
||||||
|
if(type !== userVote?.like && userVote._id !== null) {
|
||||||
|
userVote.rating = 0
|
||||||
|
sameType = false
|
||||||
|
}
|
||||||
|
if(type && userVote?.rating < 3 || !type && userVote?.rating < 2) {
|
||||||
|
userVote.rating += 1
|
||||||
|
}
|
||||||
|
if(userVote?.rating === lastRating && userVote && sameType) {
|
||||||
|
$alertStore?.show('You already gave maximum rating.', 'warning')
|
||||||
|
noVoteAlert = true
|
||||||
|
} else {
|
||||||
|
if(userVote.like !== type && userVote._id !== null) {
|
||||||
|
if(type) {
|
||||||
|
negativeWeight -= $mainStore.user.profile.yup.weight * (userVote.rating + lastRating - 1)
|
||||||
|
userVote.rating = 1
|
||||||
|
positiveWeight += $mainStore.user.profile.yup.weight * userVote.rating
|
||||||
|
} else {
|
||||||
|
positiveWeight -= $mainStore.user.profile.yup.weight * (userVote.rating + lastRating - 1)
|
||||||
|
userVote.rating = 1
|
||||||
|
negativeWeight += $mainStore.user.profile.yup.weight * userVote.rating
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(type) {
|
||||||
|
positiveWeight += $mainStore.user.profile.yup.weight * (userVote.rating - lastRating)
|
||||||
|
} else {
|
||||||
|
negativeWeight += $mainStore.user.profile.yup.weight * (userVote.rating - lastRating)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userVote.like = type
|
||||||
|
}
|
||||||
|
timer = setTimeout(async () => {
|
||||||
|
doingVote = true
|
||||||
|
let reqVote
|
||||||
|
try {
|
||||||
|
reqVote = await executeVote({
|
||||||
|
post,
|
||||||
|
userVote,
|
||||||
|
$mainStore,
|
||||||
|
$alertStore,
|
||||||
|
url,
|
||||||
|
noVoteAlert
|
||||||
|
})
|
||||||
|
if(!reqVote) {
|
||||||
|
throw new Error('Vote not executed')
|
||||||
|
}
|
||||||
|
savePositiveWeight = positiveWeight
|
||||||
|
saveNegativeWeight = negativeWeight
|
||||||
|
saveUserVote = userVote
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e)
|
||||||
|
positiveWeight = savePositiveWeight
|
||||||
|
negativeWeight = saveNegativeWeight
|
||||||
|
userVote = saveUserVote
|
||||||
|
}
|
||||||
|
if(reqVote) {
|
||||||
|
userVote = reqVote
|
||||||
|
}
|
||||||
|
doingVote = false
|
||||||
|
}, 500) as unknown as number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const deleteVote = async () => {
|
||||||
|
if(delLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
doingVote = true
|
||||||
|
delLoading = true
|
||||||
|
const reqVote = await fetch(`${API_BASE}/votes/post/${post._id.postid}/voter/${$mainStore.user.auth.userId}`)
|
||||||
|
const voteId = (await reqVote.json())[0]._id.voteid
|
||||||
|
const p1 = fetchWAuth($mainStore, `${API_BASE}/votes/${voteId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
const [req] = await Promise.all([p1])
|
||||||
|
if (req.ok) {
|
||||||
|
await getPostIntial(true)
|
||||||
|
$alertStore.show('Vote deleted!')
|
||||||
|
} else {
|
||||||
|
$alertStore.show('Vote not deleted due to error try to re-login!', 'error')
|
||||||
|
positiveWeight = savePositiveWeight
|
||||||
|
negativeWeight = saveNegativeWeight
|
||||||
|
userVote = saveUserVote
|
||||||
|
}
|
||||||
|
doingVote = false
|
||||||
|
delLoading = false
|
||||||
|
} catch (error) {
|
||||||
|
positiveWeight = savePositiveWeight
|
||||||
|
negativeWeight = saveNegativeWeight
|
||||||
|
userVote = saveUserVote
|
||||||
|
console.log(error)
|
||||||
|
$alertStore.show('The vote could not be deleted!')
|
||||||
|
delLoading = false
|
||||||
|
doingVote = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if userVote._id !== null}
|
||||||
|
<svg on:click={() => deleteVote()} aria-hidden="true" class="{`w-4 opacity-30 delete interactive-svg ${delLoading ? 'animate-ping' : ''}`}" viewBox="0 0 512 512"><title/><polygon points="337.46 240 312 214.54 256 270.54 200 214.54 174.54 240 230.54 296 174.54 352 200 377.46 256 321.46 312 377.46 337.46 352 281.46 296 337.46 240" style="fill:none"/><polygon points="337.46 240 312 214.54 256 270.54 200 214.54 174.54 240 230.54 296 174.54 352 200 377.46 256 321.46 312 377.46 337.46 352 281.46 296 337.46 240" style="fill:none"/><path d="M64,160,93.74,442.51A24,24,0,0,0,117.61,464H394.39a24,24,0,0,0,23.87-21.49L448,160ZM312,377.46l-56-56-56,56L174.54,352l56-56-56-56L200,214.54l56,56,56-56L337.46,240l-56,56,56,56Z"/><rect height="80" rx="12" ry="12" width="448" x="32" y="48"/></svg>
|
||||||
|
{/if}
|
||||||
|
<div class="{`flex justify-center main-section bt ${doingVote ? 'animate-pulse disabled' : ''}`}" class:disabled>
|
||||||
|
<div on:click={() => doVote(true)} aria-hidden="true" class="{`flex w-1/2 p-4 box h-6 bf mr-4 ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}">
|
||||||
|
{#if userVote.rating && userVote.like}
|
||||||
|
<svg class="w-8" viewBox="0 0 24 24">
|
||||||
|
<path fill="#fff" d="M12,3.172L5.586,9.586c-0.781,0.781-0.781,2.047,0,2.828s2.047,0.781,2.828,0L10,10.828v7.242c0,1.104,0.895,2,2,2 c1.104,0,2-0.896,2-2v-7.242l1.586,1.586C15.977,12.805,16.488,13,17,13s1.023-0.195,1.414-0.586c0.781-0.781,0.781-2.047,0-2.828 L12,3.172z"/>
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg class="w-8" viewBox="0 0 24 24"><g><path fill="#fff" d="M12,21c-1.654,0-3-1.346-3-3v-4.764c-1.143,1.024-3.025,0.979-4.121-0.115c-1.17-1.169-1.17-3.073,0-4.242L12,1.758 l7.121,7.121c1.17,1.169,1.17,3.073,0,4.242c-1.094,1.095-2.979,1.14-4.121,0.115V18C15,19.654,13.654,21,12,21z M11,8.414V18 c0,0.551,0.448,1,1,1s1-0.449,1-1V8.414l3.293,3.293c0.379,0.378,1.035,0.378,1.414,0c0.391-0.391,0.391-1.023,0-1.414L12,4.586 l-5.707,5.707c-0.391,0.391-0.391,1.023,0,1.414c0.379,0.378,1.035,0.378,1.414,0L11,8.414z"/></g>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
{#key positiveWeight}
|
||||||
|
<span class="ml-4">{formatNumber(positiveWeight)}</span>
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
<div on:click={() => doVote(false)} aria-hidden="true" class="{`flex w-1/2 p-4 box h-6 bf ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}">
|
||||||
|
{#if userVote.rating && !userVote.like}
|
||||||
|
<svg class="w-8 down" viewBox="0 0 24 24">
|
||||||
|
<path fill="#fff" d="M12,3.172L5.586,9.586c-0.781,0.781-0.781,2.047,0,2.828s2.047,0.781,2.828,0L10,10.828v7.242c0,1.104,0.895,2,2,2 c1.104,0,2-0.896,2-2v-7.242l1.586,1.586C15.977,12.805,16.488,13,17,13s1.023-0.195,1.414-0.586c0.781-0.781,0.781-2.047,0-2.828 L12,3.172z"/>
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg class="w-8 down" viewBox="0 0 24 24"><g><path fill="#fff" d="M12,21c-1.654,0-3-1.346-3-3v-4.764c-1.143,1.024-3.025,0.979-4.121-0.115c-1.17-1.169-1.17-3.073,0-4.242L12,1.758 l7.121,7.121c1.17,1.169,1.17,3.073,0,4.242c-1.094,1.095-2.979,1.14-4.121,0.115V18C15,19.654,13.654,21,12,21z M11,8.414V18 c0,0.551,0.448,1,1,1s1-0.449,1-1V8.414l3.293,3.293c0.379,0.378,1.035,0.378,1.414,0c0.391-0.391,0.391-1.023,0-1.414L12,4.586 l-5.707,5.707c-0.391,0.391-0.391,1.023,0,1.414c0.379,0.378,1.035,0.378,1.414,0L11,8.414z"/></g>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
{#key negativeWeight}
|
||||||
|
<span class="ml-4">{formatNumber(negativeWeight)}</span>
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.cursor-block {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
box-shadow: inset 3px -3px 6rem 11px #0000005e;
|
||||||
|
}
|
||||||
|
.bf {
|
||||||
|
border: 1px solid #d3d9dfc2;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.bf:hover {
|
||||||
|
color: #fffee5;
|
||||||
|
background-color: #0000004f;
|
||||||
|
border: 1px solid #d3d9df73;
|
||||||
|
|
||||||
|
}
|
||||||
|
.down {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
position: absolute;
|
||||||
|
top: 225px;
|
||||||
|
right: 7px;
|
||||||
|
fill: #f0f8ff;
|
||||||
|
}
|
||||||
|
</style>
|
69
src/components/RateWebsite.svelte
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import RateSingle from '@/components/RateSingle.svelte'
|
||||||
|
import { getCurrentTab } from '@/utils/chrome-misc'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import { mainStore } from '@/utils/store'
|
||||||
|
import ImgLoader from './ImgLoader.svelte';
|
||||||
|
import { isUrlInvalid } from '@/utils/misc';
|
||||||
|
|
||||||
|
let tab = null
|
||||||
|
let url
|
||||||
|
let isValid = true
|
||||||
|
let loading = true
|
||||||
|
let loader: ImgLoader
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
tab = (await getCurrentTab())[0]
|
||||||
|
isValid = !isUrlInvalid(tab?.url)
|
||||||
|
try {
|
||||||
|
url = new URL(tab.url)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
console.log(url)
|
||||||
|
loading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if(loader && (!tab?.favIconUrl || !isValid) ) {
|
||||||
|
loader.onError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if tab}
|
||||||
|
<div class="{`flex flex-col ${loading ? 'animate-pulse' :''}`}">
|
||||||
|
{#if url}
|
||||||
|
<div class="flex items-center flex-col text-[0.8rem] leading-4 mb-4">
|
||||||
|
<div class="mb relative">
|
||||||
|
<ImgLoader source={tab.favIconUrl} bind:this={loader}>
|
||||||
|
<img style="{ $mainStore.settings.theme === 'light'? 'filter: invert(0.9);' : '' }" slot="img" on:load={() => loader.onLoad()} on:error={() => loader.onError}
|
||||||
|
class="w-5 h-5 mt-2 rounded-full wicon" src="{tab.favIconUrl}" alt="favicon" />
|
||||||
|
<svg class="w-5 h-5 mt-2 rounded-full wicon" style="{ $mainStore.settings.theme === 'light'? 'filter: invert(0.9);' : '' }" slot="error" viewBox="0 0 512 512"><g><polygon points="40,38.999 40,468.998 215,293.998 270,348.998 360,258.998 470,358.998 470,38.999 " style="fill:#EFF3F6;"/>
|
||||||
|
<g><circle cx="150" cy="158.999" r="40" style="fill:#FCD884;"/></g><g><polygon points="470,358.998 470,468.998 385,468.998 385,463.998 270,348.998 360,258.998 " style="fill:#70993F;"/></g><g><polygon points="385,463.998 385,468.998 40,468.998 215,293.998 270,348.998" style="fill:#80AF52;"/></g></g><g/></svg>
|
||||||
|
</ImgLoader>
|
||||||
|
{#if isValid}
|
||||||
|
<h1 class="inline-flex text-[1.1rem] font-semibold mb-2">Rate Website</h1>
|
||||||
|
{:else}
|
||||||
|
<h1 class="inline-flex text-[1.1rem] font-semibold mb-2">Invalid URL</h1>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<span>Hostname: {url.hostname.length > 18 ? url.hostname.slice(0,16) + '...': url.hostname }</span>
|
||||||
|
<span>URL: {url.href.length > 20 ? '...' + url.href.slice(-20) : url.href}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<RateSingle url={url.href.replace(/\/$/gms, '') ?? ''} disabled={!isValid} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wicon {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: -24px;
|
||||||
|
background-color: #7878788f;
|
||||||
|
padding: 0.13rem;
|
||||||
|
}
|
||||||
|
</style>
|
4
src/constants/config.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const API_BASE = 'https://api.yup.io'
|
||||||
|
export const DEV_BASE = 'http://localhost:4566'
|
||||||
|
export const YUP_LIVE_BASE = 'https://yup-live.pages.dev'
|
||||||
|
export const APP_BASE = YUP_LIVE_BASE
|
3
src/constants/messeges.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const SEND_VOTE = 'SEND_VOTE'
|
||||||
|
export const SET_AUTH = 'SET_AUTH'
|
||||||
|
export const SEND_AUTH_NOTIF = 'SEND_AUTH_NOTIF'
|
50
src/overlay/Overlay.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Alert from "@/components/Alert.svelte";
|
||||||
|
import '@/overlay/overlay.scss'
|
||||||
|
import { getStore } from "@/utils/storage";
|
||||||
|
import { executeVote } from "@/utils/votes";
|
||||||
|
|
||||||
|
let alert
|
||||||
|
let loading = false
|
||||||
|
|
||||||
|
const submitRate = async (type = true) => {
|
||||||
|
if(loading) return
|
||||||
|
loading = true
|
||||||
|
const store = await getStore()
|
||||||
|
const payload ={
|
||||||
|
userVote: {
|
||||||
|
like: type,
|
||||||
|
rating: 1
|
||||||
|
},
|
||||||
|
post: '',
|
||||||
|
url: document.location.href.replace(/\/$/gms, ''),
|
||||||
|
$mainStore: store,
|
||||||
|
$alertStore: null
|
||||||
|
}
|
||||||
|
const vote = await executeVote(payload)
|
||||||
|
console.log(vote)
|
||||||
|
if (vote?._id){
|
||||||
|
alert.show(type ? 'Liked' : 'Disliked', 'success')
|
||||||
|
} else {
|
||||||
|
alert.show('Error submitting', 'error')
|
||||||
|
}
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="yup-overlay" >
|
||||||
|
<svg class="{`yup-logo ${loading ? 'yup-rotate': ''}`}" viewBox="0 0 366 366" fill="none" ><path d="M182.911 366C82.1487 366 0 283.851 0 182.911C0 81.9697 82.1487 0 182.911 0C283.672 0 365.821 82.1487 365.821 182.911C365.821 283.672 283.851 366 182.911 366ZM182.911 17.3604C91.6342 17.3604 17.1814 91.6342 17.1814 183.089C17.1814 274.545 91.4553 348.819 182.911 348.819C274.366 348.819 348.64 274.545 348.64 183.089C348.64 91.6342 274.366 17.3604 182.911 17.3604Z" fill="currentColor"></path><path d="M129.218 156.959C105.952 156.959 86.8018 139.241 84.4751 116.69C84.1172 114.185 86.2649 111.858 88.9495 111.858H97.0033C99.5089 111.858 101.657 113.827 102.014 116.332C104.162 129.576 115.616 139.599 129.397 139.599C143.178 139.599 154.633 129.576 156.78 116.332C157.138 113.827 159.286 111.858 161.792 111.858H169.845C172.53 111.858 174.499 114.185 174.32 116.69C171.635 139.241 152.306 156.959 129.218 156.959Z" fill="currentColor"></path><path d="M277.05 148.19H268.997C266.491 148.19 264.343 146.221 263.985 143.715C261.838 130.471 250.383 120.449 236.602 120.449C222.822 120.449 211.367 130.471 209.22 143.715C208.862 146.221 206.714 148.19 204.208 148.19H196.155C193.47 148.19 191.501 145.863 191.68 143.357C194.186 120.807 213.336 103.089 236.424 103.089C259.511 103.089 278.84 120.807 281.167 143.357C281.883 145.863 279.735 148.19 277.05 148.19Z" fill="currentColor"></path><path d="M185.058 306.939C115.616 308.192 58.8818 250.204 58.8818 180.763V178.615C58.8818 176.289 60.8505 174.32 63.1772 174.32H302.464C304.791 174.32 306.76 176.289 306.76 178.615V182.911C306.939 250.562 252.531 305.865 185.058 306.939ZM79.8217 191.68C78.2109 191.68 76.7791 193.112 76.9581 194.723C82.8642 248.057 128.144 289.579 182.91 289.579C237.676 289.579 282.956 248.057 288.862 194.723C289.041 193.112 287.788 191.68 285.999 191.68H79.8217Z" fill="currentColor"></path></svg>
|
||||||
|
<button class="{`yup-btn up ${loading? 'disabled' : ''}`}"
|
||||||
|
on:click={() => submitRate(true)}>
|
||||||
|
<svg class="yup-rate-svg" viewBox="0 0 24 24">
|
||||||
|
<path fill="#fff" d="M12,3.172L5.586,9.586c-0.781,0.781-0.781,2.047,0,2.828s2.047,0.781,2.828,0L10,10.828v7.242c0,1.104,0.895,2,2,2 c1.104,0,2-0.896,2-2v-7.242l1.586,1.586C15.977,12.805,16.488,13,17,13s1.023-0.195,1.414-0.586c0.781-0.781,0.781-2.047,0-2.828 L12,3.172z"/>
|
||||||
|
</svg></button>
|
||||||
|
<button class="{`yup-btn yup-btn-down down ${loading? 'disabled' : ''} `}" on:click={() => submitRate(true)}>
|
||||||
|
<svg class="yup-rate-svg" viewBox="0 0 24 24">
|
||||||
|
<path fill="#fff" d="M12,3.172L5.586,9.586c-0.781,0.781-0.781,2.047,0,2.828s2.047,0.781,2.828,0L10,10.828v7.242c0,1.104,0.895,2,2,2 c1.104,0,2-0.896,2-2v-7.242l1.586,1.586C15.977,12.805,16.488,13,17,13s1.023-0.195,1.414-0.586c0.781-0.781,0.781-2.047,0-2.828 L12,3.172z"/>
|
||||||
|
</svg></button>
|
||||||
|
<Alert bind:this={alert} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
95
src/overlay/overlay.scss
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
@keyframes yup-pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes yup-pulse-down {
|
||||||
|
0% {
|
||||||
|
transform: scale(1) rotate(180deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2) rotate(180deg);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1) rotate(180deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-rotate {
|
||||||
|
animation: rotate 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-overlay {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2147483647;
|
||||||
|
top: 92vh;
|
||||||
|
left: 8px;
|
||||||
|
display: flex;
|
||||||
|
opacity: 0.7;
|
||||||
|
padding: 0.3rem;
|
||||||
|
gap: 0.4rem;
|
||||||
|
background: #121212d9;
|
||||||
|
|
||||||
|
.yup-logo {
|
||||||
|
opacity: 0.75;
|
||||||
|
width: 30px;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #3344;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-btn:hover {
|
||||||
|
background: rgba(62, 62, 99, 0.267);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-btn.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-btn.up:hover {
|
||||||
|
animation: yup-pulse 0.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-btn.down:hover {
|
||||||
|
animation: yup-pulse-down 0.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-rate-svg {
|
||||||
|
display: inline;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yup-btn-down {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
138
src/pages/Entry.svelte
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// import { storage } from "@/storage";
|
||||||
|
import { extrenalNavigate } from "@/utils/chrome-misc";
|
||||||
|
import { getStore } from '@/utils/storage'
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { navigate } from '@/utils/router'
|
||||||
|
import { mainStore } from '@/utils/store'
|
||||||
|
// https://yup-live.pages.dev
|
||||||
|
import { APP_BASE } from '@/constants/config'
|
||||||
|
import Alert from '@/components/Alert.svelte'
|
||||||
|
import { alertStore } from '@/utils/store'
|
||||||
|
import PageLoader from "@/components/PageLoader.svelte";
|
||||||
|
|
||||||
|
let store
|
||||||
|
let loading = true
|
||||||
|
let currentRoute = null
|
||||||
|
let auth = false
|
||||||
|
let alert: Alert
|
||||||
|
let navigating = false
|
||||||
|
|
||||||
|
const lNavigate = async (path: string) => {
|
||||||
|
if(navigating) return
|
||||||
|
navigating = true
|
||||||
|
await navigate(path)
|
||||||
|
currentRoute = path
|
||||||
|
navigating = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
store = await getStore()
|
||||||
|
alertStore.set(alert)
|
||||||
|
auth = store?.user?.auth?.userId ?? false
|
||||||
|
if(auth){
|
||||||
|
await mainStore.set(store)
|
||||||
|
await navigate('/')
|
||||||
|
currentRoute = '/'
|
||||||
|
} else {
|
||||||
|
await navigate('/login')
|
||||||
|
currentRoute = '/login'
|
||||||
|
}
|
||||||
|
loading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="entry" style="{ $mainStore.settings.theme === 'light'? 'filter: invert(1);' : '' }">
|
||||||
|
|
||||||
|
<a href="#app" on:click="{() => extrenalNavigate(APP_BASE)}">
|
||||||
|
<h1 aria-label="logo" class="logo inline-flex items-center text-[1.6rem] font-bold gap-2.5 pl-8">
|
||||||
|
<span class="gradient-text" style="{ $mainStore.settings.theme === 'light'? 'filter: invert(1.1);' : '' }" >YUP</span>
|
||||||
|
</h1>
|
||||||
|
</a>
|
||||||
|
{#if loading}
|
||||||
|
<PageLoader />
|
||||||
|
{:else if auth}
|
||||||
|
<div class="headerMenu flex">
|
||||||
|
{#if currentRoute !== '/info'}
|
||||||
|
<svg on:click={() => lNavigate('/info')} aria-hidden="true" viewBox="0 0 512 512" class="w-6 mx-1 interactive-svg"><path fill="#fff" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
|
||||||
|
{/if}
|
||||||
|
{#if currentRoute !== '/'}
|
||||||
|
<svg on:click={() => lNavigate('/')} aria-hidden="true" class="w-6 mx-1 interactive-svg" viewBox="0 0 24 24"><g id="info"/><g><path fill="#fff" d="M12,0C5.4,0,0,5.4,0,12c0,6.6,5.4,12,12,12s12-5.4,12-12C24,5.4,18.6,0,12,0z M12,4c2.2,0,4,2.2,4,5s-1.8,5-4,5 s-4-2.2-4-5S9.8,4,12,4z M18.6,19.5C16.9,21,14.5,22,12,22s-4.9-1-6.6-2.5c-0.4-0.4-0.5-1-0.1-1.4c1.1-1.3,2.6-2.2,4.2-2.7 c0.8,0.4,1.6,0.6,2.5,0.6s1.7-0.2,2.5-0.6c1.7,0.5,3.1,1.4,4.2,2.7C19.1,18.5,19.1,19.1,18.6,19.5z" id="user2"/></g></svg>
|
||||||
|
{/if}
|
||||||
|
{#if currentRoute !== '/usage' }
|
||||||
|
<svg on:click={() => lNavigate('/usage')} aria-hidden="true" class="w-6 mx-1 interactive-svg" viewBox="0 0 512 512"><title/><path fill="#fff" d="M256,48C141.12,48,48,141.12,48,256s93.12,208,208,208,208-93.12,208-208S370.88,48,256,48ZM173.67,162.34l105,71a32.5,32.5,0,0,1-37.25,53.26,33.21,33.21,0,0,1-8-8l-71-105a8.13,8.13,0,0,1,11.32-11.32ZM256,432C159,432,80,353.05,80,256a174.55,174.55,0,0,1,53.87-126.72,14.15,14.15,0,1,1,19.64,20.37A146.53,146.53,0,0,0,108.3,256c0,81.44,66.26,147.7,147.7,147.7S403.7,337.44,403.7,256c0-76.67-58.72-139.88-133.55-147V164a14.15,14.15,0,1,1-28.3,0V94.15A14.15,14.15,0,0,1,256,80C353.05,80,432,159,432,256S353.05,432,256,432Z"/></svg>
|
||||||
|
{/if}
|
||||||
|
{#if currentRoute !== '/settings' }
|
||||||
|
<svg on:click={() => lNavigate('/settings')} aria-hidden="true" class="w-6 mx-1 interactive-svg" viewBox="0 0 32 32" ><g><path clip-rule="evenodd" d="M8,6.021V3c0-1.654-1.346-3-3-3S2,1.346,2,3v3.021 C0.792,6.936,0,8.369,0,10s0.792,3.064,2,3.977V29c0,1.654,1.346,3,3,3s3-1.346,3-3V13.977c1.208-0.912,2-2.346,2-3.977 S9.208,6.936,8,6.021z M4,3c0-0.553,0.447-1,1-1s1,0.447,1,1v2.1C5.677,5.035,5.343,5,5,5S4.323,5.035,4,5.1V3z M6,29 c0,0.553-0.447,1-1,1s-1-0.447-1-1V14.898C4.323,14.965,4.657,15,5,15s0.677-0.035,1-0.102V29z M7.865,10.84 c-0.016,0.053-0.03,0.105-0.049,0.158c-0.095,0.264-0.217,0.514-0.378,0.736c-0.004,0.006-0.01,0.01-0.014,0.016 c-0.174,0.238-0.381,0.449-0.616,0.627c-0.004,0.004-0.007,0.006-0.01,0.008c-0.241,0.182-0.51,0.328-0.799,0.43 C5.686,12.928,5.353,13,5,13s-0.686-0.072-1-0.186c-0.289-0.102-0.558-0.248-0.799-0.43c-0.003-0.002-0.006-0.004-0.01-0.008 c-0.235-0.178-0.442-0.389-0.616-0.627c-0.004-0.006-0.01-0.01-0.014-0.016c-0.161-0.223-0.283-0.473-0.378-0.736 c-0.019-0.053-0.033-0.105-0.049-0.158C2.055,10.572,2,10.293,2,10c0-0.295,0.055-0.574,0.135-0.842 c0.016-0.053,0.03-0.105,0.049-0.156C2.278,8.738,2.4,8.488,2.562,8.264c0.004-0.006,0.01-0.01,0.014-0.016 c0.174-0.236,0.381-0.449,0.616-0.627c0.004-0.002,0.007-0.006,0.01-0.008C3.442,7.434,3.711,7.287,4,7.184 C4.314,7.072,4.647,7,5,7s0.686,0.072,1,0.184c0.289,0.104,0.558,0.25,0.799,0.43c0.003,0.002,0.006,0.006,0.01,0.008 c0.235,0.178,0.442,0.391,0.616,0.627c0.004,0.006,0.01,0.01,0.014,0.016C7.6,8.488,7.722,8.738,7.816,9.002 C7.835,9.053,7.85,9.105,7.865,9.158C7.945,9.426,8,9.705,8,10C8,10.293,7.945,10.572,7.865,10.84z" fill="#fff" fill-rule="evenodd"/><path clip-rule="evenodd" d="M30,6.021V3c0-1.654-1.346-3-3-3s-3,1.346-3,3v3.021 C22.791,6.936,22,8.369,22,10s0.791,3.064,2,3.977V29c0,1.654,1.346,3,3,3s3-1.346,3-3V13.977c1.207-0.912,2-2.346,2-3.977 S31.207,6.936,30,6.021z M26,3c0-0.553,0.447-1,1-1s1,0.447,1,1v2.1C27.676,5.035,27.342,5,27,5c-0.344,0-0.678,0.035-1,0.1V3z M28,29c0,0.553-0.447,1-1,1s-1-0.447-1-1V14.898C26.322,14.965,26.656,15,27,15c0.342,0,0.676-0.035,1-0.102V29z M29.865,10.84 c-0.016,0.053-0.031,0.105-0.049,0.158c-0.096,0.264-0.217,0.514-0.379,0.736c-0.004,0.006-0.01,0.01-0.014,0.016 c-0.174,0.238-0.381,0.449-0.615,0.627c-0.004,0.004-0.008,0.006-0.01,0.008c-0.242,0.182-0.51,0.328-0.799,0.43 C27.686,12.928,27.352,13,27,13c-0.354,0-0.686-0.072-1-0.186c-0.289-0.102-0.559-0.248-0.799-0.43 c-0.004-0.002-0.006-0.004-0.01-0.008c-0.236-0.178-0.443-0.389-0.617-0.627c-0.004-0.006-0.01-0.01-0.014-0.016 c-0.16-0.223-0.283-0.473-0.377-0.736c-0.02-0.053-0.033-0.105-0.049-0.158C24.055,10.572,24,10.293,24,10 c0-0.295,0.055-0.574,0.135-0.842c0.016-0.053,0.029-0.105,0.049-0.156c0.094-0.264,0.217-0.514,0.377-0.738 c0.004-0.006,0.01-0.01,0.014-0.016c0.174-0.236,0.381-0.449,0.617-0.627c0.004-0.002,0.006-0.006,0.01-0.008 c0.24-0.18,0.51-0.326,0.799-0.43C26.314,7.072,26.646,7,27,7c0.352,0,0.686,0.072,1,0.184c0.289,0.104,0.557,0.25,0.799,0.43 c0.002,0.002,0.006,0.006,0.01,0.008c0.234,0.178,0.441,0.391,0.615,0.627c0.004,0.006,0.01,0.01,0.014,0.016 c0.162,0.225,0.283,0.475,0.379,0.738c0.018,0.051,0.033,0.104,0.049,0.156C29.945,9.426,30,9.705,30,10 C30,10.293,29.945,10.572,29.865,10.84z" fill="#fff" fill-rule="evenodd"/><path clip-rule="evenodd" d="M19,18.021V3c0-1.654-1.346-3-3-3s-3,1.346-3,3v15.021 c-1.208,0.914-2,2.348-2,3.979s0.792,3.064,2,3.977V29c0,1.654,1.346,3,3,3s3-1.346,3-3v-3.023c1.207-0.912,2-2.346,2-3.977 S20.207,18.936,19,18.021z M15,3c0-0.553,0.447-1,1-1c0.553,0,1,0.447,1,1v14.1c-0.324-0.064-0.658-0.1-1-0.1 c-0.343,0-0.677,0.035-1,0.1V3z M17,29c0,0.553-0.447,1-1,1c-0.553,0-1-0.447-1-1v-2.102C15.323,26.965,15.657,27,16,27 c0.342,0,0.676-0.035,1-0.102V29z M18.865,22.84c-0.016,0.053-0.031,0.105-0.049,0.158c-0.096,0.264-0.217,0.514-0.379,0.736 c-0.004,0.006-0.01,0.01-0.014,0.016c-0.174,0.238-0.381,0.449-0.615,0.627c-0.004,0.004-0.008,0.006-0.01,0.008 c-0.242,0.182-0.51,0.328-0.799,0.43C16.686,24.928,16.352,25,16,25c-0.353,0-0.686-0.072-1-0.186 c-0.289-0.102-0.558-0.248-0.799-0.43c-0.003-0.002-0.006-0.004-0.01-0.008c-0.235-0.178-0.442-0.389-0.616-0.627 c-0.004-0.006-0.01-0.01-0.014-0.016c-0.161-0.223-0.283-0.473-0.378-0.736c-0.019-0.053-0.033-0.105-0.049-0.158 C13.055,22.572,13,22.293,13,22c0-0.295,0.055-0.574,0.135-0.842c0.016-0.053,0.03-0.105,0.049-0.156 c0.095-0.264,0.217-0.514,0.378-0.738c0.004-0.006,0.01-0.01,0.014-0.016c0.174-0.236,0.381-0.449,0.616-0.627 c0.004-0.002,0.007-0.006,0.01-0.008c0.241-0.18,0.51-0.326,0.799-0.43C15.314,19.072,15.647,19,16,19c0.352,0,0.686,0.072,1,0.184 c0.289,0.104,0.557,0.25,0.799,0.43c0.002,0.002,0.006,0.006,0.01,0.008c0.234,0.178,0.441,0.391,0.615,0.627 c0.004,0.006,0.01,0.01,0.014,0.016c0.162,0.225,0.283,0.475,0.379,0.738c0.018,0.051,0.033,0.104,0.049,0.156 C18.945,21.426,19,21.705,19,22C19,22.293,18.945,22.572,18.865,22.84z" fill="#fff" fill-rule="evenodd"/></g></svg>
|
||||||
|
{/if}
|
||||||
|
{#if currentRoute !== '/notifications' && $mainStore.settings.notificationsEnabled }
|
||||||
|
{@const hasNew = $mainStore.settings.hasNewNotifications}
|
||||||
|
<svg class="{`w-6 mx-1 interactive-svg ${hasNew ? 'animate-pulse red' : 'white'}`}"
|
||||||
|
style="{ $mainStore.settings.theme === 'light' && hasNew ? 'filter: invert(1.1);' : '' }"
|
||||||
|
on:click={() => lNavigate('/notifications')} aria-hidden="true" viewBox="0 0 512 512">
|
||||||
|
<path fill="currentColor" d="M470.346 403.26C468.655 400.18 466.169 397.611 463.146 395.82L441.487 382.82C433.033 377.737 426.038 370.553 421.183 361.967C416.327 353.381 413.776 343.684 413.777 333.82V216.66C413.756 182.674 402.73 149.609 382.347 122.414C361.964 95.2185 333.321 75.3562 300.707 65.8C299.296 65.3984 298.054 64.5512 297.165 63.3849C296.277 62.2187 295.79 60.7962 295.777 59.33C295.777 48.899 291.633 38.8953 284.257 31.5195C276.881 24.1437 266.877 20 256.447 20C246.016 20 236.012 24.1437 228.636 31.5195C221.26 38.8953 217.116 48.899 217.116 59.33C217.102 60.7957 216.614 62.2175 215.726 63.3834C214.837 64.5493 213.596 65.397 212.186 65.8C179.572 75.3562 150.929 95.2185 130.546 122.414C110.163 149.609 99.1367 182.674 99.1164 216.66V333.8C99.1173 343.664 96.566 353.361 91.7104 361.947C86.8548 370.533 79.8601 377.717 71.4065 382.8L49.7465 395.8C46.0067 398.033 43.0981 401.426 41.4629 405.463C39.8276 409.501 39.5551 413.961 40.6867 418.168C41.8184 422.374 44.2922 426.096 47.7324 428.767C51.1726 431.439 55.3911 432.915 59.7465 432.97H453.086C456.534 432.888 459.903 431.923 462.871 430.165C465.839 428.408 468.305 425.918 470.034 422.934C471.764 419.95 472.698 416.572 472.747 413.124C472.797 409.676 471.96 406.272 470.317 403.24L470.346 403.26Z"/>
|
||||||
|
<path fill="currentColor" d="M222.447 481.12C229.872 486.373 238.414 489.832 247.402 491.225C256.39 492.619 265.579 491.908 274.246 489.15C282.913 486.392 290.822 481.661 297.351 475.329C303.881 468.998 308.853 461.238 311.876 452.66H200.987C205.035 464.132 212.53 474.073 222.447 481.12Z"/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div id="router"></div>
|
||||||
|
<Alert bind:this={alert} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 350px;
|
||||||
|
width: 250px;
|
||||||
|
background-color: #242424;
|
||||||
|
color: aliceblue;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 300;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0.81rem;
|
||||||
|
box-shadow: inset 0 0px 1px 4px #161616;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: rgba(156, 39, 39, 0.733);
|
||||||
|
}
|
||||||
|
|
||||||
|
#router {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
margin-top: 2.2rem;
|
||||||
|
height: 100%;
|
||||||
|
width: 270px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerMenu, .logo {
|
||||||
|
padding: 0.4rem 0.4rem;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerMenu {
|
||||||
|
right: 0.3rem;
|
||||||
|
top: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
left: 0rem;
|
||||||
|
top: -0.8rem;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: inset 3px -3px 6rem 11px #0000005e;
|
||||||
|
border-bottom: 1px solid #d3d9df63;
|
||||||
|
}
|
||||||
|
</style>
|
79
src/pages/Info.svelte
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import PageLoader from '@/components/PageLoader.svelte';
|
||||||
|
import { getExtensionVersion, extrenalNavigate } from '@/utils/chrome-misc';
|
||||||
|
|
||||||
|
let loading = true;
|
||||||
|
let version = '';
|
||||||
|
const extensionSourceLink = 'https://github.com/andrei0x309/yup-live-chrome-extension'
|
||||||
|
const yupLiveSourceLink = 'https://github.com/andrei0x309/yup-live'
|
||||||
|
const yupLiveLink = 'https://yup-live.pages.dev'
|
||||||
|
const yupAppLink = 'https://app.yup.io'
|
||||||
|
const discordLink = 'https://discord.com/invite/HnaTAXK'
|
||||||
|
const yupForumLink = 'https://forum.yup.io'
|
||||||
|
const yupDocsLink = 'https://docs.yup.io'
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
version = getExtensionVersion();
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<PageLoader />
|
||||||
|
{:else}
|
||||||
|
<p class="text-[1.15rem] mt-2 mb-1">About Extension</p>
|
||||||
|
<p class="text-[0.95rem] mt-1 mb-6">Version: <b>{version}</b></p>
|
||||||
|
|
||||||
|
<div class="text-[0.85rem] text-left">
|
||||||
|
<p class="text-[1.15rem] mt-1 mb-1 ml-4">External Links</p>
|
||||||
|
<ul class="list-none">
|
||||||
|
<li><svg class="w-5 falign" viewBox="0 0 512 512"><g><path fill="#fff" class="st0" d="M256,32C132.3,32,32,134.8,32,261.7c0,101.5,64.2,187.5,153.2,217.9c11.2,2.1,15.3-5,15.3-11.1 c0-5.5-0.2-19.9-0.3-39.1c-62.3,13.9-75.5-30.8-75.5-30.8c-10.2-26.5-24.9-33.6-24.9-33.6c-20.3-14.3,1.5-14,1.5-14 c22.5,1.6,34.3,23.7,34.3,23.7c20,35.1,52.4,25,65.2,19.1c2-14.8,7.8-25,14.2-30.7c-49.7-5.8-102-25.5-102-113.5 c0-25.1,8.7-45.6,23-61.6c-2.3-5.8-10-29.2,2.2-60.8c0,0,18.8-6.2,61.6,23.5c17.9-5.1,37-7.6,56.1-7.7c19,0.1,38.2,2.6,56.1,7.7 c42.8-29.7,61.5-23.5,61.5-23.5c12.2,31.6,4.5,55,2.2,60.8c14.3,16.1,23,36.6,23,61.6c0,88.2-52.4,107.6-102.3,113.3 c8,7.1,15.2,21.1,15.2,42.5c0,30.7-0.3,55.5-0.3,63c0,6.1,4,13.3,15.4,11C415.9,449.1,480,363.1,480,261.7 C480,134.8,379.7,32,256,32z"/></g></svg>
|
||||||
|
<span aria-hidden class="link" on:click={() => extrenalNavigate(extensionSourceLink)} >Extension Source code
|
||||||
|
</span></li>
|
||||||
|
<li><svg class="w-5 falign" viewBox="0 0 512 512"><g><path fill="#fff" class="st0" d="M256,32C132.3,32,32,134.8,32,261.7c0,101.5,64.2,187.5,153.2,217.9c11.2,2.1,15.3-5,15.3-11.1 c0-5.5-0.2-19.9-0.3-39.1c-62.3,13.9-75.5-30.8-75.5-30.8c-10.2-26.5-24.9-33.6-24.9-33.6c-20.3-14.3,1.5-14,1.5-14 c22.5,1.6,34.3,23.7,34.3,23.7c20,35.1,52.4,25,65.2,19.1c2-14.8,7.8-25,14.2-30.7c-49.7-5.8-102-25.5-102-113.5 c0-25.1,8.7-45.6,23-61.6c-2.3-5.8-10-29.2,2.2-60.8c0,0,18.8-6.2,61.6,23.5c17.9-5.1,37-7.6,56.1-7.7c19,0.1,38.2,2.6,56.1,7.7 c42.8-29.7,61.5-23.5,61.5-23.5c12.2,31.6,4.5,55,2.2,60.8c14.3,16.1,23,36.6,23,61.6c0,88.2-52.4,107.6-102.3,113.3 c8,7.1,15.2,21.1,15.2,42.5c0,30.7-0.3,55.5-0.3,63c0,6.1,4,13.3,15.4,11C415.9,449.1,480,363.1,480,261.7 C480,134.8,379.7,32,256,32z"/></g></svg>
|
||||||
|
<span aria-hidden class="link" on:click={() => extrenalNavigate(yupLiveSourceLink)} >Yup Live Source code
|
||||||
|
</span></li>
|
||||||
|
<li><svg class="w-5 falign" viewBox="0 0 24 24"><title/><path d="M17.3,13.35a1,1,0,0,1-.7-.29,1,1,0,0,1,0-1.41l2.12-2.12a2,2,0,0,0,0-2.83L17.3,5.28a2.06,2.06,0,0,0-2.83,0L12.35,7.4A1,1,0,0,1,10.94,6l2.12-2.12a4.1,4.1,0,0,1,5.66,0l1.41,1.41a4,4,0,0,1,0,5.66L18,13.06A1,1,0,0,1,17.3,13.35Z" fill="#fff"/><path d="M8.11,21.3a4,4,0,0,1-2.83-1.17L3.87,18.72a4,4,0,0,1,0-5.66L6,10.94A1,1,0,0,1,7.4,12.35L5.28,14.47a2,2,0,0,0,0,2.83L6.7,18.72a2.06,2.06,0,0,0,2.83,0l2.12-2.12A1,1,0,1,1,13.06,18l-2.12,2.12A4,4,0,0,1,8.11,21.3Z" fill="#fff"/><path d="M8.82,16.18a1,1,0,0,1-.71-.29,1,1,0,0,1,0-1.42l6.37-6.36a1,1,0,0,1,1.41,0,1,1,0,0,1,0,1.42L9.52,15.89A1,1,0,0,1,8.82,16.18Z" fill="#fff"/></svg>
|
||||||
|
<span aria-hidden class="link" on:click={() => extrenalNavigate(yupLiveLink)} >Yup Live Website
|
||||||
|
</span></li>
|
||||||
|
<li><svg class="w-5 falign" viewBox="0 0 640 512" ><path fill="#fff" d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"/></svg>
|
||||||
|
<span aria-hidden class="link" on:click={() => extrenalNavigate(discordLink)} >Discord community
|
||||||
|
</span></li>
|
||||||
|
<li><svg class="w-5 falign" viewBox="0 0 24 24"><title/><path d="M17.3,13.35a1,1,0,0,1-.7-.29,1,1,0,0,1,0-1.41l2.12-2.12a2,2,0,0,0,0-2.83L17.3,5.28a2.06,2.06,0,0,0-2.83,0L12.35,7.4A1,1,0,0,1,10.94,6l2.12-2.12a4.1,4.1,0,0,1,5.66,0l1.41,1.41a4,4,0,0,1,0,5.66L18,13.06A1,1,0,0,1,17.3,13.35Z" fill="#fff"/><path d="M8.11,21.3a4,4,0,0,1-2.83-1.17L3.87,18.72a4,4,0,0,1,0-5.66L6,10.94A1,1,0,0,1,7.4,12.35L5.28,14.47a2,2,0,0,0,0,2.83L6.7,18.72a2.06,2.06,0,0,0,2.83,0l2.12-2.12A1,1,0,1,1,13.06,18l-2.12,2.12A4,4,0,0,1,8.11,21.3Z" fill="#fff"/><path d="M8.82,16.18a1,1,0,0,1-.71-.29,1,1,0,0,1,0-1.42l6.37-6.36a1,1,0,0,1,1.41,0,1,1,0,0,1,0,1.42L9.52,15.89A1,1,0,0,1,8.82,16.18Z" fill="#fff"/></svg>
|
||||||
|
<span aria-hidden class="link" on:click={() => extrenalNavigate(yupForumLink)} >Yup Forum
|
||||||
|
</span></li>
|
||||||
|
<li><svg class="w-5 falign" viewBox="0 0 24 24"><title/><path d="M17.3,13.35a1,1,0,0,1-.7-.29,1,1,0,0,1,0-1.41l2.12-2.12a2,2,0,0,0,0-2.83L17.3,5.28a2.06,2.06,0,0,0-2.83,0L12.35,7.4A1,1,0,0,1,10.94,6l2.12-2.12a4.1,4.1,0,0,1,5.66,0l1.41,1.41a4,4,0,0,1,0,5.66L18,13.06A1,1,0,0,1,17.3,13.35Z" fill="#fff"/><path d="M8.11,21.3a4,4,0,0,1-2.83-1.17L3.87,18.72a4,4,0,0,1,0-5.66L6,10.94A1,1,0,0,1,7.4,12.35L5.28,14.47a2,2,0,0,0,0,2.83L6.7,18.72a2.06,2.06,0,0,0,2.83,0l2.12-2.12A1,1,0,1,1,13.06,18l-2.12,2.12A4,4,0,0,1,8.11,21.3Z" fill="#fff"/><path d="M8.82,16.18a1,1,0,0,1-.71-.29,1,1,0,0,1,0-1.42l6.37-6.36a1,1,0,0,1,1.41,0,1,1,0,0,1,0,1.42L9.52,15.89A1,1,0,0,1,8.82,16.18Z" fill="#fff"/></svg>
|
||||||
|
<span aria-hidden class="link" on:click={() => extrenalNavigate(yupAppLink)} >Yup App
|
||||||
|
</span></li>
|
||||||
|
<li><svg class="w-5 falign" viewBox="0 0 24 24"><title/><path d="M17.3,13.35a1,1,0,0,1-.7-.29,1,1,0,0,1,0-1.41l2.12-2.12a2,2,0,0,0,0-2.83L17.3,5.28a2.06,2.06,0,0,0-2.83,0L12.35,7.4A1,1,0,0,1,10.94,6l2.12-2.12a4.1,4.1,0,0,1,5.66,0l1.41,1.41a4,4,0,0,1,0,5.66L18,13.06A1,1,0,0,1,17.3,13.35Z" fill="#fff"/><path d="M8.11,21.3a4,4,0,0,1-2.83-1.17L3.87,18.72a4,4,0,0,1,0-5.66L6,10.94A1,1,0,0,1,7.4,12.35L5.28,14.47a2,2,0,0,0,0,2.83L6.7,18.72a2.06,2.06,0,0,0,2.83,0l2.12-2.12A1,1,0,1,1,13.06,18l-2.12,2.12A4,4,0,0,1,8.11,21.3Z" fill="#fff"/><path d="M8.82,16.18a1,1,0,0,1-.71-.29,1,1,0,0,1,0-1.42l6.37-6.36a1,1,0,0,1,1.41,0,1,1,0,0,1,0,1.42L9.52,15.89A1,1,0,0,1,8.82,16.18Z" fill="#fff"/></svg>
|
||||||
|
<span aria-hidden class="link" on:click={() => extrenalNavigate(yupDocsLink)} >Yup Docs
|
||||||
|
</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.link {
|
||||||
|
color: #3b82f6;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.falign {
|
||||||
|
position: relative;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
24
src/pages/Login.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { extrenalNavigate } from "@/utils/chrome-misc";
|
||||||
|
import { APP_BASE } from '@/constants/config'
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a href="#app" on:click={() => extrenalNavigate(`${APP_BASE}/login`)}>
|
||||||
|
<svg enable-background="new 0 0 32 32" viewBox="0 0 32 32" class="w-20 mb-10 mt-8 svg-fill">
|
||||||
|
<g id="lock"><path d="M25,13V9c0-4.971-4.029-9-9-9c-4.971,0-9,4.029-9,9v4c-1.657,0-3,1.343-3,3v3v1v2v1c0,4.971,4.029,9,9,9h6 c4.971,0,9-4.029,9-9v-1v-2v-1v-3C28,14.342,26.656,13,25,13z M9,9c0-3.866,3.134-7,7-7c3.866,0,7,3.134,7,7v4h-2V9.002 c0-2.762-2.238-5-5-5c-2.762,0-5,2.238-5,5V13H9V9z M20,9v0.003V13h-8V9.002V9c0-2.209,1.791-4,4-4C18.209,5,20,6.791,20,9z M26,19 v1v2v1c0,3.859-3.141,7-7,7h-6c-3.859,0-7-3.141-7-7v-1v-2v-1v-3c0-0.552,0.448-1,1-1c0.667,0,1.333,0,2,0h14c0.666,0,1.332,0,2,0 c0.551,0,1,0.448,1,1V19z"/>
|
||||||
|
<path d="M16,19c-1.104,0-2,0.895-2,2c0,0.607,0.333,1.76,0.667,2.672c0.272,0.742,0.614,1.326,1.333,1.326 c0.782,0,1.061-0.578,1.334-1.316C17.672,22.768,18,21.609,18,21C18,19.895,17.104,19,16,19z"/></g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<div class="text-[1.6rem] mb-4 uppercase">
|
||||||
|
<a href="#app" on:click="{() => extrenalNavigate(`${APP_BASE}/login`)}">
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
/
|
||||||
|
<a href="#app" on:click="{() => extrenalNavigate(`${APP_BASE}/sign-up`)}" >
|
||||||
|
Signup
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<a href="#app" on:click="{() => extrenalNavigate(`${APP_BASE}/login`)}">
|
||||||
|
<svg enable-background="new 0 0 48 48" viewBox="0 0 48 48" class="w-18 svg-fill my-4"><path d="M0.115,30.348c0-7.771,6.303-14.073,14.074-14.073h0.002h14.071V8.051l19.622,15.261l-19.622,15.26v-8.225 H10.458c-3.887,0-7.037,3.152-7.037,7.037c0,0.906,0.186,1.768,0.5,2.564C1.566,37.434,0.115,34.064,0.115,30.348z"/></svg>
|
||||||
|
</a>
|
160
src/pages/Main.svelte
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// import { storage } from "@/storage";
|
||||||
|
import { extrenalNavigate } from "@/utils/chrome-misc";
|
||||||
|
import ImgLoader from "@/components/ImgLoader.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
// https://yup-live.pages.dev
|
||||||
|
import { APP_BASE } from "@/constants/config";
|
||||||
|
import type { StorageType } from "@/utils/storage";
|
||||||
|
import RateWebsite from "@/components/RateWebsite.svelte";
|
||||||
|
import { mainStore } from "@/utils/store";
|
||||||
|
import { formatNumber, truncteEVMAddr } from "@/utils/misc";
|
||||||
|
import { alertStore } from "@/utils/store";
|
||||||
|
import { copy } from "@/utils/chrome-misc";
|
||||||
|
|
||||||
|
let avatar: string = "";
|
||||||
|
let loader: ImgLoader;
|
||||||
|
let handle = $mainStore?.user?.profile?.handle;
|
||||||
|
|
||||||
|
const copyAddress = () => {
|
||||||
|
copy($mainStore?.user?.auth?.address);
|
||||||
|
$alertStore?.show("Copied to clipboard", "success");
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
console.log($mainStore);
|
||||||
|
if ($mainStore?.user?.profile?.yup?.avatar && !$mainStore?.user?.profile?.yup?.avatar.endsWith(".mp4")) {
|
||||||
|
avatar = $mainStore?.user?.profile?.yup?.avatar;
|
||||||
|
} else if ($mainStore?.user?.profile?.lens?.avatar && !$mainStore?.user?.profile?.lens?.avatar.endsWith(".mp4")) {
|
||||||
|
avatar = $mainStore?.user?.profile?.lens?.avatar;
|
||||||
|
} else if (
|
||||||
|
$mainStore?.user?.profile?.farcaster?.avatar &&
|
||||||
|
!$mainStore?.user?.profile?.farcaster?.avatar.endsWith(".mp4")
|
||||||
|
) {
|
||||||
|
avatar = $mainStore?.user?.profile?.farcaster?.avatar;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="h-24 leading-6 main-section">
|
||||||
|
<div class="flex">
|
||||||
|
<div on:click={() => extrenalNavigate(`${APP_BASE}/score/${$mainStore.user.auth.address}`)} aria-hidden class="flex flex-col w-16 mt-1 px-2 py-3 mr-4 link">
|
||||||
|
<span class="text-[0.6rem] mb-2">Score</span><span class="text-[0.95rem] mb-2"
|
||||||
|
>{$mainStore?.user?.profile?.yupScore?.toFixed(0)}</span
|
||||||
|
><span class="text-[0.7rem]">100<br />MAX</span>
|
||||||
|
</div>
|
||||||
|
<div on:click={() => extrenalNavigate(`${APP_BASE}/profile/${$mainStore.user.auth.userId}`)} aria-hidden class="flex flex-col justify-center mb-2 w-16">
|
||||||
|
<ImgLoader source={avatar} bind:this={loader}>
|
||||||
|
<img
|
||||||
|
style="{ $mainStore.settings.theme === 'light'? 'filter: invert(1);' : '' }"
|
||||||
|
slot="img"
|
||||||
|
src={avatar}
|
||||||
|
alt="avatar"
|
||||||
|
class="h-14 w-14 avatar"
|
||||||
|
on:load={() => loader.onLoad()}
|
||||||
|
on:error={() => loader.onError()}
|
||||||
|
/>
|
||||||
|
<svg slot="error" class="h-14 w-14 avatar" viewBox="-27 24 100 100" style="{ $mainStore.settings.theme === 'light'? 'filter: invert(0.9);' : '' }"
|
||||||
|
><g
|
||||||
|
><g
|
||||||
|
><rect fill="#F5EEE5" x="-27" y="24" /><g
|
||||||
|
><defs
|
||||||
|
><path
|
||||||
|
d="M36,95.9c0,4,4.7,5.2,7.1,5.8c7.6,2,22.8,5.9,22.8,5.9c3.2,1.1,5.7,3.5,7.1,6.6v9.8H-27v-9.8 c1.3-3.1,3.9-5.5,7.1-6.6c0,0,15.2-3.9,22.8-5.9c2.4-0.6,7.1-1.8,7.1-5.8c0-4,0-10.9,0-10.9h26C36,85,36,91.9,36,95.9z"
|
||||||
|
id="shoulders"
|
||||||
|
/></defs
|
||||||
|
><use fill="#E6C19C" overflow="visible" xlink:href="#shoulders" /><clipPath id="shoulders_1_"
|
||||||
|
><use overflow="visible" xlink:href="#shoulders" /></clipPath
|
||||||
|
><path
|
||||||
|
clip-path="url(#shoulders_1_)"
|
||||||
|
d="M23.2,35c0.1,0,0.1,0,0.2,0c0,0,0,0,0,0 c3.3,0,8.2,0.2,11.4,2c3.3,1.9,7.3,5.6,8.5,12.1c2.4,13.7-2.1,35.4-6.3,42.4c-4,6.7-9.8,9.2-13.5,9.4c0,0-0.1,0-0.1,0 c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0c0,0-0.1,0-0.1,0c-3.7-0.2-9.5-2.7-13.5-9.4c-4.2-7-8.7-28.7-6.3-42.4 c1.2-6.5,5.2-10.2,8.5-12.1c3.2-1.8,8.1-2,11.4-2c0,0,0,0,0,0C23.1,35,23.1,35,23.2,35L23.2,35z"
|
||||||
|
fill="#D4B08C"
|
||||||
|
id="head-shadow"
|
||||||
|
/></g
|
||||||
|
></g
|
||||||
|
><path
|
||||||
|
d="M22.6,40c19.1,0,20.7,13.8,20.8,15.1c1.1,11.9-3,28.1-6.8,33.7c-4,5.9-9.8,8.1-13.5,8.3 c-0.2,0-0.2,0-0.3,0c-0.1,0-0.1,0-0.2,0C18.8,96.8,13,94.6,9,88.7c-3.8-5.6-7.9-21.8-6.8-33.8C2.3,53.7,3.5,40,22.6,40z"
|
||||||
|
fill="#F2CEA5"
|
||||||
|
id="head"
|
||||||
|
/></g
|
||||||
|
></svg
|
||||||
|
>
|
||||||
|
</ImgLoader>
|
||||||
|
{#if handle}
|
||||||
|
<span class="text-[0.6rem] mt-4 -ml-1">{handle.length >= 12 ? handle.slice(0, 10) + "..." : handle}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div on:click={() => extrenalNavigate(`${APP_BASE}/profile/${$mainStore.user.auth.userId}`)} aria-hidden class="flex flex-col w-16 mt-1 ml-4 px-2 py-3 link">
|
||||||
|
<span class="text-[0.6rem] mb-2">Influence</span><span class="text-[0.95rem] mb-2"
|
||||||
|
>{$mainStore?.user?.profile?.yup?.weight}</span
|
||||||
|
><span class="text-[0.7rem]">10<br />MAX</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="address text-[0.8rem]">
|
||||||
|
<span
|
||||||
|
class="mb-2"
|
||||||
|
>Address: {truncteEVMAddr($mainStore?.user?.auth?.address)}
|
||||||
|
<svg on:click={() => copyAddress()} aria-hidden="true" class="w-4 interactive-svg" viewBox="0 0 24 24"
|
||||||
|
><path
|
||||||
|
fill="#eee"
|
||||||
|
d="M14 8H4c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2h10c1.103 0 2-.897 2-2V10c0-1.103-.897-2-2-2z"
|
||||||
|
/><path fill="#eee" d="M20 2H10a2 2 0 0 0-2 2v2h8a2 2 0 0 1 2 2v8h2a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" /></svg
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
<span>Balance: {formatNumber($mainStore?.user?.profile.yup.balance, 2)}
|
||||||
|
{#if $mainStore?.user?.profile.yup.balance > 0 && $mainStore?.settings?.coinGeckoPrice > 0}
|
||||||
|
<span class="text-[0.6rem] opacity-70 ml-1">${formatNumber($mainStore?.user?.profile.yup.balance * $mainStore?.settings.coinGeckoPrice, 2)}</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RateWebsite />
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.address {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
border: 1px solid #2c2c2c;
|
||||||
|
box-shadow: inset -1px 0px 18px 0px #0000004f;
|
||||||
|
background: #1a1a1a;
|
||||||
|
height: 3.4rem;
|
||||||
|
width: 130%;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: -2rem;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
border: 2px solid #d3d9df1a;
|
||||||
|
box-shadow: 0px 0px 0px 4px #0000004f;
|
||||||
|
transition: all .4s ease-in-out;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.4s ease-in-out;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #00000027;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar:hover,
|
||||||
|
.link:hover {
|
||||||
|
color: #fffee5;
|
||||||
|
background-color: #0000004f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
border: 1px solid #d3d9df73;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar:hover {
|
||||||
|
border: 2px solid #d3d9df73;
|
||||||
|
}
|
||||||
|
</style>
|
89
src/pages/Notifications.svelte
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { getNotifStorage, setSettings } from '@/utils/storage'
|
||||||
|
import { mainStore } from '@/utils/store'
|
||||||
|
import PageLoader from '@/components/PageLoader.svelte';
|
||||||
|
import { getNotifications } from '@/utils/notifications';
|
||||||
|
import Notification from '@/components/Notification.svelte';
|
||||||
|
import { clearNotifications } from '@/utils/notifications';
|
||||||
|
import { clearBadge } from '@/utils/chrome-misc'
|
||||||
|
|
||||||
|
let loading = true;
|
||||||
|
let noNotifications = false;
|
||||||
|
let notifs = [];
|
||||||
|
let pastNotifsPromise
|
||||||
|
let type = 'all'
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
notifs = await getNotifications({
|
||||||
|
userId: $mainStore.user.auth.userId,
|
||||||
|
type,
|
||||||
|
skip: '0',
|
||||||
|
limit: '15'
|
||||||
|
})
|
||||||
|
pastNotifsPromise = getNotifStorage()
|
||||||
|
|
||||||
|
console.log(notifs);
|
||||||
|
loading = false;
|
||||||
|
clearNotifications($mainStore).then(
|
||||||
|
() => {
|
||||||
|
clearBadge()
|
||||||
|
$mainStore.settings.hasNewNotifications = false
|
||||||
|
setSettings($mainStore.settings)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const changeNotifsType = async (t :string) => {
|
||||||
|
if(type === t) return;
|
||||||
|
if(!['all', 'rewards'].includes(t)){
|
||||||
|
console.error('Invalid type');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
type = t;
|
||||||
|
loading = true;
|
||||||
|
notifs = await getNotifications({
|
||||||
|
userId: $mainStore.user.auth.userId,
|
||||||
|
type: t,
|
||||||
|
skip: '0',
|
||||||
|
limit: '15'
|
||||||
|
})
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<PageLoader />
|
||||||
|
{:else if notifs.length === 0}
|
||||||
|
|
||||||
|
{#await pastNotifsPromise}
|
||||||
|
|
||||||
|
{:then pastNotifs}
|
||||||
|
{#if (pastNotifs.notifs.reverse() ?? []).length > 0}
|
||||||
|
{noNotifications = true}
|
||||||
|
{:else}
|
||||||
|
{#each pastNotifs.notifs.reverse() as notif}
|
||||||
|
<Notification {notif} />
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
{:else}
|
||||||
|
<div class="text-[0.75rem] py-1">
|
||||||
|
<span on:click={() => changeNotifsType('all')} aria-hidden class="inline-block mr-2 interactive-svg text-blue-200 interactive-svg" >All</span>
|
||||||
|
<span on:click={() => changeNotifsType('rewards')} aria-hidden class="text-blue-200 interactive-svg interactive-svg text-blue-200 interactive-svg">Rewards</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
{#each notifs.reverse() as notif}
|
||||||
|
<Notification {notif} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if noNotifications }
|
||||||
|
<div class="flex flex-col items-center justify-center h-full">
|
||||||
|
<p class="text-2xl font-semibold">No Notifications</p>
|
||||||
|
<p class="text-lg">You have no notifications</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
149
src/pages/Settings.svelte
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import '@/popup/scss/settings.scss';
|
||||||
|
import { wipeStorage, setSettings } from "@/utils/storage";
|
||||||
|
import { mainStore } from '@/utils/store'
|
||||||
|
import { navigate } from "@/utils/router";
|
||||||
|
import { storageDefault } from '@/utils/storage'
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import PageLoader from "@/components/PageLoader.svelte";
|
||||||
|
import { clearBadge, reloadExtension } from '@/utils/chrome-misc';
|
||||||
|
|
||||||
|
let settings: typeof $mainStore['settings'] = null;
|
||||||
|
let settingLoading = false;
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
wipeStorage().then(async () => {
|
||||||
|
await Promise.all([mainStore.set(storageDefault),
|
||||||
|
clearBadge(),
|
||||||
|
navigate("/login")]);
|
||||||
|
reloadExtension()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSettingsLocal = async (setting: string, value = '') => {
|
||||||
|
settingLoading = true;
|
||||||
|
if(value) $mainStore.settings[setting] = value;
|
||||||
|
else {
|
||||||
|
$mainStore.settings[setting] = !$mainStore.settings[setting];
|
||||||
|
}
|
||||||
|
settings = $mainStore.settings;
|
||||||
|
await setSettings($mainStore.settings);
|
||||||
|
settingLoading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
settings = $mainStore.settings
|
||||||
|
console.log(settings)
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{#if settings}
|
||||||
|
<button on:click={() => logout() } class="logout" >
|
||||||
|
<svg class="w-5" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id=""/><g id=""><g>
|
||||||
|
<path fill="#fff" d="M20.9,11.6c-0.1-0.1-0.1-0.2-0.2-0.3l-3-3c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l1.3,1.3H13c-0.6,0-1,0.4-1,1s0.4,1,1,1h4.6 l-1.3,1.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l3-3c0.1-0.1,0.2-0.2,0.2-0.3C21,12.1,21,11.9,20.9,11.6z "/>
|
||||||
|
<path fill="#fff" d="M15.5,18.1C14.4,18.7,13.2,19,12,19c-3.9,0-7-3.1-7-7s3.1-7,7-7c1.2,0,2.4,0.3,3.5,0.9c0.5,0.3,1.1,0.1,1.4-0.4 c0.3-0.5,0.1-1.1-0.4-1.4C15.1,3.4,13.6,3,12,3c-5,0-9,4-9,9s4,9,9,9c1.6,0,3.1-0.4,4.5-1.2c0.5-0.3,0.6-0.9,0.4-1.4 C16.6,18,16,17.8,15.5,18.1z"/>
|
||||||
|
</g></g>
|
||||||
|
</svg> Logout
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<section class="switches-settings flex flex-col w-full justify-center" data-theme="blue">
|
||||||
|
<h2 class="text-1xl font-bold">Settings</h2>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="switch switch--4 text-[0.8rem] flex flex-col mb-4">
|
||||||
|
<span class="inline-block">Enable Notifications</span>
|
||||||
|
<label aria-hidden class="switch__label mt-2">
|
||||||
|
<input type="checkbox" class="switch__input"
|
||||||
|
on:click={() => setSettingsLocal('notificationsEnabled')}
|
||||||
|
checked={settings.notificationsEnabled}
|
||||||
|
>
|
||||||
|
<span class="switch__design"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="switch switch--4 text-[0.8rem] flex flex-col mb-4">
|
||||||
|
<span class="inline-block switch-txt">Light Theme</span>
|
||||||
|
<label class="switch__label mt-2">
|
||||||
|
<input type="checkbox" class="switch__input"
|
||||||
|
on:click={() => setSettingsLocal('theme', settings.theme === 'light' ? 'dark' : 'light' )}
|
||||||
|
checked={settings.theme === 'light'}
|
||||||
|
>
|
||||||
|
<span class="switch__design"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="switch switch--4 text-[0.8rem] flex flex-col mb-4">
|
||||||
|
<span class="inline-block switch-txt">Inject overlay for all websites</span>
|
||||||
|
<label class="switch__label mt-2">
|
||||||
|
<input type="checkbox" class="switch__input"
|
||||||
|
on:click={() => setSettingsLocal('injectEmbed')}
|
||||||
|
checked={settings.injectEmbed}
|
||||||
|
>
|
||||||
|
<span class="switch__design"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="switch switch--4 text-[0.8rem] flex flex-col mb-4">
|
||||||
|
<span class="inline-block">Browser notification at reward</span>
|
||||||
|
<label aria-hidden class="switch__label mt-2">
|
||||||
|
<input type="checkbox" class="switch__input"
|
||||||
|
on:click={() => setSettingsLocal('chromeNotifWhenReward')}
|
||||||
|
checked={settings.chromeNotifWhenReward}
|
||||||
|
>
|
||||||
|
<span class="switch__design"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="switch switch--4 text-[0.8rem] flex flex-col mb-4">
|
||||||
|
<span class="inline-block">Browser notification actions refill</span>
|
||||||
|
<label class="switch__label mt-2">
|
||||||
|
<input on:click={() => setSettingsLocal('chromeNotifWhenAbleToVote')} type="checkbox" class="switch__input"
|
||||||
|
checked={settings.chromeNotifWhenAbleToVote}
|
||||||
|
>
|
||||||
|
<span class="switch__design"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{:else}
|
||||||
|
<PageLoader />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
.switch-txt{
|
||||||
|
top: -0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #160e0e;
|
||||||
|
box-shadow: inset 1px 1px 16px 3px #004985d1;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.2rem;
|
||||||
|
color: #f0f8ff;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0.4rem 1rem;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
}
|
||||||
|
.logout:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout:active {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
0
src/pages/SiteOverlay.svelte
Normal file
104
src/pages/Usage.svelte
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { createActionUsage } from '@/utils/user';
|
||||||
|
import { mainStore } from '@/utils/store';
|
||||||
|
import { getTimeRemaining } from '@/utils/time';
|
||||||
|
import PageLoader from '@/components/PageLoader.svelte';
|
||||||
|
|
||||||
|
let isReset = false;
|
||||||
|
let hoursSpan = '0'
|
||||||
|
let minutesSpan = '0'
|
||||||
|
let secondsSpan = '0'
|
||||||
|
|
||||||
|
|
||||||
|
let data: Awaited<ReturnType<typeof createActionUsage>> = null
|
||||||
|
|
||||||
|
|
||||||
|
function initializeClock(endtime) {
|
||||||
|
function updateClock() {
|
||||||
|
var t = getTimeRemaining(endtime);
|
||||||
|
|
||||||
|
hoursSpan = ('0' + t.hours).slice(-2);
|
||||||
|
minutesSpan = ('0' + t.minutes).slice(-2);
|
||||||
|
secondsSpan = ('0' + t.seconds).slice(-2);
|
||||||
|
|
||||||
|
if (t.total <= 0) {
|
||||||
|
clearInterval(timeinterval);
|
||||||
|
isReset = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateClock();
|
||||||
|
var timeinterval = setInterval(updateClock, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
data = await createActionUsage($mainStore.user.auth.userId, $mainStore.user.profile.yup.balance);
|
||||||
|
const resetDate = new Date(data.nextReset)
|
||||||
|
isReset = getTimeRemaining(resetDate).total <= 0;
|
||||||
|
if(!isReset) initializeClock(resetDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if data}
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<p class="semi-bold text-[1.05rem]">Likes Remaining:</p>
|
||||||
|
<p class="semi-bold text-[0.95rem] my-1">{data.actionBars.vote}</p>
|
||||||
|
<p class="semi-bold text-[1.05rem]">Follows Remaining:</p>
|
||||||
|
<p class="semi-bold text-[0.95rem] my-1">{data.actionBars.follow}</p>
|
||||||
|
</div>
|
||||||
|
{#if !isReset}
|
||||||
|
<p class="semi-bold text-[1.15rem]">Refill Coutdown</p>
|
||||||
|
<div id="clockdiv">
|
||||||
|
<div>
|
||||||
|
<span class="hours">{hoursSpan}</span>
|
||||||
|
<div class="smalltext">Hours</div>
|
||||||
|
</div>
|
||||||
|
<div><span class="minutes">{minutesSpan}</span>
|
||||||
|
<div class="smalltext">Minutes</div>
|
||||||
|
</div>
|
||||||
|
<div><span class="seconds">{secondsSpan}</span>
|
||||||
|
<div class="smalltext">Seconds</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p>✨</p>
|
||||||
|
<p class="semi-bold text-[1.05rem]">There have been more than 24h since your last reset.
|
||||||
|
<span class="mt-2 block">You can now reset your actions by doing a rating.</span></p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{:else}
|
||||||
|
<PageLoader />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
#clockdiv {
|
||||||
|
color: aliceblue;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 100;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clockdiv > div {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #00000057;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clockdiv div > span {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #3b45917a;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smalltext {
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 0.77rem
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
13
src/popup/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import "./popup.scss"
|
||||||
|
import Entry from "@/pages/Entry.svelte";
|
||||||
|
// import { storage } from "src/storage";
|
||||||
|
|
||||||
|
const target = document.getElementById("app");
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
// storage.get().then(({ count }) => {
|
||||||
|
new Entry({target});
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", render);
|
11
src/popup/popup.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Popup</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="./index.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
158
src/popup/popup.scss
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
::-webkit-scrollbar{
|
||||||
|
height: 7px;
|
||||||
|
width: 7px;
|
||||||
|
background: #747474;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(34, 34, 34);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:horizontal{
|
||||||
|
background: rgb(34, 34, 34);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html{
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Crete Round';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('../assets//fonts/55xoey1sJNPjPiv1ZZZrxK110b3wKg.woff2') format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-section {
|
||||||
|
width: 252px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Crete Round', serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #e4edff;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
transition: color 0.5s ease-in-out;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #eeb845;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.svg-fill {
|
||||||
|
fill: #e4edff;
|
||||||
|
transition: fill 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-fill:hover {
|
||||||
|
fill: #eeb845;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-text {
|
||||||
|
background-color: #ca4246;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
#ca4246 16.666%,
|
||||||
|
#e16541 16.666%,
|
||||||
|
#e16541 33.333%,
|
||||||
|
#f18f43 33.333%,
|
||||||
|
#f18f43 50%,
|
||||||
|
#8b9862 50%,
|
||||||
|
#8b9862 66.666%,
|
||||||
|
#476098 66.666%,
|
||||||
|
#476098 83.333%,
|
||||||
|
#a7489b 83.333%
|
||||||
|
);
|
||||||
|
|
||||||
|
background-size: 100%;
|
||||||
|
background-repeat: repeat;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
animation: rainbow-text-simple-animation-rev 0.75s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-text:hover {
|
||||||
|
animation: rainbow-text-simple-animation 0.5s ease-in forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbow-text-simple-animation-rev {
|
||||||
|
0% {
|
||||||
|
background-size: 650%;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
background-size: 650%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbow-text-simple-animation {
|
||||||
|
0% {
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
background-size: 650%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-size: 650%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin-reverse {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(-360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactive-svg {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactive-svg:hover {
|
||||||
|
transform: scale(1.2);
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactive-svg:active {
|
||||||
|
transform: scale(1.1);
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
121
src/popup/scss/settings.scss
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
.switches-settings {
|
||||||
|
|
||||||
|
&[data-theme="green"] {
|
||||||
|
--primary-light: hsl(160, 79%, 46%);
|
||||||
|
--primary-dark: hsl(160, 79%, 16%);
|
||||||
|
--ripple: hsla(160, 79%, 46%, .1);
|
||||||
|
--focus: hsl(160, 69%, 46%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-theme="blue"] {
|
||||||
|
--primary-light: hsl(200, 79%, 46%);
|
||||||
|
--primary-dark: hsl(200, 79%, 16%);
|
||||||
|
--ripple: hsla(200, 79%, 46%, .1);
|
||||||
|
--focus: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
& {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// main
|
||||||
|
// ==========================================================
|
||||||
|
.switch {
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
z-index: -1;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input:focus+&__design {
|
||||||
|
box-shadow: 0 0 0 .1rem var(--global-background), 0 0 0 .2rem var(--focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// variables
|
||||||
|
// ----------------------------------------------------
|
||||||
|
& {
|
||||||
|
--width: 2.6rem;
|
||||||
|
--height: 1.4rem;
|
||||||
|
|
||||||
|
--background: hsl(0, 0%, 30%);
|
||||||
|
--checked-background: var(--primary-dark);
|
||||||
|
|
||||||
|
--thumb-size: 0.9rem;
|
||||||
|
--thumb-ripple-color: var(--ripple);
|
||||||
|
--thumb-background: hsl(0, 0%, 65%);
|
||||||
|
--checked-thumb-background: var(--primary-light);
|
||||||
|
--thumb-space-between-edges: .6rem;
|
||||||
|
--thumb-out: var(--thumb-space-between-edges);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&--4 {
|
||||||
|
--border: 1px solid hsl(0, 0%, 60%);
|
||||||
|
--background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// appearance
|
||||||
|
// ----------------------------------------------------
|
||||||
|
&__design {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
width: var(--width);
|
||||||
|
height: var(--height);
|
||||||
|
border: var(--border);
|
||||||
|
border-radius: 100rem;
|
||||||
|
|
||||||
|
background: var(--background);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
transition: .2s, box-shadow 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__design::before {
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
left: var(--thumb-out);
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
width: var(--thumb-size);
|
||||||
|
height: var(--thumb-size);
|
||||||
|
border-radius: 100rem;
|
||||||
|
|
||||||
|
background: var(--thumb-background);
|
||||||
|
|
||||||
|
transition: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// states
|
||||||
|
// ----------------------------------------------------
|
||||||
|
&__input:checked+&__design {
|
||||||
|
border-color: transparent;
|
||||||
|
background: var(--checked-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input:checked+&__design::before {
|
||||||
|
left: calc(100% - (var(--thumb-size) + var(--thumb-out)));
|
||||||
|
background: var(--checked-thumb-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
9
src/utils/auth.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const fetchWAuth = async (store, endpoint: string, options?: any) => {
|
||||||
|
if (!options) options = {}
|
||||||
|
if (!options.headers) options.headers = {}
|
||||||
|
if (!options.headers['Content-Type']) options.headers['Content-Type'] = 'application/json'
|
||||||
|
if (!options.headers['Authorization']) options.headers['Authorization'] = 'Bearer ' + store.user.auth.authToken
|
||||||
|
return fetch(endpoint, options)
|
||||||
|
}
|
37
src/utils/chrome-misc.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export const copy = async (text: string) => {
|
||||||
|
return await navigator.clipboard.writeText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const chromeUrl = (url: string) => {
|
||||||
|
return chrome.runtime.getURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const extrenalNavigate = (url) => {
|
||||||
|
chrome.tabs.create({url})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setBadge = async (text: string, color = '#222') => {
|
||||||
|
chrome?.action?.setBadgeBackgroundColor({color})
|
||||||
|
chrome?.action?.setBadgeText({text})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearBadge = async () => {
|
||||||
|
chrome?.action?.setBadgeBackgroundColor({color: '#00000000'})
|
||||||
|
chrome?.action?.setBadgeText({text: ''})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCurrentTab = () => {
|
||||||
|
return chrome.tabs.query({
|
||||||
|
active: true,
|
||||||
|
lastFocusedWindow: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getExtensionVersion = () => {
|
||||||
|
return chrome.runtime.getManifest().version
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reloadExtension = () => {
|
||||||
|
chrome.runtime.reload()
|
||||||
|
}
|
12
src/utils/misc.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const formatNumber = (num: number, digits = 0) => {
|
||||||
|
return Intl.NumberFormat('en-US', {
|
||||||
|
notation: 'compact',
|
||||||
|
maximumFractionDigits: digits
|
||||||
|
}).format(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const truncteEVMAddr = (addr: string) => ((addr ?? '').length > 4 ? addr.substring(0, 5) + '...' + addr.substring(addr.length - 3) : '')
|
||||||
|
|
||||||
|
export const isUrlInvalid = (url: string) => !url ||
|
||||||
|
!/^http(s)?/gms.test(url) || url.startsWith('http://localhost')
|
||||||
|
|| /^d+\.d+\.d+\.d+\./gms.test(url) || /^http(s)?:\/\/\[/gms.test(url)
|
97
src/utils/notifications.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import {API_BASE} from '@/constants/config'
|
||||||
|
import type { StorageType } from '@/utils/storage'
|
||||||
|
import { fetchWAuth } from '@/utils/auth'
|
||||||
|
import { wait } from '@/utils/time'
|
||||||
|
|
||||||
|
export const getNotifications = async (
|
||||||
|
{ type, limit, skip, userId } = { type: 'all', limit: '10', skip: '0' } as { userId: string, type: string; limit?: string; skip?: string }
|
||||||
|
) => {
|
||||||
|
let req
|
||||||
|
|
||||||
|
if (type === 'all') {
|
||||||
|
req = await fetch(`${API_BASE}/notifications/${userId}?skip=${skip}&limit=${limit}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=utf-8'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (type === 'vote') {
|
||||||
|
req = await fetch(`${API_BASE}/notifications/${userId}?skip=${skip}&limit=${limit}&type=vote`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=utf-8'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
req = await fetch(`${API_BASE}/notifications/${userId}?skip=${skip}&limit=${limit}&type=reward`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=utf-8'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.ok) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const data = await req.json()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearNotifications = async (store: StorageType) => {
|
||||||
|
let req
|
||||||
|
let failed = true
|
||||||
|
let retry = 0
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
failed = false
|
||||||
|
req = await fetchWAuth(store, `${API_BASE}/notifications/${store.user.auth.userId}`)
|
||||||
|
} catch {
|
||||||
|
failed = true
|
||||||
|
await console.log(
|
||||||
|
`[ Account: ${store.user.auth.userId} ] Failed fetch on 'notifications' (probably 💩 network) Recursive retry in 2.5s`
|
||||||
|
)
|
||||||
|
retry++
|
||||||
|
if (retry > 3) {
|
||||||
|
await console.log(`[ Account: ${store.user.auth.userId} ] Failed fetch on 'notifications' (probably 💩 network)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await wait(2500)
|
||||||
|
}
|
||||||
|
} while (failed)
|
||||||
|
|
||||||
|
retry = 0
|
||||||
|
|
||||||
|
if (req.ok) {
|
||||||
|
const notif = await req.json()
|
||||||
|
|
||||||
|
for (const n of notif) {
|
||||||
|
if (!n.seen) {
|
||||||
|
const data = {
|
||||||
|
id: n['_id']
|
||||||
|
}
|
||||||
|
failed = true
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
failed = false
|
||||||
|
await fetchWAuth(store, `${API_BASE}/notifications/seen`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
failed = true
|
||||||
|
await console.log(
|
||||||
|
`[ Account: ${store.user.auth.userId} ] Failed to mark notification ${data.id} as seen (probably 💩 network) retry in 2.5 seconds.`
|
||||||
|
)
|
||||||
|
retry++
|
||||||
|
if (retry > 3) {
|
||||||
|
await console.log(`[ Account: ${store.user.auth.userId} ] Failed fetch on 'notifications seen set' (probably 💩 network)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await wait(2500)
|
||||||
|
}
|
||||||
|
} while (failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/utils/router.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
|
||||||
|
const routes = {
|
||||||
|
'/': {
|
||||||
|
component: () => import('@/pages/Main.svelte'),
|
||||||
|
title: 'Main'
|
||||||
|
},
|
||||||
|
'/login': {
|
||||||
|
component: () => import('@/pages/Login.svelte'),
|
||||||
|
title: 'Login'
|
||||||
|
},
|
||||||
|
'/usage' : {
|
||||||
|
component: () => import('@/pages/Usage.svelte'),
|
||||||
|
title: 'Usage'
|
||||||
|
},
|
||||||
|
'/settings' : {
|
||||||
|
component: () => import('@/pages/Settings.svelte'),
|
||||||
|
title: 'Settings'
|
||||||
|
},
|
||||||
|
'/notifications' : {
|
||||||
|
component: () => import('@/pages/Notifications.svelte'),
|
||||||
|
title: 'Notifications'
|
||||||
|
},
|
||||||
|
'/info': {
|
||||||
|
component: () => import('@/pages/Info.svelte'),
|
||||||
|
title: 'Info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastMountedRoute = null
|
||||||
|
|
||||||
|
export const getCurentRouteComponent = () => {
|
||||||
|
const path = window.location.pathname
|
||||||
|
return routes[path]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCurrentRoutePath = () => {
|
||||||
|
return window.location.pathname
|
||||||
|
}
|
||||||
|
|
||||||
|
export const navigate = (path: string, props: {} = {}) => {
|
||||||
|
new Promise<void>((resolve) => {
|
||||||
|
window.history.pushState({}, '', path)
|
||||||
|
const { component, title } = getCurentRouteComponent()
|
||||||
|
component().then(({ default: Component }) => {
|
||||||
|
console.log({ target: document.getElementById('router'), props })
|
||||||
|
if (lastMountedRoute) lastMountedRoute.$destroy()
|
||||||
|
lastMountedRoute = new Component({ target: document.getElementById('router'), props })
|
||||||
|
document.title = title
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
137
src/utils/storage.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { SEND_AUTH_NOTIF } from '@/constants/messeges'
|
||||||
|
|
||||||
|
export const storageDefault = {
|
||||||
|
user: {
|
||||||
|
auth: {
|
||||||
|
ethSignature: '',
|
||||||
|
userId: '',
|
||||||
|
address: '',
|
||||||
|
authToken: '',
|
||||||
|
username: '',
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
_id: '',
|
||||||
|
handle: '',
|
||||||
|
yupScore: 0,
|
||||||
|
avatar: '',
|
||||||
|
yup: {
|
||||||
|
_id: '',
|
||||||
|
handle: '',
|
||||||
|
bio: '',
|
||||||
|
avatar: '',
|
||||||
|
weight: 0,
|
||||||
|
balance: 0,
|
||||||
|
},
|
||||||
|
lens: {
|
||||||
|
avatar: '',
|
||||||
|
profileId: '',
|
||||||
|
handle: '',
|
||||||
|
},
|
||||||
|
farcaster: {
|
||||||
|
fid: '',
|
||||||
|
handle: '',
|
||||||
|
avatar: '',
|
||||||
|
},
|
||||||
|
ens: {
|
||||||
|
handle: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
theme: 'dark',
|
||||||
|
notificationsEnabled: false,
|
||||||
|
injectEmbed: false,
|
||||||
|
chromeNotifWhenReward: false,
|
||||||
|
chromeNotifWhenAbleToVote: false,
|
||||||
|
coinGeckoPrice: 0,
|
||||||
|
hasNewNotifications: false,
|
||||||
|
refilNotifTimestamp: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const storageNotifsDefault = {
|
||||||
|
lastRewardNotif: {
|
||||||
|
createdAt: 0,
|
||||||
|
id: '',
|
||||||
|
},
|
||||||
|
notifs: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StorageType = typeof storageDefault
|
||||||
|
|
||||||
|
export const wipeStorage = async () => {
|
||||||
|
await chrome.storage.local.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initStorage = async () => {
|
||||||
|
const store = await chrome.storage.local.get('store')
|
||||||
|
const updateStore = { store: { ...storageDefault, ...store.store } }
|
||||||
|
const updateCondition = !store.store || !store.store.user || !store.store.user.auth ||
|
||||||
|
store.store.user.auth.authToken || !store.store.settings || !store.store.settings.coinGeckoPrice
|
||||||
|
|| !store.store.user.profile || !store.store.user.profile._id
|
||||||
|
if(updateCondition) {
|
||||||
|
await chrome.storage.local.set(updateStore)
|
||||||
|
console.info('Storage initialized')
|
||||||
|
}
|
||||||
|
const notifs = await chrome.storage.local.get('notifs')
|
||||||
|
const updateNotifs = { ...storageNotifsDefault, ...notifs }
|
||||||
|
const updateNotifsCondition = !notifs.notifs || !notifs.lastRewardNotif
|
||||||
|
if(updateNotifsCondition) {
|
||||||
|
await chrome.storage.local.set({ notifs: updateNotifs })
|
||||||
|
console.info('Notifs storage initialized')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNotifStorage = async () => {
|
||||||
|
const notifs = await chrome.storage.local.get('notifs')
|
||||||
|
return notifs ? notifs as typeof storageNotifsDefault : storageNotifsDefault as typeof storageNotifsDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setNotifStorageNotifs = async (notifs: any[]) => {
|
||||||
|
const notifsStorage = await getNotifStorage()
|
||||||
|
await chrome.storage.local.set({ ...notifsStorage, notifs: notifs })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setNotifStorageLastRewardNotif = async (lastRewardNotif) => {
|
||||||
|
const notifsStorage = await getNotifStorage()
|
||||||
|
await chrome.storage.local.set({ ...notifsStorage, lastRewardNotif: lastRewardNotif })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setAuth = async (auth) => {
|
||||||
|
const storeAuth = await chrome.storage.local.get('store')
|
||||||
|
let profile
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://api.yup.io/web3-profiles/' + auth.address)
|
||||||
|
profile = await res.json()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching profile', error)
|
||||||
|
}
|
||||||
|
const updateObj = { store: { ...storeAuth.store, user: { auth: { ...auth }, } } }
|
||||||
|
if(profile){
|
||||||
|
updateObj.store.user.profile = profile
|
||||||
|
}
|
||||||
|
await chrome.storage.local.set(updateObj)
|
||||||
|
chrome.runtime.sendMessage({ type: SEND_AUTH_NOTIF })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setProfile = async (profile) => {
|
||||||
|
const store = await chrome.storage.local.get('store')
|
||||||
|
await chrome.storage.local.set({ store: { ...store.store, user: { ...store.store.user, profile } } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setSettings = async (settings) => {
|
||||||
|
const store = await chrome.storage.local.get('store')
|
||||||
|
await chrome.storage.local.set({ store: { ...store.store, settings: { ...store.store.settings, ...settings } } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStore = async () => {
|
||||||
|
const store = await chrome.storage.local.get('store')
|
||||||
|
return store ? store.store as StorageType : storageDefault as StorageType
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSettings = async () => {
|
||||||
|
const store = await chrome.storage.local.get('store')
|
||||||
|
return store ? store.store.settings as StorageType['settings'] : storageDefault.settings as StorageType['settings']
|
||||||
|
}
|
||||||
|
|
7
src/utils/store.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import type { StorageType } from './storage';
|
||||||
|
import { storageDefault } from './storage';
|
||||||
|
import type Alert from '@/components/Alert.svelte';
|
||||||
|
|
||||||
|
export const mainStore = writable<StorageType>(storageDefault)
|
||||||
|
export const alertStore = writable<Alert>(null)
|
60
src/utils/time.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
export const getTimeRemaining = (endtime) => {
|
||||||
|
const t = Date.parse(endtime) - Date.parse(new Date().toISOString())
|
||||||
|
return {
|
||||||
|
'total': t,
|
||||||
|
'days': Math.floor(t / (1000 * 60 * 60 * 24)),
|
||||||
|
'hours': Math.floor((t / (1000 * 60 * 60)) % 24),
|
||||||
|
'minutes': Math.floor((t / 1000 / 60) % 60),
|
||||||
|
'seconds': Math.floor((t / 1000) % 60)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const timeSince = (date: Date) => {
|
||||||
|
|
||||||
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
||||||
|
let interval = seconds / 31536000;
|
||||||
|
let intervalType: string;
|
||||||
|
|
||||||
|
if (interval >= 1) {
|
||||||
|
intervalType = 'year';
|
||||||
|
} else {
|
||||||
|
interval = Math.floor(seconds / 2592000);
|
||||||
|
if (interval >= 1) {
|
||||||
|
intervalType = 'month';
|
||||||
|
} else {
|
||||||
|
interval = Math.floor(seconds / 86400);
|
||||||
|
if (interval >= 1) {
|
||||||
|
intervalType = 'day';
|
||||||
|
} else {
|
||||||
|
interval = Math.floor(seconds / 3600);
|
||||||
|
if (interval >= 1) {
|
||||||
|
intervalType = "hour";
|
||||||
|
} else {
|
||||||
|
interval = Math.floor(seconds / 60);
|
||||||
|
if (interval >= 1) {
|
||||||
|
intervalType = "minute";
|
||||||
|
} else {
|
||||||
|
interval = seconds;
|
||||||
|
intervalType = "second";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interval > 1 || interval === 0) {
|
||||||
|
intervalType += 's';
|
||||||
|
}
|
||||||
|
|
||||||
|
return interval + ' ' + intervalType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wait = (ms: number) => {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const closeTo = (date1: Date, date2: Date, tolerance: number) => {
|
||||||
|
const a = date1.getTime()
|
||||||
|
const b = date2.getTime()
|
||||||
|
return Math.abs(a - b) < tolerance
|
||||||
|
}
|
38
src/utils/types.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export interface Vote {
|
||||||
|
influence: number
|
||||||
|
like: boolean
|
||||||
|
postid: string
|
||||||
|
rating: number
|
||||||
|
lastUpdated: string
|
||||||
|
timestamp: string
|
||||||
|
voter: string
|
||||||
|
_id: {
|
||||||
|
voteid: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface Notification {
|
||||||
|
_id: string
|
||||||
|
action: string
|
||||||
|
image: string
|
||||||
|
invoker: {
|
||||||
|
username: string
|
||||||
|
eosname: string
|
||||||
|
}
|
||||||
|
like: boolean
|
||||||
|
post: {
|
||||||
|
postid: string
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
tag: string
|
||||||
|
}
|
||||||
|
seen: boolean
|
||||||
|
postid: string
|
||||||
|
rating: number
|
||||||
|
recipient: string
|
||||||
|
voter: string
|
||||||
|
createdAt: string
|
||||||
|
quantity?: string
|
||||||
|
message?: string
|
||||||
|
}
|
43
src/utils/user.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
import {API_BASE} from '@/constants/config'
|
||||||
|
|
||||||
|
export const getNormalizedValue = (val: number, min: number, max: number) => {
|
||||||
|
return Math.floor(((val - min) / (max - min)) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMaxVote = (balance: number) => {
|
||||||
|
return balance >= 100 ? 250 : balance >= 0.5 ? 190 : 130
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makePercentage = (val: number) => {
|
||||||
|
return `${val}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MAX_DELETE_VOTE = 600
|
||||||
|
export const MAX_FOLLOW_USAGE = 60
|
||||||
|
|
||||||
|
export const getActionUsage = async (userId: string) => {
|
||||||
|
try {
|
||||||
|
const req = await fetch(`${API_BASE}/accounts/actionusage/${userId}`)
|
||||||
|
if (req.ok) {
|
||||||
|
return { error: false, data: await req.json() }
|
||||||
|
}
|
||||||
|
return { error: true, msg: "API didn't return expected response." }
|
||||||
|
} catch {
|
||||||
|
return { error: true, msg: 'API is not available' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createActionUsage = async (userId: string, balance: number) => {
|
||||||
|
const data = await getActionUsage(userId)
|
||||||
|
if (data.error) return { error: true, msg: 'API returned error' }
|
||||||
|
const MAX_VOTE = getMaxVote(balance)
|
||||||
|
return {
|
||||||
|
nextReset: new Date(data.data.lastReset + 864e5).toLocaleString(),
|
||||||
|
actionBars: {
|
||||||
|
deleteVote: makePercentage(getNormalizedValue(MAX_DELETE_VOTE - data.data.deleteVoteCount, 0, MAX_DELETE_VOTE)),
|
||||||
|
follow: makePercentage(getNormalizedValue(MAX_FOLLOW_USAGE - data.data.followCount, 0, MAX_FOLLOW_USAGE)),
|
||||||
|
vote: makePercentage(getNormalizedValue(MAX_VOTE - data.data.createVoteCount, 0, MAX_VOTE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
src/utils/votes.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import type { Vote } from './types'
|
||||||
|
import { fetchWAuth } from './auth'
|
||||||
|
|
||||||
|
const API_BASE = 'https://api.yup.io'
|
||||||
|
|
||||||
|
export const getPost = async (url: string): Promise<any | null> => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://api.yup.io/posts/post/url', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if(res.ok) {
|
||||||
|
return (await res.json())?.[0] ?? null
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasVote = (postId: string, account: string): Promise<Vote[]> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fetch(`${API_BASE}/votes/post/${postId}/voter/${account}`).then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
res.json().then((json) => {
|
||||||
|
resolve(json)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve([] as Vote[])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const executeVote = async ({
|
||||||
|
userVote,
|
||||||
|
post,
|
||||||
|
url,
|
||||||
|
$mainStore,
|
||||||
|
$alertStore,
|
||||||
|
noVoteAlert = false
|
||||||
|
}) => {
|
||||||
|
const body = {} as Record<string, unknown>
|
||||||
|
let voteid = ''
|
||||||
|
if (userVote?._id) {
|
||||||
|
voteid = userVote._id.voteid
|
||||||
|
} else if(post) {
|
||||||
|
body.postid = post._id.postid
|
||||||
|
} else {
|
||||||
|
body.url = url
|
||||||
|
}
|
||||||
|
body.rating = userVote.rating
|
||||||
|
body.voter = $mainStore.user.auth.userId
|
||||||
|
console.log(body.voter)
|
||||||
|
if (userVote.like) {
|
||||||
|
body.like = true
|
||||||
|
} else {
|
||||||
|
body.like = false
|
||||||
|
}
|
||||||
|
const req = await fetchWAuth($mainStore, `${API_BASE}/votes${voteid ? '/' + voteid : ''}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
if (req.ok) {
|
||||||
|
noVoteAlert || $alertStore?.show('Rating submited!')
|
||||||
|
return await req.json()
|
||||||
|
} else {
|
||||||
|
const err = await req.text()
|
||||||
|
if (err.includes('limit')) {
|
||||||
|
$alertStore?.show('Rating limit consumed!!!', 'warning')
|
||||||
|
} else if(err.includes('requests')) {
|
||||||
|
$alertStore?.show('You have made too many request try aagin after 24h', 'warning')
|
||||||
|
} else if(err.toLocaleLowerCase().includes('unauthorized')) {
|
||||||
|
$alertStore?.show('Seem your auth token is not valid anymore re-login!!', 'error')
|
||||||
|
} else {
|
||||||
|
$alertStore?.show('Vote not submited due to error try to re-login!', 'error')
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
2
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
9
svelte.config.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import sveltePreprocess from "svelte-preprocess";
|
||||||
|
import { windi } from 'svelte-windicss-preprocess';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: [windi({}), sveltePreprocess()]
|
||||||
|
};
|
29
tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
/**
|
||||||
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
|
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||||
|
* Note that setting allowJs false does not prevent the use
|
||||||
|
* of JS in `.svelte` files.
|
||||||
|
*/
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"isolatedModules": false,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*", "./dist/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.js",
|
||||||
|
"src/**/*.svelte"
|
||||||
|
],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
11
tsconfig.node.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// vite tsconfig
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts", "manifest.json"]
|
||||||
|
}
|
18
vite.config.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { crx } from "@crxjs/vite-plugin";
|
||||||
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
import { resolve } from "path";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import manifest from "./manifest.json";
|
||||||
|
|
||||||
|
const srcDir = resolve(__dirname, "src");
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [svelte(), crx({ manifest })],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
src: srcDir,
|
||||||
|
'@': srcDir
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|