tg buttons /viewContent, tags, validation address form and more /uploadContent
This commit is contained in:
1 parent
372f84d1b1
commit
7705908484
16 files changed
+336
-89
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import "~/app/styles/globals.css";
|
||||
import '~/shared/libs/buffer';
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useExpand, useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
|
||||
+66
-39
@@ -26,18 +26,26 @@
|
||||
/*Input Range*/
|
||||
/* Custom styles for the range input */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none; /* Remove default appearance */
|
||||
width: 100%; /* Full width */
|
||||
height: 2px; /* Track height */
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
white 0%, /* Start color of passed track */
|
||||
white var(--value-percentage, 0%), /* End color of passed track */
|
||||
gray var(--value-percentage, 0%), /* Start color of remaining track */
|
||||
gray 100% /* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px; /* Rounded-full track */
|
||||
outline: none; /* Remove outline */
|
||||
-webkit-appearance: none;
|
||||
/* Remove default appearance */
|
||||
width: 100%;
|
||||
/* Full width */
|
||||
height: 2px;
|
||||
/* Track height */
|
||||
background: linear-gradient(to right,
|
||||
white 0%,
|
||||
/* Start color of passed track */
|
||||
white var(--value-percentage, 0%),
|
||||
/* End color of passed track */
|
||||
gray var(--value-percentage, 0%),
|
||||
/* Start color of remaining track */
|
||||
gray 100%
|
||||
/* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full track */
|
||||
outline: none;
|
||||
/* Remove outline */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
@@ -45,45 +53,64 @@
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 9px; /* Thumb width */
|
||||
height: 9px; /* Thumb height */
|
||||
background: #fff; /* Thumb color (blue-500) */
|
||||
border-radius: 9999px; /* Rounded-full thumb */
|
||||
cursor: pointer; /* Pointer cursor on hover */
|
||||
width: 9px;
|
||||
/* Thumb width */
|
||||
height: 9px;
|
||||
/* Thumb height */
|
||||
background: #fff;
|
||||
/* Thumb color (blue-500) */
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full thumb */
|
||||
cursor: pointer;
|
||||
/* Pointer cursor on hover */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
appearance: none;
|
||||
width: 9px; /* Thumb width */
|
||||
height: 9px; /* Thumb height */
|
||||
background: #fff; /* Thumb color (blue-500) */
|
||||
border-radius: 9999px; /* Rounded-full thumb */
|
||||
cursor: pointer; /* Pointer cursor on hover */
|
||||
width: 9px;
|
||||
/* Thumb width */
|
||||
height: 9px;
|
||||
/* Thumb height */
|
||||
background: #fff;
|
||||
/* Thumb color (blue-500) */
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full thumb */
|
||||
cursor: pointer;
|
||||
/* Pointer cursor on hover */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-track {
|
||||
-webkit-appearance: none; /* Remove default appearance */
|
||||
width: 100%; /* Full width */
|
||||
height: 2px; /* Track height */
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
white 0%, /* Start color of passed track */
|
||||
white var(--value-percentage, 0%), /* End color of passed track */
|
||||
gray var(--value-percentage, 0%), /* Start color of remaining track */
|
||||
gray 100% /* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px; /* Rounded-full track */
|
||||
outline: none; /* Remove outline */
|
||||
-webkit-appearance: none;
|
||||
/* Remove default appearance */
|
||||
width: 100%;
|
||||
/* Full width */
|
||||
height: 2px;
|
||||
/* Track height */
|
||||
background: linear-gradient(to right,
|
||||
white 0%,
|
||||
/* Start color of passed track */
|
||||
white var(--value-percentage, 0%),
|
||||
/* End color of passed track */
|
||||
gray var(--value-percentage, 0%),
|
||||
/* Start color of remaining track */
|
||||
gray 100%
|
||||
/* End color of remaining track */
|
||||
);
|
||||
border-radius: 9999px;
|
||||
/* Rounded-full track */
|
||||
outline: none;
|
||||
/* Remove outline */
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
/* English comment: override react-tag-input classes */
|
||||
.ReactTags__tagInputField {
|
||||
@apply bg-transparent text-gray placeholder:text-gray outline-none w-full h-8;
|
||||
@apply border border-white py-[20px] !important;
|
||||
@apply bg-[#2B2B2B] outline-none w-full h-8 text-sm !important;
|
||||
@apply border border-white px-[10px] py-[18px] !important;
|
||||
@apply whitespace-pre !important;
|
||||
/* English comment:
|
||||
'bg-transparent' to blend with your dark background
|
||||
'text-white' to have white text
|
||||
@@ -94,7 +121,7 @@
|
||||
|
||||
/* English comment: style for the tag itself when it's rendered */
|
||||
.ReactTags__selected .ReactTags__tag {
|
||||
@apply bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 rounded mr-1;
|
||||
@apply bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 mb-2 rounded mr-1 !important;
|
||||
/* English comment:
|
||||
'bg-[#363636]' to have a dark gray background
|
||||
'text-white' keeps the text white
|
||||
@@ -105,11 +132,11 @@
|
||||
}
|
||||
|
||||
.ReactTags__selected .ReactTags__remove {
|
||||
@apply ml-1 text-gray hover:text-white cursor-pointer;
|
||||
@apply ml-1 text-gray hover:text-white cursor-pointer !important;
|
||||
/* English comment:
|
||||
'ml-1' a small margin to separate the 'x' or close symbol
|
||||
'text-gray-400' by default, and change to white on hover
|
||||
'cursor-pointer' so it looks clickable
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
|
||||
content: fileUploadResult.content_id_v1,
|
||||
image: coverUploadResult.content_id_v1,
|
||||
price: String(rootStore.price * 10 ** 9),
|
||||
|
||||
hashtags: rootStore.hashtags,
|
||||
royaltyParams: rootStore.royalty.map((member) => ({
|
||||
...member,
|
||||
value: member.value * 100,
|
||||
@@ -151,6 +151,24 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
{rootStore.author && (
|
||||
<FormLabel label={"Теги"}>
|
||||
<div
|
||||
className="flex flex-wrap gap-1">
|
||||
{rootStore.hashtags.map((tag, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={
|
||||
"bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 rounded mr-1"
|
||||
}
|
||||
>
|
||||
{tag}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FormLabel>
|
||||
)}
|
||||
|
||||
<FormLabel label={"Цена"}>
|
||||
<div
|
||||
className={
|
||||
|
||||
@@ -9,10 +9,10 @@ import { Button } from "~/shared/ui/button";
|
||||
import { useRootStore } from "~/shared/stores/root";
|
||||
import { BackButton } from "~/shared/ui/back-button";
|
||||
|
||||
const MIN_PRICE = 0.07;
|
||||
const MIN_RESALE_PRICE = 0.07;
|
||||
const MIN_PRICE = 0.15;
|
||||
const MIN_RESALE_PRICE = 0.15;
|
||||
|
||||
const RECOMMENDED_PRICE = 0.15;
|
||||
// const RECOMMENDED_PRICE = 0.15;
|
||||
// const RECOMMENDED_RESALE_PRICE = 0.15;
|
||||
|
||||
type PriceStepProps = {
|
||||
@@ -25,9 +25,9 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
||||
|
||||
const formSchema = useMemo(() => {
|
||||
const parsePrice = (value: unknown) => {
|
||||
if (typeof value === "string") {
|
||||
// Replace commas with dots and parse the value
|
||||
const parsedValue = parseFloat(value.replace(",", "."));
|
||||
if (typeof value === "string" || typeof value === "number") {
|
||||
const stringValue = value.toString().replace(",", ".");
|
||||
const parsedValue = parseFloat(stringValue);
|
||||
return isNaN(parsedValue) ? undefined : parsedValue;
|
||||
}
|
||||
return undefined;
|
||||
@@ -37,10 +37,10 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
||||
return z.object({
|
||||
price: z.preprocess(
|
||||
parsePrice,
|
||||
z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
),
|
||||
resaleLicensePrice: z
|
||||
.preprocess(parsePrice, z.number().min(MIN_RESALE_PRICE, `Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`))
|
||||
.preprocess(parsePrice, z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_RESALE_PRICE, `Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`))
|
||||
.optional(),
|
||||
});
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
||||
return z.object({
|
||||
price: z.preprocess(
|
||||
parsePrice,
|
||||
z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
|
||||
),
|
||||
});
|
||||
}, [rootStore.allowResale]);
|
||||
@@ -59,9 +59,9 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
||||
resolver: zodResolver(formSchema),
|
||||
mode: "onChange",
|
||||
defaultValues: {
|
||||
price: rootStore.price,
|
||||
price: rootStore.price || MIN_PRICE,
|
||||
//@ts-expect-error Fix typings
|
||||
resaleLicensePrice: rootStore?.licenseResalePrice,
|
||||
resaleLicensePrice: rootStore?.licenseResalePrice || MIN_RESALE_PRICE,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -98,13 +98,22 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
|
||||
<FormLabel label={"Цена продажи TON"}>
|
||||
<div className={"my-2 flex flex-col gap-1.5"}>
|
||||
<p className={"text-xs"}>Минимальная стоимость {MIN_PRICE} TON.</p>
|
||||
<p className={"text-xs"}>Рекомендуемая стоимость {RECOMMENDED_PRICE} TON.</p>
|
||||
{/* <p className={"text-xs"}>Рекомендуемая стоимость {RECOMMENDED_PRICE} TON.</p> */}
|
||||
</div>
|
||||
<Input
|
||||
error={form.formState.errors?.price}
|
||||
placeholder={"[ Введите цену ]"}
|
||||
{...form.register("price")}
|
||||
/>
|
||||
inputMode="decimal"
|
||||
pattern="[0-9]*[.,]?[0-9]*"
|
||||
{...form.register("price", {
|
||||
onChange: (e) => {
|
||||
const value = e.target.value;
|
||||
if (!/^\d*[.,]?\d*$/.test(value)) {
|
||||
e.target.value = value.replace(/[^\d.,]/g, '');
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</FormLabel>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -12,17 +12,30 @@ import { ConfirmModal } from "~/pages/root/steps/royalty-step/components/confirm
|
||||
import { useRootStore } from "~/shared/stores/root";
|
||||
import { BackButton } from "~/shared/ui/back-button";
|
||||
import { useTonConnectUI } from "@tonconnect/ui-react";
|
||||
import { Address } from "@ton/core";
|
||||
import { FieldError } from "react-hook-form";
|
||||
|
||||
type RoyaltyStepProps = {
|
||||
prevStep(): void;
|
||||
nextStep(): void;
|
||||
};
|
||||
|
||||
const isValidTonAddress = (address: string): boolean => {
|
||||
try {
|
||||
if (!address) return false;
|
||||
Address.parse(address);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
||||
const [impactOccurred] = useHapticFeedback();
|
||||
|
||||
const [isDeleteAllOpen, setDeleteAllOpen] = useState(false);
|
||||
const [isSpreadOpen, setSpreadOpen] = useState(false);
|
||||
const [addressErrors, setAddressErrors] = useState<Record<number, FieldError | undefined>>({});
|
||||
|
||||
const { royalty, setRoyalty, isPercentHintOpen, setPercentHintOpen } =
|
||||
useRootStore();
|
||||
@@ -42,11 +55,23 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
||||
};
|
||||
|
||||
const handleWalletChange = (index: number, address: string) => {
|
||||
const isValid = isValidTonAddress(address);
|
||||
setAddressErrors({
|
||||
...addressErrors,
|
||||
[index]: !isValid
|
||||
? {
|
||||
type: 'validation',
|
||||
message: 'Неверный адрес TON'
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
|
||||
const newRoyalty = royalty.map((member, i) =>
|
||||
i === index ? { ...member, address } : member,
|
||||
i === index ? { ...member, address } : member
|
||||
);
|
||||
setRoyalty(newRoyalty);
|
||||
};
|
||||
|
||||
|
||||
const handlePercentChange = (index: number, value: string) => {
|
||||
const percentNumber = parseInt(value, 10) || 0;
|
||||
@@ -67,20 +92,28 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
return (
|
||||
royalty.every((member) => member.address && member.value >= 0) &&
|
||||
royalty.every((member) => isValidTonAddress(member.address) && member.value >= 0) &&
|
||||
royalty.reduce((acc, curr) => acc + curr.value, 0) === 100
|
||||
);
|
||||
}, [royalty]);
|
||||
|
||||
const [tonConnectUI] = useTonConnectUI();
|
||||
|
||||
// Устанавливаем адрес из tonConnectUI.account при загрузке страницы
|
||||
useEffect(() => {
|
||||
console.log('tonconnectUI', tonConnectUI)
|
||||
if (tonConnectUI.account) {
|
||||
setRoyalty([{ address: tonConnectUI.account.address, value: 100 }]);
|
||||
if (!tonConnectUI.account) return;
|
||||
|
||||
if (royalty.length === 0) {
|
||||
// First initialization with 100%
|
||||
setRoyalty([{
|
||||
address: Address.parse(tonConnectUI.account.address).toString({
|
||||
bounceable: true,
|
||||
urlSafe: true,
|
||||
testOnly: false,
|
||||
}),
|
||||
value: 100
|
||||
}]);
|
||||
}
|
||||
}, [tonConnectUI.account, setRoyalty]);
|
||||
}, [tonConnectUI.account, setRoyalty, royalty]);
|
||||
|
||||
return (
|
||||
<section className={"mt-4 px-4 pb-8"}>
|
||||
@@ -128,7 +161,7 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
||||
<section className={"flex flex-col gap-1.5"}>
|
||||
{royalty.map((member, index) => (
|
||||
<div key={index} className={"flex flex-col gap-[20px]"}>
|
||||
<div className={"flex w-full items-center gap-1"}>
|
||||
<div className={"flex w-full items-start gap-1"}>
|
||||
<div className={"w-[83%]"}>
|
||||
<FormLabel
|
||||
labelClassName={"flex"}
|
||||
@@ -145,7 +178,8 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
|
||||
value={member.address}
|
||||
onChange={(e) => handleWalletChange(index, e.target.value)}
|
||||
placeholder={"[ Введите адрес криптокошелька TON ]"}
|
||||
/>
|
||||
error={addressErrors[index]}
|
||||
/>
|
||||
</FormLabel>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import ReactPlayer from "react-player/lazy";
|
||||
import { useTonConnectUI } from "@tonconnect/ui-react";
|
||||
import { useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
|
||||
import { Button } from "~/shared/ui/button";
|
||||
import { usePurchaseContent, useViewContent } from "~/shared/services/content";
|
||||
import { fromNanoTON } from "~/shared/utils";
|
||||
import {useCallback, useEffect, useMemo} from "react";
|
||||
@@ -72,7 +71,43 @@ export const ViewContentPage = () => {
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const mainButton = WebApp.MainButton;
|
||||
const secondaryButton = WebApp.SecondaryButton;
|
||||
|
||||
try {
|
||||
// Set main button color
|
||||
mainButton.color = '#e40615';
|
||||
mainButton.textColor = '#FFFFFF';
|
||||
|
||||
// Set secondary button color
|
||||
secondaryButton.color = '#363636';
|
||||
secondaryButton.textColor = '#FFFFFF';
|
||||
if (!haveLicense) {
|
||||
mainButton.text = `Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`;
|
||||
mainButton.show();
|
||||
mainButton.onClick(handleBuyContent);
|
||||
} else {
|
||||
mainButton.hide();
|
||||
}
|
||||
|
||||
secondaryButton.text = 'Загрузить свой контент';
|
||||
secondaryButton.show();
|
||||
secondaryButton.onClick(() => {
|
||||
WebApp.openTelegramLink('https://t.me/MY_UploaderRobot');
|
||||
});
|
||||
|
||||
return () => {
|
||||
mainButton.hide();
|
||||
mainButton.offClick(handleBuyContent);
|
||||
secondaryButton.hide();
|
||||
secondaryButton.offClick();
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error setting up Telegram WebApp buttons:', error);
|
||||
}
|
||||
}, [content, haveLicense, WebApp, handleBuyContent]);
|
||||
|
||||
return (
|
||||
<main className={"flex w-full flex-col gap-[50px] px-4"}>
|
||||
{content?.data?.content_type.startsWith("audio") && content?.data?.display_options?.metadata?.image && (
|
||||
@@ -111,7 +146,7 @@ export const ViewContentPage = () => {
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{!haveLicense && <Button
|
||||
{/* {!haveLicense && <Button
|
||||
onClick={handleBuyContent}
|
||||
className={"mb-4 mt-[30px] h-[48px]"}
|
||||
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`}
|
||||
@@ -125,7 +160,7 @@ export const ViewContentPage = () => {
|
||||
}}
|
||||
className={"mb-4 mt-[-20px] h-[48px] bg-darkred"}
|
||||
label={`Загрузить свой контент`}
|
||||
/>
|
||||
/> */}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
import { Buffer } from 'buffer';
|
||||
globalThis.Buffer = Buffer;
|
||||
@@ -8,6 +8,7 @@ type UseCreateNewContentPayload = {
|
||||
content: string;
|
||||
image: string;
|
||||
description: string;
|
||||
hashtags: string[];
|
||||
price: string;
|
||||
resaleLicensePrice: string; // nanoTON bignum (default = 0)
|
||||
allowResale: boolean;
|
||||
|
||||
@@ -80,7 +80,7 @@ export const useRootStore = create<RootStore>((set) => ({
|
||||
royalty: [{ address: "", value: 100 }],
|
||||
setRoyalty: (royalty) => set({ royalty }),
|
||||
|
||||
price: 0,
|
||||
price: 0.15,
|
||||
setPrice: (price: number) => set({ price }),
|
||||
|
||||
allowResale: false,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { WithContext as ReactTags } from "react-tag-input";
|
||||
import { WithContext as ReactTags, SEPARATORS } from "react-tag-input";
|
||||
import { useRootStore } from "~/shared/stores/root";
|
||||
|
||||
// English comment: no extra Tag interface, just cast to any
|
||||
export const HashtagInput = () => {
|
||||
const { hashtags, setHashtags } = useRootStore();
|
||||
@@ -9,9 +8,7 @@ export const HashtagInput = () => {
|
||||
// English comment: local state as string[] for simplicity
|
||||
const [tags, setTags] = useState<string[]>(hashtags);
|
||||
|
||||
const KeyCodes = { comma: 188, enter: 13 };
|
||||
const delimiters = [KeyCodes.comma, KeyCodes.enter];
|
||||
|
||||
const separators = [SEPARATORS.ENTER, SEPARATORS.COMMA]
|
||||
const handleDelete = (i: number) => {
|
||||
const newTags = tags.filter((_, index) => index !== i);
|
||||
setTags(newTags);
|
||||
@@ -20,20 +17,47 @@ export const HashtagInput = () => {
|
||||
|
||||
// English comment: pass "any" to the function
|
||||
const handleAddition = (newTag: any) => {
|
||||
// English comment: newTag might be { id, text, ... } from react-tag-input
|
||||
const updatedTags = [...tags, newTag?.text || newTag?.id || ""];
|
||||
// Clean up text from commas and trim whitespace
|
||||
const text = newTag?.text || newTag?.id || "";
|
||||
const cleanText = text.replace(/,/g, '').trim();
|
||||
|
||||
// Skip empty tags
|
||||
if (!cleanText) return;
|
||||
|
||||
const updatedTags = [...tags, cleanText];
|
||||
setTags(updatedTags);
|
||||
setHashtags(updatedTags);
|
||||
};
|
||||
|
||||
// Simulate Enter keypress for Android
|
||||
const enterEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
which: 13,
|
||||
keyCode: 13,
|
||||
});
|
||||
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Unidentified') {
|
||||
const lastChar = e.currentTarget.value.slice(-1);
|
||||
if (lastChar === ',') {
|
||||
e.currentTarget.dispatchEvent(enterEvent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactTags
|
||||
tags={tags.map((t) => ({ id: t, text: t })) as any}
|
||||
delimiters={delimiters as any}
|
||||
separators={separators as any}
|
||||
handleDelete={handleDelete as any}
|
||||
handleAddition={handleAddition as any}
|
||||
allowDragDrop={false}
|
||||
placeholder="[ enter a hashtag ]"
|
||||
placeholder="[ введите тэги через запятую ]"
|
||||
inputProps={{
|
||||
onKeyUp: handleKeyUp as any
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user