diff --git a/firestore.rules b/firestore.rules index 4645343d..28ff4485 100644 --- a/firestore.rules +++ b/firestore.rules @@ -21,16 +21,15 @@ service cloud.firestore { allow update: if resource.data.id == request.auth.uid && request.resource.data.diff(resource.data).affectedKeys() .hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'referredByContractId']); - allow update: if resource.data.id == request.auth.uid - && request.resource.data.diff(resource.data).affectedKeys() - .hasOnly(['referredByUserId']) - // only one referral allowed per user - && !("referredByUserId" in resource.data) - // user can't refer themselves - && (resource.data.id != request.resource.data.referredByUserId) - // user can't refer someone who referred them quid pro quo - && get(/databases/$(database)/documents/users/$(request.resource.data.referredByUserId)).referredByUserId != resource.data.id; - + allow update: if resource.data.id == request.auth.uid + && request.resource.data.diff(resource.data).affectedKeys() + .hasOnly(['referredByUserId']) + // only one referral allowed per user + && !("referredByUserId" in resource.data) + // user can't refer themselves + && !(resource.data.id == request.resource.data.referredByUserId); + // quid pro quos enabled (only once though so nbd) - bc I can't make this work: + // && (get(/databases/$(database)/documents/users/$(request.resource.data.referredByUserId)).referredByUserId == resource.data.id); } match /{somePath=**}/portfolioHistory/{portfolioHistoryId} { diff --git a/functions/package.json b/functions/package.json index ee7bc92d..93bea621 100644 --- a/functions/package.json +++ b/functions/package.json @@ -5,7 +5,7 @@ "firestore": "dev-mantic-markets.appspot.com" }, "scripts": { - "build": "yarn compile && rm -rf dist && mkdir -p dist/functions && cp -R ../common/lib dist/common && cp -R lib/src dist/functions/src && cp ../yarn.lock dist && cp package.json dist", + "build": "yarn compile && rm -rf dist && mkdir -p dist/functions && cp -R ../common/lib dist/common && cp -R lib/src dist/functions/src && cp ../yarn.lock dist && cp package.json dist && cp .env dist", "compile": "tsc -b", "watch": "tsc -w", "shell": "yarn build && firebase functions:shell", diff --git a/web/components/filter-select-users.tsx b/web/components/filter-select-users.tsx index 93badf20..8d2dbbae 100644 --- a/web/components/filter-select-users.tsx +++ b/web/components/filter-select-users.tsx @@ -1,4 +1,4 @@ -import { UserIcon } from '@heroicons/react/outline' +import { UserIcon, XIcon } from '@heroicons/react/outline' import { useUsers } from 'web/hooks/use-users' import { User } from 'common/user' import { Fragment, useMemo, useState } from 'react' @@ -6,13 +6,24 @@ import clsx from 'clsx' import { Menu, Transition } from '@headlessui/react' import { Avatar } from 'web/components/avatar' import { Row } from 'web/components/layout/row' +import { UserLink } from 'web/components/user-page' export function FilterSelectUsers(props: { setSelectedUsers: (users: User[]) => void selectedUsers: User[] ignoreUserIds: string[] + showSelectedUsersTitle?: boolean + selectedUsersClassName?: string + maxUsers?: number }) { - const { ignoreUserIds, selectedUsers, setSelectedUsers } = props + const { + ignoreUserIds, + selectedUsers, + setSelectedUsers, + showSelectedUsersTitle, + selectedUsersClassName, + maxUsers, + } = props const users = useUsers() const [query, setQuery] = useState('') const [filteredUsers, setFilteredUsers] = useState([]) @@ -29,89 +40,118 @@ export function FilterSelectUsers(props: { }) ) }, [beginQuerying, users, selectedUsers, ignoreUserIds, query]) - + const shouldShow = maxUsers ? selectedUsers.length < maxUsers : true return (
-
-
-
- setQuery(e.target.value)} - className="input input-bordered block w-full pl-10 focus:border-gray-300 " - placeholder="Austin Chen" - /> -
- - {({}) => ( - +
+
+
+ setQuery(e.target.value)} + className="input input-bordered block w-full pl-10 focus:border-gray-300 " + placeholder="Austin Chen" + /> +
+ - -
- {filteredUsers.map((user: User) => ( - - {({ active }) => ( - ( + + +
+ {filteredUsers.map((user: User) => ( + + {({ active }) => ( + { + setQuery('') + setSelectedUsers([...selectedUsers, user]) + }} + > + + {user.name} + )} - onClick={() => { - setQuery('') - setSelectedUsers([...selectedUsers, user]) - }} - > - - {user.name} - - )} - - ))} -
-
-
- )} -
+ + ))} +
+ + + )} + + + )} {selectedUsers.length > 0 && ( <> -
Added members:
- +
+ {showSelectedUsersTitle && 'Added members:'} +
+ {selectedUsers.map((user: User) => ( -
- + + + + + + setSelectedUsers([ + ...selectedUsers.filter((u) => u.id != user.id), + ]) + } + className=" h-5 w-5 cursor-pointer text-gray-400" + aria-hidden="true" /> - {user.name}
))}
diff --git a/web/components/nav/nav-bar.tsx b/web/components/nav/nav-bar.tsx index 5a997b46..9f0f8ddd 100644 --- a/web/components/nav/nav-bar.tsx +++ b/web/components/nav/nav-bar.tsx @@ -63,6 +63,7 @@ export function BottomNavBar() { currentPage={currentPage} item={{ name: formatMoney(user.balance), + trackingEventName: 'profile', href: `/${user.username}?tab=bets`, icon: () => ( @@ -102,7 +104,7 @@ function NavBarItem(props: { item: Item; currentPage: string }) { 'block w-full py-1 px-3 text-center hover:bg-indigo-200 hover:text-indigo-700', currentPage === item.href && 'bg-gray-200 text-indigo-700' )} - onClick={trackCallback('navbar: ' + item.name)} + onClick={track} > {item.icon && } {item.name} diff --git a/web/components/nav/sidebar.tsx b/web/components/nav/sidebar.tsx index 8c3ceb02..5ce9e239 100644 --- a/web/components/nav/sidebar.tsx +++ b/web/components/nav/sidebar.tsx @@ -120,6 +120,7 @@ function getMoreMobileNav() { export type Item = { name: string + trackingEventName?: string href: string icon?: React.ComponentType<{ className?: string }> } diff --git a/web/components/referrals-button.tsx b/web/components/referrals-button.tsx index c23958fc..74fc113d 100644 --- a/web/components/referrals-button.tsx +++ b/web/components/referrals-button.tsx @@ -10,9 +10,11 @@ import { Row } from 'web/components/layout/row' import { Avatar } from 'web/components/avatar' import { UserLink } from 'web/components/user-page' import { useReferrals } from 'web/hooks/use-referrals' +import { FilterSelectUsers } from 'web/components/filter-select-users' +import { getUser, updateUser } from 'web/lib/firebase/users' -export function ReferralsButton(props: { user: User }) { - const { user } = props +export function ReferralsButton(props: { user: User; currentUser?: User }) { + const { user, currentUser } = props const [isOpen, setIsOpen] = useState(false) const referralIds = useReferrals(user.id) @@ -28,6 +30,7 @@ export function ReferralsButton(props: { user: User }) { referralIds={referralIds ?? []} isOpen={isOpen} setIsOpen={setIsOpen} + currentUser={currentUser} /> ) @@ -38,8 +41,21 @@ function ReferralsDialog(props: { referralIds: string[] isOpen: boolean setIsOpen: (isOpen: boolean) => void + currentUser?: User }) { - const { user, referralIds, isOpen, setIsOpen } = props + const { user, referralIds, isOpen, setIsOpen, currentUser } = props + const [referredBy, setReferredBy] = useState([]) + const [isSubmitting, setIsSubmitting] = useState(false) + const [errorText, setErrorText] = useState('') + + const [referredByUser, setReferredByUser] = useState() + useEffect(() => { + if (isOpen && !referredByUser && user?.referredByUserId) { + getUser(user.referredByUserId).then((user) => { + setReferredByUser(user) + }) + } + }, [isOpen, referredByUser, user.referredByUserId]) useEffect(() => { prefetchUsers(referralIds) @@ -56,6 +72,75 @@ function ReferralsDialog(props: { title: 'Referrals', content: , }, + { + title: 'Referred by', + content: ( + <> + {user.id === currentUser?.id && !referredByUser ? ( + <> + + + + + + {referredBy.length > 0 && + 'Careful: you can only set who referred you once!'} + + {errorText} + + ) : ( +
+ {referredByUser ? ( + + + + + ) : ( + No one... + )} +
+ )} + + ), + }, ]} /> diff --git a/web/components/user-page.tsx b/web/components/user-page.tsx index d72a2a16..07f722d7 100644 --- a/web/components/user-page.tsx +++ b/web/components/user-page.tsx @@ -159,7 +159,7 @@ export function UserPage(props: { @@ -202,7 +202,7 @@ export function UserPage(props: { - + diff --git a/web/pages/account.tsx b/web/pages/account.tsx deleted file mode 100644 index 59d938c3..00000000 --- a/web/pages/account.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react' -import { Page } from 'web/components/page' -import { UserPage } from 'web/components/user-page' -import { useUser } from 'web/hooks/use-user' -import { firebaseLogin } from 'web/lib/firebase/users' - -function SignInCard() { - return ( -
-
- -
-
-

Welcome!

-

Sign in to get started

-
- -
-
-
- ) -} - -export default function Account() { - const user = useUser() - return user ? ( - - ) : ( - - - - ) -} diff --git a/web/pages/admin.tsx b/web/pages/admin.tsx index db24996d..e709e875 100644 --- a/web/pages/admin.tsx +++ b/web/pages/admin.tsx @@ -62,13 +62,19 @@ function UsersTable() { class="hover:underline hover:decoration-indigo-400 hover:decoration-2" href="/${cell}">@${cell}`), }, + { + id: 'name', + name: 'Name', + formatter: (cell) => + html(`${cell}`), + }, { id: 'email', name: 'Email', }, { id: 'createdTime', - name: 'Created Time', + name: 'Created', formatter: (cell) => html( `${dayjs(cell as number).format( diff --git a/web/pages/link/[slug].tsx b/web/pages/link/[slug].tsx index 60966756..eed68e1a 100644 --- a/web/pages/link/[slug].tsx +++ b/web/pages/link/[slug].tsx @@ -46,7 +46,7 @@ export default function ClaimPage() { if (result.data.status == 'error') { throw new Error(result.data.message) } - router.push('/account?claimed-mana=yes') + user && router.push(`/${user.username}?claimed-mana=yes`) } catch (e) { console.log(e) const message =