Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c8cdf56
feat(earns): revamp User Positions page to match new design
tienkane Mar 17, 2026
87e0c12
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
tienkane Mar 17, 2026
184477b
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
tienkane Mar 18, 2026
9b7fd24
feat: get position header for smart exit
tienkane Mar 19, 2026
3bab118
feat(earns): revamp position detail page with tabbed layout and new U…
tienkane Mar 23, 2026
f0ca00a
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
tienkane Mar 23, 2026
730086e
fix(earns): keep skeleton visible until liquidity chart is fully ready
tienkane Mar 23, 2026
e803405
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
tienkane Mar 23, 2026
6c037b1
fix(earns): restore reposition/increase liquidity sub-action link
tienkane Mar 23, 2026
8a5ee6c
refactor(earns): extract PositionDetailContext to eliminate prop dril…
tienkane Mar 23, 2026
9fb19e4
feat(earns): improve price range visualization with tick-based positi…
tienkane Mar 23, 2026
3dbc790
feat(earns): interpolate current price indicator color along gradient
tienkane Mar 23, 2026
d5613db
fix(earns): truncate long protocol info badge in position list
tienkane Mar 23, 2026
a85e1e6
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
tienkane Mar 23, 2026
3bd3e5e
perf(earns): consolidate duplicate hooks and fix re-render issues in …
tienkane Mar 23, 2026
2a6d69e
feat(earns): add UX animations to position list and banner
tienkane Mar 23, 2026
28be396
feat(earns): add UX animations and AnimatedNumber to position detail …
tienkane Mar 23, 2026
78e74c6
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
tienkane Mar 31, 2026
8c35bb2
Squash
neikop Mar 6, 2026
cf2499f
feat: enhance pool detail page with improved context and analytics tr…
neikop Mar 23, 2026
fdeceac
feat: refactor zap functionality and integrate new APIs
neikop Mar 23, 2026
2dd2267
Update charts
neikop Mar 24, 2026
c335b7a
Fix liquidity amount
neikop Mar 24, 2026
98c2e43
Update Apr history chart
neikop Mar 30, 2026
f084b8d
Update earnings tab
neikop Mar 30, 2026
6907cf5
Update pool price chart
neikop Mar 30, 2026
3d94344
Update logic, earnings tab
neikop Mar 30, 2026
ea788ce
Action form to the right
neikop Mar 30, 2026
894102b
Update charts
neikop Mar 30, 2026
295e9d8
Merge branch 'feat/implement-pool-detail-page' into feat/positions-re…
neikop Mar 31, 2026
8d621b0
Update add position detail api
neikop Mar 31, 2026
7551820
Merge branch 'feat/implement-pool-detail-page' into feat/positions-re…
neikop Mar 31, 2026
208331d
update: use pool details chart components
neikop Mar 31, 2026
f3af97b
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
tienkane Apr 1, 2026
eaba91f
update: link position detail header to new pool detail page
tienkane Apr 1, 2026
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ dist-ssr
.npmrc

i18n-extract

# TypeScript build info
*.tsbuildinfo
1 change: 1 addition & 0 deletions apps/kyberswap-interface/.env
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ VITE_CAMPAIGN_URL=https://arbitrum-stip.kyberswap.com/api
VITE_REFERRAL_URL=https://referral.kyberswap.com/api

VITE_TOKEN_API_URL=https://token-api.kyberswap.com/api
VITE_ZAP_API_URL=https://zap-api.kyberswap.com
VITE_ZAP_EARN_URL=https://earn-service.kyberswap.com/api

VITE_AFFILIATE_SERVICE=https://pre-affiliate-service.kyberengineering.io/api
Expand Down
1 change: 1 addition & 0 deletions apps/kyberswap-interface/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ VITE_CAMPAIGN_URL=https://arbitrum-stip.kyberswap.com/api
VITE_REFERRAL_URL=https://referral.kyberswap.com/api

VITE_TOKEN_API_URL=https://token-api.kyberswap.com/api
VITE_ZAP_API_URL=https://zap-api.kyberswap.com
VITE_ZAP_EARN_URL=https://earn-service.kyberswap.com/api

