diff --git a/frontend/src/locale/en/translation.json b/frontend/src/locale/en/translation.json index 6b594ea..42e6f16 100644 --- a/frontend/src/locale/en/translation.json +++ b/frontend/src/locale/en/translation.json @@ -115,8 +115,6 @@ "command-response-placeholder": "Hello {0}!", "command-acl": "Access level", "command-acl-help": "This specifies the minimum level, eg. if you choose VIPs, moderators and streamer can still use the command", - "command-action-new": "Create", - "command-action-edit": "Edit", "acl": { "everyone": "Everyone", "subscribers": "Subscribers", @@ -135,8 +133,6 @@ "timer-header-edit": "Edit timer", "timer-name": "Timer name", "timer-name-placeholder": "my-timer", - "timer-action-new": "Create", - "timer-action-edit": "Edit", "remove-timer-title": "Remove timer {{name}}?", "timer-parameters": "every {{time}}, ≥ {{messages}} messages in the last {{interval}}", "timer-interval": "Minimul interval", @@ -160,7 +156,14 @@ "subscription-enable": "Enable subscription message", "gift_sub-enable": "Enable gifted subscription message", "raid-enable": "Enable raid message", - "cheer-enable": "Enable cheering message" + "cheer-enable": "Enable cheering message", + "events": { + "follow": "New follow", + "subscription": "Subscription", + "gift-sub": "Gift sub", + "raid": "Raid", + "cheer": "Cheer" + } }, "loyalty-settings": { "title": "Loyalty system configuration", @@ -189,7 +192,9 @@ "date": "Date", "request": "Request", "no-redeems": "No pending redeems", - "no-users": "No viewers found" + "no-users": "No viewers found", + "refund": "Refund", + "accept": "Accept" }, "debug": { "dismiss-warning": "I am not afraid! ...well ok maybe a little", @@ -197,7 +202,15 @@ "disclaimer-header": "Big scary disclaimer" }, "loyalty-rewards": { - "title": "Rewards and goals" + "title": "Rewards and goals", + "rewards-tab": "Rewards", + "goals-tab": "Goals", + "subtitle": "Set up rewards and community goals for your viewers to play with", + "reward-filter": "Search by reward name", + "create-reward": "Create reward", + "reward-id": "Reward ID", + "id-already-in-use": "ID already in use by another reward", + "reward-name": "Reward name" }, "strimertul": { "need-help": "Need help?", @@ -218,7 +231,8 @@ "cancel": "Cancel", "ok": "OK", "add": "Add", - "warning-delete": "This cannot be undone" + "warning-delete": "This cannot be undone", + "create": "Create" }, "debug": { "dev-build": "Development build" diff --git a/frontend/src/ui/pages/BotCommands.tsx b/frontend/src/ui/pages/BotCommands.tsx index 86718ac..87112b8 100644 --- a/frontend/src/ui/pages/BotCommands.tsx +++ b/frontend/src/ui/pages/BotCommands.tsx @@ -263,7 +263,7 @@ function CommandDialog({ diff --git a/frontend/src/ui/pages/BotTimers.tsx b/frontend/src/ui/pages/BotTimers.tsx index 99db156..c360cb5 100644 --- a/frontend/src/ui/pages/BotTimers.tsx +++ b/frontend/src/ui/pages/BotTimers.tsx @@ -277,7 +277,7 @@ function TimerDialog({ diff --git a/frontend/src/ui/pages/ChatAlerts.tsx b/frontend/src/ui/pages/ChatAlerts.tsx index 1e88377..cc073bf 100644 --- a/frontend/src/ui/pages/ChatAlerts.tsx +++ b/frontend/src/ui/pages/ChatAlerts.tsx @@ -42,11 +42,19 @@ export default function ChatAlertsPage(): React.ReactElement { - New follow - Subscription - Gift sub - Raid - Cheer + + {t('pages.alerts.events.follow')} + + + {t('pages.alerts.events.subscription')} + + + {t('pages.alerts.events.gift-sub')} + + {t('pages.alerts.events.raid')} + + {t('pages.alerts.events.cheer')} + diff --git a/frontend/src/ui/pages/LoyaltyQueue.tsx b/frontend/src/ui/pages/LoyaltyQueue.tsx index 3437830..7e84e7c 100644 --- a/frontend/src/ui/pages/LoyaltyQueue.tsx +++ b/frontend/src/ui/pages/LoyaltyQueue.tsx @@ -4,7 +4,6 @@ import { useDispatch } from 'react-redux'; import { useModule, useUserPoints } from '../../lib/react-utils'; import { SortFunction } from '../../lib/type-utils'; import { modules, removeRedeem, setUserPoints } from '../../store/api/reducer'; -import { LoyaltyRedeem } from '../../store/api/types'; import { DataTable } from '../components/DataTable'; import DialogContent from '../components/DialogContent'; import { @@ -112,7 +111,7 @@ function RewardQueue() { dispatch(removeRedeem(entry)); }} > - Accept + {t('pages.loyalty-queue.accept')} @@ -196,7 +195,7 @@ function UserList() { } }} > - + @@ -212,7 +211,7 @@ function UserList() { } /> - + @@ -259,7 +258,7 @@ function UserList() { } }} > - + @@ -269,7 +268,7 @@ function UserList() { value={currentEntry?.username ?? ''} /> - + @@ -303,7 +302,7 @@ function UserList() { setGivePointDialog({ open: true, user: '', points: 0 }) } > - Give points + {t('pages.loyalty-queue.give-points-dialog')} {entry.points} diff --git a/frontend/src/ui/pages/LoyaltyRewards.tsx b/frontend/src/ui/pages/LoyaltyRewards.tsx index e629840..17185df 100644 --- a/frontend/src/ui/pages/LoyaltyRewards.tsx +++ b/frontend/src/ui/pages/LoyaltyRewards.tsx @@ -1,6 +1,173 @@ -import React from 'react'; +import { PlusIcon } from '@radix-ui/react-icons'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { PageContainer, PageHeader, PageTitle } from '../theme'; +import { useDispatch } from 'react-redux'; +import { useModule } from '../../lib/react-utils'; +import { modules } from '../../store/api/reducer'; +import { LoyaltyReward } from '../../store/api/types'; +import DialogContent from '../components/DialogContent'; +import { + Button, + Dialog, + DialogActions, + Field, + FlexRow, + InputBox, + Label, + PageContainer, + PageHeader, + PageTitle, + TabButton, + TabContainer, + TabContent, + TabList, + TextBlock, +} from '../theme'; + +function RewardsPage() { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [rewards, setRewards] = useModule(modules.loyaltyRewards); + const [filter, setFilter] = useState(''); + const [dialogReward, setDialogReward] = useState<{ + open: boolean; + new: boolean; + reward: LoyaltyReward; + }>({ open: false, new: false, reward: null }); + + return ( + <> + + setDialogReward({ ...dialogReward, open: state }) + } + > + +
{ + e.preventDefault(); + if (!(e.target as HTMLFormElement).checkValidity()) { + return; + } + const reward = dialogReward.reward; + const index = rewards.findIndex((t) => t.id == reward.id); + if (index >= 0) { + const newRewards = rewards.slice(0); + newRewards[index] = reward; + dispatch(setRewards(newRewards)); + } else { + dispatch(setRewards([...rewards, reward])); + } + setDialogReward({ ...dialogReward, open: false }); + }} + > + + + { + setDialogReward({ + ...dialogReward, + reward: { + ...dialogReward?.reward, + id: e.target.value, + }, + }); + if ( + dialogReward.new && + rewards.find((r) => r.id === e.target.value) + ) { + (e.target as HTMLInputElement).setCustomValidity( + t('pages.loyalty-rewards.id-already-in-use'), + ); + } else { + (e.target as HTMLInputElement).setCustomValidity(''); + } + }} + /> + + + + { + setDialogReward({ + ...dialogReward, + reward: { + ...dialogReward?.reward, + name: e.target.value, + }, + }); + }} + /> + + + + + +
+
+
+ + + + setFilter(e.target.value)} + /> + + + + ); +} + +function GoalsPage() { + const { t } = useTranslation(); + + return <>; +} export default function LoyaltyRewardsPage(): React.ReactElement { const { t } = useTranslation(); @@ -9,7 +176,24 @@ export default function LoyaltyRewardsPage(): React.ReactElement { {t('pages.loyalty-rewards.title')} + {t('pages.loyalty-rewards.subtitle')} + + + + {t('pages.loyalty-rewards.rewards-tab')} + + + {t('pages.loyalty-rewards.goals-tab')} + + + + + + + + + ); } diff --git a/frontend/src/ui/theme/forms.ts b/frontend/src/ui/theme/forms.ts index dac60d1..395d14e 100644 --- a/frontend/src/ui/theme/forms.ts +++ b/frontend/src/ui/theme/forms.ts @@ -63,6 +63,9 @@ export const InputBox = styled('input', { borderColor: '$gray5', color: '$gray8', }, + '&:invalid': { + borderColor: '$red5', + }, variants: { border: { none: { @@ -91,6 +94,9 @@ export const Textarea = styled('textarea', { borderColor: '$gray5', color: '$gray8', }, + '&:invalid': { + borderColor: '$red5', + }, variants: { border: { none: {