Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion api/ApiHelper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ export function initAPI(returnSSRResponse: boolean = false): API {
return {
id: t.id || 0,
name: t.name || '',
isDisabled: false,
isDisabled: t.isDisabled ?? false,
priority: 0
}
})
Expand All @@ -568,6 +568,9 @@ export function initAPI(returnSSRResponse: boolean = false): API {
resolve()
})
.catch(e => {
// The listener was already created but linking the subscription failed.
// Roll it back so it doesn't linger invisibly and count against the limit.
unsubscribe(listener).catch(() => {})
let errorMessage = typeof e === 'string' ? e : e?.message || ''
if (errorMessage.includes('subscription_limit_reached')) {
toast.error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import styles from './ExportArchivedData.module.css'
import HelpIcon from '@mui/icons-material/Help'
import Tooltip from '../../Tooltip/Tooltip'
import NumberElement from '../../Number/Number'
import { isValidDiscordWebhookUrl } from '../../../utils/NotificationChannelUtils'

interface Props {
itemTag: string
Expand All @@ -23,7 +24,7 @@ function ExportArchivedData(props: Props) {
let [showConfirmDialog, setShowConfirmDialog] = useState(false)
let [isExportRunning, setIsExportRunning] = useState(false)

let isDiscordWebhookUrlValid = discordWebhookUrl && (discordWebhookUrl.startsWith('https://discord.com/api/webhooks/') || discordWebhookUrl.startsWith('https://discordapp.com/api/webhooks/'))
let isDiscordWebhookUrlValid = !!discordWebhookUrl && isValidDiscordWebhookUrl(discordWebhookUrl)

useEffect(() => {
setShowModal(props.show)
Expand Down Expand Up @@ -80,7 +81,7 @@ function ExportArchivedData(props: Props) {
onChange={e => setDiscordWebhookUrl(e.target.value)}
placeholder="Discord Webhook Url (https://discord.com/api/... or https://discordapp.com/api/...)"
/>
{discordWebhookUrl && !discordWebhookUrl?.startsWith('https://discord.com/api/') && !discordWebhookUrl?.startsWith('https://discordapp.com/api/webhooks/') ? (
{discordWebhookUrl && !isValidDiscordWebhookUrl(discordWebhookUrl) ? (
<div>
<span style={{ color: 'red' }}>The Discord Webhook URL has to start with "https://discord.com/api/..." or "https://discordapp.com/api/webhooks/..."</span>
</div>
Expand Down
2 changes: 2 additions & 0 deletions components/Favorites/FavoriteItemCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export default function FavoriteItemCard({ favorite, movement, isMovementLoading
<SubscribeButton
topic={favorite.tag}
type={favorite.bazaar ? 'bazaar' : 'item'}
currentPrice={currentPrice}
buttonContent={<span>Notify</span>}
/>
<FavoriteToggle item={item} size="small" />
Expand All @@ -135,6 +136,7 @@ export default function FavoriteItemCard({ favorite, movement, isMovementLoading
<SubscribeButton
topic={favorite.tag}
type={favorite.bazaar ? 'bazaar' : 'item'}
currentPrice={currentPrice}
buttonContent={
<span className={styles.mobileDropdownItem}>
<NotificationsOutlinedIcon />
Expand Down
7 changes: 5 additions & 2 deletions components/NotificationTargets/NotificationTargetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button, Form } from 'react-bootstrap'
import api from '../../api/ApiHelper'
import askForNotificationPermissons from '../../utils/NotificationPermisson'
import { getNotficationWhenEnumAsString, getNotificationTypeAsString } from '../../utils/NotificationUtils'
import { isValidDiscordWebhookUrl } from '../../utils/NotificationChannelUtils'

const TYPE_OPTIONS: NotificationType[] = ['DiscordWebhook', 'FIREBASE', 'InGame']
const WHEN_OPITIONS: NotificationWhen[] = ['NEVER', 'AfterFail', 'ALWAYS']
Expand All @@ -22,6 +23,8 @@ function NotificationTargetForm(props: Props) {
let [when, setWhen] = useState<NotificationWhen | number>(props.defaultNotificationTarget?.when || 'ALWAYS')
let [disabled, setDisabled] = useState(false)

let discordUrlInvalid = type === 'DiscordWebhook' && !!target && !isValidDiscordWebhookUrl(target)

async function addNotificationTarget() {
let targetToSend = target

Expand Down Expand Up @@ -81,9 +84,9 @@ function NotificationTargetForm(props: Props) {
type="text"
onChange={e => setTarget(e.target.value)}
placeholder={type === 'DiscordWebhook' ? 'Discord Webhook Url (https://discord.com/api/... or https://discordapp.com/api/...)' : 'Webhook Url'}
isInvalid={(type === 'DiscordWebhook' && target && !target?.startsWith('https://discord.com/api/') && !target?.startsWith('https://discordapp.com/api/webhooks/')) === true}
isInvalid={discordUrlInvalid}
/>
{type === 'DiscordWebhook' && target && !target?.startsWith('https://discord.com/api/') && !target?.startsWith('https://discordapp.com/api/webhooks/') ? (
{discordUrlInvalid ? (
<div>
<span style={{ color: 'red' }}>The Discord Webhook URL has to start with "https://discord.com/api/..." or "https://discordapp.com/api/webhooks/..."</span>
</div>
Expand Down
22 changes: 17 additions & 5 deletions components/NotificationTargets/NotificationTargets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useEffect, useState } from 'react'
import { Button, Table } from 'react-bootstrap'
import { toast } from 'react-toastify'
import api from '../../api/ApiHelper'
import NotificationTargetForm from './NotificationTargetForm'
import DeleteIcon from '@mui/icons-material/Delete'
Expand Down Expand Up @@ -29,9 +30,20 @@ function NotificationTargets() {
}

function deleteNotificationTarget(target: NotificationTarget) {
api.deleteNotificationTarget(target).then(() => {
setNotificationTargets(notificationTargets.filter(t => t.name !== target.name))
})
api.deleteNotificationTarget(target)
.then(() => {
setNotificationTargets(notificationTargets.filter(t => t.name !== target.name))
})
.catch(error => {
// the http layer rejects with a plain string like `HTTP 500: {"slug":"subscription_depends",...}`,
// so apiErrorHandler (which only handles error.message) stays silent - surface it here instead.
let message = typeof error === 'string' ? error : error?.message
if (message && message.includes('subscription_depends')) {
toast.error(`"${target.name}" is still used by at least one notifier. Switch those notifiers to another channel first, then delete it.`)
} else {
toast.error(message || 'Could not delete this channel. Please try again.')
}
})
}

function sendTestNotification(target: NotificationTarget) {
Expand All @@ -42,7 +54,7 @@ function NotificationTargets() {
<>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
{!showAddNotificationTarget ? (
<Button onClick={() => setShowAddNotificationTarget(true)}>Add Notification Target</Button>
<Button onClick={() => setShowAddNotificationTarget(true)}>Add channel</Button>
) : (
<Button
onClick={() => {
Expand Down Expand Up @@ -87,7 +99,7 @@ function NotificationTargets() {
) : (
notificationTargets.map(target => {
return (
<tr>
<tr key={target.id ?? target.name}>
<td className="ellipse" style={{ maxWidth: '250px' }} title={target.target || ''}>
{target.name}
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ function AuctionHousePriceGraph(props: Props) {
</div>

<div className="d-flex align-items-center" style={{ gap: '8px', minWidth: '220px', justifyContent: 'flex-end' }}>
<SubscribeButton type="item" topic={props.item.tag} />
<SubscribeButton type="item" topic={props.item.tag} currentPrice={avgPrice || undefined} />
<ShareButton
title={'Prices for ' + props.item.name}
text="See list, search and filter item prices from the auction house and bazar in Hypixel Skyblock"
Expand Down
7 changes: 6 additions & 1 deletion components/PriceGraph/BazaarPriceGraph/BazaarPriceGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,12 @@ function BazaarPriceGraph(props: Props) {
) : null}
</div>
<div>
<SubscribeButton type="bazaar" topic={props.item.tag} />
<SubscribeButton
type="bazaar"
topic={props.item.tag}
currentPrice={avgBuyPrice || undefined}
currentSellPrice={avgSellPrice || undefined}
/>
</div>
<div>
<ShareButton title={'Prices for ' + props.item.name} text="Browse the Bazaar history in Hypixel Skyblock" />
Expand Down
86 changes: 86 additions & 0 deletions components/SubscribeButton/ChannelPicker/ChannelPicker.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.channelPicker {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 5px;
}

.channel {
display: flex;
align-items: center;
gap: 11px;
border: 1px solid var(--bs-border-color, #444);
border-radius: 9px;
padding: 10px 12px;
margin: 0;
cursor: pointer;
}

.channelColumn {
flex-direction: column;
align-items: stretch;
}

.channelRow {
display: flex;
align-items: center;
gap: 11px;
margin: 0;
cursor: pointer;
}

.channelActive {
border-color: var(--bs-primary, #0d6efd);
background-color: rgba(13, 110, 253, 0.08);
}

.channel :global(.form-check) {
margin: 0;
min-height: auto;
flex: none;
}

.icon {
flex: none;
opacity: 0.85;
}

.spinner {
flex: none;
margin: 2px;
}

.text {
display: flex;
flex-direction: column;
min-width: 0;
}

.title {
font-weight: 600;
line-height: 1.3;
}

.subtitle {
font-size: 0.8rem;
opacity: 0.7;
}

.error {
font-size: 0.8rem;
color: var(--bs-danger, #dc3545);
margin-top: 2px;
}

.disabled {
opacity: 0.6;
cursor: not-allowed;
}

.discordExpand {
display: flex;
flex-direction: column;
gap: 5px;
margin-top: 10px;
padding-left: 30px;
}
Loading
Loading