VITE_AFFILIATE_SERVICE=https://pre-affiliate-service.kyberengineering.io/api
Expand Down
4 changes: 3 additions & 1 deletion apps/kyberswap-interface/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ VITE_CAMPAIGN_URL=https://arbitrum-stip.kyberswap.com/api
VITE_REFERRAL_URL=https://referral.kyberswap.com/api

VITE_TOKEN_API_URL=https://token-api.kyberswap.com/api
VITE_ZAP_EARN_URL=https://earn-service.kyberswap.com/api
VITE_ZAP_API_URL=https://zap-api.kyberswap.com
# VITE_ZAP_EARN_URL=https://earn-service.kyberswap.com/api
VITE_ZAP_EARN_URL=https://pre-zap-earn-service.kyberengineering.io/api

VITE_AFFILIATE_SERVICE=https://affiliate-service.kyberswap.com/api
VITE_SOLANA_RPC=https://solana.kyberengineering.io
Expand Down
1 change: 1 addition & 0 deletions apps/kyberswap-interface/.env.stg
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ VITE_CAMPAIGN_URL=https://arbitrum-stip.kyberswap.com/api
VITE_REFERRAL_URL=https://referral.kyberswap.com/api

VITE_TOKEN_API_URL=https://token-api.kyberswap.com/api
VITE_ZAP_API_URL=https://zap-api.kyberswap.com
VITE_ZAP_EARN_URL=https://earn-service.kyberswap.com/api


Expand Down
2 changes: 2 additions & 0 deletions apps/kyberswap-interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.4",
"@cypress/grep": "^3.1.5",
"@kyber/hooks": "workspace:^",
"@kyber/rpc-client": "workspace:*",
"@kyber/schema": "workspace:^",
"@kyber/token-selector": "workspace:^",
"@kyber/ui": "workspace:^",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions apps/kyberswap-interface/src/assets/svg/kyber/ic_bag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 9 additions & 9 deletions apps/kyberswap-interface/src/components/Pagination/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export const PaginationContainer = styled.ul`
display: flex;
justify-content: center;
list-style-type: none;
background: ${({ theme }) => theme.background};
background: ${({ theme }) => rgba(theme.subText, 0.04)};
margin: 0;
padding: 16px;
padding: 12px;
gap: 4px;
border-radius: 0 0 20px 20px;
`

export const PaginationItem = styled.li<{ $disabled?: boolean; $selected?: boolean }>`
Expand Down Expand Up @@ -42,22 +44,20 @@ export const PaginationButton = styled.div<{ active?: boolean; haveBg?: boolean
justify-content: center;
align-items: center;
cursor: pointer;
padding: 8px;
padding: 0;
border-radius: 50%;

color: ${({ theme, active }) => (active ? theme.primary : theme.subText)};
color: ${({ theme, active }) => (active ? theme.primary : rgba(theme.white, 0.6))};
background: ${({ theme, active, haveBg }) =>
!haveBg
? active
? theme.background
: rgba(theme.background, 0.4)
? rgba(theme.black, 0.48)
: rgba(theme.black, 0.2)
: active
? theme.buttonBlack
: rgba(theme.buttonBlack, 0.4)};
padding: 0;
border-radius: 50%;

&:hover {
background: ${({ theme, haveBg }) => (haveBg ? theme.buttonBlack : theme.background)};
background: ${({ theme, haveBg }) => (haveBg ? theme.buttonBlack : rgba(theme.black, 0.4))};
}
`
105 changes: 105 additions & 0 deletions apps/kyberswap-interface/src/components/SegmentedControl/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { type ReactNode } from 'react'
import styled, { css } from 'styled-components'

export type SegmentedControlOption<T extends string = string> = {
label: ReactNode
value: T
disabled?: boolean
}

type SegmentedControlProps<T extends string> = {
onChange?: (value: T) => void
options?: readonly SegmentedControlOption<T>[]
size?: 'sm' | 'md'
value?: T
}

