diff --git a/src/App.tsx b/src/App.tsx index 95183ab..fc48012 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,12 @@ import { RouterProvider } from 'react-router-dom' -import { useEffect } from 'react' -import { routerWithNdkContext } from 'routes' +import { useEffect, useMemo } from 'react' +import { routerWithNdkContext as routerWithState } from 'routes' import { useNDKContext } from 'hooks' import './styles/styles.css' function App() { const ndkContext = useNDKContext() + const router = useMemo(() => routerWithState(ndkContext), [ndkContext]) useEffect(() => { // Find the element with id 'root' @@ -24,7 +25,7 @@ function App() { } }, []) - return + return } export default App diff --git a/src/assets/categories/categories.json b/src/assets/categories/categories.json index c9b97f3..15f8314 100644 --- a/src/assets/categories/categories.json +++ b/src/assets/categories/categories.json @@ -8,5 +8,6 @@ "total conversions", "translation", "multiplayer", - "clothing" + "clothing", + "Mod Manager" ] \ No newline at end of file diff --git a/src/assets/games/Games_Steam4.csv b/src/assets/games/Games_Steam4.csv index 555ce9d..1fc1dba 100644 --- a/src/assets/games/Games_Steam4.csv +++ b/src/assets/games/Games_Steam4.csv @@ -22391,8 +22391,7 @@ Climbing Over It with a Spear,, Land of the Survivors Demo,, Nie No Hakoniwa,, Arcana Alchemia,, -MiSide,, -MiSide Demo,, +MiSide,,https://i.imgur.com/CAOt6Lc.jpeg Uncharted Ocean 2,, Roll The Bones,, Farm Manager World Playtest,, diff --git a/src/components/ModForm.tsx b/src/components/ModForm.tsx index 98cdc22..43c030b 100644 --- a/src/components/ModForm.tsx +++ b/src/components/ModForm.tsx @@ -222,7 +222,7 @@ export const ModForm = () => { {

- We recommend to upload images to https://nostr.build/ + We recommend to upload images to https://imgur.com/upload

{formState.screenshotsUrls.map((url, index) => ( @@ -607,7 +607,7 @@ const ScreenshotUrlFields = React.memo( type='text' className='inputMain' inputMode='url' - placeholder='We recommend to upload images to https://nostr.build/' + placeholder='We recommend to upload images to https://imgur.com/upload' value={url} onChange={handleChange} /> diff --git a/src/contexts/NDKContext.tsx b/src/contexts/NDKContext.tsx index 853f5c3..fe9b773 100644 --- a/src/contexts/NDKContext.tsx +++ b/src/contexts/NDKContext.tsx @@ -252,7 +252,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => { }) .catch((err) => { log( - true, + false, // Too many failed requests, turned off for clarity LogType.Error, `An error occurred in fetching user's (${hexKey}) ${userRelaysType}`, err diff --git a/src/hooks/useComments.ts b/src/hooks/useComments.ts index e62f918..4de0403 100644 --- a/src/hooks/useComments.ts +++ b/src/hooks/useComments.ts @@ -39,7 +39,7 @@ export const useComments = ( }) .catch((err) => { log( - true, + false, // Too many failed requests, turned off for clarity LogType.Error, `An error occurred in fetching user's (${author}) ${UserRelaysType.Read}`, err diff --git a/src/layout/footer.tsx b/src/layout/footer.tsx index 5ddedb3..d0ea3d7 100644 --- a/src/layout/footer.tsx +++ b/src/layout/footer.tsx @@ -1,4 +1,6 @@ +import { Link } from 'react-router-dom' import styles from '../styles/footer.module.scss' +import { appRoutes, getProfilePageRoute } from 'routes' export const Footer = () => { return ( @@ -7,6 +9,7 @@ export const Footer = () => {

Built with  { Nostr {' '} by  - Freakoverse - + , with the support of{' '} - + Supporters - + . Check our  - + Backup Plan - + .

diff --git a/src/layout/header.tsx b/src/layout/header.tsx index 1953ab6..7871062 100644 --- a/src/layout/header.tsx +++ b/src/layout/header.tsx @@ -451,6 +451,8 @@ const RegisterButtonWithDialog = () => { A: DEG Mods can't ban you or delete your content (we can only hide you), and the consequence of that is this kind of registration/login system. +

+ Warning: Make sure you backup your private key somewhere safe. If you lose it or it gets leaked, we actually can't help you.

diff --git a/src/layout/index.tsx b/src/layout/index.tsx index f936f04..cdddc70 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -44,7 +44,7 @@ export const Layout = () => { }) } } - }, [ndk, dispatch]) + }, [dispatch, ndk]) // calculate user's wot useEffect(() => { @@ -60,7 +60,7 @@ export const Layout = () => { toast.error('An error occurred in calculating user web-of-trust!') }) } - }, [ndk, userState.user, dispatch]) + }, [dispatch, ndk, userState.user?.pubkey]) // get site's wot level useEffect(() => { @@ -106,7 +106,7 @@ export const Layout = () => { }) } } - }, [userState.user, dispatch, fetchEventFromUserRelays]) + }, [dispatch, fetchEventFromUserRelays, userState.user?.pubkey]) return ( <> diff --git a/src/pages/404.tsx b/src/pages/404.tsx index d3e5a77..f2db88b 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,4 +1,4 @@ -import { Link, useRouteError } from 'react-router-dom' +import { Link, useLocation, useRouteError } from 'react-router-dom' import { appRoutes } from 'routes' interface NotFoundPageProps { @@ -12,6 +12,8 @@ export const NotFoundPage = ({ }: Partial) => { const error = useRouteError() as Partial + const location = useLocation() + return (
@@ -23,7 +25,19 @@ export const NotFoundPage = ({

{error?.message || message}

-
+
+ + Try again + { + return ( + +
+

+ {type === 'exe' ? type.toUpperCase() : capitalizeEachWord(type)}:{' '} + {name} +

+
+
+ ) +} + +export const BackupPage = () => { + return ( +
+
+
+
+
+
+
+

+ Backup Plan: Repos, Alts, EXE +

+ +

+ It's pretty clear that authoritarianism and censorship is on + the rise, on all fronts, and from what can be seen, any idea + that push for the opposite gets attacked. That's why DEG + Mods is running on Nostr, and that's why we're also writing + this backup plan. +
+

+

Repositories

+

+ Wherever we can, we'll put DEG Mods' code on multiple + repositories such as Github, and (github but on nostr). + Below you can find the links where we've uploaded the site's + code to. +
+

+

Alternatives

+

+ With the repositories for DEG Mods is up on multiple places, + we encourage people to take the code and duplicate it + elsewhere. Fork it, change the design, remove or add systems + and features, and make your own version. Below you can find + links of alts that we've found. +
+

+

EXE

+

+ One last push we'd like to do is to create a .exe that'll + open up DEG Mods on your PC, as if you've opened the website + normally, with almost all of the functionalities you'd + expect (if not all). We want to do this so that in case + there are no alternatives, or that they're getting shut + down, then you can just rely on this instead. The link to it + will be added here the moment it becomes available. +
+

+
+ {BACKUP_LIST.map((b) => ( + + ))} +
+
+
+
+
+
+
+
+ ) +} diff --git a/src/pages/blog/loader.ts b/src/pages/blog/loader.ts index 0b06b50..f0fc114 100644 --- a/src/pages/blog/loader.ts +++ b/src/pages/blog/loader.ts @@ -13,6 +13,7 @@ import { } from 'types' import { DEFAULT_FILTER_OPTIONS, + getFallbackPubkey, getLocalStorageItem, log, LogType @@ -41,7 +42,8 @@ export const blogRouteLoader = } const userState = store.getState().user - const loggedInUserPubkey = userState?.user?.pubkey as string | undefined + const loggedInUserPubkey = + (userState?.user?.pubkey as string | undefined) || getFallbackPubkey() // Check if editing and the user is the original author // Redirect if NOT diff --git a/src/pages/mod/loader.ts b/src/pages/mod/loader.ts index 032d697..d311109 100644 --- a/src/pages/mod/loader.ts +++ b/src/pages/mod/loader.ts @@ -16,10 +16,12 @@ import { DEFAULT_FILTER_OPTIONS, extractBlogCardDetails, extractModData, + getFallbackPubkey, getLocalStorageItem, getReportingSet, log, - LogType + LogType, + timeout } from 'utils' export const modRouteLoader = @@ -46,7 +48,8 @@ export const modRouteLoader = } const userState = store.getState().user - const loggedInUserPubkey = userState?.user?.pubkey as string | undefined + const loggedInUserPubkey = + (userState?.user?.pubkey as string | undefined) || getFallbackPubkey() try { // Set up the filters @@ -82,8 +85,8 @@ export const modRouteLoader = // Parallel fetch mod event, latest events, mute, nsfw, repost lists const settled = await Promise.allSettled([ - ndkContext.fetchEvent(modFilter), - ndkContext.fetchEvents(latestFilter), + Promise.race([ndkContext.fetchEvent(modFilter), timeout(2000)]), + Promise.race([ndkContext.fetchEvents(latestFilter), timeout(2000)]), ndkContext.getMuteLists(loggedInUserPubkey), // Pass pubkey for logged-in users getReportingSet(CurationSetIdentifiers.NSFW, ndkContext), getReportingSet(CurationSetIdentifiers.Repost, ndkContext) diff --git a/src/pages/mods/loader.ts b/src/pages/mods/loader.ts index 24fc3a4..e7a05ba 100644 --- a/src/pages/mods/loader.ts +++ b/src/pages/mods/loader.ts @@ -1,7 +1,13 @@ import { NDKContextType } from 'contexts/NDKContext' import { store } from 'store' import { MuteLists } from 'types' -import { getReportingSet, CurationSetIdentifiers, log, LogType } from 'utils' +import { + getReportingSet, + CurationSetIdentifiers, + log, + LogType, + getFallbackPubkey +} from 'utils' export interface ModsPageLoaderResult { muteLists: { @@ -31,15 +37,11 @@ export const modsRouteLoader = (ndkContext: NDKContextType) => async () => { // Get the current state const userState = store.getState().user - - // Check if current user is logged in - let userPubkey: string | undefined - if (userState.auth && userState.user?.pubkey) { - userPubkey = userState.user.pubkey as string - } + const loggedInUserPubkey = + (userState?.user?.pubkey as string | undefined) || getFallbackPubkey() const settled = await Promise.allSettled([ - ndkContext.getMuteLists(userPubkey), + ndkContext.getMuteLists(loggedInUserPubkey), getReportingSet(CurationSetIdentifiers.NSFW, ndkContext), getReportingSet(CurationSetIdentifiers.Repost, ndkContext) ]) diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 3761a5b..05a9a30 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -46,7 +46,6 @@ export const ProfilePage = () => { profilePubkey, profile, isBlocked: _isBlocked, - isOwnProfile, repostList, muteLists, nsfwList @@ -60,6 +59,10 @@ export const ProfilePage = () => { const displayName = profile?.displayName || profile?.name || '[name not set up]' const [showReportPopUp, setShowReportPopUp] = useState(false) + const isOwnProfile = + userState.auth && + userState.user?.pubkey && + userState.user.pubkey === profilePubkey const [isBlocked, setIsBlocked] = useState(_isBlocked) const handleBlock = async () => { @@ -661,7 +664,7 @@ const ReportUserPopup = ({ } const ProfileTabBlogs = () => { - const { profile, muteLists, nsfwList } = + const { profilePubkey, muteLists, nsfwList } = useLoaderData() as ProfilePageLoaderResult const navigation = useNavigation() const { fetchEvents } = useNDKContext() @@ -669,7 +672,7 @@ const ProfileTabBlogs = () => { const [isLoading, setIsLoading] = useState(true) const blogfilter: NDKFilter = useMemo(() => { const filter: NDKFilter = { - authors: [profile?.pubkey as string], + authors: [profilePubkey], kinds: [kinds.LongFormArticle] } @@ -683,13 +686,13 @@ const ProfileTabBlogs = () => { } return filter - }, [filterOptions.nsfw, filterOptions.source, profile?.pubkey]) + }, [filterOptions.nsfw, filterOptions.source, profilePubkey]) const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(false) const [blogs, setBlogs] = useState[]>([]) useEffect(() => { - if (profile) { + if (profilePubkey) { // Initial blog fetch, go beyond limit to check for next const filter: NDKFilter = { ...blogfilter, @@ -704,7 +707,7 @@ const ProfileTabBlogs = () => { setIsLoading(false) }) } - }, [blogfilter, fetchEvents, profile]) + }, [blogfilter, fetchEvents, profilePubkey]) const handleNext = useCallback(() => { if (isLoading) return @@ -758,7 +761,7 @@ const ProfileTabBlogs = () => { let _blogs = blogs || [] const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB const isOwner = - userState.user?.pubkey && userState.user.pubkey === profile?.pubkey + userState.user?.pubkey && userState.user.pubkey === profilePubkey const isUnmoderatedFully = filterOptions.moderated === ModeratedFilter.Unmoderated_Fully @@ -815,7 +818,7 @@ const ProfileTabBlogs = () => { muteLists.user.authors, muteLists.user.replaceableEvents, nsfwList, - profile?.pubkey, + profilePubkey, userState.user?.npub, userState.user?.pubkey ]) @@ -826,10 +829,7 @@ const ProfileTabBlogs = () => { )} - +
{moderatedAndSortedBlogs.map((b) => ( diff --git a/src/pages/profile/loader.ts b/src/pages/profile/loader.ts index f5bc1b6..13eef6c 100644 --- a/src/pages/profile/loader.ts +++ b/src/pages/profile/loader.ts @@ -6,6 +6,7 @@ import { store } from 'store' import { MuteLists, UserProfile } from 'types' import { CurationSetIdentifiers, + getFallbackPubkey, getReportingSet, log, LogType, @@ -16,7 +17,6 @@ export interface ProfilePageLoaderResult { profilePubkey: string profile: UserProfile isBlocked: boolean - isOwnProfile: boolean muteLists: { admin: MuteLists user: MuteLists @@ -58,21 +58,17 @@ export const profileRouteLoader = // Get the current state const userState = store.getState().user - - // Check if current user is logged in - let userPubkey: string | undefined - if (userState.auth && userState.user?.pubkey) { - userPubkey = userState.user.pubkey as string - } + const loggedInUserPubkey = + (userState?.user?.pubkey as string | undefined) || getFallbackPubkey() // Redirect if profile naddr is missing // - home if user is not logged let profileRoute = appRoutes.home - if (!profilePubkey && userPubkey) { + if (!profilePubkey && loggedInUserPubkey) { // - own profile profileRoute = getProfilePageRoute( nip19.nprofileEncode({ - pubkey: userPubkey + pubkey: loggedInUserPubkey }) ) } @@ -83,7 +79,6 @@ export const profileRouteLoader = profilePubkey: profilePubkey, profile: {}, isBlocked: false, - isOwnProfile: false, muteLists: { admin: { authors: [], @@ -98,14 +93,9 @@ export const profileRouteLoader = repostList: [] } - // Check if user the user is logged in - if (userState.auth && userState.user?.pubkey) { - result.isOwnProfile = userState.user.pubkey === profilePubkey - } - const settled = await Promise.allSettled([ ndkContext.findMetadata(profilePubkey), - ndkContext.getMuteLists(userPubkey), + ndkContext.getMuteLists(loggedInUserPubkey), getReportingSet(CurationSetIdentifiers.NSFW, ndkContext), getReportingSet(CurationSetIdentifiers.Repost, ndkContext) ]) diff --git a/src/pages/search.tsx b/src/pages/search.tsx index 683696f..68ff768 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -39,6 +39,7 @@ import { scrollIntoView } from 'utils' import { useCuratedSet } from 'hooks/useCuratedSet' +import dedup from 'utils/nostr' enum SearchKindEnum { Mods = 'Mods', @@ -508,6 +509,11 @@ const GamesResult = ({ searchTerm }: GamesResultProps) => { return ( <> + {searchTerm !== '' && filteredGames.length === 0 && ( +
+ Game not found. Send us a message where you can reach us to add it +
+ )}
{filteredGames @@ -521,20 +527,14 @@ const GamesResult = ({ searchTerm }: GamesResultProps) => { ))}
- + {searchTerm !== '' && filteredGames.length > MAX_GAMES_PER_PAGE && ( + + )} ) } -function dedup(event1: NDKEvent, event2: NDKEvent) { - // return the newest of the two - if (event1.created_at! > event2.created_at!) { - return event1 - } - - return event2 -} diff --git a/src/pages/supporters.tsx b/src/pages/supporters.tsx new file mode 100644 index 0000000..afa326c --- /dev/null +++ b/src/pages/supporters.tsx @@ -0,0 +1,3 @@ +export const SupportersPage = () => { + return

WIP

+} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index bee8638..20bd612 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -28,6 +28,8 @@ import { BlogPage } from '../pages/blog' import { blogRouteLoader } from '../pages/blog/loader' import { blogRouteAction } from '../pages/blog/action' import { reportRouteAction } from '../actions/report' +import { BackupPage } from 'pages/backup' +import { SupportersPage } from 'pages/supporters' export const appRoutes = { home: '/', @@ -51,7 +53,9 @@ export const appRoutes = { settingsAdmin: '/settings-admin', profile: '/profile/:nprofile?', feed: '/feed', - notifications: '/notifications' + notifications: '/notifications', + backup: '/backup', + supporters: '/supporters' } export const getGamePageRoute = (name: string) => @@ -185,6 +189,14 @@ export const routerWithNdkContext = (context: NDKContextType) => } ] }, + { + path: appRoutes.backup, + element: + }, + { + path: appRoutes.supporters, + element: + }, { path: '*', element: diff --git a/src/styles/backup.css b/src/styles/backup.css new file mode 100644 index 0000000..c2ece79 --- /dev/null +++ b/src/styles/backup.css @@ -0,0 +1,36 @@ +.backupList { + width: 100%; + display: flex; + flex-direction: column; + grid-gap: 15px; +} + +.backupListLink { + transition: ease 0.4s; + overflow: hidden; + padding: 15px; + box-shadow: 0 0 8px 0 rgb(0,0,0,0.1); + border-radius: 10px; + /*border: solid 1px rgba(255,255,255,0.1);*/ + position: relative; + color: rgba(255,255,255,0.75); + min-height: 150px; + display: flex; + flex-direction: column; +} + +.backupListLink:hover { + transition: ease 0.4s; + text-decoration: unset; + color: rgb(255,255,255); + transform: scale(1.02); +} + +.backupListLinkInside { + display: flex; + flex-direction: column; + grid-gap: 0px; + flex-grow: 1; + justify-content: end; +} + diff --git a/src/styles/styles.css b/src/styles/styles.css index 95669e4..de6f31d 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -705,3 +705,28 @@ a:hover { transform: rotate(360deg); } } + +.uploadBoxMain { + background: hsl(0deg 0% 0% / 10%); + border-radius: 10px; + height: 10px; + padding: 10px; + border: solid 1px hsl(0deg 0% 100% / 5%); +} + +.uploadBoxMain:hover > .uploadBoxMainInside { + padding: 5px; +} + +.uploadBoxMainInside { + padding: 10px; + border: dashed 2px hsl(0deg 0% 100% / 5%); + border-radius: 8px; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: hsl(0deg 0% 100% / 20%); + grid-gap: 10px; +} \ No newline at end of file diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index 772c37b..47ab343 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -274,3 +274,16 @@ export function orderEventsChronologically( return events } + +/** + * Receives two events and returns the "correct" event to use. + * #nip-33 + */ +export default function dedup(event1: NDKEvent, event2: NDKEvent) { + // return the newest of the two + if (event1.created_at! > event2.created_at!) { + return event1 + } + + return event2 +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index bb740e5..e28027d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -160,3 +160,23 @@ export const parseFormData = (formData: FormData) => { export const capitalizeEachWord = (str: string): string => { return str.replace(/\b\w/g, (char) => char.toUpperCase()) } + +/** + * nostr-login - helper function + * should only be used as the fallback + * user state is not updated before `onAuth` triggers but loaders are faster + */ +export const getFallbackPubkey = () => { + try { + // read nostr-login conf from localStorage + const stored = window.localStorage.getItem('__nostrlogin_nip46') + if (!stored) return + + const info = JSON.parse(stored) + if (info && !info.pubkey) return + + return info.pubkey as string + } catch { + // Silently ignore + } +}