Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENG-4013 feat(portal): create readonly route folder structure and add in read only views #839

Merged
merged 19 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2da982b
Initial route scaffolding for readonly+
jonathanprozzi Sep 12, 2024
0df45bb
Continues scaffolding out the readonly views
jonathanprozzi Sep 12, 2024
fed9ed3
Begins scaffolding the identity route
jonathanprozzi Sep 12, 2024
808dfb4
Remove unused imports after readonly pruning
jonathanprozzi Sep 12, 2024
bd61ba5
Initial pass for claim routes
jonathanprozzi Sep 12, 2024
d5c01f3
Initial pass for claim parent route
jonathanprozzi Sep 12, 2024
90ed99c
Adds in parent loader at readonly+/_layout and resolves some lint errors
jonathanprozzi Sep 12, 2024
d7efdfd
Removes stake + position from readonly wallet views
jonathanprozzi Sep 12, 2024
9b1f044
Scaffolds stripping out some pieces from the readonly profile view --…
jonathanprozzi Sep 12, 2024
045ec20
Continues to refactor each of the readonly views
jonathanprozzi Sep 12, 2024
a399576
Addresses most issues on read only profile -- connections is still ha…
jonathanprozzi Sep 12, 2024
fbb42d7
Bypasses the auth redirects if route starts with /readonly
jonathanprozzi Sep 12, 2024
75fe1c2
Continues removing auth guards from individual routes
jonathanprozzi Sep 12, 2024
682b2dc
Removes connections from ReadOnly views because of the reliance on us…
jonathanprozzi Sep 12, 2024
8cc6afb
Removes remaining instances of auth guard from routes, temporarily ad…
jonathanprozzi Sep 12, 2024
1b13bc3
Removes remaining auth guards from the read only profile routes
jonathanprozzi Sep 12, 2024
54d06cd
feat: Add readonly banner
0xjojikun Sep 12, 2024
4f2acaa
Update apps/portal/app/consts/paths.ts
0xjojikun Sep 12, 2024
4a832c3
Merge branch 'main' into eng-4013-create-readonly-route-folder-structure
0xjojikun Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/portal/app/.server/multivault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const baseVault = BigInt(0)
export async function getVaultDetails(
contract: string,
vid: string,
wallet?: Address,
wallet?: Address | null,
counterVault?: string,
) {
const multiVaultContract = createMultiVaultContract(contract)
Expand Down
1 change: 0 additions & 1 deletion apps/portal/app/.server/privy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export const getPrivyAccessToken = (req: Request): string | null => {
req.headers.get('Authorization')?.replace('Bearer ', '') ||
cookies['privy-id-token']
if (authIdToken) {
logger('authIdToken', authIdToken)
return authIdToken
}

Expand Down
2 changes: 1 addition & 1 deletion apps/portal/app/components/list/tags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function TagsList({
claims: ClaimPresenter[]
claim: ClaimPresenter
tag?: IdentityPresenter | null
wallet: string
wallet?: string
pagination?: PaginationType
paramPrefix?: string
enableHeader?: boolean
Expand Down
82 changes: 82 additions & 0 deletions apps/portal/app/components/read-only-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
BannerVariant,
Button,
ButtonVariant,
cn,
Icon,
IconName,
IconNameType,
Text,
TextVariant,
} from '@0xintuition/1ui'

import { Link } from '@remix-run/react'
import { cva, VariantProps } from 'class-variance-authority'

const DEFAULT_READ_ONLY_BANNER_TITLE =
'This is a read only view. To stake or interact, click the link below to view it in the app.'

const readOnlyBannerVariants = cva(
'flex flex-col w-full justify-between rounded-lg border p-3 items-center gap-3',
{
variants: {
variant: {
[BannerVariant.info]: 'text-accent border-accent/40 bg-accent/10',
[BannerVariant.warning]: 'text-warning border-warning/40 bg-warning/10',
[BannerVariant.error]:
'text-destructive border-destructive/40 bg-destructive/10',
},
},
defaultVariants: {
variant: BannerVariant.info,
},
},
)

export interface IReadOnlyBannerProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof readOnlyBannerVariants> {
title?: string
to?: string
}
export default function ReadOnlyBanner({
variant,
title = DEFAULT_READ_ONLY_BANNER_TITLE,
to,
children,
className,
...props
}: IReadOnlyBannerProps) {
let iconName: IconNameType = IconName.circleInfo
let buttonVariant = ButtonVariant.accent

if (variant === BannerVariant.warning) {
iconName = IconName.triangleExclamation
buttonVariant = ButtonVariant.warning
} else if (variant === BannerVariant.error) {
iconName = IconName.circleX
buttonVariant = ButtonVariant.destructive
}
return (
<div
className={cn(readOnlyBannerVariants({ variant }), 'p-3', className)}
{...props}
>
<div className="flex gap-2 items-center">
<Icon name={iconName} className="w-5 h-5 shrink-0" />
<Text variant={TextVariant.body} className="text-inherit">
{title}
</Text>
</div>
{children}
{to && (
<Link to={to} className="w-full">
<Button variant={buttonVariant} className="w-full">
View in App
<Icon name={IconName.squareArrowTopRight} className="w-4 h-4" />
</Button>
</Link>
)}
</div>
)
}
19 changes: 19 additions & 0 deletions apps/portal/app/consts/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,25 @@ export const activityRouteOptions = [
},
]

export const readOnlyUserIdentityRouteOptions = [
{ value: 'overview', label: 'Overview', basePath: PATHS.READONLY_PROFILE },
{
value: 'data-about',
label: 'Data About',
basePath: PATHS.READONLY_PROFILE,
},
{
value: 'data-created',
label: 'Data Created',
basePath: PATHS.READONLY_PROFILE,
},
{
value: 'lists',
label: 'Lists',
basePath: PATHS.READONLY_PROFILE,
},
]

// SPECIAL ATOMS

export const TAG_PREDICATE_VAULT_ID_TESTNET = 3 // used in testnet tag claim as predicate
Expand Down
5 changes: 5 additions & 0 deletions apps/portal/app/consts/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ export const PATHS = {
// Other
MAINTENANCE: '/maintenance',
THE_BIG_BANG: '/the-big-bang',
// ReadOnly
READONLY_LIST: '/readonly/list',
READONLY_IDENTITY: '/readonly/identity',
READONLY_CLAIM: '/readonly/claim',
READONLY_PROFILE: '/readonly/profile',
}
7 changes: 7 additions & 0 deletions apps/portal/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
const cookie = await onboardingModalCookie.parse(cookieHeader)
const redirectTo = new URL(request.url).searchParams.get('redirectTo')

const url = new URL(request.url)
const isReadonlyRoute = url.pathname.startsWith('/readonly/')

if (isReadonlyRoute) {
return json({})
}

if (!cookie) {
throw redirect('/intro')
}
Expand Down
8 changes: 8 additions & 0 deletions apps/portal/app/routes/readonly+/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Outlet } from '@remix-run/react'

// This is the parent loader and layout for the /readonly routes
// We can add any loader logic we need to here

export default function ReadOnlyLayout() {
return <Outlet />
}
193 changes: 193 additions & 0 deletions apps/portal/app/routes/readonly+/claim+/$id+/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { Suspense, useEffect, useState } from 'react'

import {
Claim,
Identity,
Tabs,
TabsList,
TabsTrigger,
Text,
} from '@0xintuition/1ui'
import { ClaimsService, IdentityPresenter, VaultType } from '@0xintuition/api'

import { ErrorPage } from '@components/error-page'
import { PositionsOnClaim } from '@components/list/positions-on-claim'
import { PaginatedListSkeleton, TabsSkeleton } from '@components/skeleton'
import { useLiveLoader } from '@lib/hooks/useLiveLoader'
import { getPositionsOnClaim } from '@lib/services/positions'
import {
getAtomDescription,
getAtomImage,
getAtomIpfsLink,
getAtomLabel,
getAtomLink,
invariant,
} from '@lib/utils/misc'
import { defer, LoaderFunctionArgs } from '@remix-run/node'
import {
Await,
useNavigation,
useRouteLoaderData,
useSearchParams,
} from '@remix-run/react'
import { ReadOnlyClaimDetailsLoaderData } from '@routes/readonly+/claim+/$id'
import { fetchWrapper } from '@server/api'
import { NO_CLAIM_ERROR, NO_PARAM_ID_ERROR } from 'app/consts'
import { PaginationType } from 'app/types/pagination'

export async function loader({ request, params }: LoaderFunctionArgs) {
const id = params.id
invariant(id, NO_PARAM_ID_ERROR)

const url = new URL(request.url)
const searchParams = new URLSearchParams(url.search)

return defer({
positionsData: getPositionsOnClaim({
request,
claimId: id,
searchParams,
}),
claim: fetchWrapper(request, {
method: ClaimsService.getClaimById,
args: { id },
}),
})
}

export default function ReadOnlyClaimOverview() {
const { positionsData } = useLiveLoader<typeof loader>(['attest', 'create'])
const { claim } =
useRouteLoaderData<ReadOnlyClaimDetailsLoaderData>(
'routes/readonly+/claim+/$id',
) ?? {}
invariant(claim, NO_CLAIM_ERROR)

const [searchParams, setSearchParams] = useSearchParams()
const [isNavigating, setIsNavigating] = useState(false)

const { state } = useNavigation()

function handleTabChange(value: VaultType | null) {
const newParams = new URLSearchParams(searchParams)
if (value === null) {
newParams.delete('positionDirection')
} else {
newParams.set('positionDirection', value)
}
newParams.delete('positionsSearch')
newParams.set('page', '1')
setSearchParams(newParams)
setIsNavigating(true)
}

useEffect(() => {
if (state === 'idle') {
setIsNavigating(false)
}
}, [state])

return (
<div className="flex-col justify-start items-start flex w-full gap-6">
<div className="flex-row hidden md:flex">
<Claim
size="xl"
maxIdentityLength={60}
subject={{
variant: claim.subject?.is_user ? Identity.user : Identity.nonUser,
label: getAtomLabel(claim.subject as IdentityPresenter),
imgSrc: getAtomImage(claim.subject as IdentityPresenter),
id: claim.subject?.identity_id,
description: getAtomDescription(claim.subject as IdentityPresenter),
ipfsLink: getAtomIpfsLink(claim.subject as IdentityPresenter),
link: getAtomLink(claim.subject as IdentityPresenter),
}}
predicate={{
variant: claim.predicate?.is_user
? Identity.user
: Identity.nonUser,
label: getAtomLabel(claim.predicate as IdentityPresenter),
imgSrc: getAtomImage(claim.predicate as IdentityPresenter),
id: claim.predicate?.identity_id,
description: getAtomDescription(
claim.predicate as IdentityPresenter,
),
ipfsLink: getAtomIpfsLink(claim.predicate as IdentityPresenter),
link: getAtomLink(claim.predicate as IdentityPresenter),
}}
object={{
variant: claim.object?.is_user ? Identity.user : Identity.nonUser,
label: getAtomLabel(claim.object as IdentityPresenter),
imgSrc: getAtomImage(claim.object as IdentityPresenter),
id: claim.object?.identity_id,
description: getAtomDescription(claim.object as IdentityPresenter),
ipfsLink: getAtomIpfsLink(claim.object as IdentityPresenter),
link: getAtomLink(claim.object as IdentityPresenter),
}}
/>
</div>
<div className="self-stretch justify-between items-center inline-flex mt-6">
<Text
variant="headline"
weight="medium"
className="text-secondary-foreground w-full"
>
Positions on this Claim
</Text>
</div>
<Tabs defaultValue="all">
<Suspense fallback={<TabsSkeleton numOfTabs={3} />}>
<Await resolve={positionsData}>
<TabsList>
<TabsTrigger
value="all"
label="All"
totalCount={claim.num_positions}
onClick={(e) => {
e.preventDefault()
handleTabChange(null)
}}
/>
<TabsTrigger
value="for"
label="For"
totalCount={claim.for_num_positions}
onClick={(e) => {
e.preventDefault()
handleTabChange('for')
}}
/>
<TabsTrigger
value="against"
label="Against"
totalCount={claim.against_num_positions}
onClick={(e) => {
e.preventDefault()
handleTabChange('against')
}}
/>
</TabsList>
</Await>
</Suspense>
</Tabs>
<Suspense fallback={<PaginatedListSkeleton />}>
{isNavigating ? (
<PaginatedListSkeleton />
) : (
<Await resolve={positionsData}>
{(resolvedPositionsData) => (
<PositionsOnClaim
positions={resolvedPositionsData.data}
pagination={resolvedPositionsData.pagination as PaginationType}
/>
)}
</Await>
)}
</Suspense>
</div>
)
}

export function ErrorBoundary() {
return <ErrorPage routeName="claim/$id/index" />
}
Loading
Loading