const sizeStyles = {
sm: css`
padding: 8px;
`,
md: css`
padding: 8px 12px;
`,
}

const Container = styled.div<{ $optionCount: number }>`
display: grid;
position: relative;
grid-template-columns: repeat(${({ $optionCount }) => $optionCount}, minmax(0, 1fr));
align-items: center;
border: 1px solid ${({ theme }) => theme.border};
border-radius: 999px;
background: ${({ theme }) => theme.background};
`

const ActivePill = styled.div<{ $activeIndex: number; $optionCount: number }>`
position: absolute;
top: 1px;
bottom: 1px;
left: 1px;
width: calc((100% - 2px) / ${({ $optionCount }) => $optionCount});
border-radius: 999px;
background: ${({ theme }) => theme.tabActive};
transform: translateX(calc(100% * ${({ $activeIndex }) => $activeIndex}));
transition: transform 200ms ease, background 200ms ease;
pointer-events: none;
`

const OptionButton = styled.button<{ $active: boolean; $size: 'sm' | 'md' }>`
position: relative;
z-index: 1;
min-width: 48px;
border: 0;
border-radius: 999px;
background: transparent;
color: ${({ theme, $active }) => ($active ? theme.text : theme.subText)};
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: color 200ms ease, background 200ms ease;

${({ $size }) => sizeStyles[$size]}

:hover:not(:disabled) {
background: ${({ theme, $active }) => ($active ? 'transparent' : theme.buttonGray)};
}

:disabled {
cursor: not-allowed;
opacity: 0.5;
}
`

const SegmentedControl = <T extends string>({
onChange,
options = [],
size = 'sm',
value,
}: SegmentedControlProps<T>) => {
if (!options.length) return null

const activeIndex = options.findIndex(option => option.value === value)

return (
<Container $optionCount={options.length} role="tablist">
<ActivePill $activeIndex={Math.max(activeIndex, 0)} $optionCount={options.length} />
{options.map(option => (
<OptionButton
$active={option.value === value}
$size={size}
aria-selected={option.value === value}
disabled={option.disabled || !onChange}
key={option.value}
onClick={() => !option.disabled && onChange?.(option.value)}
role="tab"
type="button"
>
{option.label}
</OptionButton>
))}
</Container>
)
}

export default SegmentedControl
37 changes: 37 additions & 0 deletions apps/kyberswap-interface/src/components/Skeleton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type ComponentProps } from 'react'
import LoadingSkeleton from 'react-loading-skeleton'
import { createGlobalStyle } from 'styled-components'

import useTheme from 'hooks/useTheme'

export type SkeletonProps = ComponentProps<typeof LoadingSkeleton>

const SKELETON_CONTAINER_CLASSNAME = 'ks-skeleton-container'

const SkeletonGlobalStyle = createGlobalStyle`
.${SKELETON_CONTAINER_CLASSNAME} {
display: block;
line-height: 0;
}
`

const Skeleton = ({ baseColor, containerClassName, highlightColor, ...props }: SkeletonProps) => {
const theme = useTheme()
const mergedContainerClassName = containerClassName
? `${SKELETON_CONTAINER_CLASSNAME} ${containerClassName}`
: SKELETON_CONTAINER_CLASSNAME

return (
<>
<SkeletonGlobalStyle />
<LoadingSkeleton
{...props}
baseColor={baseColor ?? theme.background}
containerClassName={mergedContainerClassName}
highlightColor={highlightColor ?? theme.buttonGray}
/>
</>
)
}

export default Skeleton
60 changes: 60 additions & 0 deletions apps/kyberswap-interface/src/components/Stack/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { CSSProperties, ComponentProps } from 'react'
import { Box } from 'rebass/styled-components'
import styled from 'styled-components'

const getSpacing = (value?: CSSProperties['gap']) => {
if (typeof value === 'number') {
return `${value}px`
}
return value
}

const getLength = (value?: CSSProperties['borderRadius']) => {
if (typeof value === 'number') return `${value}px`
return value
}

type BoxProps = ComponentProps<typeof Box>

