feat: implement header component with Enka, FreeSR, and extra setting management modals
Gitea Auto Deploy / Deploy-Container (push) Successful in 38s
Gitea Auto Deploy / Deploy-Container (push) Successful in 38s
This commit is contained in:
@@ -79,7 +79,7 @@ export default function ExtraSettingBar() {
|
||||
const currentLineup = extraData?.theory_craft?.custom_lineup || []
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-4 pb-2">
|
||||
<div className="w-full space-y-4 pb-2">
|
||||
{/* THEORY CRAFT */}
|
||||
{extraData?.theory_craft && (
|
||||
<section className="relative z-30 rounded-2xl bg-base-200/40 border border-white/10 shadow-xs backdrop-blur-sm transition-all duration-200">
|
||||
|
||||
@@ -637,13 +637,13 @@ export default function Header() {
|
||||
{modalConfigs?.map(({ id, title, onClose, content }) => (
|
||||
<dialog key={id} id={id} className="modal">
|
||||
<div className={`modal-box w-11/12 ${
|
||||
id === 'monster_modal'
|
||||
? 'max-w-[92vw] sm:max-w-[88vw] h-[85vh]'
|
||||
: id === 'extra_modal'
|
||||
? 'max-w-3xl lg:max-w-4xl max-h-[88vh]'
|
||||
id === 'monster_modal' || id === 'import_modal' || id === 'extra_modal'
|
||||
? 'max-w-[95vw] sm:max-w-[90vw] lg:max-w-[85vw] 2xl:max-w-[1500px] h-[88vh]'
|
||||
: id === 'connect_modal'
|
||||
? 'max-w-lg max-h-[85vh]'
|
||||
: 'max-w-2xl max-h-[90vh]'
|
||||
? 'max-w-[95vw] sm:max-w-2xl md:max-w-3xl lg:max-w-4xl max-h-[88vh]'
|
||||
: id === 'changelog_modal'
|
||||
? 'max-w-[95vw] sm:max-w-2xl md:max-w-3xl lg:max-w-4xl h-[85vh]'
|
||||
: 'max-w-[92vw] sm:max-w-[88vw] max-h-[90vh]'
|
||||
} max-h-[90vh] flex flex-col bg-base-100 text-base-content border border-base-content/10 shadow-2xl rounded-2xl p-0 overflow-hidden`}>
|
||||
{/* Modal Header */}
|
||||
<div className="sticky top-0 z-20 flex items-center justify-between px-6 py-4 border-b border-base-content/10 bg-base-100 shrink-0">
|
||||
|
||||
@@ -167,85 +167,83 @@ export default function EnkaImport() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-base-100 p-4 sm:p-6 rounded-2xl">
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
{/* Header */}
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-base-content text-xl sm:text-2xl font-bold">HSR UID</h1>
|
||||
<div className="w-full space-y-6">
|
||||
{/* Header */}
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-base-content text-xl sm:text-2xl font-bold">HSR UID</h1>
|
||||
|
||||
<div className="flex flex-wrap gap-3 items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={uidInput}
|
||||
onChange={(e) => setUidInput(e.target.value)}
|
||||
className="input input-bordered w-full max-w-xs rounded-xl bg-base-200 border-base-300 text-sm focus:border-primary"
|
||||
placeholder="Enter UID"
|
||||
/>
|
||||
<button onClick={handlerFetchData} className="btn btn-sm btn-success rounded-xl font-bold">
|
||||
{transI18n("getData")}
|
||||
<div className="flex flex-wrap gap-3 items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={uidInput}
|
||||
onChange={(e) => setUidInput(e.target.value)}
|
||||
className="input input-bordered w-full max-w-xs rounded-xl bg-base-200 border-base-300 text-sm focus:border-primary"
|
||||
placeholder="Enter UID"
|
||||
/>
|
||||
<button onClick={handlerFetchData} className="btn btn-sm btn-success rounded-xl font-bold">
|
||||
{transI18n("getData")}
|
||||
</button>
|
||||
{selectedCharacters.length > 0 && (
|
||||
<button onClick={handleImport} className="btn btn-sm btn-primary rounded-xl font-bold shadow-xs">
|
||||
{transI18n("import")}
|
||||
</button>
|
||||
{selectedCharacters.length > 0 && (
|
||||
<button onClick={handleImport} className="btn btn-sm btn-primary rounded-xl font-bold shadow-xs">
|
||||
{transI18n("import")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{Error && (
|
||||
<div className="text-error text-sm font-semibold mt-2">😭 {Error}</div>
|
||||
)}
|
||||
|
||||
{/* Player Info */}
|
||||
{enkaData && (
|
||||
<div className="bg-base-200 p-4 rounded-xl border border-base-300 text-base-content space-y-1">
|
||||
<div className="text-sm font-semibold">Name: <span className="font-bold text-primary">{enkaData.detailInfo.nickname}</span></div>
|
||||
<div className="text-sm font-semibold">UID: <span className="font-mono">{enkaData.detailInfo.uid}</span></div>
|
||||
<div className="text-sm font-semibold">Level: <span className="font-bold">{enkaData.detailInfo.level}</span></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Selection Info */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 bg-base-200 border border-base-300 p-3.5 rounded-2xl shadow-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs sm:text-sm font-bold text-base-content">
|
||||
{transI18n("selectedCharacters")}:
|
||||
</span>
|
||||
<span className="badge badge-primary font-mono font-bold text-xs">
|
||||
{selectedCharacters.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={clearSelection}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-error hover:bg-error/10 border border-error/30 rounded-xl"
|
||||
>
|
||||
{transI18n("clearAll")}
|
||||
</button>
|
||||
<button
|
||||
onClick={selectAll}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-primary hover:bg-primary/10 border border-primary/30 rounded-xl"
|
||||
>
|
||||
{transI18n("selectAll")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
|
||||
{Error && (
|
||||
<div className="text-error text-sm font-semibold mt-2">😭 {Error}</div>
|
||||
)}
|
||||
|
||||
{/* Player Info */}
|
||||
{enkaData && (
|
||||
<div className="bg-base-200 p-4 rounded-xl border border-base-300 text-base-content space-y-1">
|
||||
<div className="text-sm font-semibold">Name: <span className="font-bold text-primary">{enkaData.detailInfo.nickname}</span></div>
|
||||
<div className="text-sm font-semibold">UID: <span className="font-mono">{enkaData.detailInfo.uid}</span></div>
|
||||
<div className="text-sm font-semibold">Level: <span className="font-bold">{enkaData.detailInfo.level}</span></div>
|
||||
</div>
|
||||
)}
|
||||
{/* Character Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{validCharacters.map((character) => (
|
||||
<CharacterInfoCard
|
||||
key={character.avatarId}
|
||||
character={toCharacterInfoCard(character)}
|
||||
selectedCharacters={selectedCharacters}
|
||||
onCharacterToggle={handleCharacterToggle}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Selection Info */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 bg-base-200 border border-base-300 p-3.5 rounded-2xl shadow-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs sm:text-sm font-bold text-base-content">
|
||||
{transI18n("selectedCharacters")}:
|
||||
</span>
|
||||
<span className="badge badge-primary font-mono font-bold text-xs">
|
||||
{selectedCharacters.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={clearSelection}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-error hover:bg-error/10 border border-error/30 rounded-xl"
|
||||
>
|
||||
{transI18n("clearAll")}
|
||||
</button>
|
||||
<button
|
||||
onClick={selectAll}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-primary hover:bg-primary/10 border border-primary/30 rounded-xl"
|
||||
>
|
||||
{transI18n("selectAll")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
|
||||
</div>
|
||||
)}
|
||||
{/* Character Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4 sm:gap-6">
|
||||
{validCharacters.map((character) => (
|
||||
<CharacterInfoCard
|
||||
key={character.avatarId}
|
||||
character={toCharacterInfoCard(character)}
|
||||
selectedCharacters={selectedCharacters}
|
||||
onCharacterToggle={handleCharacterToggle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -189,74 +189,72 @@ export default function FreeSRImport() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-base-100 p-4 sm:p-6 rounded-2xl">
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
{/* Header */}
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-base-content text-xl sm:text-2xl font-bold">{transI18n("freeSRImport")}</h1>
|
||||
<div className="w-full space-y-6">
|
||||
{/* Header */}
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-base-content text-xl sm:text-2xl font-bold">{transI18n("freeSRImport")}</h1>
|
||||
|
||||
<div className="flex flex-wrap gap-4 items-center">
|
||||
<fieldset className="fieldset">
|
||||
<legend className="fieldset-legend text-xs font-semibold">{transI18n("pickAFile")}</legend>
|
||||
<input type="file" accept=".json" className="file-input file-input-sm file-input-bordered rounded-xl" onChange={handlerReadFile} />
|
||||
<label className="label text-[11px] text-base-content/60">{transI18n("onlySupportFreeSRJsonFile")}</label>
|
||||
</fieldset>
|
||||
<div className="flex flex-wrap gap-4 items-center">
|
||||
<fieldset className="fieldset">
|
||||
<legend className="fieldset-legend text-xs font-semibold">{transI18n("pickAFile")}</legend>
|
||||
<input type="file" accept=".json" className="file-input file-input-sm file-input-bordered rounded-xl" onChange={handlerReadFile} />
|
||||
<label className="label text-[11px] text-base-content/60">{transI18n("onlySupportFreeSRJsonFile")}</label>
|
||||
</fieldset>
|
||||
|
||||
{selectedCharacters.length > 0 && (
|
||||
<button onClick={handleImport} className="btn btn-sm btn-primary rounded-xl font-bold shadow-xs">
|
||||
{transI18n("import")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{Error && (
|
||||
<div className="text-error text-sm font-semibold mt-2">😭 {Error}</div>
|
||||
{selectedCharacters.length > 0 && (
|
||||
<button onClick={handleImport} className="btn btn-sm btn-primary rounded-xl font-bold shadow-xs">
|
||||
{transI18n("import")}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Selection Info */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 bg-base-200 border border-base-300 p-3.5 rounded-2xl shadow-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs sm:text-sm font-bold text-base-content">
|
||||
{transI18n("selectedCharacters")}:
|
||||
</span>
|
||||
<span className="badge badge-primary font-mono font-bold text-xs">
|
||||
{selectedCharacters.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={clearSelection}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-error hover:bg-error/10 border border-error/30 rounded-xl"
|
||||
>
|
||||
{transI18n("clearAll")}
|
||||
</button>
|
||||
<button
|
||||
onClick={selectAll}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-primary hover:bg-primary/10 border border-primary/30 rounded-xl"
|
||||
>
|
||||
{transI18n("selectAll")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
|
||||
</div>
|
||||
{Error && (
|
||||
<div className="text-error text-sm font-semibold mt-2">😭 {Error}</div>
|
||||
)}
|
||||
{/* Character Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{validFreeSRData && Object.values(validFreeSRData.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => {
|
||||
return (
|
||||
<CharacterInfoCard
|
||||
key={character.avatar_id}
|
||||
character={toCharacterInfoCard(validFreeSRData, character)}
|
||||
selectedCharacters={selectedCharacters}
|
||||
onCharacterToggle={handleCharacterToggle}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Selection Info */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 bg-base-200 border border-base-300 p-3.5 rounded-2xl shadow-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs sm:text-sm font-bold text-base-content">
|
||||
{transI18n("selectedCharacters")}:
|
||||
</span>
|
||||
<span className="badge badge-primary font-mono font-bold text-xs">
|
||||
{selectedCharacters.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={clearSelection}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-error hover:bg-error/10 border border-error/30 rounded-xl"
|
||||
>
|
||||
{transI18n("clearAll")}
|
||||
</button>
|
||||
<button
|
||||
onClick={selectAll}
|
||||
className="btn btn-xs sm:btn-sm btn-ghost text-primary hover:bg-primary/10 border border-primary/30 rounded-xl"
|
||||
>
|
||||
{transI18n("selectAll")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white"></div>
|
||||
</div>
|
||||
)}
|
||||
{/* Character Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4 sm:gap-6">
|
||||
{validFreeSRData && Object.values(validFreeSRData.avatars).filter(it => mapAvatar?.[it.avatar_id]).map((character) => {
|
||||
return (
|
||||
<CharacterInfoCard
|
||||
key={character.avatar_id}
|
||||
character={toCharacterInfoCard(validFreeSRData, character)}
|
||||
selectedCharacters={selectedCharacters}
|
||||
onCharacterToggle={handleCharacterToggle}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user