-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #269 from Offline-Project/241-dca-5-create-the-con…
…tent-upload-and-editing-interface 241 dca 5 create the content upload and editing interface
- Loading branch information
Showing
55 changed files
with
1,590 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 15 additions & 1 deletion
16
libs/features/back-office/content-spaces-types/src/lib/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,21 @@ | ||
import type { GetContentSpacesFromOrganizerIdTableQuery } from '@gql/admin/types'; | ||
import { FileSummary } from '@bytescale/sdk'; | ||
import type { | ||
GetContentSpacesFromOrganizerIdTableQuery, | ||
GetContentSpaceWithPassesOrganizerQuery, | ||
} from '@gql/admin/types'; | ||
|
||
export type ContentSpaceFromOrganizerTable = NonNullable< | ||
NonNullable< | ||
GetContentSpacesFromOrganizerIdTableQuery['organizer'] | ||
>['contentSpaces'] | ||
>[number]; | ||
|
||
export type ContentSpaceFromOrganizerWithPasses = NonNullable< | ||
GetContentSpaceWithPassesOrganizerQuery['contentSpace'] | ||
>; | ||
|
||
export type EventPass = ContentSpaceFromOrganizerWithPasses['eventPasses'][0]; | ||
|
||
export interface ContentSpaceFileWithName extends FileSummary { | ||
fileName: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export { ContentSpaceSheet } from './lib/pages/ContentSpaceSheet/ContentSpaceSheet'; | ||
export { | ||
ContentSpaceTableSkeleton, | ||
ContentSpacesPage, | ||
} from './lib/pages/ContentSpacesPage'; | ||
} from './lib/pages/ContentSpacesPage/ContentSpacesPage'; |
57 changes: 57 additions & 0 deletions
57
libs/features/back-office/content-spaces/src/lib/actions/deleteContentSpaceFile.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { FileWrapper } from '@file-upload/admin'; | ||
import { revalidateTag } from 'next/cache'; | ||
import { | ||
deleteContentSpaceFile, | ||
DeleteContentSpaceFileProps, | ||
} from './deleteContentSpaceFile'; | ||
|
||
jest.mock('@file-upload/admin'); | ||
jest.mock('next/cache'); | ||
|
||
describe('deleteContentSpaceFile', () => { | ||
const mockDeleteFile = jest.fn(); | ||
const mockRevalidateTag = jest.fn(); | ||
beforeAll(() => { | ||
// Mock the FileWrapper instance method | ||
FileWrapper.prototype.deleteFile = mockDeleteFile; | ||
|
||
// Mock the revalidateTag function | ||
(revalidateTag as jest.Mock) = mockRevalidateTag; | ||
}); | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
it('should call deleteFile and revalidateTag with correct arguments', async () => { | ||
const props: DeleteContentSpaceFileProps = { | ||
organizerId: 'testOrganizerId', | ||
contentSpaceId: 'testContentSpaceId', | ||
filePath: | ||
'/local/organizers/testOrganizerId/content-spaces/testContentSpaceId/testFile', | ||
}; | ||
|
||
await deleteContentSpaceFile(props); | ||
|
||
expect(mockDeleteFile).toHaveBeenCalledWith({ | ||
accountId: expect.any(String), // UPLOAD_ACCOUNT_ID | ||
filePath: | ||
'/local/organizers/testOrganizerId/content-spaces/testContentSpaceId/testFile', | ||
}); | ||
|
||
expect(mockRevalidateTag).toHaveBeenCalledWith( | ||
`${props.organizerId}-${props.contentSpaceId}-getContentSpaceFiles`, | ||
); | ||
}); | ||
|
||
it('should handle error from deleteFile', async () => { | ||
const props: DeleteContentSpaceFileProps = { | ||
organizerId: 'testOrganizerId', | ||
contentSpaceId: 'testContentSpaceId', | ||
filePath: | ||
'/local/organizers/testOrganizerId/content-spaces/testContentSpaceId/testFile', | ||
}; | ||
|
||
mockDeleteFile.mockRejectedValueOnce(new Error('Test error')); | ||
|
||
await expect(deleteContentSpaceFile(props)).rejects.toThrow('Test error'); | ||
}); | ||
}); |
20 changes: 20 additions & 0 deletions
20
libs/features/back-office/content-spaces/src/lib/actions/deleteContentSpaceFile.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
'use server'; | ||
|
||
import env from '@env/server'; | ||
import { GetContentSpaceFolderPath } from '@features/content-space-common'; | ||
import { FileWrapper } from '@file-upload/admin'; | ||
import { revalidateTag } from 'next/cache'; | ||
|
||
export type DeleteContentSpaceFileProps = GetContentSpaceFolderPath & { | ||
filePath: string; | ||
}; | ||
|
||
export async function deleteContentSpaceFile({ | ||
organizerId, | ||
contentSpaceId, | ||
filePath, | ||
}: DeleteContentSpaceFileProps) { | ||
const fileApi = new FileWrapper(); | ||
await fileApi.deleteFile({ accountId: env.UPLOAD_ACCOUNT_ID, filePath }); | ||
revalidateTag(`${organizerId}-${contentSpaceId}-getContentSpaceFiles`); | ||
} |
104 changes: 104 additions & 0 deletions
104
libs/features/back-office/content-spaces/src/lib/actions/deleteContentSpaceFiles.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import env from '@env/server'; | ||
import { FileWrapper } from '@file-upload/admin'; | ||
import { revalidateTag } from 'next/cache'; | ||
import { | ||
deleteContentSpaceFiles, | ||
DeleteContentSpaceFilesProps, | ||
} from './deleteContentSpaceFiles'; | ||
|
||
jest.mock('@file-upload/admin'); | ||
jest.mock('next/cache'); | ||
|
||
describe('deleteContentSpaceFiles', () => { | ||
const mockDeleteFilesBatchWithRetry = jest.fn(); | ||
const mockRevalidateTag = jest.fn(); | ||
beforeAll(() => { | ||
// Mock the FileWrapper instance method | ||
FileWrapper.prototype.deleteFilesBatchWithRetry = | ||
mockDeleteFilesBatchWithRetry; | ||
|
||
// Mock the revalidateTag function | ||
(revalidateTag as jest.Mock) = mockRevalidateTag; | ||
}); | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
it('should call deleteFilesBatchWithRetry and revalidateTag with correct arguments', async () => { | ||
const props: DeleteContentSpaceFilesProps = { | ||
organizerId: 'testOrganizerId', | ||
contentSpaceId: 'testContentSpaceId', | ||
filesSelected: { file1: true, file2: false }, | ||
}; | ||
|
||
await deleteContentSpaceFiles(props); | ||
|
||
expect(mockDeleteFilesBatchWithRetry).toHaveBeenCalledWith( | ||
expect.any(String), // UPLOAD_ACCOUNT_ID | ||
[ | ||
`/${env.UPLOAD_PATH_PREFIX}/organizers/testOrganizerId/content-spaces/testContentSpaceId/file1`, | ||
], // filesToDelete | ||
); | ||
|
||
expect(mockRevalidateTag).toHaveBeenCalledWith( | ||
`${props.organizerId}-${props.contentSpaceId}-getContentSpaceFiles`, | ||
); | ||
}); | ||
it('should call deleteFilesBatchWithRetry with all files when all are selected', async () => { | ||
const props: DeleteContentSpaceFilesProps = { | ||
organizerId: 'testOrganizerId', | ||
contentSpaceId: 'testContentSpaceId', | ||
filesSelected: { file1: true, file2: true }, | ||
}; | ||
|
||
await deleteContentSpaceFiles(props); | ||
|
||
expect(mockDeleteFilesBatchWithRetry).toHaveBeenCalledWith( | ||
expect.any(String), | ||
[ | ||
`/${env.UPLOAD_PATH_PREFIX}/organizers/testOrganizerId/content-spaces/testContentSpaceId/file1`, | ||
`/${env.UPLOAD_PATH_PREFIX}/organizers/testOrganizerId/content-spaces/testContentSpaceId/file2`, | ||
], | ||
); | ||
}); | ||
it('should throw an error when no files are selected', async () => { | ||
const props: DeleteContentSpaceFilesProps = { | ||
organizerId: 'testOrganizerId', | ||
contentSpaceId: 'testContentSpaceId', | ||
filesSelected: {}, | ||
}; | ||
|
||
await expect(deleteContentSpaceFiles(props)).rejects.toThrow( | ||
'No files to delete selected', | ||
); | ||
expect(mockDeleteFilesBatchWithRetry).not.toHaveBeenCalled(); | ||
expect(mockRevalidateTag).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should not call deleteFilesBatchWithRetry when filesSelected only contains false', async () => { | ||
const props: DeleteContentSpaceFilesProps = { | ||
organizerId: 'testOrganizerId', | ||
contentSpaceId: 'testContentSpaceId', | ||
filesSelected: { file1: false, file2: false }, | ||
}; | ||
|
||
await expect(deleteContentSpaceFiles(props)).rejects.toThrow( | ||
'No files to delete selected', | ||
); | ||
expect(mockDeleteFilesBatchWithRetry).not.toHaveBeenCalled(); | ||
expect(mockRevalidateTag).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should handle error from deleteFilesBatchWithRetry', async () => { | ||
const props: DeleteContentSpaceFilesProps = { | ||
organizerId: 'testOrganizerId', | ||
contentSpaceId: 'testContentSpaceId', | ||
filesSelected: { file1: true }, | ||
}; | ||
|
||
mockDeleteFilesBatchWithRetry.mockRejectedValueOnce( | ||
new Error('Test error'), | ||
); | ||
|
||
await expect(deleteContentSpaceFiles(props)).rejects.toThrow('Test error'); | ||
}); | ||
}); |
32 changes: 32 additions & 0 deletions
32
libs/features/back-office/content-spaces/src/lib/actions/deleteContentSpaceFiles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
'use server'; | ||
|
||
import env from '@env/server'; | ||
import { | ||
getContentSpaceFolderPath, | ||
type GetContentSpaceFolderPath, | ||
} from '@features/content-space-common'; | ||
import { FileWrapper } from '@file-upload/admin'; | ||
import { RowSelectionState } from '@tanstack/react-table'; | ||
import { revalidateTag } from 'next/cache'; | ||
|
||
export type DeleteContentSpaceFilesProps = GetContentSpaceFolderPath & { | ||
filesSelected: RowSelectionState; | ||
}; | ||
|
||
export async function deleteContentSpaceFiles({ | ||
organizerId, | ||
contentSpaceId, | ||
filesSelected, | ||
}: DeleteContentSpaceFilesProps) { | ||
const folderPath = getContentSpaceFolderPath({ | ||
organizerId, | ||
contentSpaceId, | ||
}); | ||
const fileApi = new FileWrapper(); | ||
const filesToDelete = Object.entries(filesSelected) | ||
.filter(([_fileName, selected]) => selected) | ||
.map(([fileName, _selected]) => `${folderPath}/${fileName}`); | ||
if (!filesToDelete.length) throw new Error('No files to delete selected'); | ||
await fileApi.deleteFilesBatchWithRetry(env.UPLOAD_ACCOUNT_ID, filesToDelete); | ||
revalidateTag(`${organizerId}-${contentSpaceId}-getContentSpaceFiles`); | ||
} |
25 changes: 25 additions & 0 deletions
25
libs/features/back-office/content-spaces/src/lib/actions/getContentSpaceFiles.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { FileSummary } from '@bytescale/sdk'; | ||
import env from '@env/server'; | ||
import { | ||
GetContentSpaceFolderPath, | ||
getContentSpaceFolderPath, | ||
} from '@features/content-space-common'; | ||
import { FolderWrapper } from '@file-upload/admin'; | ||
import { cacheWithDynamicKeys } from '@next/cache'; | ||
|
||
export type GetContentSpaceFilesProps = GetContentSpaceFolderPath; | ||
|
||
export const getContentSpaceFiles = cacheWithDynamicKeys( | ||
async (props: GetContentSpaceFilesProps) => { | ||
const folder = new FolderWrapper(); | ||
const folderPath = getContentSpaceFolderPath(props); | ||
const list = await folder.listFolder({ | ||
accountId: env.UPLOAD_ACCOUNT_ID, | ||
folderPath, | ||
}); | ||
return list.items.filter((item): item is FileSummary => 'filePath' in item); | ||
}, | ||
(props: [GetContentSpaceFilesProps]) => [ | ||
`${props[0].organizerId}-${props[0].contentSpaceId}-getContentSpaceFiles`, | ||
], | ||
); |
File renamed without changes.
60 changes: 60 additions & 0 deletions
60
...nt-spaces/src/lib/organisms/ContentSpaceEventPassesTable/ContentSpaceEventPassesTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { ContentSpaceFromOrganizerWithPasses } from '@features/back-office/content-spaces-types'; | ||
import { | ||
Table, | ||
TableBody, | ||
TableCell, | ||
TableHead, | ||
TableHeader, | ||
TableRow, | ||
TableSkeleton, | ||
} from '@ui/components'; | ||
import { useTranslations } from 'next-intl'; | ||
|
||
export interface ContentSpaceEventPassesTableProps | ||
extends Pick<ContentSpaceFromOrganizerWithPasses, 'eventPasses'> {} | ||
|
||
export function ContentSpaceEventPassesTable({ | ||
eventPasses, | ||
}: ContentSpaceEventPassesTableProps) { | ||
const eventPassesByEvent = eventPasses.reduce( | ||
(acc, eventPass) => { | ||
const event = eventPass.event; | ||
if (!event) return acc; | ||
if (!acc[event.slug]) acc[event.slug] = []; | ||
acc[event.slug].push(eventPass); | ||
return acc; | ||
}, | ||
{} as Record<string, ContentSpaceFromOrganizerWithPasses['eventPasses']>, | ||
); | ||
|
||
const t = useTranslations( | ||
'OrganizerContentSpaces.Sheet.ContentSpaceEventPassesTable', | ||
); | ||
|
||
return ( | ||
<> | ||
{Object.entries(eventPassesByEvent).map(([slug, eventPasses]) => ( | ||
<Table key={slug}> | ||
<TableHeader> | ||
<TableRow> | ||
<TableHead className="w-[100px]"> | ||
{t('event-pass-for-event', { slug })} | ||
</TableHead> | ||
</TableRow> | ||
</TableHeader> | ||
<TableBody> | ||
{eventPasses.map((eventPass) => ( | ||
<TableRow key={eventPass.id}> | ||
<TableCell>{eventPass.name}</TableCell> | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
))} | ||
</> | ||
); | ||
} | ||
|
||
export function ContentSpaceEventPassesTableSkeleton() { | ||
return <TableSkeleton rows={4} cols={2} />; | ||
} |
34 changes: 34 additions & 0 deletions
34
...es/back-office/content-spaces/src/lib/organisms/ContentSpaceEventPassesTable/examples.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { EventPass } from '@features/back-office/content-spaces-types'; | ||
|
||
export const eventPassWorldCupFinale2023VIP: EventPass = { | ||
id: '1', | ||
name: 'VIP Pass', | ||
event: { | ||
slug: 'world-cup-finale-2023', | ||
title: 'World Cup Finale 2023', | ||
}, | ||
}; | ||
|
||
export const eventPassWorldCupFinale2023Premium: EventPass = { | ||
id: '2', | ||
name: 'Premium Pass', | ||
event: { | ||
slug: 'world-cup-finale-2023', | ||
title: 'World Cup Finale 2023', | ||
}, | ||
}; | ||
|
||
export const eventPassWorldCupFinale2023MeetAndGreet: EventPass = { | ||
id: '3', | ||
name: 'Meet and Greet', | ||
event: { | ||
slug: 'world-cup-finale-2023', | ||
title: 'World Cup Finale 2023', | ||
}, | ||
}; | ||
|
||
export const eventPassesWorldCupFinale: EventPass[] = [ | ||
eventPassWorldCupFinale2023VIP, | ||
eventPassWorldCupFinale2023Premium, | ||
eventPassWorldCupFinale2023MeetAndGreet, | ||
]; |
Oops, something went wrong.