type StackStyleProps = Pick<
CSSProperties,
| 'alignItems'
| 'background'
| 'border'
| 'borderRadius'
| 'columnGap'
| 'flexDirection'
| 'flexWrap'
| 'gap'
| 'justifyContent'
| 'position'
| 'rowGap'
>

export type StackProps = BoxProps &
StackStyleProps & {
direction?: StackStyleProps['flexDirection']
spacing?: StackStyleProps['gap']
align?: StackStyleProps['alignItems']
justify?: StackStyleProps['justifyContent']
wrap?: StackStyleProps['flexWrap']
}

export const Stack = styled(Box)<StackProps>`
display: flex;
position: ${({ position }) => position};
flex-direction: ${({ direction }) => direction || 'column'};
align-items: ${({ alignItems, align }) => alignItems || align || 'stretch'};
justify-content: ${({ justifyContent, justify }) => justifyContent || justify || 'flex-start'};
flex-wrap: ${({ wrap }) => wrap || 'nowrap'};
gap: ${({ gap, spacing }) => getSpacing(gap || spacing)};
row-gap: ${({ rowGap }) => getSpacing(rowGap)};
column-gap: ${({ columnGap }) => getSpacing(columnGap)};
background: ${({ background }) => background};
border: ${({ border }) => border};
border-radius: ${({ borderRadius }) => getLength(borderRadius)};
`

export const HStack = styled(Stack)`
flex-direction: row;
`
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ const Routing = ({
handleScroll()
}, [tradeComposition, maxHeight, handleScroll])

const isSwapRouteV3 = tradeComposition?.every(item => 'pool' in item)
const isSwapRouteV3 =
(tradeComposition as Array<SwapRouteV2 | SwapRouteV3> | undefined)?.every(
(item): item is SwapRouteV3 => 'pool' in item,
) ?? false

return (
<Shadow ref={shadowRef}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,15 +281,13 @@ const CreatePoolModal = ({ isOpen, filterChainId, onDismiss, onSubmit }: Props)
fullWidth
options={chainOptions}
value={selectedChainId.toString()}
alignLeft
mobileFullWidth
onChange={value => setSelectedChainId(Number(value) as ChainId)}
/>
<DropdownMenu
fullWidth
options={protocolOptions}
value={selectedProtocol}
alignLeft
mobileFullWidth
onChange={value => setSelectedProtocol(value as Exchange)}
/>
Expand Down
1 change: 1 addition & 0 deletions apps/kyberswap-interface/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export const APP_PATHS = {
PARTNER_SWAP: '/partner-swap',
FIND_POOL: '/find',
POOLS: '/pools',
ADD_LIQUIDITY: '/pools/add-liquidity',
CLASSIC_CREATE_POOL: '/create',
CLASSIC_ADD_LIQ: '/add',
CLASSIC_REMOVE_POOL: '/remove',
Expand Down
16 changes: 3 additions & 13 deletions apps/kyberswap-interface/src/hooks/usePermitNft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers
const notify = useNotify()
const [isSigningInProgress, setIsSigningInProgress] = useState(false)
const [permitData, setPermitData] = useState<PermitNftResult | null>(null)
const [detectedVersion, setDetectedVersion] = useState<'v3' | 'v4' | null>(null)

const nftContract = useReadingContract(contractAddress, NFT_PERMIT_ABI)

Expand All @@ -66,20 +65,11 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers
const actualVersion = useMemo(() => {
if (version !== 'auto') return version

if (detectedVersion) return detectedVersion
if (noncesState?.result && !noncesState.error) return 'v4'
if (positionsState?.result && !positionsState.error) return 'v3'

if (noncesState?.result && !noncesState.error) {
setDetectedVersion('v4')
return 'v4'
}

// Try to detect based on available data
if (positionsState?.result && !positionsState.error) {
setDetectedVersion('v3')
return 'v3'
}
return 'v4' // Default to v4 if uncertain
}, [version, detectedVersion, positionsState, noncesState])
}, [version, positionsState, noncesState])

const permitState = useMemo(() => {
if (!account || !contractAddress || !tokenId || !spender) {
Expand Down
Loading
Loading