diff --git a/src/Router.tsx b/src/Router.tsx
index 2c019de3..0a264f02 100644
--- a/src/Router.tsx
+++ b/src/Router.tsx
@@ -1,5 +1,6 @@
-import { Component, createReaction, createResource, lazy, Resource } from 'solid-js';
+import { Component, createResource, lazy } from 'solid-js';
import { Routes, Route, Navigate, RouteDataFuncArgs } from "@solidjs/router"
+
import Home from './pages/Home';
import Layout from './components/Layout/Layout';
import Explore from './pages/Explore';
@@ -7,9 +8,20 @@ import Thread from './pages/Thread';
import Messages from './pages/Messages';
import Notifications from './pages/Notifications';
import Downloads from './pages/Downloads';
-import Settings from './pages/Settings';
+import Settings from './pages/Settings/Settings';
import Help from './pages/Help';
-// import Profile from './pages/Profile';
+import Search from './pages/Search';
+import NotFound from './pages/NotFound';
+import EditProfile from './pages/EditProfile';
+
+import NotifSettings from './pages/Settings/Notifications';
+import Appearance from './pages/Settings/Appearance';
+import HomeFeeds from './pages/Settings/HomeFeeds';
+import ZapSettings from './pages/Settings/Zaps';
+import Muted from './pages/Settings/Muted';
+import Network from './pages/Settings/Network';
+import Menu from './pages/Settings/Menu';
+
import { PrimalWindow } from './types/primal';
import { useHomeContext } from './contexts/HomeContext';
import { useExploreContext } from './contexts/ExploreContext';
@@ -17,14 +29,12 @@ import { useThreadContext } from './contexts/ThreadContext';
import { useAccountContext } from './contexts/AccountContext';
import { useProfileContext } from './contexts/ProfileContext';
import { useSettingsContext } from './contexts/SettingsContext';
-import NotFound from './pages/NotFound';
import { fetchKnownProfiles } from './lib/profile';
-import Search from './pages/Search';
import { useMessagesContext } from './contexts/MessagesContext';
import { useMediaContext } from './contexts/MediaContext';
import { useNotificationsContext } from './contexts/NotificationsContext';
import { useSearchContext } from './contexts/SearchContext';
-import EditProfile from './pages/EditProfile';
+
const primalWindow = window as PrimalWindow;
@@ -78,7 +88,15 @@ const Router: Component = () => {
} />;
-
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/icons/add.svg b/src/assets/icons/add.svg
new file mode 100644
index 00000000..6a1dc99f
--- /dev/null
+++ b/src/assets/icons/add.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/chevron_right.svg b/src/assets/icons/chevron_right.svg
new file mode 100644
index 00000000..ee463065
--- /dev/null
+++ b/src/assets/icons/chevron_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/connect.svg b/src/assets/icons/connect.svg
new file mode 100644
index 00000000..b26103b3
--- /dev/null
+++ b/src/assets/icons/connect.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/components/PageCaption/PageCaption.module.scss b/src/components/PageCaption/PageCaption.module.scss
index 63209e81..3a9cbab2 100644
--- a/src/components/PageCaption/PageCaption.module.scss
+++ b/src/components/PageCaption/PageCaption.module.scss
@@ -8,8 +8,22 @@
font-weight: 300;
font-size: 32px;
line-height: 34px;
- color: var(--brand-text);
+ color: var(--text-secondary);
text-transform: lowercase;
+
+ a {
+ font-weight: 300;
+ font-size: 32px;
+ line-height: 34px;
+ color: var(--text-secondary);
+ text-transform: lowercase;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ // text-decoration-thickness: 1px;
+ }
+ }
}
.logo {
diff --git a/src/components/SettingsNotifications/SettingsNotifications.module.scss b/src/components/SettingsNotifications/SettingsNotifications.module.scss
index c652c99b..2cfd8504 100644
--- a/src/components/SettingsNotifications/SettingsNotifications.module.scss
+++ b/src/components/SettingsNotifications/SettingsNotifications.module.scss
@@ -14,7 +14,8 @@
.caption {
font-weight: 400;
- font-size: 14px;
- line-height: 32px;
+ font-size: 18px;
+ line-height: 20px;
color: var(--text-secondary);
+ margin-bottom: 16px;
}
diff --git a/src/components/SettingsNotifications/SettingsNotifications.tsx b/src/components/SettingsNotifications/SettingsNotifications.tsx
index 380c6919..69caf6cd 100644
--- a/src/components/SettingsNotifications/SettingsNotifications.tsx
+++ b/src/components/SettingsNotifications/SettingsNotifications.tsx
@@ -27,7 +27,7 @@ import { useSettingsContext } from '../../contexts/SettingsContext';
import { useIntl } from '@cookbook/solid-intl';
import Checkbox from '../Checkbox/Checkbox';
-const SettingsZap: Component = () => {
+const SettingsNotifications: Component = () => {
const settings = useSettingsContext();
const intl = useIntl();
@@ -173,4 +173,4 @@ const SettingsZap: Component = () => {
);
}
-export default SettingsZap;
+export default SettingsNotifications;
diff --git a/src/components/SettingsSidebar/SettingsSidebar.module.scss b/src/components/SettingsSidebar/SettingsSidebar.module.scss
new file mode 100644
index 00000000..7c1ad197
--- /dev/null
+++ b/src/components/SettingsSidebar/SettingsSidebar.module.scss
@@ -0,0 +1,102 @@
+@mixin heading {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0px;
+ width: 100%;
+ height: 44px;
+ // background-color: var(--background-site);
+ background: var(--fade-gradient-vertical);
+ z-index: 5;
+ padding-bottom: 22px;
+ display:flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ font-size: 18px;
+ font-weight: 800;
+ line-height: 22px;
+ color: var(--text-secondary-2);
+ text-transform: uppercase;
+ >div{
+ display: flex;
+ height: 22px;
+ >span {
+ color: var(--text-tertiary-2);
+ text-transform: lowercase;
+ margin-left: 6px;
+ }
+ }
+}
+
+.headingConnectedRelays {
+ @include heading;
+}
+
+.headingCachingService {
+ @include heading();
+ margin-top: 34px;
+ z-index: 10px;
+}
+
+.relayEntry {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 28px;
+ color: var(--text-secondary-2);
+ text-align: left;
+
+ .connected {
+ background-color: #66E205;
+ width: 6px;
+ height: 6px;
+ border-radius: 2px;
+ margin-right: 8px;
+ }
+
+ .disconnected {
+ background-color: #E20505;
+ width: 6px;
+ height: 6px;
+ border-radius: 2px;
+ margin-right: 8px;
+ }
+
+ .relayActions {
+ display: flex;
+ font-size: 12px;
+ line-height: 12px;
+ font-weight: 400;
+ min-width: 100px;
+ margin-left: 20px;
+
+ >span {
+ display: inline;
+ }
+
+ >button {
+ border: none;
+ background: none;
+ font-size: 12px;
+ line-height: 12px;
+ font-weight: 400;
+ margin: 2px;
+ padding: 0px;
+ color: var(--warning-color);
+ display: none;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ &:hover {
+ >span {
+ display: inline;
+ }
+ >button {
+ display: flex;
+ }
+ }
+ }
+}
diff --git a/src/components/SettingsSidebar/SettingsSidebar.tsx b/src/components/SettingsSidebar/SettingsSidebar.tsx
new file mode 100644
index 00000000..12b0428a
--- /dev/null
+++ b/src/components/SettingsSidebar/SettingsSidebar.tsx
@@ -0,0 +1,79 @@
+import { useIntl } from '@cookbook/solid-intl';
+import { Component, For, Show } from 'solid-js';
+import { useAccountContext } from '../../contexts/AccountContext';
+import { settings as t } from '../../translations';
+
+// @ts-ignore Bad types in nostr-tools
+import { Relay, relayInit } from "nostr-tools";
+
+import styles from './SettingsSidebar.module.scss';
+import { cacheServer, isConnected, socket } from '../../sockets';
+
+const SettingsSidebar: Component = () => {
+
+ const intl = useIntl();
+ const account = useAccountContext();
+
+ const connectedRelays = () => account?.relays || [];
+
+ const disconnectedRelays = () => {
+ const allRelayUrls = Object.keys(account?.relaySettings || {});
+ const connectedUrls = connectedRelays().map(r => r.url);
+
+ return allRelayUrls.reduce(
+ (acc: Relay[], url) => connectedUrls.includes(url) ? acc : [...acc, relayInit(url)],
+ [],
+ );
+ };
+
+ return (
+ <>
+
+
+ {intl.formatMessage(t.relays)}
+
+
+
+
+ {relay => (
+
+ )}
+
+
+ {relay => (
+
+ )}
+
+
+
+
+ {intl.formatMessage(t.cashingService)}
+
+
+
+
+
}
+ >
+
+
+
+ {socket()?.url || cacheServer}
+
+
+ >
+ )
+}
+
+export default SettingsSidebar;
diff --git a/src/components/SettingsZap/SettingsZap.module.scss b/src/components/SettingsZap/SettingsZap.module.scss
index 4246a364..17e77b44 100644
--- a/src/components/SettingsZap/SettingsZap.module.scss
+++ b/src/components/SettingsZap/SettingsZap.module.scss
@@ -35,3 +35,25 @@ input.zapInput {
border: 1px solid var(--subtile-devider);
border-radius: 6px;
}
+
+.restoreZaps {
+ display: flex;
+ justify-content: flex-start;
+ margin-top: 36px;
+
+ button {
+ background: none;
+ color: var(--accent-1);
+ width: auto;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 20px;
+ border: none;
+ margin: 0;
+ padding: 0;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
diff --git a/src/components/SettingsZap/SettingsZap.tsx b/src/components/SettingsZap/SettingsZap.tsx
index 3aa5e1fe..6ea5807b 100644
--- a/src/components/SettingsZap/SettingsZap.tsx
+++ b/src/components/SettingsZap/SettingsZap.tsx
@@ -1,13 +1,25 @@
-import { Component, For } from 'solid-js';
+import { Component, createSignal, For } from 'solid-js';
import styles from './SettingsZap.module.scss';
import { useSettingsContext } from '../../contexts/SettingsContext';
import { debounce } from '../../utils';
+import { useIntl } from '@cookbook/solid-intl';
+import ConfirmModal from '../ConfirmModal/ConfirmModal';
+import { settings as t } from '../../translations';
const SettingsZap: Component = () => {
+ const intl = useIntl();
const settings = useSettingsContext();
+
+ const [isRestoringZaps, setIsRestoringZaps] = createSignal(false);
+
+ const onRestoreZaps = () => {
+ settings?.actions.resetZapOptionsToDefault();
+ setIsRestoringZaps(false);
+ };
+
const changeDefaultZap = (e: InputEvent) => {
debounce(() => {
const target = e.target as HTMLInputElement;
@@ -65,6 +77,21 @@ const SettingsZap: Component = () => {
+
+
+
+
+
+ setIsRestoringZaps(false)}
+ />
);
}
diff --git a/src/constants.ts b/src/constants.ts
index 432927bc..70731e96 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -261,3 +261,14 @@ export const iosCheck = '8/01/23';
export const andCheck = '8/07/23';
export const refreshFeedDelay = 2_500;
+
+export const defaultZapAmount = 10;
+
+export const defaultZapOptions = [
+ 21,
+ 420,
+ 10_000,
+ 69_420,
+ 100_000,
+ 1_000_000,
+];
diff --git a/src/contexts/AccountContext.tsx b/src/contexts/AccountContext.tsx
index 9372902c..08340289 100644
--- a/src/contexts/AccountContext.tsx
+++ b/src/contexts/AccountContext.tsx
@@ -12,6 +12,7 @@ import {
NostrEOSE,
NostrEvent,
NostrMutedContent,
+ NostrRelay,
NostrRelays,
NostrWindow,
PrimalNote,
@@ -21,7 +22,7 @@ import { Kind, relayConnectingTimeout } from "../constants";
import { isConnected, refreshSocketListeners, removeSocketListeners, socket, subscribeTo } from "../sockets";
import { sendContacts, sendLike, sendMuteList } from "../lib/notes";
// @ts-ignore Bad types in nostr-tools
-import { Relay } from "nostr-tools";
+import { Relay, relayInit } from "nostr-tools";
import { APP_ID } from "../App";
import { getLikes, getProfileContactList, getProfileMuteList, getUserProfiles } from "../lib/profile";
import { getStorage, saveFollowing, saveLikes, saveMuted, saveMuteList, saveRelaySettings } from "../lib/localStore";
@@ -43,6 +44,7 @@ export type AccountContextStore = {
hasPublicKey: () => boolean,
isKeyLookupDone: boolean,
quotedNote: string | undefined,
+ connectToPrimaryRelays: boolean,
actions: {
showNewNoteForm: () => void,
hideNewNoteForm: () => void,
@@ -54,6 +56,9 @@ export type AccountContextStore = {
quoteNote: (noteId: string | undefined) => void,
addToMuteList: (pubkey: string) => void,
removeFromMuteList: (pubkey: string) => void,
+ addRelay: (url: string) => void,
+ removeRelay: (url: string) => void,
+ setConnectToPrimaryRelays: (flag: boolean) => void,
},
}
@@ -71,6 +76,7 @@ const initialData = {
mutedSince: 0,
isKeyLookupDone: false,
quotedNote: undefined,
+ connectToPrimaryRelays: true,
};
export const AccountContext = createContext();
@@ -97,14 +103,16 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX
}
return { ...acc, [url]: settings[url] };
- }, {});
+ }, rs);
+
+ console.log('TO SAVE: ', toSave)
if (Object.keys(toSave).length === 0) {
return;
}
updateStore('relaySettings', () => ({ ...toSave }));
- saveRelaySettings(store.publicKey, settings)
+ saveRelaySettings(store.publicKey, toSave)
}
const attachDefaultRelays = (relaySettings: NostrRelays) => {
@@ -116,9 +124,14 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX
let relayAtempts: Record = {};
const relayAtemptLimit = 10;
+ let relaysExplicitlyClosed: string[] = [];
let relayReliability: Record = {};
+ const setConnectToPrimaryRelays = (flag: boolean) => {
+ updateStore('connectToPrimaryRelays', () => flag);
+ }
+
const connectToRelays = (relaySettings: NostrRelays) => {
if (Object.keys(relaySettings).length === 0) {
@@ -126,7 +139,9 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX
return;
}
- const relaysToConnect = attachDefaultRelays(relaySettings);
+ const relaysToConnect = store.connectToPrimaryRelays ?
+ attachDefaultRelays(relaySettings) :
+ relaySettings;
const onConnect = (connectedRelay: Relay) => {
if (store.relays.find(r => r.url === connectedRelay.url)) {
@@ -149,6 +164,11 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX
updateStore('relays', (rs) => rs.filter(r => r.url !== failedRelay.url));
+ if (relaysExplicitlyClosed.includes(failedRelay.url)) {
+ relaysExplicitlyClosed = relaysExplicitlyClosed.filter(u => u !== failedRelay.url);
+ return;
+ }
+
if ((relayAtempts[failedRelay.url] || 0) < relayAtemptLimit) {
relayAtempts[failedRelay.url] = (relayAtempts[failedRelay.url] || 0) + 1;
@@ -229,6 +249,90 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX
return success;
};
+ const addRelay = (url: string) => {
+ const relay: NostrRelays = { [url]: { write: true, read: true }};
+
+ setRelaySettings(relay);
+
+ relaysExplicitlyClosed = relaysExplicitlyClosed.filter(u => u !== url);
+
+ const unsub = subscribeTo(`before_add_relay_${APP_ID}`, async (type, subId, content) => {
+ if (type === 'EOSE') {
+
+ const relayInfo = JSON.stringify(store.relaySettings);
+ const date = Math.floor((new Date()).getTime() / 1000);
+ const following = [...store.following];
+
+ const { success } = await sendContacts(following, date, relayInfo, store.relays, store.relaySettings);
+
+ if (success) {
+ updateStore('following', () => following);
+ updateStore('followingSince', () => date);
+ saveFollowing(store.publicKey, following, date);
+ }
+
+ unsub();
+ return;
+ }
+
+ if (content &&
+ content.kind === Kind.Contacts &&
+ content.created_at &&
+ content.created_at > store.followingSince
+ ) {
+ updateContacts(content);
+ }
+ });
+
+ getProfileContactList(store.publicKey, `before_add_relay_${APP_ID}`);
+ };
+
+ const removeRelay = (url: string) => {
+ const relay: Relay = store.relays.find(r => r.url === url);
+
+ if (relay) {
+ relay.close();
+ updateStore('relays', () => [...store.relays.filter(r => r.url !== url)]);
+ }
+
+ relaysExplicitlyClosed.push(url);
+ relayAtempts[url] = 0;
+
+ updateStore('relaySettings', () => ({ [url]: undefined }));
+
+ setRelaySettings(store.relaySettings);
+
+ const unsub = subscribeTo(`before_remove_relay_${APP_ID}`, async (type, subId, content) => {
+ if (type === 'EOSE') {
+
+ const relayInfo = JSON.stringify(store.relaySettings);
+ const date = Math.floor((new Date()).getTime() / 1000);
+ const following = [...store.following];
+
+ const { success } = await sendContacts(following, date, relayInfo, store.relays, store.relaySettings);
+
+ if (success) {
+ updateStore('following', () => following);
+ updateStore('followingSince', () => date);
+ saveFollowing(store.publicKey, following, date);
+ }
+
+ unsub();
+ return;
+ }
+
+ if (content &&
+ content.kind === Kind.Contacts &&
+ content.created_at &&
+ content.created_at > store.followingSince
+ ) {
+ updateContacts(content);
+ }
+ });
+
+ getProfileContactList(store.publicKey, `before_remove_relay_${APP_ID}`);
+ };
+
const updateContacts = (content: NostrContactsContent) => {
const followingSince = content.created_at;
@@ -516,6 +620,27 @@ export function AccountProvider(props: { children: number | boolean | Node | JSX
}
});
+ createEffect(() => {
+ const rels: string[] = import.meta.env.PRIMAL_PRIORITY_RELAYS?.split(',') || [];
+
+ if (store.connectToPrimaryRelays) {
+ const relaySettings = rels.reduce((acc, r) => ({ ...acc, [r]: { read: true, write: true } }), {});
+
+ connectToRelays(relaySettings)
+ }
+ else {
+ for (let i = 0; i < rels.length; i++) {
+ const url = rels[i];
+ const relay = store.relays.find(r => r.url === url);
+
+ if (relay) {
+ relay.close();
+ updateStore('relays', () => [...store.relays.filter(r => r.url !== url)]);
+ }
+ }
+ }
+ });
+
onCleanup(() => {
removeSocketListeners(
socket(),
@@ -601,6 +726,9 @@ const [store, updateStore] = createStore({
quoteNote,
addToMuteList,
removeFromMuteList,
+ addRelay,
+ removeRelay,
+ setConnectToPrimaryRelays,
},
});
diff --git a/src/contexts/SettingsContext.tsx b/src/contexts/SettingsContext.tsx
index 54d17296..2cf84e9e 100644
--- a/src/contexts/SettingsContext.tsx
+++ b/src/contexts/SettingsContext.tsx
@@ -1,6 +1,6 @@
import { createStore } from "solid-js/store";
import { useToastContext } from "../components/Toaster/Toaster";
-import { defaultFeeds, defaultNotificationSettings, themes, trendingFeed } from "../constants";
+import { defaultFeeds, defaultNotificationSettings, defaultZapAmount, defaultZapOptions, themes, trendingFeed } from "../constants";
import {
createContext,
createEffect,
@@ -55,6 +55,7 @@ export type SettingsContextStore = {
loadSettings: (pubkey: string) => void,
setDefaultZapAmount: (amount: number) => void,
setZapOptions: (amount:number, index: number) => void,
+ resetZapOptionsToDefault: (temp?: boolean) => void,
updateNotificationSettings: (key: string, value: boolean, temp?: boolean) => void,
restoreDefaultFeeds: () => void,
}
@@ -66,15 +67,8 @@ export const initialData = {
themes,
availableFeeds: [],
defaultFeed: defaultFeeds[0],
- defaultZapAmount: 10,
- availableZapOptions: [
- 21,
- 420,
- 10_000,
- 69_420,
- 100_000,
- 1_000_000,
- ],
+ defaultZapAmount: defaultZapAmount,
+ availableZapOptions: defaultZapOptions,
notificationSettings: { ...defaultNotificationSettings },
};
@@ -99,6 +93,12 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
!temp && saveSettings();
};
+ const resetZapOptionsToDefault = (temp?: boolean) => {
+ updateStore('availableZapOptions', () => defaultZapOptions);
+ updateStore('defaultZapAmount', () => defaultZapAmount);
+ !temp && saveSettings();
+ }
+
const setTheme = (theme: PrimalTheme | null, temp?: boolean) => {
if (!theme) {
return;
@@ -466,6 +466,7 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
restoreDefaultFeeds,
setDefaultZapAmount,
setZapOptions,
+ resetZapOptionsToDefault,
updateNotificationSettings,
},
});
diff --git a/src/lib/profile.ts b/src/lib/profile.ts
index 0f8c5a2f..0356982f 100644
--- a/src/lib/profile.ts
+++ b/src/lib/profile.ts
@@ -165,10 +165,6 @@ export const sendProfile = async (metaData: any, relays: Relay[], relaySettings?
return await sendEvent(event, relays, relaySettings);
};
-export const muteUser = async (pubkey: string, relays: Relay[], relaySettings?: NostrRelays) => {
-
-};
-
export const reportUser = async (pubkey: string, subid: string, user?: PrimalUser) => {
if (!pubkey) {
return;
diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx
index a8dd10a0..0bc3932d 100644
--- a/src/pages/Profile.tsx
+++ b/src/pages/Profile.tsx
@@ -563,7 +563,7 @@ const Profile: Component = () => {
}
diff --git a/src/pages/Settings.module.scss b/src/pages/Settings.module.scss
deleted file mode 100644
index 22737624..00000000
--- a/src/pages/Settings.module.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-.settingsContainer {
- background-color: var(--background-card);
- padding-inline: 28px;
- min-height: 100vh;
- padding-bottom: 20px;
-}
-
-.fullHeader {
- display: grid;
- height: 128px;
- align-items: center;
- justify-content: left;
-
- >div {
- font-weight: 300;
- font-size: 32px;
- line-height: 34px;
- color: var(--brand-text);
- text-transform: lowercase;
- }
-}
-
-.comingSoon {
- font-weight: 300;
- font-size: 18px;
- line-height: 34px;
- color: var(--text-secondary);
-}
-
-.settingsCaption {
- font-size: 18px;
- font-weight: 800;
- line-height: 20px;
- color: var(--text-secondary);
- margin-bottom: 20px;
-}
-
-.feedCaption {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: -10px;
- .settingsCaption {
- margin-bottom: 0px;
- }
-
- .restoreFeedsButton {
- background-color: var(--background-site);
- color: var(--text-tertiary);
- width: auto;
- font-size: 14px;
- font-weight: 400;
- line-height: 20px;
- border: none;
- margin: 0;
- padding-block: 10px;
- padding-inline: 10px;
-
- &:hover {
- color: var(--text-secondary);
- }
- }
-}
-
-.devider {
- width: 100%;
- border-bottom: solid 1px var(--subtile-devider);
- margin-block: 32px;
-}
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
deleted file mode 100644
index b65d4835..00000000
--- a/src/pages/Settings.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { Component, createSignal } from 'solid-js';
-import Branding from '../components/Branding/Branding';
-import styles from './Settings.module.scss';
-
-import FeedSorter from '../components/FeedSorter/FeedSorter';
-import ThemeChooser from '../components/ThemeChooser/ThemeChooser';
-import Wormhole from '../components/Wormhole/Wormhole';
-import { useIntl } from '@cookbook/solid-intl';
-import SettingsZap from '../components/SettingsZap/SettingsZap';
-import Search from '../components/Search/Search';
-import SettingsNotifications from '../components/SettingsNotifications/SettingsNotifications';
-import { settings as t } from '../translations';
-import { useSettingsContext } from '../contexts/SettingsContext';
-import ConfirmModal from '../components/ConfirmModal/ConfirmModal';
-import PageCaption from '../components/PageCaption/PageCaption';
-
-const Settings: Component = () => {
-
- const intl = useIntl();
- const settings = useSettingsContext();
-
- const [isRestoringFeeds, setIsRestoringFeeds] = createSignal(false);
-
- const onRestoreFeeds = () => {
- settings?.actions.restoreDefaultFeeds();
- setIsRestoringFeeds(false);
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {intl.formatMessage(t.theme)}
-
-
-
-
-
-
-
-
- {intl.formatMessage(t.feeds)}
-
-
-
-
-
setIsRestoringFeeds(false)}
- >
-
-
-
-
-
-
-
-
-
-
- {intl.formatMessage(t.feeds)}
-
-
-
-
-
-
-
-
-
- {intl.formatMessage(t.notifications.title)}
-
-
-
-
-
-
- )
-}
-
-export default Settings;
diff --git a/src/pages/Settings/Appearance.tsx b/src/pages/Settings/Appearance.tsx
new file mode 100644
index 00000000..1743d92d
--- /dev/null
+++ b/src/pages/Settings/Appearance.tsx
@@ -0,0 +1,30 @@
+import { Component } from 'solid-js';
+import styles from './Settings.module.scss';
+
+import ThemeChooser from '../../components/ThemeChooser/ThemeChooser';
+import { useIntl } from '@cookbook/solid-intl';
+import { settings as t } from '../../translations';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link } from '@solidjs/router';
+
+const Appearance: Component = () => {
+
+ const intl = useIntl();
+
+ return (
+
+
+ {intl.formatMessage(t.index.title)}:
+ {intl.formatMessage(t.appearance.title)}
+
+
+
+ {intl.formatMessage(t.appearance.caption)}
+
+
+
+
+ )
+}
+
+export default Appearance;
diff --git a/src/pages/Settings/HomeFeeds.tsx b/src/pages/Settings/HomeFeeds.tsx
new file mode 100644
index 00000000..ee84b46d
--- /dev/null
+++ b/src/pages/Settings/HomeFeeds.tsx
@@ -0,0 +1,58 @@
+import { Component, createSignal } from 'solid-js';
+import styles from './Settings.module.scss';
+
+import { useIntl } from '@cookbook/solid-intl';
+import { settings as t } from '../../translations';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link } from '@solidjs/router';
+import ConfirmModal from '../../components/ConfirmModal/ConfirmModal';
+import { useSettingsContext } from '../../contexts/SettingsContext';
+import FeedSorter from '../../components/FeedSorter/FeedSorter';
+
+const HomeFeeds: Component = () => {
+
+ const intl = useIntl();
+ const settings = useSettingsContext();
+
+ const [isRestoringFeeds, setIsRestoringFeeds] = createSignal(false);
+
+ const onRestoreFeeds = () => {
+ settings?.actions.restoreDefaultFeeds();
+ setIsRestoringFeeds(false);
+ };
+
+ return (
+
+
+ {intl.formatMessage(t.index.title)}:
+ {intl.formatMessage(t.homeFeeds.title)}
+
+
+
+
+ {intl.formatMessage(t.homeFeeds.caption)}
+
+
+
+
+
setIsRestoringFeeds(false)}
+ >
+
+
+
+
+
+
+ )
+}
+
+export default HomeFeeds;
diff --git a/src/pages/Settings/Menu.tsx b/src/pages/Settings/Menu.tsx
new file mode 100644
index 00000000..ee227113
--- /dev/null
+++ b/src/pages/Settings/Menu.tsx
@@ -0,0 +1,47 @@
+import { Component } from 'solid-js';
+import styles from './Settings.module.scss';
+
+import { useIntl } from '@cookbook/solid-intl';
+import { settings as t } from '../../translations';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link } from '@solidjs/router';
+
+const Menu: Component = () => {
+
+ const intl = useIntl();
+
+ return (
+
+
+
+
+
+ {intl.formatMessage(t.appearance.title)}
+
+
+
+ {intl.formatMessage(t.homeFeeds.title)}
+
+
+
+ {intl.formatMessage(t.muted.title)}
+
+
+
+ {intl.formatMessage(t.notifications.title)}
+
+
+
+ {intl.formatMessage(t.network.title)}
+
+
+
+ {intl.formatMessage(t.zaps)}
+
+
+
+
+ )
+}
+
+export default Menu;
diff --git a/src/pages/Settings/Muted.tsx b/src/pages/Settings/Muted.tsx
new file mode 100644
index 00000000..190b1243
--- /dev/null
+++ b/src/pages/Settings/Muted.tsx
@@ -0,0 +1,89 @@
+import { Component, createEffect, For } from 'solid-js';
+import styles from './Settings.module.scss';
+
+import { useIntl } from '@cookbook/solid-intl';
+import { settings as t, actions as tActions } from '../../translations';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link } from '@solidjs/router';
+import { useAccountContext } from '../../contexts/AccountContext';
+import { getUserProfiles } from '../../lib/profile';
+import { APP_ID } from '../../App';
+import { subscribeTo } from '../../sockets';
+import { convertToUser, nip05Verification, userName } from '../../stores/profile';
+import { Kind } from '../../constants';
+import { createStore } from 'solid-js/store';
+import { PrimalUser } from '../../types/primal';
+import Avatar from '../../components/Avatar/Avatar';
+
+const Muted: Component = () => {
+
+ const intl = useIntl();
+ const account = useAccountContext();
+
+ const [mutedUsers, setMutedUsers] = createStore([]);
+
+ const mutedMetadataSubId = `muted_metadata_${APP_ID}`;
+
+ createEffect(() => {
+ if (account && account.isKeyLookupDone) {
+
+ let users: PrimalUser[] = [];
+
+ const unsub = subscribeTo(mutedMetadataSubId, (type, subId, content) => {
+ if (type === 'EVENT') {
+ if (content?.kind === Kind.Metadata) {
+ users.push(convertToUser(content));
+ }
+ }
+
+ if (type === 'EOSE') {
+ setMutedUsers(() => [ ...users ]);
+ unsub();
+ }
+ });
+
+ getUserProfiles(account.muted, mutedMetadataSubId);
+ }
+ });
+
+ const unMuteUser = (user: PrimalUser) => {
+ account?.actions.removeFromMuteList(user.pubkey);
+ };
+
+ return (
+
+
+ {intl.formatMessage(t.index.title)}:
+ {intl.formatMessage(t.muted.title)}
+
+
+
+
+ {intl.formatMessage(t.muted.empty)}
+
+ }
+ >
+ {user => (
+
+
+
+
+
{userName(user)}
+
{nip05Verification(user)}
+
+
+
+
+ )}
+
+
+
+ )
+}
+
+export default Muted;
diff --git a/src/pages/Settings/Network.tsx b/src/pages/Settings/Network.tsx
new file mode 100644
index 00000000..1b7a0050
--- /dev/null
+++ b/src/pages/Settings/Network.tsx
@@ -0,0 +1,250 @@
+import { Component, createEffect, createSignal, For, onMount, Show } from 'solid-js';
+// @ts-ignore Bad types in nostr-tools
+import { Relay, relayInit } from "nostr-tools";
+import styles from './Settings.module.scss';
+
+import { useIntl } from '@cookbook/solid-intl';
+import { settings as t } from '../../translations';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link } from '@solidjs/router';
+import { useAccountContext } from '../../contexts/AccountContext';
+import { getDefaultRelays } from '../../lib/relays';
+import { APP_ID } from '../../App';
+import { isConnected as isSocketConnected, socket, subscribeTo } from '../../sockets';
+import { createStore } from 'solid-js/store';
+import Checkbox from '../../components/Checkbox/Checkbox';
+import { store } from '../../services/StoreService';
+import ConfirmModal from '../../components/ConfirmModal/ConfirmModal';
+
+const Network: Component = () => {
+
+ const intl = useIntl();
+ const account = useAccountContext();
+
+ const [recomendedRelays, setRecomendedRelays] = createStore([]);
+ const [confirmRemoveRelay, setConfirmRemoveRelay] = createSignal('');
+ const [invalidCustomRelay, setInvalidCustomRelay] = createSignal(false);
+
+ let customRelayInput: HTMLInputElement | undefined;
+
+ const relays = () => {
+ let settingsRelays = [];
+
+ for (let url in (account?.relaySettings || {})) {
+ settingsRelays.push(relayInit(url))
+ }
+
+ return settingsRelays;
+ };
+
+ const otherRelays = () => {
+ const myRelays: string[] = relays().map(r => r.url);
+
+ let unusedRelays: string[] = [];
+
+ for (let i = 0; i < recomendedRelays.length; i++) {
+ const relay = recomendedRelays[i];
+
+ const exists = myRelays.find(r => {
+
+ const a = new URL(r);
+ const b = new URL(relay.url);
+
+ return a.origin === b.origin;
+ });
+
+ if (!exists) {
+ unusedRelays.push(relay.url);
+ }
+ }
+
+ return unusedRelays;
+ }
+
+ const isConnected = (url: string) => {
+ const relay: Relay | undefined = account?.relays.find(r => r.url === url);
+ return relay && relay.status === WebSocket.OPEN;
+ };
+
+ const isPrimalRelayInUserSettings = () => {
+ const rels: string[] = import.meta.env.PRIMAL_PRIORITY_RELAYS?.split(',') || [];
+
+ return Object.keys(account?.relaySettings || {}).includes(rels[0]);
+ }
+
+ const onCheckPrimalRelay = () => {
+ account?.actions.setConnectToPrimaryRelays(!account.connectToPrimaryRelays)
+ };
+
+ createEffect(() => {
+ const unsub = subscribeTo(`settings_drs_${APP_ID}`, (type, subId, content) => {
+ if (type === 'EVENT' && content) {
+ const urls = JSON.parse(content.content || '[]');
+ setRecomendedRelays(() => urls.map(relayInit));
+ }
+
+ if (type === 'EOSE') {
+ unsub();
+ }
+ });
+
+ getDefaultRelays(`settings_drs_${APP_ID}`);
+ });
+
+ const onAddRelay = (url: string) => {
+ const rels: string[] = import.meta.env.PRIMAL_PRIORITY_RELAYS?.split(',') || [];
+ if (rels.includes(url)) {
+ account?.actions.setConnectToPrimaryRelays(true);
+ }
+ account?.actions.addRelay(url);
+ };
+
+ const onRemoveRelay = (url: string) => {
+ account?.actions.removeRelay(url);
+ };
+
+ const onCustomRelayInput = () => {
+ if (!customRelayInput) {
+ return;
+ }
+
+ try {
+ const url = new URL(customRelayInput.value);
+ if (!url.origin.startsWith('wss://')) {
+ throw(new Error('must be a wss'))
+ }
+
+ customRelayInput.value = '';
+ account?.actions.addRelay(url.origin);
+ setInvalidCustomRelay(false);
+ } catch (e) {
+ console.log('invalid url');
+ setInvalidCustomRelay(true);
+ }
+ }
+
+ return (
+
+
+ {intl.formatMessage(t.index.title)}:
+ {intl.formatMessage(t.network.title)}
+
+
+
+ {intl.formatMessage(t.network.relays)}
+
+
+
+ {intl.formatMessage(t.network.myRelays)}
+
+
+
+ {relay => (
+
+
+
+ )}
+
+
+
+ onCheckPrimalRelay()}
+ label={`Post a copy of all content to the Primal relay (${import.meta.env.PRIMAL_PRIORITY_RELAYS})`}
+ />
+
+
+
+ {intl.formatMessage(t.network.recomended)}
+
+
+
+ {url => (
+
+ )}
+
+
+
+ {intl.formatMessage(t.network.customRelay)}
+
+
+
+
+
+
+ Invalid relay url
+
+
+
+
+ {intl.formatMessage(t.network.cachingService)}
+
+
+
+ {intl.formatMessage(t.network.connectedCachingService)}
+
+
+
+
+
}
+ >
+
+
+
+
+ {socket()?.url}
+
+
+
+
+ 0}
+ description="Remove Relay?"
+ onConfirm={() => {
+ onRemoveRelay(confirmRemoveRelay())
+ setConfirmRemoveRelay('');
+ }}
+ onAbort={() => setConfirmRemoveRelay('')}
+ />
+
+ )
+}
+
+export default Network;
diff --git a/src/pages/Settings/Notifications.tsx b/src/pages/Settings/Notifications.tsx
new file mode 100644
index 00000000..b2f90114
--- /dev/null
+++ b/src/pages/Settings/Notifications.tsx
@@ -0,0 +1,28 @@
+import { Component } from 'solid-js';
+import styles from './Settings.module.scss';
+
+import { useIntl } from '@cookbook/solid-intl';
+import { settings as t } from '../../translations';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link } from '@solidjs/router';
+import SettingsNotifications from '../../components/SettingsNotifications/SettingsNotifications';
+
+const Notifications: Component = () => {
+
+ const intl = useIntl();
+
+ return (
+
+
+ {intl.formatMessage(t.index.title)}:
+ {intl.formatMessage(t.notifications.title)}
+
+
+
+
+
+
+ )
+}
+
+export default Notifications;
diff --git a/src/pages/Settings/Settings.module.scss b/src/pages/Settings/Settings.module.scss
new file mode 100644
index 00000000..41f3319e
--- /dev/null
+++ b/src/pages/Settings/Settings.module.scss
@@ -0,0 +1,411 @@
+.settingsContainer {
+ background-color: var(--background-card);
+ padding-inline: 28px;
+ min-height: 100vh;
+ padding-bottom: 20px;
+}
+
+.fullHeader {
+ display: grid;
+ height: 128px;
+ align-items: center;
+ justify-content: left;
+
+ >div {
+ font-weight: 300;
+ font-size: 32px;
+ line-height: 34px;
+ color: var(--brand-text);
+ text-transform: lowercase;
+ }
+}
+
+.comingSoon {
+ font-weight: 300;
+ font-size: 18px;
+ line-height: 34px;
+ color: var(--text-secondary);
+}
+
+.bigCaption {
+ color: var(--text-primary);
+ font-size: 18px;
+ font-weight: 600;
+ line-height: 20px;
+ text-transform: uppercase;
+ margin-bottom: 24px;
+}
+
+.secondBigCaption {
+ margin-top: 48px;
+}
+
+.settingsCaption {
+ font-size: 18px;
+ font-weight: 400;
+ line-height: 20px;
+ color: var(--text-secondary);
+ margin-bottom: 20px;
+}
+
+.secondCaption {
+ margin-top: 24px;
+}
+
+.feedCaption {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ .settingsCaption {
+ margin-bottom: 0px;
+ }
+
+ .restoreFeedsButton {
+ background: none;
+ color: var(--accent-1);
+ width: auto;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 20px;
+ border: none;
+ margin: 0;
+ padding: 0;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+.devider {
+ width: 100%;
+ border-bottom: solid 1px var(--subtile-devider);
+ margin-block: 32px;
+}
+
+.subpageLinks {
+ display: flex;
+ flex-direction: column;
+ a {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+
+ padding-block: 16px;
+ padding-right: 20px;
+ border-bottom: 1px solid var(--background-input);
+ width: 100%;
+ color: var(--text-primary);
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 28px;
+ text-decoration: none;
+
+ >div {
+ width: 12px;
+ height: 20px;
+ background-color: var(--text-tertiary);
+ -webkit-mask: url(../../assets/icons/chevron_right.svg) no-repeat 0 / 100%;
+ mask: url(../../assets/icons/chevron_right.svg) no-repeat 0 / 100%;
+ }
+
+ &:hover {
+ >div {
+ background-color: var(--text-primary);
+ }
+ }
+ }
+}
+
+.emptyListBanner {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: var(--text-secondary);
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 20px;
+ text-transform: uppercase;
+}
+
+.mutedUser {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-block: 12px;
+ border-bottom: 1px solid var(--background-input);
+ .userInfo {
+ display: flex;
+ align-items: center;
+ .userName {
+ height: 36px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ margin-left: 8px;
+ .title {
+ color: var(--text-primary);
+ font-size: 16px;
+ font-weight: 700;
+ line-height: 16px;
+ }
+ .verification {
+ color: var(--text-tertiary-2);
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 14px;
+ }
+ }
+ }
+
+ button {
+ width: auto;
+ border-radius: 6px;
+ padding: 0px;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 18px;
+ padding-block: 7px;
+ padding-inline: 12px;
+ margin: 0px;
+ color: var(--text-primary);
+ background: linear-gradient(var(--background-card), var(--background-card)) padding-box,
+ var(--brand-gradient) border-box;
+ border: 1px solid transparent;
+
+ &:hover {
+ background: linear-gradient(var(--background-site), var(--background-site)) padding-box,
+ var(--brand-gradient) border-box;
+ }
+ }
+}
+
+.closeIcon {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ background-color: var(--text-secondary);
+ -webkit-mask: url(../../assets/icons/close.svg) no-repeat 0 / 100%;
+ mask: url(../../assets/icons/close.svg) no-repeat 0 / 100%;
+}
+
+.addIcon {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ background-color: var(--text-secondary);
+ -webkit-mask: url(../../assets/icons/add.svg) no-repeat 0 / 100%;
+ mask: url(../../assets/icons/add.svg) no-repeat 0 / 100%;
+}
+
+.webIcon {
+ width: 18px;
+ height: 18px;
+ background-color: var(--text-secondary);
+ margin-inline: 8px;
+ -webkit-mask: url(../../assets/icons/global.svg) no-repeat 0 / 100%;
+ mask: url(../../assets/icons/global.svg) no-repeat 0 / 100%;
+}
+
+.connectIcon {
+ width: 18px;
+ height: 18px;
+ background-color: var(--text-secondary);
+ margin-inline: 8px;
+ -webkit-mask: url(../../assets/icons/connect.svg) no-repeat 0 / 100%;
+ mask: url(../../assets/icons/connect.svg) no-repeat 0 / 100%;
+}
+
+.relayItem {
+ display: flex;
+ justify-content: space-between;
+ padding-block: 11px;
+ padding-inline: 0;
+ border-bottom: 1px solid var(--background-input);
+ border: none;
+ outline: none;
+ margin: 0;
+ background: none;
+
+ .relayEntry {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 18px;
+ color: var(--text-secondary-2);
+ text-align: left;
+
+ .connected {
+ background-color: #66E205;
+ width: 6px;
+ height: 6px;
+ border-radius: 2px;
+ margin-right: 8px;
+ }
+
+ .disconnected {
+ background-color: #E20505;
+ width: 6px;
+ height: 6px;
+ border-radius: 2px;
+ margin-right: 8px;
+ }
+
+ .relayActions {
+ display: flex;
+ font-size: 12px;
+ line-height: 12px;
+ font-weight: 400;
+ min-width: 100px;
+ margin-left: 20px;
+
+ >span {
+ display: inline;
+ }
+
+ >button {
+ border: none;
+ background: none;
+ font-size: 12px;
+ line-height: 12px;
+ font-weight: 400;
+ margin: 2px;
+ padding: 0px;
+ color: var(--warning-color);
+ display: none;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ &:hover {
+ >span {
+ display: inline;
+ }
+ >button {
+ display: flex;
+ }
+ }
+ }
+ }
+
+ .remove, .add {
+ width: auto;
+ border: none;
+ outline: none;
+ margin: 0;
+ padding: 0;
+ color: var(--text-tertiary-2);
+ background: none;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 12px;
+ transition: color 0s linear;
+
+ .removeIcon, .addIcon {
+ background-color: var(--text-tertiary-2);
+ }
+ }
+
+ &:hover {
+
+ .relayEntry {
+ color: var(--text-primary);
+
+ .webIcon, .addIcon {
+ background-color: var(--text-primary);
+ }
+ }
+
+ .remove {
+ color: #E20505;
+ transition: color 0s linear;
+
+ .closeIcon {
+ background-color: #E20505;
+ }
+ }
+
+ .add {
+ color: #66E205;
+ transition: color 0s linear;
+
+ .addIcon {
+ background-color: #66E205;
+ }
+ }
+ }
+
+}
+
+.relayInput {
+ position: relative;
+
+ .webIcon {
+ position: absolute;
+ top: 11px;
+ left: 4px;
+ }
+
+ input {
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 20px;
+ padding-inline: 40px;
+ height: 40px;
+ border: 1px solid var(--subtile-devider);
+ border-radius: 8px;
+ color: var(--text-secondary);
+ margin-bottom: 0;
+
+ &::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
+ color: var(--text-tertiary-2);
+ opacity: 1; /* Firefox */
+ }
+
+ &:-ms-input-placeholder { /* Internet Explorer 10-11 */
+ color: var(--text-tertiary-2);
+ }
+
+ &::-ms-input-placeholder { /* Microsoft Edge */
+ color: var(--text-tertiary-2);
+ }
+
+ &:focus {
+ border-color: var(--text-tertiary);
+ }
+ }
+
+ button {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ width: 40px;
+ height: 40px;
+ background: none;
+ margin: 0;
+ padding: 2px;
+ border-radius: 8px;
+ border: none;
+ outline: none;
+ box-shadow: none;
+
+ &:hover {
+ .connectIcon {
+ background-color: var(--text-primary);
+ }
+ }
+ }
+}
+
+.invalidInput {
+ color:#E20505;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 20px;
+ text-align: left;
+}
diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx
new file mode 100644
index 00000000..7a25b682
--- /dev/null
+++ b/src/pages/Settings/Settings.tsx
@@ -0,0 +1,40 @@
+import { Component } from 'solid-js';
+import Branding from '../../components/Branding/Branding';
+import styles from './Settings.module.scss';
+
+import Wormhole from '../../components/Wormhole/Wormhole';
+import { useIntl } from '@cookbook/solid-intl';
+import Search from '../../components/Search/Search';
+import { settings as t } from '../../translations';
+import { useSettingsContext } from '../../contexts/SettingsContext';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link, Outlet } from '@solidjs/router';
+import HomeSidebar from '../../components/HomeSidebar/HomeSidebar';
+import StickySidebar from '../../components/StickySidebar/StickySidebar';
+import SettingsSidebar from '../../components/SettingsSidebar/SettingsSidebar';
+
+const Settings: Component = () => {
+
+ const intl = useIntl();
+ const settings = useSettingsContext();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Settings;
diff --git a/src/pages/Settings/Zaps.tsx b/src/pages/Settings/Zaps.tsx
new file mode 100644
index 00000000..d5a928a8
--- /dev/null
+++ b/src/pages/Settings/Zaps.tsx
@@ -0,0 +1,28 @@
+import { Component } from 'solid-js';
+import styles from './Settings.module.scss';
+
+import { useIntl } from '@cookbook/solid-intl';
+import { settings as t } from '../../translations';
+import PageCaption from '../../components/PageCaption/PageCaption';
+import { Link } from '@solidjs/router';
+import SettingsZap from '../../components/SettingsZap/SettingsZap';
+
+const Zaps: Component = () => {
+
+ const intl = useIntl();
+
+ return (
+
+
+ {intl.formatMessage(t.index.title)}:
+ {intl.formatMessage(t.zaps)}
+
+
+
+
+
+
+ )
+}
+
+export default Zaps;
diff --git a/src/sockets.tsx b/src/sockets.tsx
index 78a65d88..4df7f9ba 100644
--- a/src/sockets.tsx
+++ b/src/sockets.tsx
@@ -27,9 +27,11 @@ const onError = (error: Event) => {
console.log("ws error: ", error);
};
+export let cacheServer = '';
+
export const connect = () => {
if (isNotConnected()) {
- const cacheServer =
+ cacheServer =
localStorage.getItem('cacheServer') ||
import.meta.env.PRIMAL_CACHE_URL ||
'wss://cache3.primal.net/cache17';
diff --git a/src/translations.ts b/src/translations.ts
index d0c838f7..4d67f244 100644
--- a/src/translations.ts
+++ b/src/translations.ts
@@ -91,9 +91,9 @@ export const actions = {
defaultMessage: 'Add {name} to your mute list?',
description: 'Label for mute user confirmation',
},
- mute: {
- id: 'actions.mute',
- defaultMessage: 'click to unmute',
+ unmute: {
+ id: 'actions.unmute',
+ defaultMessage: 'unmute',
description: 'Label un-mute button',
},
profileContext: {
@@ -812,6 +812,96 @@ export const search = {
};
export const settings = {
+ index: {
+ title: {
+ id: 'settings.index.title',
+ defaultMessage: 'Settings',
+ description: 'Title of the settings page',
+ },
+ },
+ appearance: {
+ title: {
+ id: 'settings.appearance.title',
+ defaultMessage: 'Appearance',
+ description: 'Title of the appearance settings sub-page',
+ },
+ caption: {
+ id: 'settings.appearance.caption',
+ defaultMessage: 'Select a theme',
+ description: 'Caption for theme selection',
+ },
+ },
+ homeFeeds: {
+ title: {
+ id: 'settings.homeFeeds.title',
+ defaultMessage: 'Home Feeds',
+ description: 'Title of the home feeds settings sub-page',
+ },
+ caption: {
+ id: 'settings.homeFeeds.caption',
+ defaultMessage: 'Edit and order your home page feeds',
+ description: 'Caption for home feed ordering',
+ },
+ },
+ muted: {
+ title: {
+ id: 'settings.muted.title',
+ defaultMessage: 'Muted Accounts',
+ description: 'Title of the muted accounts settings sub-page',
+ },
+ empty: {
+ id: 'settings.muted.empty',
+ defaultMessage: 'No muted users',
+ description: 'Caption indicating that there are no muted users',
+ },
+ },
+ network: {
+ title: {
+ id: 'settings.network.title',
+ defaultMessage: 'Network',
+ description: 'Title of the network settings sub-page',
+ },
+ relays: {
+ id: 'settings.network.relays',
+ defaultMessage: 'Relays',
+ description: 'Title of the Relays section of the Network settings sub-page',
+ },
+ myRelays: {
+ id: 'settings.network.myRelays',
+ defaultMessage: 'My Relays',
+ description: 'Title of the My Relays section of the Network settings sub-page',
+ },
+ recomended: {
+ id: 'settings.network.recomended',
+ defaultMessage: 'Recomended Relays',
+ description: 'Title of the Recomended Relays section of the Network settings sub-page',
+ },
+ customRelay: {
+ id: 'settings.network.customRelay',
+ defaultMessage: 'Connect to relay',
+ description: 'Title of the Customd Relays section of the Network settings sub-page',
+ },
+ cachingService: {
+ id: 'settings.network.cachingService',
+ defaultMessage: 'Caching Service',
+ description: 'Title of the Caching Service section of the Network settings sub-page',
+ },
+ connectedCachingService: {
+ id: 'settings.network.connectedCachingService',
+ defaultMessage: 'Connected caching service',
+ description: 'Title of the Caching Service section of the Network settings sub-page',
+ },
+ },
+ relays: {
+ id: 'settings.relays',
+ defaultMessage: 'Relays',
+ description: 'Title of the relays sections of the settings sidebar',
+ },
+ cashingService: {
+ id: 'settings.cashingService',
+ defaultMessage: 'Caching Service',
+ description: 'Title of the caching service sections of the settings sidebar',
+ },
title: {
id: 'settings.title',
defaultMessage: 'Settings',
@@ -829,7 +919,7 @@ export const settings = {
},
feedsRestore: {
id: 'settings.feedsRestore',
- defaultMessage: 'Reset to default feeds',
+ defaultMessage: 'restore defaults',
description: 'Label for the button for restoring default feeds to the feeds list',
},
feedsRestoreConfirm: {
@@ -837,6 +927,11 @@ export const settings = {
defaultMessage: 'Restoring default feeds will erase all your custom feed settings',
description: 'Label explaining the impact of restoring default feeds',
},
+ zapsRestoreConfirm: {
+ id: 'settings.zapsRestoreConfirm',
+ defaultMessage: 'This action will restore all your zap settings to their default values',
+ description: 'Label explaining the impact of restoring default zaps',
+ },
feedLatest: {
id: 'feeds.latestFollowing',
defaultMessage: 'Latest',