Skip to content

Commit 0769e4b

Browse files
committed
Merge develop Keep cleanup
2 parents 737f86b + f50e613 commit 0769e4b

13 files changed

Lines changed: 173 additions & 13 deletions

File tree

backend/src/main/java/net/modtale/service/auth/OAuthProviderProfileService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private String extractProfileUrl(String provider, OAuth2User user, String userna
9797
return user.getAttribute("web_url");
9898
}
9999
if ("twitter".equals(provider)) {
100-
return "https://twitter.com/" + username;
100+
return "https://x.com/" + username;
101101
}
102102
if ("github".equals(provider)) {
103103
return "https://github.com/" + username;

backend/src/main/java/net/modtale/service/project/validation/ValidationService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class ValidationService {
2222
"Economy", "Protection", "Admin Tools", "Chat", "Anti-Cheat", "Performance", "NPCs",
2323
"Library", "API", "Mechanics", "World Gen", "Recipes", "Loot Tables", "Functions",
2424
"Decoration", "Vanilla+", "Kitchen Sink", "City", "Landscape", "Spawn", "Lobby",
25+
"Prefab", "Structure", "Dungeon", "Adventure Map", "Server Hub", "Template",
2526
"Medieval", "Modern", "Futuristic", "Models", "Textures", "Animations", "Particles"
2627
);
2728

backend/src/test/java/net/modtale/service/auth/OAuthProviderProfileServiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void extractUsesTwitterDataEnvelope() {
6060
assertEquals(OAuthProvider.TWITTER, profile.provider());
6161
assertEquals("tw-1", profile.providerId());
6262
assertEquals("modtale_dev", profile.username());
63-
assertEquals("https://twitter.com/modtale_dev", profile.profileUrl());
63+
assertEquals("https://x.com/modtale_dev", profile.profileUrl());
6464
}
6565

6666
@Test

backend/src/test/java/net/modtale/service/project/validation/ValidationServiceTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ class ValidationServiceTest {
1818

1919
@Test
2020
void validateTagsNormalizesKnownTagsAndRejectsUnknownOnes() {
21-
assertIterableEquals(List.of("Adventure", "Admin Tools"), validationService.validateTags(List.of("adventure", "admin tools")));
21+
assertIterableEquals(
22+
List.of("Adventure", "Admin Tools", "Prefab", "Server Hub"),
23+
validationService.validateTags(List.of("adventure", "admin tools", "prefab", "server hub"))
24+
);
2225

2326
IllegalArgumentException error = assertThrows(
2427
IllegalArgumentException.class,

frontend/src/data/categories.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const GLOBAL_TAGS = [
1818
'Economy', 'Protection', 'Admin Tools', 'Chat', 'Anti-Cheat', 'Performance', 'NPCs',
1919
'Library', 'API', 'Mechanics', 'World Gen', 'Recipes', 'Loot Tables', 'Functions',
2020
'Decoration', 'Vanilla+', 'Kitchen Sink', 'City', 'Landscape', 'Spawn', 'Lobby',
21+
'Prefab', 'Structure', 'Dungeon', 'Adventure Map', 'Server Hub', 'Template',
2122
'Medieval', 'Modern', 'Futuristic', 'Models', 'Textures', 'Animations', 'Particles'
2223
].sort();
2324

frontend/src/modules/core/views/ApiDocs.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,24 @@ export const ApiDocs: React.FC = () => {
15091509
<div className="text-3xl font-black text-purple-900 dark:text-white mb-1">{fmt(enterpriseTier?.readPerMinute)} <span className="text-sm font-medium text-purple-400">req/min</span></div>
15101510
<div className="text-xl font-bold text-purple-700 dark:text-purple-300">{fmt(enterpriseTier?.writePerMinute)} <span className="text-sm font-medium text-purple-400">write/min</span></div>
15111511
<p className="text-xs text-purple-700 dark:text-purple-300 mt-2">For high-volume integrations.</p>
1512+
<a href="mailto:support@modtale.net?subject=Modtale%20Enterprise%20API" className="relative z-10 mt-3 inline-flex items-center gap-1 text-xs font-bold text-purple-700 dark:text-purple-300 hover:underline">
1513+
Contact support for enterprise access <ExternalLink className="h-3 w-3" />
1514+
</a>
1515+
</div>
1516+
</div>
1517+
1518+
<div className="mt-6 rounded-2xl border border-slate-200 dark:border-white/10 bg-slate-50/80 dark:bg-white/5 p-5 text-left">
1519+
<h3 className="text-sm font-black text-slate-900 dark:text-white">Building something with the Modtale API?</h3>
1520+
<p className="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
1521+
Share integrations, request missing API capabilities, or contribute improvements on GitHub. Useful community projects may be highlighted as examples for other developers.
1522+
</p>
1523+
<div className="mt-3 flex flex-wrap gap-3 text-sm font-bold">
1524+
<a href="https://github.com/Modtale/modtale/issues/new/choose" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-modtale-accent hover:underline">
1525+
Open an issue <ExternalLink className="h-3.5 w-3.5" />
1526+
</a>
1527+
<a href="https://github.com/Modtale/modtale/pulls" target="_blank" rel="noreferrer" className="inline-flex items-center gap-1 text-modtale-accent hover:underline">
1528+
Contribute a pull request <ExternalLink className="h-3.5 w-3.5" />
1529+
</a>
15121530
</div>
15131531
</div>
15141532
</div>

frontend/src/modules/project/components/dialogs/PostDownloadModal.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ interface PostDownloadModalProps {
1212
channel?: 'RELEASE' | 'BETA' | 'ALPHA';
1313
isBundle?: boolean;
1414
fileName?: string;
15+
tags?: string[];
1516
}
1617

1718
export const PostDownloadModal: React.FC<PostDownloadModalProps> = ({
18-
isOpen, onClose, classification, title, channel = 'RELEASE', isBundle = false, fileName = ''
19+
isOpen, onClose, classification, title, channel = 'RELEASE', isBundle = false, fileName = '', tags = []
1920
}) => {
2021
useScrollLock(isOpen);
2122
const [os, setOs] = useState<'windows' | 'macos' | 'linux'>('windows');
@@ -35,7 +36,8 @@ export const PostDownloadModal: React.FC<PostDownloadModalProps> = ({
3536
if (!isOpen) return null;
3637

3738
const isWorld = classification === 'SAVE';
38-
const folderName = isWorld ? 'Saves' : 'Mods';
39+
const isPrefab = isWorld && tags.some(tag => tag.toLowerCase() === 'prefab');
40+
const folderName = isWorld && !isPrefab ? 'Saves' : 'Mods';
3941

4042
const paths = {
4143
windows: `C:\\Program Files\\Hypixel Studios\\Hytale Launcher\\UserData\\${folderName}`,
@@ -181,7 +183,11 @@ export const PostDownloadModal: React.FC<PostDownloadModalProps> = ({
181183
<div className={`flex gap-4 p-4 rounded-2xl border shadow-sm ${theme.colors.bgBase} ${theme.colors.border}`}>
182184
<div className={`w-8 h-8 rounded-full ${themeColors.bgAlpha} ${themeColors.text} font-black flex items-center justify-center shrink-0 shadow-inner`}>{isBundle ? '3' : '2'}</div>
183185
<div className="w-full min-w-0 pt-1.5">
184-
<p className="mb-3">{isWorld ? 'Extract and move the world folder into your Hytale Saves directory:' : `Move ${isBundle ? 'ALL extracted files' : 'the downloaded file'} to your Hytale Mods directory:`}</p>
186+
<p className="mb-3">{isPrefab
187+
? 'If the download is archived, extract it, then place the prefab files inside a Hytale asset pack in your Mods directory:'
188+
: isWorld
189+
? 'Extract and move the world folder into your Hytale Saves directory:'
190+
: `Move ${isBundle ? 'ALL extracted files' : 'the downloaded file'} to your Hytale Mods directory:`}</p>
185191
<div className={`flex items-center gap-3 border rounded-xl p-2 pl-3 ${theme.colors.bgSurface} ${theme.colors.border}`}>
186192
<code className={`flex-1 font-mono text-[11px] ${theme.colors.textSecondary} break-all select-all leading-relaxed`}>{paths[os]}</code>
187193
<button type="button" onClick={handleCopy} className={`p-2 rounded-lg border transition-colors shadow-sm shrink-0 self-start ${theme.colors.bgBase} ${theme.colors.border} ${theme.colors.textMuted} hover:${theme.colors.textPrimary} ${theme.colors.bgSurfaceHover}`} title="Copy Path">
@@ -194,7 +200,11 @@ export const PostDownloadModal: React.FC<PostDownloadModalProps> = ({
194200
<div className={`flex gap-4 p-4 rounded-2xl border shadow-sm ${theme.colors.bgBase} ${theme.colors.border}`}>
195201
<div className={`w-8 h-8 rounded-full ${themeColors.bgAlpha} ${themeColors.text} font-black flex items-center justify-center shrink-0 shadow-inner`}>{isBundle ? '4' : '3'}</div>
196202
<div className="pt-1.5">
197-
{isWorld ? 'Launch Hytale and select the world from the Singleplayer menu.' : `Restart your Hytale Launcher to load the new project${isBundle ? 's' : ''}.`}
203+
{isPrefab
204+
? 'Enable the asset pack in the target world, load the prefab from the Prefab List, then use the Paste Tool to place it.'
205+
: isWorld
206+
? 'Launch Hytale and select the world from the Singleplayer menu.'
207+
: `Restart your Hytale Launcher to load the new project${isBundle ? 's' : ''}.`}
198208
</div>
199209
</div>
200210
</div>

frontend/src/modules/project/views/ProjectDetails.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ export const ProjectDetails: React.FC<ProjectDetailViewProps> = ({
594594
<Suspense fallback={null}>
595595
{isShareOpen && <ShareModal isOpen={isShareOpen} onClose={() => setIsShareOpen(false)} url={window.location.href} title={project.title} author={project.author} />}
596596
{isReportOpen && <ReportModal isOpen={isReportOpen} onClose={() => setIsReportOpen(false)} targetId={project.id} targetType="PROJECT" targetTitle={project.title} />}
597-
{showPostDownloadModal && <PostDownloadModal isOpen={showPostDownloadModal} onClose={() => setShowPostDownloadModal(false)} classification={project.classification!} title={project.title} channel={lastDownloadChannel} isBundle={lastDownloadWasBundle} fileName={lastDownloadedFileName} />}
597+
{showPostDownloadModal && <PostDownloadModal isOpen={showPostDownloadModal} onClose={() => setShowPostDownloadModal(false)} classification={project.classification!} title={project.title} channel={lastDownloadChannel} isBundle={lastDownloadWasBundle} fileName={lastDownloadedFileName} tags={project.tags} />}
598598

599599
{(isHistoryOpen || isDownloadOpen) && versionPayloadPending && (
600600
<div className={theme.components.modalOverlay}>

frontend/src/modules/user/components/ProfileLayout.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import { ImageCropperModal } from '@/components/ui/ImageCropperModal';
55
import { Spinner } from '@/components/ui/Spinner';
66
import { OptimizedImage } from '@/components/ui/OptimizedImage';
77
import { StatusModal } from '@/components/ui/StatusModal';
8-
import { DiscordBrandIcon, GitHubBrandIcon, GitLabBrandIcon, HytaleBrandIcon, XBrandIcon } from '@/components/ui/icons/BrandIcons';
8+
import { BlueskyBrandIcon, DiscordBrandIcon, GitHubBrandIcon, GitLabBrandIcon, HytaleBrandIcon, XBrandIcon } from '@/components/ui/icons/BrandIcons';
99
import type { User } from '@/types';
1010
import { BACKEND_URL } from '@/utils/api';
1111
import { SiteRoutes } from '@/utils/routes';
1212
import { Link } from 'react-router-dom';
13+
import { getConnectedAccountProfileUrl } from '@/modules/user/utils/connectedAccountLinks';
1314

1415
const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
1516
const MAX_UPLOAD_ERROR_MESSAGE = 'File exceeds 100MB limit. Cloudflare only supports uploads up to 100MB.';
@@ -174,7 +175,8 @@ export const ProfileLayout: React.FC<ProfileLayoutProps> = ({
174175
switch (provider?.toLowerCase()) {
175176
case 'github': return { icon: GitHubBrandIcon, label: 'GitHub', activeClass: 'group-hover/social:text-slate-900 dark:group-hover/social:text-white group-hover/social:border-slate-300 dark:group-hover/social:border-white/20', iconBg: 'bg-slate-900/10 dark:bg-white/10 text-slate-900 dark:text-white', profileBtnBg: 'bg-[#24292e]' };
176177
case 'gitlab': return { icon: GitLabBrandIcon, label: 'GitLab', activeClass: 'group-hover/social:text-[#FC6D26] group-hover/social:border-[#FC6D26]/30', iconBg: 'bg-[#FC6D26]/10 text-[#FC6D26]', profileBtnBg: 'bg-[#FC6D26]' };
177-
case 'twitter': return { icon: XBrandIcon, label: 'Twitter', activeClass: 'group-hover/social:text-[#1DA1F2] group-hover/social:border-[#1DA1F2]/30', iconBg: 'bg-[#1DA1F2]/10 text-[#1DA1F2]', profileBtnBg: 'bg-[#1DA1F2]' };
178+
case 'twitter': return { icon: XBrandIcon, label: 'X / Twitter', activeClass: 'group-hover/social:text-slate-900 dark:group-hover/social:text-white group-hover/social:border-slate-400 dark:group-hover/social:border-white/30', iconBg: 'bg-slate-900/10 dark:bg-white/10 text-slate-900 dark:text-white', profileBtnBg: 'bg-black' };
179+
case 'bluesky': return { icon: BlueskyBrandIcon, label: 'Bluesky', activeClass: 'group-hover/social:text-[#1185FE] group-hover/social:border-[#1185FE]/30', iconBg: 'bg-[#1185FE]/10 text-[#1185FE]', profileBtnBg: 'bg-[#1185FE]' };
178180
case 'discord': return { icon: DiscordBrandIcon, label: 'Discord', activeClass: 'group-hover/social:text-[#5865F2] group-hover/social:border-[#5865F2]/30', iconBg: 'bg-[#5865F2]/10 text-[#5865F2]', profileBtnBg: 'bg-[#5865F2]' };
179181
case 'hytale': return { icon: HytaleBrandIcon, label: 'Hytale', activeClass: 'group-hover/social:text-cyan-400 group-hover/social:border-cyan-400/30', iconBg: 'bg-cyan-500/10 text-cyan-400', profileBtnBg: 'bg-slate-900' };
180182
default: return { icon: Globe, label: 'Website', activeClass: 'group-hover/social:text-blue-500 group-hover/social:border-blue-500/30', iconBg: 'bg-blue-500/10 text-blue-500', profileBtnBg: 'bg-blue-500' };
@@ -192,9 +194,7 @@ export const ProfileLayout: React.FC<ProfileLayoutProps> = ({
192194

193195
const SocialButton = ({ account, compact = false, isRightMost = false }: { account: any, compact?: boolean, isRightMost?: boolean }) => {
194196
const { icon: Icon, label, activeClass, iconBg, profileBtnBg } = getProviderDetails(account.provider);
195-
const displayUrl = account.profileUrl || '#';
196-
const isDiscord = account.provider?.toLowerCase() === 'discord';
197-
const finalUrl = isDiscord ? `https://discord.com/users/${account.providerId}` : displayUrl;
197+
const finalUrl = getConnectedAccountProfileUrl(account);
198198

199199
const popupPositionClasses = isRightMost ? "right-0 left-auto translate-x-0" : "left-1/2 -translate-x-1/2";
200200
const trianglePositionClasses = isRightMost ? "right-3 left-auto translate-x-0" : "left-1/2 -translate-x-1/2";
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { ConnectedAccount } from '@/types';
2+
3+
type ConnectedAccountLink = Pick<ConnectedAccount, 'provider' | 'providerId' | 'profileUrl' | 'username'>;
4+
5+
export const getConnectedAccountProfileUrl = (account: ConnectedAccountLink): string => {
6+
const provider = account.provider?.trim().toLowerCase();
7+
const username = account.username?.trim().replace(/^@+/, '');
8+
9+
if (provider === 'discord' && account.providerId) {
10+
return `https://discord.com/users/${encodeURIComponent(account.providerId)}`;
11+
}
12+
if (provider === 'twitter' && username) {
13+
return `https://x.com/${encodeURIComponent(username)}`;
14+
}
15+
if (provider === 'bluesky' && username) {
16+
return `https://bsky.app/profile/${encodeURIComponent(username)}`;
17+
}
18+
19+
return account.profileUrl || '#';
20+
};

0 commit comments

Comments
 (0)