Fixed pagination problem: The numbering of aliases were incorrect
126
webpage/bug-report.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Bug Report File
|
||||
|
||||
|
||||
## 2025/12/13 00:33
|
||||
|
||||
Trying to delete an alias from a image, but got error "500 Internal Server Error".
|
||||
|
||||
Using uptrace and here's the log message:
|
||||
```
|
||||
{
|
||||
"id": 0,
|
||||
"groupId": "9814518078643611785",
|
||||
"system": "log:error",
|
||||
"name": "exception",
|
||||
"displayName": "github.com/uptrace/bun/driver/pgdriver.Error: ERROR: syntax error at or near \")\" (SQLSTATE=42601)",
|
||||
"time": "2025-12-12T16:31:55.153Z",
|
||||
"attrs": {
|
||||
"service_name": "go2025-backend",
|
||||
"service_version": "v0.0.1",
|
||||
"telemetry_sdk_language": "go",
|
||||
"telemetry_sdk_name": "opentelemetry",
|
||||
"exception_param_SQLSTATE": "42601",
|
||||
"exception_type": "github.com/uptrace/bun/driver/pgdriver.Error",
|
||||
"otel_library_name": "github.com/uptrace/bun",
|
||||
"telemetry_sdk_version": "1.38.0",
|
||||
"exception_param_log_severity": "ERROR",
|
||||
"host_name": "7d8c05cf36bb",
|
||||
"log_severity": "ERROR"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 2025/12/12 23:41
|
||||
|
||||
getting 401 when trying to access backend API `GET /api/aliases` right after start up of docker compose, but after 1 ~ 2 minutes, it's ok.
|
||||
|
||||
Log message:
|
||||
```
|
||||
backend-1 | {"level":"info","ts":1765553837.4421663,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/gen-login-url"}
|
||||
backend-1 | {"level":"info","ts":1765553839.3232543,"caller":"middlewares/accessLog.go:13","msg":"OPTIONS /*any"}
|
||||
backend-1 | {"level":"info","ts":1765553839.323296,"caller":"middlewares/accessLog.go:13","msg":"OPTIONS /*any"}
|
||||
backend-1 | {"level":"info","ts":1765553839.3233085,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"warn","ts":1765553839.3233905,"caller":"middlewares/errorHandler.go:78","msg":"user did not login","error":"http: named cookie not present"}
|
||||
backend-1 | {"level":"info","ts":1765553839.324581,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"warn","ts":1765553839.3246245,"caller":"middlewares/errorHandler.go:78","msg":"user did not login","error":"http: named cookie not present"}
|
||||
backend-1 | {"level":"info","ts":1765553839.3252084,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/login"}
|
||||
backend-1 | {"level":"info","ts":1765553839.325493,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/login"}
|
||||
backend-1 | {"level":"info","ts":1765553839.3793862,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"info","ts":1765553839.3855033,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"info","ts":1765553872.7971,"caller":"middlewares/accessLog.go:13","msg":"POST /auth/login"}
|
||||
backend-1 | {"level":"info","ts":1765553872.842072,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"info","ts":1765553905.793059,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"info","ts":1765553905.7976384,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"info","ts":1765553905.7985706,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
backend-1 | {"level":"info","ts":1765553905.7997246,"caller":"middlewares/accessLog.go:13","msg":"GET /api/aliases"}
|
||||
```
|
||||
|
||||
I suspect it's the race condition happening at App.tsx (conflicts between "login" and "fetch aliases"). Please investigate the possible issue.
|
||||
|
||||
<!-- check if all other useEffect() are conflicting with the initial login process (getting cookie from backend) -->
|
||||
|
||||
|
||||
## 2025/12/12 23:14
|
||||
|
||||
|
||||
When I tried to delete an alias from a image, I got err "500 Internal Server Error". Log message:
|
||||
```
|
||||
:8080/api/image/2/aliases:1
|
||||
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
|
||||
api.ts:114
|
||||
PUT http://localhost:8080/api/image/2/aliases 500 (Internal Server Error)
|
||||
updateImageAliases @ api.ts:114
|
||||
handleSaveImage @ App.tsx:101
|
||||
handleSave @ ImageModal.tsx:48
|
||||
```
|
||||
|
||||
When an image is deleted, the alias entries at side barand buttons "previous page" and "next page" at top & bottom of main region of webpage are not updated synchronously.
|
||||
|
||||
|
||||
|
||||
|
||||
## 2025/12/12 22:54
|
||||
|
||||
The aliases handling mechanism seems to have some bugs:
|
||||
I test with single image huh.png, added 16 testing aliases: ["huh", "what", "huh2", "huh3", 4, 5, 6, 7, 8, 9, 0, 1, 2, 3].
|
||||
- The side bar's numbering at page 1 isn't correct, it should be `#1, #2, #3, #4, #5`, but it's `#1, #3, #5, #6, #7`. Page 2, 3 are correct.
|
||||
- There are more than 5 alias rows at the main region of webpage, and the alias string at each row is wrong.
|
||||
- Page 1: `4, huh2, huh3, Alias #8, Alias #9, Alias #10, Alias #11, Alias #12, Alias #13, Alias #14, Alias #15, Alias #16`
|
||||
- Page 2: `5, 6, 7, 8, 9, Alias #5, Alias #6, Alias #7, Alias #13, Alias #14, Alias #15, Alias #16`
|
||||
- Page 3: `0, 1, 2, 3, Alias #5, Alias #6, Alias #7, Alias #8, Alias #9, Alias #10, Alias #11, Alias #12`
|
||||
It seems I didn't modify the pagination mechanism correctly (originally you implemented 5 aliases per page, but I want it to be 10 aliases per page).
|
||||
|
||||
|
||||
|
||||
|
||||
## 2025/12/12 22:01
|
||||
|
||||
It seems there is a permission issue when backend tried to run `make swagger`.
|
||||
|
||||
```
|
||||
make 280ms Fri Dec 12 22:00:30 2025
|
||||
go run github.com/swaggo/swag/cmd/swag@v1.16.4 fmt
|
||||
go run github.com/swaggo/swag/cmd/swag@v1.16.4 init -o docs -g cmds/serve.go -pdl 1
|
||||
2025/12/12 22:00:34 Generate swagger docs....
|
||||
2025/12/12 22:00:34 Generate general API Info, search dir:./
|
||||
2025/12/12 22:00:34 pkg /Users/polarbear03617/Documents/交大/大三上/Go程式設計/backend/cmds cannot find all dependencies, <nil>
|
||||
go: writing stat cache: open /Users/polarbear03617/go/pkg/mod/cache/download/github.com/go-resty/resty/v2/@v/v2.17.0.info640656495.tmp: permission denied
|
||||
exit status 1
|
||||
make: *** [swagger] Error 1
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## 2025/12/11 12:42
|
||||
|
||||
I correctly access the backend API with the cookie provided by backend, but why do I still see this error:
|
||||
Failed to load resource: the server responded with a status of 401 (Unauthorized) (Login and seeing webpage is ok, but can't fetch aliases)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,41 @@
|
||||
# Webpage for Memebot
|
||||
|
||||
|
||||
|
||||
## Modify the uploadmodal's and imagemodal's alias adding/removing mechanism
|
||||
|
||||
Modify the uploadmodal's and imagemodal's alias adding/removing mechanism to the following:
|
||||
[ (press Enter or click ADD to add alias) ] [ADD]
|
||||
|
||||
After typing alias and click ADD
|
||||
[ test_alias ] [REMOVE]
|
||||
[ (press Enter or click ADD to add alias) ] [ADD]
|
||||
|
||||
The [ADD] and [REMOVE] are buttons, and should both have the same width. Also, the protection mechanism (zero alias = not allow to save modifications / upload images) should be retained.
|
||||
|
||||
|
||||
|
||||
|
||||
## Modify the webpage
|
||||
|
||||
Modify the webpage: We don't want one page shows only an alias.
|
||||
We want a paging feature on aliases, so that each page shows a fixed number of aliases and their corresponding images (the fixed number can be stored in a variable in api.ts). And the "next page" and "previous page" buttons should be located at both the top (below search bar) and the bottom of the page.
|
||||
The sidebar is also modified to show the current page number, the aliases on the current page, and the total number of pages.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Initial Setup
|
||||
|
||||
This webpage is used to manage the images and aliases used by our Discord memebot.
|
||||
|
||||
Relationship between images and aliases: many to many.
|
||||
|
||||
|
||||
|
||||
## Layout
|
||||
### Layout
|
||||
|
||||
React + TypeScript (with Tailwind CSS)
|
||||
|
||||
@@ -33,7 +62,7 @@ The top is the Navbar:
|
||||
- The right side of the Navbar contains function buttons, currently only Upload Image and Login.
|
||||
|
||||
|
||||
### Find
|
||||
#### Find
|
||||
|
||||
Find (Viewing and Searching)
|
||||
- The left sidebar serves as a table of content for all aliases, similar to the navigation feature in HackMD or mdBook.
|
||||
@@ -66,7 +95,7 @@ Find (Viewing and Searching)
|
||||
|
||||
|
||||
|
||||
### Upload Image
|
||||
#### Upload Image
|
||||
|
||||
When the user clicks this button, a floating window (modal) appears:
|
||||
|
||||
@@ -89,12 +118,9 @@ When the user clicks this button, a floating window (modal) appears:
|
||||
======================================
|
||||
```
|
||||
|
||||
## Login (Authentication)
|
||||
#### Login (Authentication)
|
||||
|
||||
Authentication is used to verify the current Discord user operating the frontend. Users who are not logged in can only perform viewing-related operations; they cannot edit, upload, or delete.
|
||||
|
||||
|
||||
<!-- Modify the webpage: We don't want one page shows only an alias.
|
||||
We want a paging feature on aliases, so that each page shows a fixed number of aliases and their corresponding images (the fixed number can be stored in a variable in api.ts). And the "next page" and "previous page" buttons should be located at both the top (below search bar) and the bottom of the page.
|
||||
The sidebar is also modified to show the current page number, the aliases on the current page, and the total number of pages. -->
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { Image, Alias } from './types';
|
||||
|
||||
function App() {
|
||||
const [isLoginMode, setIsLoginMode] = useState(false);
|
||||
const [isInitializing, setIsInitializing] = useState(true);
|
||||
const [images, setImages] = useState<Image[]>([]);
|
||||
const [allAliases, setAllAliases] = useState<Alias[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -20,8 +21,6 @@ function App() {
|
||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
console.log("Hello");
|
||||
|
||||
// Check if URL has a login token
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -36,22 +35,26 @@ function App() {
|
||||
window.history.replaceState({}, document.title, '/');
|
||||
// Exit login mode and load data
|
||||
setIsLoginMode(false);
|
||||
setIsInitializing(false);
|
||||
loadData();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Login failed:", error);
|
||||
setIsLoginMode(true); // Show login page on error
|
||||
setIsLoginMode(true);
|
||||
setIsInitializing(false);
|
||||
});
|
||||
} else {
|
||||
setIsInitializing(false);
|
||||
loadData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoginMode) {
|
||||
// Don't load data during initial mount when login is in progress
|
||||
if (!isLoginMode && !isInitializing) {
|
||||
loadData();
|
||||
}
|
||||
}, [searchQuery, isLoginMode]);
|
||||
}, [searchQuery, isLoginMode, isInitializing]);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset to page 1 when search query changes
|
||||
@@ -105,6 +108,8 @@ function App() {
|
||||
const handleDeleteImage = async (imageId: number) => {
|
||||
await api.deleteImage(imageId);
|
||||
await loadData();
|
||||
// After deletion, check if current page is still valid
|
||||
// This will be handled by the useEffect below
|
||||
};
|
||||
|
||||
// Show login page if in login mode
|
||||
@@ -121,6 +126,14 @@ function App() {
|
||||
|
||||
// Pagination calculations
|
||||
const totalPages = Math.max(1, Math.ceil(filteredAliases.length / ALIASES_PER_PAGE));
|
||||
|
||||
// Adjust current page if it's now out of bounds
|
||||
useEffect(() => {
|
||||
if (currentPage > totalPages) {
|
||||
setCurrentPage(Math.max(1, totalPages));
|
||||
}
|
||||
}, [totalPages, currentPage]);
|
||||
|
||||
const startIndex = (currentPage - 1) * ALIASES_PER_PAGE;
|
||||
const endIndex = startIndex + ALIASES_PER_PAGE;
|
||||
const currentPageAliases = filteredAliases.slice(startIndex, endIndex);
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Image, Alias } from './types';
|
||||
const API_BASE_URL = 'http://localhost:8080';
|
||||
|
||||
// Pagination configuration
|
||||
export const ALIASES_PER_PAGE = 5; // Number of aliases to show per page
|
||||
export const ALIASES_PER_PAGE = 10; // Number of aliases to show per page
|
||||
|
||||
class ApiService {
|
||||
// Authentication
|
||||
@@ -111,6 +111,8 @@ class ApiService {
|
||||
// PUT /api/image/{id}/aliases
|
||||
// Body: {aliases: string[]}
|
||||
async updateImageAliases(id: number, aliasNames: string[]): Promise<void> {
|
||||
console.log("Update aliases of image ", id);
|
||||
console.log("Alias names: ", aliasNames);
|
||||
const response = await fetch(`${API_BASE_URL}/api/image/${id}/aliases`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 MiB |
@@ -1,137 +0,0 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"id": "1",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-20T12:00:00Z",
|
||||
"aliases": ["daisuke"],
|
||||
"url": "/api/images/1/file"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-20T12:05:00Z",
|
||||
"aliases": ["huh"],
|
||||
"url": "/api/images/2/file"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-21T09:00:00Z",
|
||||
"aliases": ["killme"],
|
||||
"url": "/api/images/3/file"
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-21T09:30:00Z",
|
||||
"aliases": ["nofriend"],
|
||||
"url": "/api/images/4/file"
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-21T10:00:00Z",
|
||||
"aliases": ["orz"],
|
||||
"url": "/api/images/5/file"
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-22T11:20:00Z",
|
||||
"aliases": ["ramen"],
|
||||
"url": "/api/images/6/file"
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-22T14:00:00Z",
|
||||
"aliases": ["rrrr"],
|
||||
"url": "/api/images/7/file"
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-23T08:00:00Z",
|
||||
"aliases": ["sleep"],
|
||||
"url": "/api/images/8/file"
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-23T08:05:00Z",
|
||||
"aliases": ["sleep"],
|
||||
"url": "/api/images/9/file"
|
||||
},
|
||||
{
|
||||
"id": "10",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-24T15:00:00Z",
|
||||
"aliases": ["你要出多少"],
|
||||
"url": "/api/images/10/file"
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-24T16:00:00Z",
|
||||
"aliases": ["好ㄘ"],
|
||||
"url": "/api/images/11/file"
|
||||
},
|
||||
{
|
||||
"id": "12",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-25T10:00:00Z",
|
||||
"aliases": ["宅斃了"],
|
||||
"url": "/api/images/12/file"
|
||||
},
|
||||
{
|
||||
"id": "13",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-25T10:10:00Z",
|
||||
"aliases": ["宅斃了"],
|
||||
"url": "/api/images/13/file"
|
||||
},
|
||||
{
|
||||
"id": "14",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-25T10:20:00Z",
|
||||
"aliases": ["宅斃了"],
|
||||
"url": "/api/images/14/file"
|
||||
},
|
||||
{
|
||||
"id": "15",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-26T12:00:00Z",
|
||||
"aliases": ["幹波大的"],
|
||||
"url": "/api/images/15/file"
|
||||
},
|
||||
{
|
||||
"id": "16",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-26T13:00:00Z",
|
||||
"aliases": ["我什麼都沒有"],
|
||||
"url": "/api/images/16/file"
|
||||
},
|
||||
{
|
||||
"id": "17",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-27T09:00:00Z",
|
||||
"aliases": ["欸嘿"],
|
||||
"url": "/api/images/17/file"
|
||||
},
|
||||
{
|
||||
"id": "18",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-25T10:20:00Z",
|
||||
"aliases": ["宅斃了"],
|
||||
"url": "/api/images/18/file"
|
||||
},
|
||||
{
|
||||
"id": "19",
|
||||
"uploaded_user_id": "konchin.shih",
|
||||
"uploaded_at": "2023-10-25T10:20:00Z",
|
||||
"aliases": ["宅斃了"],
|
||||
"url": "/api/images/19/file"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 448 KiB |
|
Before Width: | Height: | Size: 450 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 3.6 MiB |
|
Before Width: | Height: | Size: 423 KiB |
|
Before Width: | Height: | Size: 758 KiB |
|
Before Width: | Height: | Size: 408 KiB |
|
Before Width: | Height: | Size: 758 KiB |
|
Before Width: | Height: | Size: 758 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
@@ -8,19 +8,23 @@ interface ImageGridProps {
|
||||
}
|
||||
|
||||
export default function ImageGrid({ images, aliases, onImageClick }: ImageGridProps) {
|
||||
// Create a map of alias ID to alias name
|
||||
// Create a map of alias ID to alias name for current page aliases only
|
||||
const aliasMap = new Map(aliases.map(a => [a.id, a.name]));
|
||||
const currentPageAliasIds = new Set(aliases.map(a => a.id));
|
||||
|
||||
// Group images by alias
|
||||
// Group images by alias, but only for aliases on the current page
|
||||
const groupedImages = images.reduce((acc, image) => {
|
||||
if (image.aliasesIds.length === 0) {
|
||||
if (!acc['__no_alias__']) acc['__no_alias__'] = [];
|
||||
acc['__no_alias__'].push(image);
|
||||
} else {
|
||||
// Only process aliases that are on the current page
|
||||
image.aliasesIds.forEach((aliasId) => {
|
||||
const aliasName = aliasMap.get(aliasId) || `Alias #${aliasId}`;
|
||||
if (!acc[aliasName]) acc[aliasName] = [];
|
||||
acc[aliasName].push(image);
|
||||
if (currentPageAliasIds.has(aliasId)) {
|
||||
const aliasName = aliasMap.get(aliasId) || `Alias #${aliasId}`;
|
||||
if (!acc[aliasName]) acc[aliasName] = [];
|
||||
acc[aliasName].push(image);
|
||||
}
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
|
||||
@@ -105,64 +105,36 @@ export default function ImageModal({
|
||||
<div className="space-y-2">
|
||||
{aliases.map((alias) => (
|
||||
<div key={alias} className="flex items-center gap-2">
|
||||
{(
|
||||
<button
|
||||
onClick={() => handleRemoveAlias(alias)}
|
||||
className="text-red-600 hover:text-red-700"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<input
|
||||
type="text"
|
||||
value={alias}
|
||||
readOnly
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg bg-gray-50"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemoveAlias(alias)}
|
||||
className="w-20 px-3 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors font-medium"
|
||||
>
|
||||
REMOVE
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{(
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleAddAlias}
|
||||
className="text-green-600 hover:text-green-700"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Add a new alias"
|
||||
value={newAlias}
|
||||
onChange={(e) => setNewAlias(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="press Enter or click ADD to add alias"
|
||||
value={newAlias}
|
||||
onChange={(e) => setNewAlias(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<button
|
||||
onClick={handleAddAlias}
|
||||
className="w-20 px-3 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
|
||||
>
|
||||
ADD
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,7 +160,7 @@ export default function ImageModal({
|
||||
{(
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
disabled={isSaving || aliases.length === 0}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save'}
|
||||
|
||||
@@ -24,10 +24,10 @@ export default function Sidebar({
|
||||
</div>
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{aliases.map((alias) => (
|
||||
{aliases.map((alias, index) => (
|
||||
<li key={alias.id}>
|
||||
<div className="w-full text-left px-3 py-2 rounded-lg bg-blue-50 text-blue-700">
|
||||
<span className="text-xs text-blue-500 mr-2">#{alias.id}</span>
|
||||
<span className="text-xs text-blue-500 mr-2">#{index + 1}</span>
|
||||
{alias.name}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -171,61 +171,35 @@ export default function UploadModal({ onClose, onUpload }: UploadModalProps) {
|
||||
<div className="space-y-2">
|
||||
{aliases.map((alias) => (
|
||||
<div key={alias} className="flex items-center gap-2">
|
||||
{/* Red "X" Button: Remove this alias from the list */}
|
||||
<button
|
||||
onClick={() => handleRemoveAlias(alias)}
|
||||
className="text-red-600 hover:text-red-700"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
value={alias}
|
||||
readOnly
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg bg-gray-50"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemoveAlias(alias)}
|
||||
className="w-20 px-3 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors font-medium"
|
||||
>
|
||||
REMOVE
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Green "+" Button: Add new alias to the list */}
|
||||
<button
|
||||
onClick={handleAddAlias}
|
||||
className="text-green-600 hover:text-green-700"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Add a new alias"
|
||||
placeholder="press Enter or click ADD to add alias"
|
||||
value={newAlias}
|
||||
onChange={(e) => setNewAlias(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<button
|
||||
onClick={handleAddAlias}
|
||||
className="w-20 px-3 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
|
||||
>
|
||||
ADD
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||