[frontend]: add create account page
This commit is contained in:
parent
34e4d3559d
commit
89a307e962
@ -15,6 +15,8 @@
|
|||||||
"@graphql-codegen/typescript": "4.1.6",
|
"@graphql-codegen/typescript": "4.1.6",
|
||||||
"@graphql-codegen/typescript-operations": "4.6.1",
|
"@graphql-codegen/typescript-operations": "4.6.1",
|
||||||
"@graphql-codegen/typescript-react-apollo": "4.3.3",
|
"@graphql-codegen/typescript-react-apollo": "4.3.3",
|
||||||
|
"@hookform/resolvers": "5.2.1",
|
||||||
|
"@radix-ui/react-label": "2.1.7",
|
||||||
"@radix-ui/react-slot": "1.2.3",
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
"class-variance-authority": "0.7.1",
|
"class-variance-authority": "0.7.1",
|
||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
@ -25,7 +27,10 @@
|
|||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"tailwind-merge": "3.3.1"
|
"react-hook-form": "7.61.1",
|
||||||
|
"sonner": "2.0.6",
|
||||||
|
"tailwind-merge": "3.3.1",
|
||||||
|
"zod": "4.0.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "3.3.1",
|
"@eslint/eslintrc": "3.3.1",
|
||||||
|
|||||||
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 986 B |
@ -1 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 391 B |
@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB |
@ -1,5 +1,621 @@
|
|||||||
{
|
{
|
||||||
|
"layout": {
|
||||||
|
"header": {
|
||||||
|
"logo": {
|
||||||
|
"platform": "Streaming platform"
|
||||||
|
},
|
||||||
|
"headerMenu": {
|
||||||
|
"login": "Sign In",
|
||||||
|
"register": "Sign Up",
|
||||||
|
"profileMenu": {
|
||||||
|
"notifications": {
|
||||||
|
"heading": "Notifications",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"empty": "You have no notifications"
|
||||||
|
},
|
||||||
|
"successMessage": "Successfully logged out",
|
||||||
|
"errorMessage": "Logout error",
|
||||||
|
"channel": "My channel",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"logout": "Sign out"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"header": {
|
||||||
|
"expand": "Expand",
|
||||||
|
"collapse": "Collapse",
|
||||||
|
"navigation": "Navigation"
|
||||||
|
},
|
||||||
|
"userNav": {
|
||||||
|
"home": "Home",
|
||||||
|
"categories": "Categories",
|
||||||
|
"streams": "Streams",
|
||||||
|
"recommended": "Recommended"
|
||||||
|
},
|
||||||
|
"recommended": {
|
||||||
|
"heading": "Recommendations"
|
||||||
|
},
|
||||||
|
"dashboardNav": {
|
||||||
|
"settings": "Settings",
|
||||||
|
"streamSettings": "Stream Settings",
|
||||||
|
"keys": "Stream Keys",
|
||||||
|
"chatSettings": "Chat Settings",
|
||||||
|
"followers": "Followers",
|
||||||
|
"sponsors": "Sponsors",
|
||||||
|
"premium": "Premium Plans",
|
||||||
|
"transactions": "Transactions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Home page"
|
"streamsHeading": "Channels You Might Like",
|
||||||
|
"categoriesHeading": "Categories You Might Like"
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"heading": "Categories",
|
||||||
|
"overview": {
|
||||||
|
"heading": "Streams for this category"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": {
|
||||||
|
"heading": "Streams",
|
||||||
|
"searchHeading": "Search by query"
|
||||||
|
},
|
||||||
|
"stream": {
|
||||||
|
"video": {
|
||||||
|
"offline": "is offline",
|
||||||
|
"loading": "Connecting...",
|
||||||
|
"player": {
|
||||||
|
"volume": "Volume",
|
||||||
|
"fullscreen": {
|
||||||
|
"open": "Fullscreen",
|
||||||
|
"exit": "Exit Fullscreen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"viewers": "viewers",
|
||||||
|
"offline": "Offline"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"follow": {
|
||||||
|
"confirmUnfollowHeading": "Unfollow",
|
||||||
|
"confirmUnfollowMessage": "You will no longer receive notifications from this channel and it will disappear from your followed list.",
|
||||||
|
"unfollowButton": "Unfollow",
|
||||||
|
"followButton": "Follow",
|
||||||
|
"successFollowMessage": "You have successfully followed",
|
||||||
|
"errorFollowMessage": "Error while following",
|
||||||
|
"successUnfollowMessage": "You have successfully unfollowed",
|
||||||
|
"errorUnfollowMessage": "Error while unfollowing"
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"alreadySponsor": "You are already a sponsor",
|
||||||
|
"supportAuthor": "Support the author",
|
||||||
|
"perMonth": "per month",
|
||||||
|
"choose": "Choose",
|
||||||
|
"errorMessage": "Error while creating payment"
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"heading": "Share"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aboutChannel": {
|
||||||
|
"heading": "About",
|
||||||
|
"followersCount": "followers",
|
||||||
|
"noDescription": "Description not provided."
|
||||||
|
},
|
||||||
|
"sponsors": {
|
||||||
|
"heading": "Channel Sponsors"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"heading": "Chat",
|
||||||
|
"unavailable": "Chat unavailable",
|
||||||
|
"unavailableMessage": "Chat is unavailable while there is no stream on the channel. Please check back later!",
|
||||||
|
"loading": "Connecting...",
|
||||||
|
"info": {
|
||||||
|
"authRequired": "Authorization required",
|
||||||
|
"chatDisabled": "Chat disabled",
|
||||||
|
"premiumFollowersOnly": "For sponsors only",
|
||||||
|
"followersOnly": "For followers only"
|
||||||
|
},
|
||||||
|
"sendMessage": {
|
||||||
|
"placeholder": "Send a message",
|
||||||
|
"emojiPlaceholder": "Search",
|
||||||
|
"errorMessage": "Error while sending message."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"heading": "Streaming Settings",
|
||||||
|
"thumbnail": {
|
||||||
|
"updateButton": "Update Image",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Stream Thumbnail Removal",
|
||||||
|
"message": "Are you sure you want to delete the stream thumbnail? This action cannot be undone."
|
||||||
|
},
|
||||||
|
"info": "Supported formats: JPG, JPEG, PNG, or GIF. Max size: 10 MB.",
|
||||||
|
"successUpdateMessage": "Stream image successfully updated",
|
||||||
|
"errorUpdateMessage": "Error updating the stream image",
|
||||||
|
"successRemoveMessage": "Stream image successfully removed",
|
||||||
|
"errorRemoveMessage": "Error removing the stream image"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"titleLabel": "Title",
|
||||||
|
"titlePlaceholder": "GTA 5 Stream",
|
||||||
|
"titleDescription": "Enter the title of your stream. It should be short and memorable.",
|
||||||
|
"categoryLabel": "Category",
|
||||||
|
"categoryPlaceholder": "Select a category",
|
||||||
|
"categoryDescription": "Select the category your content belongs to. This will help viewers find your streams more easily.",
|
||||||
|
"submitButton": "Save Changes",
|
||||||
|
"successMessage": "Stream settings successfully updated",
|
||||||
|
"errorMessage": "Error updating stream settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"heading": "Payment Successful!",
|
||||||
|
"details": {
|
||||||
|
"heading": "Sponsorship Details:",
|
||||||
|
"price": "Price:",
|
||||||
|
"duration": "Duration:",
|
||||||
|
"channel": "Channel:"
|
||||||
|
},
|
||||||
|
"congratulations": "You now have access to the channel's premium content!",
|
||||||
|
"backToHome": "Back to Home",
|
||||||
|
"backToChannel": "Back to Channel",
|
||||||
|
"support": "If you have any questions, contact our support."
|
||||||
|
},
|
||||||
|
"notFound": {
|
||||||
|
"description": "Oops, something went wrong.",
|
||||||
|
"backToHome": "Back to Home"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"register": {
|
||||||
|
"heading": "Register on TeaStream",
|
||||||
|
"backButtonLabel": "Already have an account? Sign in",
|
||||||
|
"usernameLabel": "Username",
|
||||||
|
"usernameDescription": "This is the name other users will know you by. You can change it later.",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailDescription": "We may use your email for sending account-related notifications.",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"passwordDescription": "The password must contain at least 8 characters.",
|
||||||
|
"submitButton": "Sign up",
|
||||||
|
"successAlertTitle": "Check Your Email",
|
||||||
|
"successAlertDescription": "A verification email has been sent to your inbox. If you don't see the email, check your spam folder",
|
||||||
|
"errorMessage": "Error when creating an account"
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"heading": "Account verification",
|
||||||
|
"successMessage": "Account verified",
|
||||||
|
"errorMessage": "Verification error"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"heading": "Login to TeaStream",
|
||||||
|
"backButtonLabel": "Don't have an account? Sign up!",
|
||||||
|
"loginLabel": "Login",
|
||||||
|
"loginDescription": "Your username or email that you used during registration.",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"passwordDescription": "The password you used during registration.",
|
||||||
|
"pinLabel": "6-digit code",
|
||||||
|
"pinDescription": "Enter the code from your authentication app.",
|
||||||
|
"forgotPassword": "Forgot password?",
|
||||||
|
"submitButton": "Login",
|
||||||
|
"successMessage": "You have successfully logged in",
|
||||||
|
"errorMessage": "Error when logging in"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"heading": "Reset password",
|
||||||
|
"backButtonLabel": "Already have an account? Sign in",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailDescription": "The email you used during registration",
|
||||||
|
"submitButton": "Reset password",
|
||||||
|
"successAlertTitle": "Link sent",
|
||||||
|
"successAlertDescription": "We have sent a password reset link to your email. If you have enabled Telegram notifications, the link was sent there as well",
|
||||||
|
"errorMessage": "Password reset error"
|
||||||
|
},
|
||||||
|
"newPassword": {
|
||||||
|
"heading": "New password",
|
||||||
|
"backButtonLabel": "Already have an account? Sign in",
|
||||||
|
"passwordLabel": "New password",
|
||||||
|
"passwordDescription": "The password you used during registration",
|
||||||
|
"passwordRepeatLabel": "Repeat password",
|
||||||
|
"passwordRepeatDescription": "Repeat your new password for confirmation",
|
||||||
|
"submitButton": "Set new password",
|
||||||
|
"successMessage": "Password successfully changed",
|
||||||
|
"errorMessage": "Error when setting a new password"
|
||||||
|
},
|
||||||
|
"deactivate": {
|
||||||
|
"heading": "Deactivate Account",
|
||||||
|
"backButtonLabel": "Go to Dashboard",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailDescription": "Your email that you used during registration",
|
||||||
|
"passwordLabel": "Password",
|
||||||
|
"passwordDescription": "The password you used during registration",
|
||||||
|
"pinLabel": "6-digit code",
|
||||||
|
"pinDescription": "A code has been sent to your email. If you enabled Telegram notifications, we sent the code there as well",
|
||||||
|
"submitButton": "Deactivate",
|
||||||
|
"successMessage": "You have successfully deactivated your account",
|
||||||
|
"errorMessage": "Deactivation error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"settings": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Settings",
|
||||||
|
"description": "Here you can manage your settings",
|
||||||
|
"profile": "Profile",
|
||||||
|
"account": "Account",
|
||||||
|
"appearance": "Appearance",
|
||||||
|
"notifications": "Notifications",
|
||||||
|
"sessions": "Sessions"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Profile",
|
||||||
|
"description": "Customize your profile by changing your avatar, personal information, and social media links. Update your information so that other users can learn more about you."
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"heading": "Profile Image",
|
||||||
|
"updateButton": "Update Profile Image",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Remove Avatar",
|
||||||
|
"message": "Are you sure you want to remove your profile image? This action cannot be undone."
|
||||||
|
},
|
||||||
|
"info": "Supported formats: JPG, JPEG, PNG, WEBP or GIF. Max size: 10 MB.",
|
||||||
|
"successUpdateMessage": "Profile image updated successfully.",
|
||||||
|
"errorUpdateMessage": "Error updating profile image.",
|
||||||
|
"successRemoveMessage": "Profile image removed successfully.",
|
||||||
|
"errorRemoveMessage": "Error removing profile image."
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"heading": "Profile Settings",
|
||||||
|
"usernameLabel": "Username",
|
||||||
|
"usernamePlaceholder": "johndoe",
|
||||||
|
"usernameDescription": "This is how other users will know you.",
|
||||||
|
"displayNameLabel": "Display Name",
|
||||||
|
"displayNamePlaceholder": "John Doe",
|
||||||
|
"displayNameDescription": "You can use uppercase letters in your display name as you wish.",
|
||||||
|
"bioLabel": "About Me",
|
||||||
|
"bioPlaceholder": "I love programming",
|
||||||
|
"bioDescription": "The information about yourself should contain no more than 300 characters.",
|
||||||
|
"successMessage": "Profile successfully updated",
|
||||||
|
"errorMessage": "Error updating profile",
|
||||||
|
"submitButton": "Save Changes"
|
||||||
|
},
|
||||||
|
"socialLinks": {
|
||||||
|
"createForm": {
|
||||||
|
"heading": "Social Media Links",
|
||||||
|
"titleLabel": "Link Title",
|
||||||
|
"titlePlaceholder": "Youtube",
|
||||||
|
"titleDescription": "Link text",
|
||||||
|
"urlLabel": "Link URL",
|
||||||
|
"urlPlaceholder": "https://github.com/TeaCoder52",
|
||||||
|
"urlDescription": "Where does this link lead? Please enter the full URL, e.g., https://github.com/TeaCoder52",
|
||||||
|
"submitButton": "Add",
|
||||||
|
"successMessage": "Social media link successfully added",
|
||||||
|
"errorMessage": "Error adding social media link"
|
||||||
|
},
|
||||||
|
"editForm": {
|
||||||
|
"cancelButton": "Cancel",
|
||||||
|
"submitButton": "Save",
|
||||||
|
"successUpdateMessage": "Social media link successfully updated",
|
||||||
|
"errorUpdateMessage": "Error updating social media link",
|
||||||
|
"successRemoveMessage": "Social media link successfully removed",
|
||||||
|
"errorRemoveMessage": "Error removing social media link"
|
||||||
|
},
|
||||||
|
"successReorderMessage": "Order of social media links changed",
|
||||||
|
"errorReorderMessage": "Error changing order of social media links"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Account",
|
||||||
|
"description": "Manage your account settings, including access changes, security, and deactivation options.",
|
||||||
|
"securityHeading": "Security",
|
||||||
|
"securityDescription": "Set up two-factor authentication and auto-renewals for enhanced protection of your data.",
|
||||||
|
"deactivationHeading": "Deactivation",
|
||||||
|
"deactivationDescription": "If you want to temporarily or permanently disable your account, use this option. Be aware of the consequences."
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"heading": "Email Address",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailDescription": "Enter your new email address.",
|
||||||
|
"submitButton": "Save",
|
||||||
|
"successMessage": "Email successfully updated",
|
||||||
|
"errorMessage": "Error during mail update"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"heading": "Account Password",
|
||||||
|
"oldPasswordLabel": "Old Password",
|
||||||
|
"oldPasswordDescription": "Enter your old password to verify your identity before changing your password. This is necessary to ensure the security of your account.",
|
||||||
|
"newPasswordLabel": "New Password",
|
||||||
|
"newPasswordDescription": "Your new password must be at least 8 characters long. It is recommended to use special characters for added security.",
|
||||||
|
"submitButton": "Save",
|
||||||
|
"successMessage": "Password successfully updated",
|
||||||
|
"errorMessage": "Error when updating password"
|
||||||
|
},
|
||||||
|
"twoFactor": {
|
||||||
|
"heading": "TOTP Authentication",
|
||||||
|
"description": "Enhance your account security by enabling TOTP authentication. This additional layer of protection requires entering a unique code, making your account less vulnerable to unauthorized access.",
|
||||||
|
"enable": {
|
||||||
|
"trigger": "Enable",
|
||||||
|
"heading": "Enabling TOTP",
|
||||||
|
"qrInstructions": "Scan the QR code to add TOTP",
|
||||||
|
"secretCodeLabel": "Secret Code: ",
|
||||||
|
"pinLabel": "PIN Code",
|
||||||
|
"pinDescription": "Please enter the 6-digit code provided by your authentication app.",
|
||||||
|
"submitButton": "Confirm",
|
||||||
|
"successMessage": "TOTP authentication enabled",
|
||||||
|
"errorMessage": "Error verifying the code"
|
||||||
|
},
|
||||||
|
"disable": {
|
||||||
|
"trigger": "Disable",
|
||||||
|
"heading": "Are you sure you want to disable TOTP?",
|
||||||
|
"message": "Disabling TOTP authentication will make your account less secure. Are you sure you want to proceed?",
|
||||||
|
"successMessage": "TOTP authentication has been disabled",
|
||||||
|
"errorMessage": "Error disabling"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deactivation": {
|
||||||
|
"heading": "Account Deactivation",
|
||||||
|
"description": "After deactivating your account, you will not be able to log in. Your account will be completely deleted after 7 days. Until then, you can contact support to restore access and unlock your account.",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Deactivation Confirmation",
|
||||||
|
"message": "Are you sure you want to deactivate your account? Access can only be restored within 7 days."
|
||||||
|
},
|
||||||
|
"button": "Deactivate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Appearance",
|
||||||
|
"description": "Customize the look of the website interface to your liking. Choose a theme and language that make your experience more comfortable."
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"heading": "Change Theme",
|
||||||
|
"description": "You can choose a dark or light theme for the website interface.",
|
||||||
|
"successMessage": "The website theme has been successfully changed"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"heading": "Website Language",
|
||||||
|
"description": "Select the language that will be used on the website.",
|
||||||
|
"selectPlaceholder": "Select a language",
|
||||||
|
"successMessage": "The website language has been successfully changed"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"heading": "Accent Color",
|
||||||
|
"description": "Choose an accent color for the interface"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Notifications",
|
||||||
|
"description": "Configure how you want to receive notifications from TeaStream. You will receive notifications about the start of streams on the channels you are subscribed to, as well as about new followers."
|
||||||
|
},
|
||||||
|
"siteNotifications": {
|
||||||
|
"heading": "Site Notifications",
|
||||||
|
"description": "If you turn off site notifications, you will not receive notifications about new subscribers to your channel and notifications about new streams."
|
||||||
|
},
|
||||||
|
"telegramNotifications": {
|
||||||
|
"heading": "Telegram Notifications",
|
||||||
|
"description": "If you enable notifications via Telegram, your TeaStream account will be linked to your Telegram account, and you will receive messages about new subscriptions, new streams, password resets, and two-factor authentication codes."
|
||||||
|
},
|
||||||
|
"successMessage": "Notification settings have been successfully updated",
|
||||||
|
"errorMessage": "Error when updating notification settings"
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Sessions",
|
||||||
|
"description": "Sessions are devices that you are currently using or have used to access your TeaStream account. Here you can see the active sessions right now."
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"current": "Current session",
|
||||||
|
"active": "Active sessions",
|
||||||
|
"notFound": "No active sessions found"
|
||||||
|
},
|
||||||
|
"sessionItem": {
|
||||||
|
"deleteButton": "Delete",
|
||||||
|
"detailsButton": "Details",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Delete Session",
|
||||||
|
"message": "Are you sure you want to delete this session? This action will result in the irreversible deletion of the session on this device and will terminate all active actions associated with it."
|
||||||
|
},
|
||||||
|
"successMessage": "Session deleted"
|
||||||
|
},
|
||||||
|
"sessionModal": {
|
||||||
|
"heading": "Session Information",
|
||||||
|
"device": "Device:",
|
||||||
|
"location": "Location:",
|
||||||
|
"ipAddress": "IP Address:",
|
||||||
|
"createdAt": "Created At:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Stream Keys",
|
||||||
|
"description": "Generate a URL and key for streaming."
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"heading": "URL"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"heading": "Stream Key"
|
||||||
|
},
|
||||||
|
"instructionModal": {
|
||||||
|
"trigger": "Instruction",
|
||||||
|
"heading": "🛠️ OBS Setup Guide for Live Streaming 🛠️",
|
||||||
|
"description": "To start sharing your content with your audience, follow this step-by-step guide to connecting OBS Studio. Just a few steps, and you're ready to go live!",
|
||||||
|
"step1Title": "🔑 Step 1: Download OBS Studio and Generate Server URL and Stream Key",
|
||||||
|
"step1Description": "To start streaming, you need two key elements: Server URL and Stream Key. Follow these steps:",
|
||||||
|
"downloadObs": "Download OBS Studio",
|
||||||
|
"downloadObsDescription": "Before setting up your stream, download and install the program",
|
||||||
|
"obsLinkText": "OBS Studio",
|
||||||
|
"copyKeys": "Copy Server URL and Stream Key",
|
||||||
|
"copyKeysDescription": "Once generated, you will see two fields: Server URL and Stream Key. Click the copy icons next to each field to save them to your clipboard.",
|
||||||
|
"step2Title": "🎛️ Step 2: Set Up OBS Studio",
|
||||||
|
"step2Description": "Now that you have the required data, let’s set up OBS to work with your stream:",
|
||||||
|
"openObs": "Open OBS Studio",
|
||||||
|
"openObsDescription": "If OBS is not installed yet, download it from the official site and install it. Once launched, the main OBS page will open.",
|
||||||
|
"openStreamSettings": "Open Stream Settings",
|
||||||
|
"openStreamSettingsDescription": "In the top menu, click “File” → “Settings.” Go to the “Stream” tab.",
|
||||||
|
"enterDetails": "Enter Your Details",
|
||||||
|
"enterDetailsDescription": "In the 'Service' field, select 'Custom...'. Paste the previously copied Server URL into the 'URL' field. In the 'Stream Key' field, enter your Stream Key.",
|
||||||
|
"saveSettings": "Save Settings",
|
||||||
|
"saveSettingsDescription": "Click 'Apply' and then 'OK' to save the changes.",
|
||||||
|
"step3Title": "🚀 Step 3: Start Streaming",
|
||||||
|
"step3Description": "After successfully setting up OBS, you’re ready to start a live broadcast:",
|
||||||
|
"startStream": "Start Streaming",
|
||||||
|
"startStreamDescription": "Return to the main OBS screen and click the 'Start Streaming' button.",
|
||||||
|
"monitorStream": "Monitor Your Stream",
|
||||||
|
"monitorStreamDescription": "Your stream will begin in real-time, and you can monitor it both on the platform and in OBS.",
|
||||||
|
"congrats": "✨ Congratulations! You've successfully set up OBS for streaming on our platform. Your audience awaits new exciting moments and content! 🎉",
|
||||||
|
"close": "Close"
|
||||||
|
},
|
||||||
|
"createModal": {
|
||||||
|
"trigger": "Generate Keys",
|
||||||
|
"heading": "Generate Keys",
|
||||||
|
"ingressTypeLabel": "Connection Type",
|
||||||
|
"ingressTypePlaceholder": "Select connection type",
|
||||||
|
"ingressTypeDescription": "Select RTMP for classic broadcasting or WHIP for WebRTC connections.",
|
||||||
|
"submitButton": "Generate",
|
||||||
|
"successMessage": "Ingress created",
|
||||||
|
"errorMessage": "Error creating ingress"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Chat Settings",
|
||||||
|
"description": "Here you can manage your chat settings."
|
||||||
|
},
|
||||||
|
"isChatEnabled": {
|
||||||
|
"heading": "Enable Chat",
|
||||||
|
"description": "If you enable chat, viewers will be able to send messages in the chat during your broadcast."
|
||||||
|
},
|
||||||
|
"isChatFollowersOnly": {
|
||||||
|
"heading": "Followers-Only Chat",
|
||||||
|
"description": "If you enable this setting, only your followers will be able to send messages in the chat."
|
||||||
|
},
|
||||||
|
"isChatPremiumFollowersOnly": {
|
||||||
|
"heading": "Premium Followers-Only Chat",
|
||||||
|
"description": "If you enable this setting, only premium followers will be able to send messages in the chat."
|
||||||
|
},
|
||||||
|
"successMessage": "Chat settings updated successfully",
|
||||||
|
"errorMessage": "Error updating chat settings"
|
||||||
|
},
|
||||||
|
"followers": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Followers",
|
||||||
|
"description": "Here is a list of all your followers."
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Date",
|
||||||
|
"user": "User",
|
||||||
|
"actions": "Actions",
|
||||||
|
"viewChannel": "View channel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sponsors": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Sponsors",
|
||||||
|
"description": "Here is a list of all your sponsors."
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Expiration Date",
|
||||||
|
"user": "User",
|
||||||
|
"plan": "Plan",
|
||||||
|
"actions": "Actions",
|
||||||
|
"viewChannel": "View Channel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plans": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Premium Plans",
|
||||||
|
"description": "Here is the list of premium plans for your channel."
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"heading": "Channel verification required",
|
||||||
|
"description": "To create premium plans, your channel must be verified. You only need 10 subscribers to get the checkmark and unlock access to this feature."
|
||||||
|
},
|
||||||
|
"createForm": {
|
||||||
|
"trigger": "Create Plan",
|
||||||
|
"heading": "Create Plan",
|
||||||
|
"titleLabel": "Title",
|
||||||
|
"titlePlaceholder": "Premium subscription for the channel",
|
||||||
|
"titleDescription": "Enter the name of your plan. This name will be displayed to your subscribers.",
|
||||||
|
"descriptionLabel": "Description",
|
||||||
|
"descriptionPlaceholder": "Describe what your plan includes",
|
||||||
|
"descriptionDescription": "Describe what your plan includes. This will help your subscribers understand the benefits of the subscription.",
|
||||||
|
"priceLabel": "Price",
|
||||||
|
"priceDescription": "Specify the price of your plan in currency. This is the amount users will pay for the subscription.",
|
||||||
|
"submitButton": "Create",
|
||||||
|
"successMessage": "Premium plan created",
|
||||||
|
"errorMessage": "Error while creating the plan"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Creation Date",
|
||||||
|
"title": "Title",
|
||||||
|
"price": "Price",
|
||||||
|
"actions": "Actions",
|
||||||
|
"remove": "Remove",
|
||||||
|
"successMessage": "Plan successfully removed",
|
||||||
|
"removeMessage": "Error while removing the plan"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"transactions": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Transactions",
|
||||||
|
"description": "Here are the transactions related to channel sponsorship."
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Creation Date",
|
||||||
|
"status": "Status",
|
||||||
|
"success": "Successful",
|
||||||
|
"pending": "Pending",
|
||||||
|
"failed": "Failed",
|
||||||
|
"expired": "Expired",
|
||||||
|
"amount": "Amount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"liveBadge": {
|
||||||
|
"text": "LIVE"
|
||||||
|
},
|
||||||
|
"confirmModal": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"continue": "Continue"
|
||||||
|
},
|
||||||
|
"copyButton": {
|
||||||
|
"successMessage": "Copied"
|
||||||
|
},
|
||||||
|
"dataTable": {
|
||||||
|
"notFound": "Nothing found"
|
||||||
|
},
|
||||||
|
"emptyState": {
|
||||||
|
"heading": "Nothing found",
|
||||||
|
"text": "We've searched every corner of our site, but unfortunately, nothing was found for your request. Maybe you could try something else?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"formatDate": {
|
||||||
|
"months": {
|
||||||
|
"january": "january",
|
||||||
|
"february": "february",
|
||||||
|
"march": "march",
|
||||||
|
"april": "april",
|
||||||
|
"may": "may",
|
||||||
|
"june": "june",
|
||||||
|
"july": "july",
|
||||||
|
"august": "august",
|
||||||
|
"september": "september",
|
||||||
|
"october": "october",
|
||||||
|
"november": "november",
|
||||||
|
"december": "december"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,622 @@
|
|||||||
{
|
{
|
||||||
|
"layout": {
|
||||||
|
"header": {
|
||||||
|
"logo": {
|
||||||
|
"platform": "Платформа для стримов"
|
||||||
|
},
|
||||||
|
"headerMenu": {
|
||||||
|
"login": "Войти",
|
||||||
|
"register": "Регистрация",
|
||||||
|
"profileMenu": {
|
||||||
|
"notifications": {
|
||||||
|
"heading": "Уведомления",
|
||||||
|
"loading": "Загрузка...",
|
||||||
|
"empty": "У вас нет уведомлений"
|
||||||
|
},
|
||||||
|
"successMessage": "Вы успешно вышли из системы",
|
||||||
|
"errorMessage": "Ошибка при выходе",
|
||||||
|
"channel": "Мой канал",
|
||||||
|
"dashboard": "Панель управления",
|
||||||
|
"logout": "Выйти"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Поиск"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"header": {
|
||||||
|
"expand": "Развернуть",
|
||||||
|
"collapse": "Свернуть",
|
||||||
|
"navigation": "Навигация"
|
||||||
|
},
|
||||||
|
"dashboardNav": {
|
||||||
|
"settings": "Настройки",
|
||||||
|
"streamSettings": "Настройки стрима",
|
||||||
|
"keys": "Ключи для стрима",
|
||||||
|
"chatSettings": "Настройки чата",
|
||||||
|
"followers": "Подписчики",
|
||||||
|
"sponsors": "Спонсоры",
|
||||||
|
"premium": "Премиум планы",
|
||||||
|
"transactions": "Транзакции"
|
||||||
|
},
|
||||||
|
"userNav": {
|
||||||
|
"home": "Главная",
|
||||||
|
"categories": "Категории",
|
||||||
|
"streams": "Трансляции",
|
||||||
|
"recommended": "Рекомендации"
|
||||||
|
},
|
||||||
|
"recommended": {
|
||||||
|
"heading": "Рекомендации"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Домашняя страница"
|
"streamsHeading": "Каналы, которые могут вам понравиться",
|
||||||
|
"categoriesHeading": "Категории, которые могут вам понравиться"
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"heading": "Категории",
|
||||||
|
"overview": {
|
||||||
|
"heading": "Стримы к этой категории"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": {
|
||||||
|
"heading": "Трансляции",
|
||||||
|
"searchHeading": "Поиск по запросу"
|
||||||
|
},
|
||||||
|
"stream": {
|
||||||
|
"video": {
|
||||||
|
"offline": "не в сети",
|
||||||
|
"loading": "Подключение...",
|
||||||
|
"player": {
|
||||||
|
"volume": "Громкость",
|
||||||
|
"fullscreen": {
|
||||||
|
"open": "Полноэкранный режим",
|
||||||
|
"exit": "Выйти из полноэкранного режима"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"viewers": "зрителей",
|
||||||
|
"offline": "Не в сети"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"follow": {
|
||||||
|
"confirmUnfollowHeading": "Перестать отслеживать",
|
||||||
|
"confirmUnfollowMessage": "Вы больше не будете получать уведомления от этого канала и он пропадет из списка отслеживаемых",
|
||||||
|
"unfollowButton": "Перестать отслеживать",
|
||||||
|
"followButton": "Отслеживать",
|
||||||
|
"successFollowMessage": "Вы успешно подписались",
|
||||||
|
"errorFollowMessage": "Ошибка при подписке",
|
||||||
|
"successUnfollowMessage": "Вы успешно описались",
|
||||||
|
"errorUnfollowMessage": "Ошибка при описке"
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"alreadySponsor": "Вы уже спонсор",
|
||||||
|
"supportAuthor": "Поддержать автора",
|
||||||
|
"perMonth": "в месяц",
|
||||||
|
"choose": "Выбрать",
|
||||||
|
"errorMessage": "Ошибка при создании платежа"
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"heading": "Поделиться с помощью"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aboutChannel": {
|
||||||
|
"heading": "Информация про",
|
||||||
|
"followersCount": "фолловеров",
|
||||||
|
"noDescription": "Описание не указано"
|
||||||
|
},
|
||||||
|
"sponsors": {
|
||||||
|
"heading": "Спонсоры канала"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"heading": "Чат",
|
||||||
|
"unavailable": "Чат не доступен",
|
||||||
|
"unavailableMessage": "Чат недоступен, пока на канале нет трансляции. Пожалуйста, загляните позже!",
|
||||||
|
"loading": "Подключение...",
|
||||||
|
"info": {
|
||||||
|
"authRequired": "Требуется авторизация",
|
||||||
|
"chatDisabled": "Чат отключен",
|
||||||
|
"premiumFollowersOnly": "Только для спонсоров",
|
||||||
|
"followersOnly": "Только для подписчиков"
|
||||||
|
},
|
||||||
|
"sendMessage": {
|
||||||
|
"placeholder": "Отправить сообщение",
|
||||||
|
"emojiPlaceholder": "Поиск",
|
||||||
|
"errorMessage": "Ошибка при отправке сообщения"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"heading": "Параметры трансляции",
|
||||||
|
"thumbnail": {
|
||||||
|
"updateButton": "Обновить изображение",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Удаление превью для стрима",
|
||||||
|
"message": "Вы уверены, что хотите удалить изображение для стрима? Это действие нельзя будет отменить."
|
||||||
|
},
|
||||||
|
"info": "Поддерживаемые форматы: JPG, JPEG, PNG или GIF. Макс. размер: 10 МБ.",
|
||||||
|
"successUpdateMessage": "Изображение стрима обновлено удалено",
|
||||||
|
"errorUpdateMessage": "Ошибка при обновлении изображения стрима",
|
||||||
|
"successRemoveMessage": "Изображение стрима обновлено удалено",
|
||||||
|
"errorRemoveMessage": "Ошибка при удалении изображения стрима"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"titleLabel": "Название",
|
||||||
|
"titlePlaceholder": "Стрим по ГТА 5",
|
||||||
|
"titleDescription": "Введите название вашего стрима. Оно должно быть коротким и запоминающимся.",
|
||||||
|
"categoryLabel": "Категория",
|
||||||
|
"categoryPlaceholder": "Выберете категорию",
|
||||||
|
"categoryDescription": "Выберите категорию, к которой относится ваш контент. Это поможет зрителям легче находить ваши трансляции.",
|
||||||
|
"submitButton": "Сохранить изменения",
|
||||||
|
"successMessage": "Настройки стрима успешно обновлены",
|
||||||
|
"errorMessage": "Ошибка при обновлении настроек стрима"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"heading": "Оплата прошла успешно!",
|
||||||
|
"details": {
|
||||||
|
"heading": "Детали спонсорства:",
|
||||||
|
"price": "Цена:",
|
||||||
|
"duration": "Длительность:",
|
||||||
|
"channel": "Канал:"
|
||||||
|
},
|
||||||
|
"congratulations": "Теперь у вас есть доступ к премиум-контенту канала!",
|
||||||
|
"backToHome": "Вернуться на главную",
|
||||||
|
"backToChannel": "Вернуться на канал",
|
||||||
|
"support": "Если у вас возникли вопросы, свяжитесь с нашей поддержкой."
|
||||||
|
},
|
||||||
|
"notFound": {
|
||||||
|
"description": "Упс, что-то пошло не так.",
|
||||||
|
"backToHome": "Перейти на главную"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"register": {
|
||||||
|
"heading": "Регистрация в TeaStream",
|
||||||
|
"backButtonLabel": "Есть учетная запись? Войти",
|
||||||
|
"usernameLabel": "Имя пользователя",
|
||||||
|
"usernameDescription": "Под этим именем вас будут знать другие пользователи.",
|
||||||
|
"emailLabel": "Почта",
|
||||||
|
"emailDescription": "Мы можем использовать вашу почту для отправки сообщений касательно учетной записи.",
|
||||||
|
"passwordLabel": "Пароль",
|
||||||
|
"passwordDescription": "Пароль должен содержать не менее 8 символов.",
|
||||||
|
"submitButton": "Зарегистрироваться",
|
||||||
|
"successAlertTitle": "Проверьте свою почту",
|
||||||
|
"successAlertDescription": "На ваш адрес электронной почты было отправлено письмо для подтверждения. Если вы не видите письмо, проверьте папку «Спам»",
|
||||||
|
"errorMessage": "Ошибка при создании аккаунта"
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"heading": "Верификация аккаунта",
|
||||||
|
"successMessage": "Аккаунт верифицирован",
|
||||||
|
"errorMessage": "Ошибка при верификации"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"heading": "Войти в TeaStream",
|
||||||
|
"backButtonLabel": "У вас нет учетной записи? Зарегистрируйтесь!",
|
||||||
|
"loginLabel": "Логин",
|
||||||
|
"loginDescription": "Ваше имя или почта, которые вы вводили при регистрации.",
|
||||||
|
"passwordLabel": "Пароль",
|
||||||
|
"passwordDescription": "Пароль, который вы вводили при регистрации.",
|
||||||
|
"pinLabel": "6-значный код",
|
||||||
|
"pinDescription": "Введите код из вашего приложения для аутентификации.",
|
||||||
|
"forgotPassword": "Забыли пароль?",
|
||||||
|
"submitButton": "Войти",
|
||||||
|
"successMessage": "Вы успешно вошли в систему",
|
||||||
|
"errorMessage": "Ошибка при входе в систему"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"heading": "Сброс пароля",
|
||||||
|
"backButtonLabel": "Есть учётная запись? Войти",
|
||||||
|
"emailLabel": "Почта",
|
||||||
|
"emailDescription": "Ваша почта, которую вы вводили при регистрации",
|
||||||
|
"submitButton": "Сбросить пароль",
|
||||||
|
"successAlertTitle": "Ссылка отправлена",
|
||||||
|
"successAlertDescription": "Мы отправили ссылку для сброса пароля на вашу почту. Если у вас включены уведомления в Telegram, ссылка также была отправлена туда",
|
||||||
|
"errorMessage": "Ошибка при сбросе пароля"
|
||||||
|
},
|
||||||
|
"newPassword": {
|
||||||
|
"heading": "Новый пароль",
|
||||||
|
"backButtonLabel": "Есть учётная запись? Войти",
|
||||||
|
"passwordLabel": "Новый пароль",
|
||||||
|
"passwordDescription": "Пароль, который вы вводили при регистрации",
|
||||||
|
"passwordRepeatLabel": "Повторите пароль",
|
||||||
|
"passwordRepeatDescription": "Повторите свой новый пароль для подтверждения",
|
||||||
|
"submitButton": "Продолжить",
|
||||||
|
"successMessage": "Пароль успешно изменён",
|
||||||
|
"errorMessage": "Ошибка при установлении нового пароля"
|
||||||
|
},
|
||||||
|
"deactivate": {
|
||||||
|
"heading": "Деактивация аккаунта",
|
||||||
|
"backButtonLabel": "Перейти в панель управления",
|
||||||
|
"emailLabel": "Почта",
|
||||||
|
"emailDescription": "Ваша почта, которую вы вводили при регистрации",
|
||||||
|
"passwordLabel": "Пароль",
|
||||||
|
"passwordDescription": "Пароль, который вы вводили при регистрации",
|
||||||
|
"pinLabel": "6-значный код",
|
||||||
|
"pinDescription": "Вам на почту был выслан код. Если вы подключали уведомления в Telegram, мы отправили код и туда",
|
||||||
|
"submitButton": "Деактивировать",
|
||||||
|
"successMessage": "Вы успешно деактивировали аккаунт",
|
||||||
|
"errorMessage": "Ошибка при деактивации"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"settings": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Настройки",
|
||||||
|
"description": "Здесь вы можете управлять вашими настройками",
|
||||||
|
"profile": "Профиль",
|
||||||
|
"account": "Аккаунт",
|
||||||
|
"appearance": "Внешний вид",
|
||||||
|
"notifications": "Уведомления",
|
||||||
|
"sessions": "Сессии"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Профиль",
|
||||||
|
"description": "Настройте ваш профиль, обновите аватар, измените информацию о себе и добавьте ссылки на социальные сети, чтобы сделать вашу страницу более привлекательной для других пользователей."
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"heading": "Изображение профиля",
|
||||||
|
"updateButton": "Обновить изображение профиля",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Удаление аватара",
|
||||||
|
"message": "Вы уверены, что хотите удалить изображение профиля? Это действие нельзя будет отменить."
|
||||||
|
},
|
||||||
|
"info": "Поддерживаемые форматы: JPG, JPEG, PNG, WEBP или GIF. Макс. размер: 10 МБ.",
|
||||||
|
"successUpdateMessage": "Изображение профиля успешно обновлено",
|
||||||
|
"errorUpdateMessage": "Ошибка при обновлении изображения профиля",
|
||||||
|
"successRemoveMessage": "Изображение профиля успешно удалено",
|
||||||
|
"errorRemoveMessage": "Ошибка при удалении изображения профиля"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"heading": "Настройки профиля",
|
||||||
|
"usernameLabel": "Имя пользователя",
|
||||||
|
"usernamePlaceholder": "johndoe",
|
||||||
|
"usernameDescription": "Под этим именем вас будут знать другие пользователи.",
|
||||||
|
"displayNameLabel": "Отображаемое имя",
|
||||||
|
"displayNamePlaceholder": "John Doe",
|
||||||
|
"displayNameDescription": "Проставьте прописные буквы в имени пользователя по своему желанию.",
|
||||||
|
"bioLabel": "О себе",
|
||||||
|
"bioPlaceholder": "Я люблю программировать",
|
||||||
|
"bioDescription": "Информация о себе должна содержать не более 300 символов.",
|
||||||
|
"submitButton": "Сохранить изменения",
|
||||||
|
"successMessage": "Профиль успешно обновлён",
|
||||||
|
"errorMessage": "Ошибка при обновлении профиля"
|
||||||
|
},
|
||||||
|
"socialLinks": {
|
||||||
|
"createForm": {
|
||||||
|
"heading": "Ссылки на соцсети",
|
||||||
|
"titleLabel": "Название ссылки",
|
||||||
|
"titlePlaceholder": "Youtube",
|
||||||
|
"titleDescription": "Текст ссылки",
|
||||||
|
"urlLabel": "URL ссылки",
|
||||||
|
"urlPlaceholder": "https://github.com/TeaCoder52",
|
||||||
|
"urlDescription": "Куда ведет эта ссылка? Введите полный URL-адрес, например https://github.com/TeaCoder52",
|
||||||
|
"submitButton": "Добавить",
|
||||||
|
"successMessage": "Ссылка на соц. сеть успешно добавлена",
|
||||||
|
"errorMessage": "Ошибка при добавлении ссылки на соц. сеть"
|
||||||
|
},
|
||||||
|
"editForm": {
|
||||||
|
"cancelButton": "Отмена",
|
||||||
|
"submitButton": "Сохранить",
|
||||||
|
"successUpdateMessage": "Ссылка на соц. сеть успешно изменена",
|
||||||
|
"errorUpdateMessage": "Ошибка при обновлении соц. сети",
|
||||||
|
"successRemoveMessage": "Ссылка на соц. сеть успешно удалена",
|
||||||
|
"errorRemoveMessage": "Ошибка при удалении соц. сети"
|
||||||
|
},
|
||||||
|
"successReorderMessage": "Порядок ссылок на соц. сети изменен",
|
||||||
|
"errorReorderMessage": "Ошибка при изменении порядка соц. сетей"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Аккаунт",
|
||||||
|
"description": "Управляйте настройками вашего аккаунта, включая изменение доступа, безопасность и возможность деактивации.",
|
||||||
|
"securityHeading": "Безопасность",
|
||||||
|
"securityDescription": "Настройте двухфакторную аутентификацию для улучшенной защиты ваших данных.",
|
||||||
|
"deactivationHeading": "Деактивация",
|
||||||
|
"deactivationDescription": "Если вы хотите временно или постоянно отключить свой аккаунт, используйте эту опцию. Обратите внимание на последствия."
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"heading": "Адрес электронной почты",
|
||||||
|
"emailLabel": "Почта",
|
||||||
|
"emailDescription": "Введите ваш новый адрес электронной почты.",
|
||||||
|
"submitButton": "Сохранить",
|
||||||
|
"successMessage": "Почта успешно обновлена",
|
||||||
|
"errorMessage": "Ошибка при обновлении почты"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"heading": "Пароль от аккаунта",
|
||||||
|
"oldPasswordLabel": "Старый пароль",
|
||||||
|
"oldPasswordDescription": "Введите свой старый пароль, чтобы подтвердить вашу личность перед изменением пароля. Это необходимо для обеспечения безопасности вашей учетной записи.",
|
||||||
|
"newPasswordLabel": "Новый пароль",
|
||||||
|
"newPasswordDescription": "Ваш новый пароль должен содержать не менее 8 символов. Рекомендуется использовать также специальные символы для повышения безопасности.",
|
||||||
|
"submitButton": "Сохранить",
|
||||||
|
"successMessage": "Пароль успешно обновлён",
|
||||||
|
"errorMessage": "Ошибка при обновлении пароля"
|
||||||
|
},
|
||||||
|
"twoFactor": {
|
||||||
|
"heading": "Аутентификация с помощью TOTP",
|
||||||
|
"description": "Увеличьте безопасность вашего аккаунта, активировав аутентификацию с помощью TOTP. Этот дополнительный уровень защиты требует ввода уникального кода, что делает ваш аккаунт менее уязвимым к несанкционированному доступу.",
|
||||||
|
"enable": {
|
||||||
|
"trigger": "Включить",
|
||||||
|
"heading": "Включение TOTP",
|
||||||
|
"qrInstructions": "Сканируйте QR-код для добавления TOTP",
|
||||||
|
"secretCodeLabel": "Секретный код: ",
|
||||||
|
"pinLabel": "PIN-код",
|
||||||
|
"pinDescription": "Пожалуйста, введите 6-значный код, предоставленный вашим приложением для аутентификации.",
|
||||||
|
"submitButton": "Подтвердить",
|
||||||
|
"successMessage": "Аутентификация с помощью TOTP включена",
|
||||||
|
"errorMessage": "Ошибка при верификации кода"
|
||||||
|
},
|
||||||
|
"disable": {
|
||||||
|
"trigger": "Выключить",
|
||||||
|
"heading": "Вы действительно хотите отключить TOTP?",
|
||||||
|
"message": "При отключении аутентификации с помощью TOTP ваш аккаунт станет менее защищенным. Вы уверены, что хотите продолжить?",
|
||||||
|
"successMessage": "Аутентификация через TOTP отключена",
|
||||||
|
"errorMessage": "Ошибка при отключении"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deactivation": {
|
||||||
|
"heading": "Деактивация аккаунта",
|
||||||
|
"description": "После деактивации аккаунта вы не сможете в него войти. Через 7 дней аккаунт будет полностью удалён. До этого времени вы можете обратиться в поддержку для восстановления доступа и разблокировки аккаунта.",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Подтверждение деактивации",
|
||||||
|
"message": "Вы уверены, что хотите деактивировать аккаунт? Восстановить доступ можно только в течение 7 дней."
|
||||||
|
},
|
||||||
|
"button": "Деактивировать"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Внешний вид",
|
||||||
|
"description": "Настройте внешний вид интерфейса сайта по своему вкусу. Выберите тему и язык, которые сделают ваше взаимодействие более комфортным."
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"heading": "Смена темы",
|
||||||
|
"description": "Вы можете выбрать темную или светлую тему для интерфейса сайта",
|
||||||
|
"successMessage": "Тема сайта успешно изменена"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"heading": "Язык сайта",
|
||||||
|
"description": "Выберите язык, который будет использован на сайте",
|
||||||
|
"selectPlaceholder": "Выберите язык",
|
||||||
|
"successMessage": "Язык сайта успешно изменен"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"heading": "Цвет акцента",
|
||||||
|
"description": "Выберите цвет акцента для интерфейса"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Уведомления",
|
||||||
|
"description": "Настройте, как вы хотите получать уведомления от TeaStream. Вы будете получать уведомления о начале трансляций на каналах, на которые вы подписаны, а также о новых подписчиках."
|
||||||
|
},
|
||||||
|
"siteNotifications": {
|
||||||
|
"heading": "Уведомления на сайте",
|
||||||
|
"description": "Если вы выключите уведомления на сайте, то не будете получать уведомления о новых подписчиках на ваш канал и уведомления о новых трансляциях."
|
||||||
|
},
|
||||||
|
"telegramNotifications": {
|
||||||
|
"heading": "Уведомления в Telegram",
|
||||||
|
"description": "Если вы включите уведомления через Telegram, ваш аккаунт TeaStream свяжется с вашим Telegram-аккаунтом, и вы будете получать сообщения о новых подписках, новых стримах, сбросе пароля и коды двухфакторной аутентификации."
|
||||||
|
},
|
||||||
|
"successMessage": "Настройки уведомлений успешно обновлены",
|
||||||
|
"errorMessage": "Ошибка при обновлении настроек уведомлений"
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Сессии",
|
||||||
|
"description": "Сессии — это устройства, которые вы используете или которые использовали для входа в вашу учетную запись TeaStream. Здесь показаны активные сессии в данный момент."
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"current": "Текущая сессия",
|
||||||
|
"active": "Активные сессии",
|
||||||
|
"notFound": "Активных сессий не найдено"
|
||||||
|
},
|
||||||
|
"sessionItem": {
|
||||||
|
"deleteButton": "Удалить",
|
||||||
|
"detailsButton": "Подробнее",
|
||||||
|
"confirmModal": {
|
||||||
|
"heading": "Удаление сессии",
|
||||||
|
"message": "Вы уверены, что хотите удалить эту сессию? Это действие приведёт к необратимому удалению сессии на этом устройстве и завершит все активные действия, связанные с ним."
|
||||||
|
},
|
||||||
|
"successMessage": "Сессия удалена",
|
||||||
|
"errorMessage": "Ошибка при удалении сессии"
|
||||||
|
},
|
||||||
|
"sessionModal": {
|
||||||
|
"heading": "Информация о сессии",
|
||||||
|
"device": "Устройство:",
|
||||||
|
"location": "Местоположение:",
|
||||||
|
"ipAddress": "IP-адрес:",
|
||||||
|
"createdAt": "Дата создания:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Ключи для стрима",
|
||||||
|
"description": "Сгенерируйте URL и ключ для проведения трансляции."
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"heading": "URL"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"heading": "Ключ"
|
||||||
|
},
|
||||||
|
"instructionModal": {
|
||||||
|
"trigger": "Инструкция",
|
||||||
|
"heading": "🛠️ Инструкция по настройке OBS для прямых трансляций 🛠️",
|
||||||
|
"description": "Чтобы начать делиться своим контентом с аудиторией, следуйте подробной инструкции по подключению OBS Studio. Это всего несколько шагов, и вы готовы выходить в эфир!",
|
||||||
|
"step1Title": "🔑 Шаг 1: Скачивание OBS Studio и генерация Server URL и Stream Key",
|
||||||
|
"step1Description": "Чтобы начать трансляцию, вам необходимо получить два важных элемента: Server URL и Stream Key (ключ трансляции). Следуйте этим шагам:",
|
||||||
|
"downloadObs": "Скачайте OBS Studio",
|
||||||
|
"downloadObsDescription": "Прежде чем настраивать трансляцию, скачайте и установите программу",
|
||||||
|
"obsLinkText": "OBS Studio",
|
||||||
|
"copyKeys": "Скопируйте Server URL и Stream Key",
|
||||||
|
"copyKeysDescription": "После генерации на экране появятся два поля: Server URL и Stream Key. Нажмите на иконки копирования рядом с каждым из полей, чтобы сохранить данные в буфер обмена.",
|
||||||
|
"step2Title": "🎛️ Шаг 2: Настройка OBS Studio",
|
||||||
|
"step2Description": "Теперь, когда у вас есть необходимые данные, давайте настроим OBS для работы с вашей трансляцией:",
|
||||||
|
"openObs": "Откройте OBS Studio",
|
||||||
|
"openObsDescription": "Если у вас еще не установлен OBS, скачайте его с официального сайта и установите. После запуска откроется главная страница OBS.",
|
||||||
|
"openStreamSettings": "Откройте настройки трансляции",
|
||||||
|
"openStreamSettingsDescription": "В верхнем меню нажмите «Файл» → «Настройки». Перейдите во вкладку «Трансляция».",
|
||||||
|
"enterDetails": "Введите ваши данные",
|
||||||
|
"enterDetailsDescription": "В поле «Сервис» выберите опцию «Пользовательский...». Вставьте скопированный ранее Server URL в поле «URL». В поле «Ключ трансляции» вставьте ваш Stream Key.",
|
||||||
|
"saveSettings": "Сохраните настройки",
|
||||||
|
"saveSettingsDescription": "Нажмите «Применить», а затем «ОК», чтобы сохранить изменения.",
|
||||||
|
"step3Title": "🚀 Шаг 3: Начало трансляции",
|
||||||
|
"step3Description": "После успешной настройки OBS вы готовы начать прямую трансляцию:",
|
||||||
|
"startStream": "Запустите трансляцию",
|
||||||
|
"startStreamDescription": "Вернитесь на главный экран OBS и нажмите кнопку «Начать трансляцию».",
|
||||||
|
"monitorStream": "Следите за своей трансляцией",
|
||||||
|
"monitorStreamDescription": "Ваша трансляция начнется в режиме реального времени, и вы сможете наблюдать за ней как на платформе, так и в OBS.",
|
||||||
|
"congrats": "✨ Поздравляем! Теперь вы успешно настроили OBS для трансляций на нашей платформе. Ваша аудитория ждет новых ярких моментов и контента! 🎉",
|
||||||
|
"close": "Закрыть"
|
||||||
|
},
|
||||||
|
"createModal": {
|
||||||
|
"trigger": "Сгенерировать ключи",
|
||||||
|
"heading": "Сгенерировать ключи",
|
||||||
|
"ingressTypeLabel": "Тип подключения",
|
||||||
|
"ingressTypePlaceholder": "Выберите тип подключения",
|
||||||
|
"ingressTypeDescription": "Выберите RTMP для классического вещания или WHIP для WebRTC подключений.",
|
||||||
|
"submitButton": "Сгенерировать",
|
||||||
|
"successMessage": "Входной поток создан",
|
||||||
|
"errorMessage": "Ошибка при создании входного потока"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Настройки чата",
|
||||||
|
"description": "Здесь вы можете управлять настройками вашего чата."
|
||||||
|
},
|
||||||
|
"isChatEnabled": {
|
||||||
|
"heading": "Включить чат",
|
||||||
|
"description": "Если вы включите чат, зрители смогут писать сообщения в чате во время вашей трансляции."
|
||||||
|
},
|
||||||
|
"isChatFollowersOnly": {
|
||||||
|
"heading": "Чат только для подписчиков",
|
||||||
|
"description": "Если вы включите эту настройку, только ваши подписчики смогут писать сообщения в чате."
|
||||||
|
},
|
||||||
|
"isChatPremiumFollowersOnly": {
|
||||||
|
"heading": "Чат только для премиум подписчиков",
|
||||||
|
"description": "Если вы включите эту настройку, только премиум подписчики смогут писать сообщения в чате."
|
||||||
|
},
|
||||||
|
"successMessage": "Настройки чата успешно обновлены",
|
||||||
|
"errorMessage": "Ошибка при обновлении настроек чата "
|
||||||
|
},
|
||||||
|
"followers": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Подписчики",
|
||||||
|
"description": "Здесь отображается список всех ваших подписчиков."
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Дата подписи",
|
||||||
|
"user": "Пользователь",
|
||||||
|
"actions": "Действия",
|
||||||
|
"viewChannel": "Перейти на канал"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sponsors": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Спонсоры",
|
||||||
|
"description": "Здесь отображается список всех ваших спонсоров."
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Дата окончания",
|
||||||
|
"user": "Пользователь",
|
||||||
|
"plan": "План",
|
||||||
|
"actions": "Действия",
|
||||||
|
"viewChannel": "Перейти на канал"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plans": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Премиум планы",
|
||||||
|
"description": "Здесь отображается список премиум планов для вашего канала."
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"heading": "Требуется верификация канала",
|
||||||
|
"description": "Для создания премиум планов ваш канал должен быть верифицирован. Для этого вам нужно всего 10 подписчиков, чтобы получить галочку и разблокировать доступ к этой функции."
|
||||||
|
},
|
||||||
|
"createForm": {
|
||||||
|
"trigger": "Создать план",
|
||||||
|
"heading": "Создать план",
|
||||||
|
"titleLabel": "Название",
|
||||||
|
"titlePlaceholder": "Премиум подписка на канал",
|
||||||
|
"titleDescription": "Введите название вашего плана. Это название будет отображаться вашим подписчикам.",
|
||||||
|
"descriptionLabel": "Описание",
|
||||||
|
"descriptionPlaceholder": "Опишите, что включает в себя ваш план",
|
||||||
|
"descriptionDescription": "Опишите, что включает в себя ваш план. Это поможет вашим подписчикам понять преимущества подписки.",
|
||||||
|
"priceLabel": "Цена",
|
||||||
|
"priceDescription": "Укажите цену вашего плана в валюте. Это сумма, которую пользователи будут платить за подписку.",
|
||||||
|
"submitButton": "Создать",
|
||||||
|
"successMessage": "Премиум план создан",
|
||||||
|
"errorMessage": "Ошибка при создании плана"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Дата создания",
|
||||||
|
"title": "Название",
|
||||||
|
"price": "Цена",
|
||||||
|
"actions": "Действия",
|
||||||
|
"remove": "Удалить",
|
||||||
|
"successMessage": "План успешно удалён",
|
||||||
|
"removeMessage": "Ошибка при удалении плана"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"transactions": {
|
||||||
|
"header": {
|
||||||
|
"heading": "Транзакции",
|
||||||
|
"description": "Здесь отображаются транзакции, связанные с оформлением спонсорства на каналы."
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"date": "Дата создания",
|
||||||
|
"status": "Статус",
|
||||||
|
"success": "Успешно",
|
||||||
|
"pending": "В ожидании",
|
||||||
|
"failed": "Ошибка",
|
||||||
|
"expired": "Истек",
|
||||||
|
"amount": "Сумма"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"liveBadge": {
|
||||||
|
"text": "В ЭФИРЕ"
|
||||||
|
},
|
||||||
|
"confirmModal": {
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"continue": "Продолжить"
|
||||||
|
},
|
||||||
|
"copyButton": {
|
||||||
|
"successMessage": "Скопировано"
|
||||||
|
},
|
||||||
|
"dataTable": {
|
||||||
|
"notFound": "Нечего не найдено"
|
||||||
|
},
|
||||||
|
"emptyState": {
|
||||||
|
"heading": "Нечего не найдено",
|
||||||
|
"text": "Мы обшарили все уголки нашего сайта, но к сожалению по вашему запросу ничего не нашлось. Может попробуете что-то другое?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"formatDate": {
|
||||||
|
"months": {
|
||||||
|
"january": "января",
|
||||||
|
"february": "февраля",
|
||||||
|
"march": "марта",
|
||||||
|
"april": "апреля",
|
||||||
|
"may": "мая",
|
||||||
|
"june": "июня",
|
||||||
|
"july": "июля",
|
||||||
|
"august": "августа",
|
||||||
|
"september": "сентября",
|
||||||
|
"october": "октября",
|
||||||
|
"november": "ноября",
|
||||||
|
"december": "декабря"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 128 B |
@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 385 B |
20
frontend/src/app/account/create/page.tsx
Normal file
20
frontend/src/app/account/create/page.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import CreateAccountForm from '@/components/features/auth/forms/CreateAccountForm';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
|
const t = await getTranslations('auth.register');
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('heading')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateAccountPage = () => {
|
||||||
|
return (
|
||||||
|
<CreateAccountForm/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateAccountPage;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import ApolloClientProvider from '@/providers/ApolloClientProvider';
|
import ApolloClientProvider from '@/providers/ApolloClientProvider';
|
||||||
import { ThemeProvider } from '@/providers/ThemeProvider';
|
import { ThemeProvider } from '@/providers/ThemeProvider';
|
||||||
|
import { ToastProvider } from '@/providers/ToastProvider';
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { NextIntlClientProvider } from 'next-intl';
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
import { getLocale, getMessages } from 'next-intl/server';
|
import { getLocale, getMessages } from 'next-intl/server';
|
||||||
@ -35,6 +36,7 @@ export default async function RootLayout({
|
|||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
|
<ToastProvider/>
|
||||||
{children}
|
{children}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
|
|||||||
38
frontend/src/components/features/auth/AuthWrapper.tsx
Normal file
38
frontend/src/components/features/auth/AuthWrapper.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { LogoImage } from '@/components/images/LogoImage';
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/common/Card';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import React, { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
type AuthWrapperProps = {
|
||||||
|
heading: string
|
||||||
|
backButtonLabel?: string
|
||||||
|
backButtonHref?: string
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
const AuthWrapper: FC<AuthWrapperProps> = (props) => {
|
||||||
|
const { children, backButtonHref, backButtonLabel, heading } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex h-full items-center justify-center'>
|
||||||
|
<Card className='w-[450px]'>
|
||||||
|
<CardHeader className='flex-row items-center justify-center gap-x-4'>
|
||||||
|
<LogoImage />
|
||||||
|
<CardTitle>{heading}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>{children}</CardContent>
|
||||||
|
<CardFooter className='-mt-2'>
|
||||||
|
{backButtonLabel && backButtonHref && (
|
||||||
|
<Link href={backButtonHref} className='w-full'>
|
||||||
|
<Button variant='ghost' className='w-full'>
|
||||||
|
{backButtonLabel}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthWrapper;
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import { createAccountSchema, TypeCreateAccountSchema } from '@/schemas/auth/create-account.schema';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { CircleCheck } from 'lucide-react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import AuthWrapper from '../AuthWrapper';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel } from '@/components/ui/common/Form';
|
||||||
|
import { Input } from '@/components/ui/common/Input';
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/common/Alert';
|
||||||
|
import { useCreateUserMutation } from '@/graphql/generated/output';
|
||||||
|
|
||||||
|
|
||||||
|
const CreateAccountForm = () => {
|
||||||
|
const [isSuccess, setIsSuccess] = useState(false)
|
||||||
|
const t = useTranslations('auth.register')
|
||||||
|
|
||||||
|
const form = useForm<TypeCreateAccountSchema>({
|
||||||
|
resolver: zodResolver(createAccountSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { isValid } = form.formState
|
||||||
|
|
||||||
|
const [create, { loading: isLoadingCreate }] = useCreateUserMutation({
|
||||||
|
onCompleted() {
|
||||||
|
setIsSuccess(true)
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSubmit(data: TypeCreateAccountSchema) {
|
||||||
|
create({ variables: { data } })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthWrapper
|
||||||
|
heading={t('heading')}
|
||||||
|
backButtonLabel={t('backButtonLabel')}
|
||||||
|
backButtonHref='/account/login'
|
||||||
|
>
|
||||||
|
{isSuccess ? (
|
||||||
|
<Alert>
|
||||||
|
<CircleCheck className='size-4' />
|
||||||
|
<AlertTitle>{t('successAlertTitle')}</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
{t('successAlertDescription')}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className='grid gap-y-3'
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='name'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('usernameLabel')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder='johndoe'
|
||||||
|
disabled={isLoadingCreate}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t('usernameDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='email'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('emailLabel')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder='john.doe@example.com'
|
||||||
|
disabled={isLoadingCreate}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t('emailDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='password'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('passwordLabel')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder='********'
|
||||||
|
type='password'
|
||||||
|
disabled={isLoadingCreate}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t('passwordDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className='mt-2 w-full'
|
||||||
|
disabled={!isValid || isLoadingCreate}
|
||||||
|
>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</AuthWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateAccountForm;
|
||||||
43
frontend/src/components/images/LogoImage.tsx
Normal file
43
frontend/src/components/images/LogoImage.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
export function LogoImage() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||||
|
viewBox='0 0 2400 2800'
|
||||||
|
xmlSpace='preserve'
|
||||||
|
width={42}
|
||||||
|
height={42}
|
||||||
|
>
|
||||||
|
<title>Logo</title>
|
||||||
|
<g>
|
||||||
|
<polygon
|
||||||
|
className='fill-white'
|
||||||
|
points='2200,1300 1800,1700 1400,1700 1050,2050 1050,1700 600,1700 600,200 2200,200 '
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<g id='Layer_1-2'>
|
||||||
|
<path
|
||||||
|
className='fill-primary'
|
||||||
|
d='M500,0L0,500v1800h600v500l500-500h400l900-900V0H500z M2200,1300l-400,400h-400l-350,350v-350H600V200h1600
|
||||||
|
V1300z'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x='1700'
|
||||||
|
y='550'
|
||||||
|
className='fill-primary'
|
||||||
|
width='200'
|
||||||
|
height='600'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x='1150'
|
||||||
|
y='550'
|
||||||
|
className='fill-primary'
|
||||||
|
width='200'
|
||||||
|
height='600'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
62
frontend/src/components/ui/common/Alert.tsx
Normal file
62
frontend/src/components/ui/common/Alert.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { cn } from '@/utils/twMerge'
|
||||||
|
import { type VariantProps, cva } from 'class-variance-authority'
|
||||||
|
import { type HTMLAttributes, forwardRef } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-background text-foreground',
|
||||||
|
destructive:
|
||||||
|
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role='alert'
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = 'Alert'
|
||||||
|
|
||||||
|
const AlertTitle = forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn('mb-1 font-medium leading-none tracking-wide', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = 'AlertTitle'
|
||||||
|
|
||||||
|
const AlertDescription = forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'mt-2 text-sm text-muted-foreground [&_p]:leading-relaxed',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = 'AlertDescription'
|
||||||
|
|
||||||
|
export { Alert, AlertDescription, AlertTitle }
|
||||||
74
frontend/src/components/ui/common/Card.tsx
Normal file
74
frontend/src/components/ui/common/Card.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { cn } from '@/utils/twMerge';
|
||||||
|
import { type HTMLAttributes, forwardRef } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'bg-card text-card-foreground border-border rounded-lg border shadow-sm',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Card.displayName = 'Card'
|
||||||
|
|
||||||
|
const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
CardHeader.displayName = 'CardHeader'
|
||||||
|
|
||||||
|
const CardTitle = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'text-2xl font-semibold leading-none tracking-wide',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
CardTitle.displayName = 'CardTitle'
|
||||||
|
|
||||||
|
const CardDescription = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-muted-foreground text-sm', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = 'CardDescription'
|
||||||
|
|
||||||
|
const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
||||||
|
)
|
||||||
|
)
|
||||||
|
CardContent.displayName = 'CardContent'
|
||||||
|
|
||||||
|
const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('flex items-center p-6 pt-0', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
CardFooter.displayName = 'CardFooter'
|
||||||
|
|
||||||
|
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
||||||
191
frontend/src/components/ui/common/Form.tsx
Normal file
191
frontend/src/components/ui/common/Form.tsx
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/twMerge';
|
||||||
|
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ComponentRef,
|
||||||
|
type HTMLAttributes,
|
||||||
|
createContext,
|
||||||
|
forwardRef,
|
||||||
|
useContext,
|
||||||
|
useId
|
||||||
|
} from 'react'
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
type ControllerProps,
|
||||||
|
type FieldPath,
|
||||||
|
type FieldValues,
|
||||||
|
FormProvider,
|
||||||
|
useFormContext
|
||||||
|
} from 'react-hook-form'
|
||||||
|
|
||||||
|
|
||||||
|
import { Label } from './Label'
|
||||||
|
|
||||||
|
const Form = FormProvider
|
||||||
|
|
||||||
|
type FormFieldContextValue<
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
> = {
|
||||||
|
name: TName
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormFieldContext = createContext<FormFieldContextValue>(
|
||||||
|
{} as FormFieldContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
const FormField = <
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
|
>({
|
||||||
|
...props
|
||||||
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
|
return (
|
||||||
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
|
<Controller {...props} />
|
||||||
|
</FormFieldContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useFormField = () => {
|
||||||
|
const fieldContext = useContext(FormFieldContext)
|
||||||
|
const itemContext = useContext(FormItemContext)
|
||||||
|
const { getFieldState, formState } = useFormContext()
|
||||||
|
|
||||||
|
const fieldState = getFieldState(fieldContext.name, formState)
|
||||||
|
|
||||||
|
if (!fieldContext) {
|
||||||
|
throw new Error('useFormField should be used within <FormField>')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = itemContext
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: fieldContext.name,
|
||||||
|
formItemId: `${id}-form-item`,
|
||||||
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
|
formMessageId: `${id}-form-item-message`,
|
||||||
|
...fieldState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormItemContextValue = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormItemContext = createContext<FormItemContextValue>(
|
||||||
|
{} as FormItemContextValue
|
||||||
|
)
|
||||||
|
|
||||||
|
const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
const id = useId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItemContext.Provider value={{ id }}>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn('space-y-2', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</FormItemContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
FormItem.displayName = 'FormItem'
|
||||||
|
|
||||||
|
const FormLabel = forwardRef<
|
||||||
|
ComponentRef<typeof LabelPrimitive.Root>,
|
||||||
|
ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { error, formItemId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(error && 'text-destructive', className)}
|
||||||
|
htmlFor={formItemId}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormLabel.displayName = 'FormLabel'
|
||||||
|
|
||||||
|
const FormControl = forwardRef<
|
||||||
|
ComponentRef<typeof Slot>,
|
||||||
|
ComponentPropsWithoutRef<typeof Slot>
|
||||||
|
>(({ ...props }, ref) => {
|
||||||
|
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||||
|
useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slot
|
||||||
|
ref={ref}
|
||||||
|
id={formItemId}
|
||||||
|
aria-describedby={
|
||||||
|
!error
|
||||||
|
? `${formDescriptionId}`
|
||||||
|
: `${formDescriptionId} ${formMessageId}`
|
||||||
|
}
|
||||||
|
aria-invalid={!!error}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormControl.displayName = 'FormControl'
|
||||||
|
|
||||||
|
const FormDescription = forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { formDescriptionId } = useFormField()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formDescriptionId}
|
||||||
|
className={cn('text-sm text-muted-foreground', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormDescription.displayName = 'FormDescription'
|
||||||
|
|
||||||
|
const FormMessage = forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, children, ...props }, ref) => {
|
||||||
|
const { error, formMessageId } = useFormField()
|
||||||
|
const body = error ? String(error?.message) : children
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formMessageId}
|
||||||
|
className={cn('text-sm font-medium text-destructive', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
FormMessage.displayName = 'FormMessage'
|
||||||
|
|
||||||
|
export {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
useFormField
|
||||||
|
}
|
||||||
22
frontend/src/components/ui/common/Input.tsx
Normal file
22
frontend/src/components/ui/common/Input.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { cn } from '@/utils/twMerge';
|
||||||
|
import { type ComponentProps, forwardRef } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
const Input = forwardRef<HTMLInputElement, ComponentProps<'input'>>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
'flex h-10 w-full rounded-md border border-border bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:border-primary focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = 'Input'
|
||||||
|
|
||||||
|
export { Input }
|
||||||
30
frontend/src/components/ui/common/Label.tsx
Normal file
30
frontend/src/components/ui/common/Label.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { cn } from '@/utils/twMerge'
|
||||||
|
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||||
|
import { type VariantProps, cva } from 'class-variance-authority'
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ComponentRef,
|
||||||
|
forwardRef
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = forwardRef<
|
||||||
|
ComponentRef<typeof LabelPrimitive.Root>,
|
||||||
|
ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
||||||
25
frontend/src/components/ui/common/Toast.tsx
Normal file
25
frontend/src/components/ui/common/Toast.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
import { Toaster as Sonner, ToasterProps } from "sonner"
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = "system" } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps["theme"]}
|
||||||
|
className="toaster group"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--normal-bg": "var(--popover)",
|
||||||
|
"--normal-text": "var(--popover-foreground)",
|
||||||
|
"--normal-border": "var(--border)",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toaster }
|
||||||
@ -591,6 +591,13 @@ export type VerificationInput = {
|
|||||||
token: Scalars['String']['input'];
|
token: Scalars['String']['input'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateUserMutationVariables = Exact<{
|
||||||
|
data: CreateUserInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type CreateUserMutation = { __typename?: 'Mutation', createUser: { __typename?: 'UserModel', name: string, password: string, email: string } };
|
||||||
|
|
||||||
export type FindChannelByUsernameQueryVariables = Exact<{
|
export type FindChannelByUsernameQueryVariables = Exact<{
|
||||||
name: Scalars['String']['input'];
|
name: Scalars['String']['input'];
|
||||||
}>;
|
}>;
|
||||||
@ -599,6 +606,41 @@ export type FindChannelByUsernameQueryVariables = Exact<{
|
|||||||
export type FindChannelByUsernameQuery = { __typename?: 'Query', findChannelByUsername: { __typename?: 'UserModel', name: string, avatar?: string | null, displayName: string, stream: { __typename?: 'StreamModel', title: string } } };
|
export type FindChannelByUsernameQuery = { __typename?: 'Query', findChannelByUsername: { __typename?: 'UserModel', name: string, avatar?: string | null, displayName: string, stream: { __typename?: 'StreamModel', title: string } } };
|
||||||
|
|
||||||
|
|
||||||
|
export const CreateUserDocument = gql`
|
||||||
|
mutation CreateUser($data: CreateUserInput!) {
|
||||||
|
createUser(data: $data) {
|
||||||
|
name
|
||||||
|
password
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type CreateUserMutationFn = Apollo.MutationFunction<CreateUserMutation, CreateUserMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useCreateUserMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useCreateUserMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useCreateUserMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [createUserMutation, { data, loading, error }] = useCreateUserMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useCreateUserMutation(baseOptions?: Apollo.MutationHookOptions<CreateUserMutation, CreateUserMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<CreateUserMutation, CreateUserMutationVariables>(CreateUserDocument, options);
|
||||||
|
}
|
||||||
|
export type CreateUserMutationHookResult = ReturnType<typeof useCreateUserMutation>;
|
||||||
|
export type CreateUserMutationResult = Apollo.MutationResult<CreateUserMutation>;
|
||||||
|
export type CreateUserMutationOptions = Apollo.BaseMutationOptions<CreateUserMutation, CreateUserMutationVariables>;
|
||||||
export const FindChannelByUsernameDocument = gql`
|
export const FindChannelByUsernameDocument = gql`
|
||||||
query FindChannelByUsername($name: String!) {
|
query FindChannelByUsername($name: String!) {
|
||||||
findChannelByUsername(name: $name) {
|
findChannelByUsername(name: $name) {
|
||||||
|
|||||||
7
frontend/src/graphql/metations/auth/CreateUser.graphql
Normal file
7
frontend/src/graphql/metations/auth/CreateUser.graphql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
mutation CreateUser($data: CreateUserInput!) {
|
||||||
|
createUser(data: $data) {
|
||||||
|
name
|
||||||
|
password
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
25
frontend/src/providers/ToastProvider.tsx
Normal file
25
frontend/src/providers/ToastProvider.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
|
import type { ComponentProps } from 'react'
|
||||||
|
import { Toaster as Sonner } from 'sonner'
|
||||||
|
|
||||||
|
type ToasterProps = ComponentProps<typeof Sonner>
|
||||||
|
|
||||||
|
export function ToastProvider({ ...props }: ToasterProps) {
|
||||||
|
const { theme = 'system' } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps['theme']}
|
||||||
|
className='toaster group'
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||||
|
description: 'text-muted-foreground'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
frontend/src/schemas/auth/create-account.schema.ts
Normal file
12
frontend/src/schemas/auth/create-account.schema.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const createAccountSchema = z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.regex(/^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/),
|
||||||
|
email: z.string().email({ pattern: z.regexes.html5Email }).min(3),
|
||||||
|
password: z.string().min(8)
|
||||||
|
})
|
||||||
|
|
||||||
|
export type TypeCreateAccountSchema = z.infer<typeof createAccountSchema>
|
||||||
@ -1180,6 +1180,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
|
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
|
||||||
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
||||||
|
|
||||||
|
"@hookform/resolvers@5.2.1":
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-5.2.1.tgz#3332b4662fe301a969ac1b795d663cf728329166"
|
||||||
|
integrity sha512-u0+6X58gkjMcxur1wRWokA7XsiiBJ6aK17aPZxhkoYiK5J+HcTx0Vhu9ovXe6H+dVpO6cjrn2FkJTryXEMlryQ==
|
||||||
|
dependencies:
|
||||||
|
"@standard-schema/utils" "^0.3.0"
|
||||||
|
|
||||||
"@humanfs/core@^0.19.1":
|
"@humanfs/core@^0.19.1":
|
||||||
version "0.19.1"
|
version "0.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
|
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
|
||||||
@ -1463,7 +1470,21 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
|
||||||
integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
|
integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
|
||||||
|
|
||||||
"@radix-ui/react-slot@^1.2.3":
|
"@radix-ui/react-label@2.1.7":
|
||||||
|
version "2.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.1.7.tgz#ad959ff9c6e4968d533329eb95696e1ba8ad72ab"
|
||||||
|
integrity sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-primitive" "2.1.3"
|
||||||
|
|
||||||
|
"@radix-ui/react-primitive@2.1.3":
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz#db9b8bcff49e01be510ad79893fb0e4cda50f1bc"
|
||||||
|
integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-slot" "1.2.3"
|
||||||
|
|
||||||
|
"@radix-ui/react-slot@1.2.3":
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz#502d6e354fc847d4169c3bc5f189de777f68cfe1"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz#502d6e354fc847d4169c3bc5f189de777f68cfe1"
|
||||||
integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
|
integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
|
||||||
@ -1490,6 +1511,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz#75989085bbbf80ee325874a0137437bde77e9baf"
|
resolved "https://registry.yarnpkg.com/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz#75989085bbbf80ee325874a0137437bde77e9baf"
|
||||||
integrity sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==
|
integrity sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==
|
||||||
|
|
||||||
|
"@standard-schema/utils@^0.3.0":
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b"
|
||||||
|
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
|
||||||
|
|
||||||
"@swc/helpers@0.5.15":
|
"@swc/helpers@0.5.15":
|
||||||
version "0.5.15"
|
version "0.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
|
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
|
||||||
@ -4248,7 +4274,7 @@ negotiator@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
|
||||||
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
|
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
|
||||||
|
|
||||||
next-intl@^4.3.4:
|
next-intl@4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/next-intl/-/next-intl-4.3.4.tgz#247ae789f1490b6518e8b18fe13d44cce703098b"
|
resolved "https://registry.yarnpkg.com/next-intl/-/next-intl-4.3.4.tgz#247ae789f1490b6518e8b18fe13d44cce703098b"
|
||||||
integrity sha512-VWLIDlGbnL/o4LnveJTJD1NOYN8lh3ZAGTWw2krhfgg53as3VsS4jzUVnArJdqvwtlpU/2BIDbWTZ7V4o1jFEw==
|
integrity sha512-VWLIDlGbnL/o4LnveJTJD1NOYN8lh3ZAGTWw2krhfgg53as3VsS4jzUVnArJdqvwtlpU/2BIDbWTZ7V4o1jFEw==
|
||||||
@ -4257,7 +4283,7 @@ next-intl@^4.3.4:
|
|||||||
negotiator "^1.0.0"
|
negotiator "^1.0.0"
|
||||||
use-intl "^4.3.4"
|
use-intl "^4.3.4"
|
||||||
|
|
||||||
next-themes@^0.4.6:
|
next-themes@0.4.6:
|
||||||
version "0.4.6"
|
version "0.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.6.tgz#8d7e92d03b8fea6582892a50a928c9b23502e8b6"
|
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.6.tgz#8d7e92d03b8fea6582892a50a928c9b23502e8b6"
|
||||||
integrity sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==
|
integrity sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==
|
||||||
@ -4668,6 +4694,11 @@ react-dom@19.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
scheduler "^0.26.0"
|
scheduler "^0.26.0"
|
||||||
|
|
||||||
|
react-hook-form@7.61.1:
|
||||||
|
version "7.61.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.61.1.tgz#8c1f086ccd921a6e90df6800787e9ca3833f86c1"
|
||||||
|
integrity sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
@ -5067,6 +5098,11 @@ snake-case@^3.0.4:
|
|||||||
dot-case "^3.0.4"
|
dot-case "^3.0.4"
|
||||||
tslib "^2.0.3"
|
tslib "^2.0.3"
|
||||||
|
|
||||||
|
sonner@2.0.6:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/sonner/-/sonner-2.0.6.tgz#623b73faec55229d63ec35226d42021f2119bfa7"
|
||||||
|
integrity sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==
|
||||||
|
|
||||||
source-map-js@^1.0.2, source-map-js@^1.2.1:
|
source-map-js@^1.0.2, source-map-js@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
@ -5747,3 +5783,8 @@ zen-observable@0.8.15:
|
|||||||
version "0.8.15"
|
version "0.8.15"
|
||||||
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
|
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
|
||||||
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
|
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
|
||||||
|
|
||||||
|
zod@4.0.14:
|
||||||
|
version "4.0.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod/-/zod-4.0.14.tgz#cc3a0a21981e63e0f99c0f4f1decd92ec3be7e9f"
|
||||||
|
integrity sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw==
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user