mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
Trip members can rate a place 1-5 stars; the place shows the average, with a who-voted custom tooltip and a star-sort toggle in the places sidebar. Saved collection places are ratable too (plus a min-rating filter), and ratings travel with a place copied between a trip and a collection — carrying only the votes of users who belong to both sides. Adds a full Collections MCP surface (list/get, CRUD lists + places, status, rate_collection_place, labels, sharing, copy-to-trip) under a new collections:read/collections:write scope, plus rate_place for trip places; rating aggregates flow through the existing place outputs. Mobile trip + collection place sheets gain the rating row. New UI strings translated across all 22 locales.
This commit is contained in:
@@ -404,6 +404,10 @@ export const placesApi = {
|
||||
fd.append('image', file)
|
||||
return postMultipart<{ place: Place }>(`/trips/${tripId}/places/${id}/image`, fd)
|
||||
},
|
||||
rate: (tripId: number | string, id: number | string, rating: number | null): Promise<{ place: Place }> =>
|
||||
rating === null
|
||||
? apiClient.delete(`/trips/${tripId}/places/${id}/rating`).then(r => r.data)
|
||||
: apiClient.put(`/trips/${tripId}/places/${id}/rating`, { rating }).then(r => r.data),
|
||||
importGpx: (tripId: number | string, file: File, opts?: { waypoints?: boolean; routes?: boolean; tracks?: boolean }) => {
|
||||
const fd = new FormData()
|
||||
fd.append('file', file)
|
||||
|
||||
@@ -74,6 +74,10 @@ export const collectionsApi = {
|
||||
postMultipart(`${base}/places/${pid}/image`, formData),
|
||||
setStatus: (pid: number, status: CollectionStatus): Promise<CollectionPlace> =>
|
||||
ax.post(`${base}/places/${pid}/status`, { status }).then((r: AxiosResponse) => r.data),
|
||||
ratePlace: (pid: number, rating: number | null): Promise<CollectionPlace> =>
|
||||
rating === null
|
||||
? ax.delete(`${base}/places/${pid}/rating`).then((r: AxiosResponse) => r.data)
|
||||
: ax.put(`${base}/places/${pid}/rating`, { rating }).then((r: AxiosResponse) => r.data),
|
||||
deletePlace: (pid: number): Promise<unknown> =>
|
||||
ax.delete(`${base}/places/${pid}`).then((r: AxiosResponse) => r.data),
|
||||
deleteMany: (ids: number[]): Promise<unknown> =>
|
||||
|
||||
@@ -7,6 +7,7 @@ describe('SCOPE_GROUPS', () => {
|
||||
const expected = [
|
||||
'trips:read', 'trips:write', 'trips:delete', 'trips:share',
|
||||
'places:read', 'places:write',
|
||||
'collections:read', 'collections:write',
|
||||
'atlas:read', 'atlas:write',
|
||||
'packing:read', 'packing:write',
|
||||
'todos:read', 'todos:write',
|
||||
@@ -16,6 +17,7 @@ describe('SCOPE_GROUPS', () => {
|
||||
'notifications:read', 'notifications:write',
|
||||
'vacay:read', 'vacay:write',
|
||||
'geo:read', 'weather:read',
|
||||
'journey:read', 'journey:write', 'journey:share',
|
||||
]
|
||||
for (const scope of expected) {
|
||||
expect(SCOPE_GROUPS).toHaveProperty(scope)
|
||||
@@ -32,8 +34,8 @@ describe('SCOPE_GROUPS', () => {
|
||||
})
|
||||
|
||||
describe('ALL_SCOPES', () => {
|
||||
it('FE-OAUTH-SCOPES-003: contains exactly 27 scopes', () => {
|
||||
expect(ALL_SCOPES).toHaveLength(27)
|
||||
it('FE-OAUTH-SCOPES-003: contains exactly 29 scopes', () => {
|
||||
expect(ALL_SCOPES).toHaveLength(29)
|
||||
})
|
||||
|
||||
it('FE-OAUTH-SCOPES-004: matches Object.keys(SCOPE_GROUPS)', () => {
|
||||
|
||||
@@ -20,6 +20,8 @@ export const SCOPE_GROUPS: Record<string, ScopeKeys> = {
|
||||
'trips:share': { labelKey: 'oauth.scope.trips:share.label', descriptionKey: 'oauth.scope.trips:share.description', groupKey: 'oauth.scope.group.trips' },
|
||||
'places:read': { labelKey: 'oauth.scope.places:read.label', descriptionKey: 'oauth.scope.places:read.description', groupKey: 'oauth.scope.group.places' },
|
||||
'places:write': { labelKey: 'oauth.scope.places:write.label', descriptionKey: 'oauth.scope.places:write.description', groupKey: 'oauth.scope.group.places' },
|
||||
'collections:read': { labelKey: 'oauth.scope.collections:read.label', descriptionKey: 'oauth.scope.collections:read.description', groupKey: 'oauth.scope.group.collections' },
|
||||
'collections:write': { labelKey: 'oauth.scope.collections:write.label', descriptionKey: 'oauth.scope.collections:write.description', groupKey: 'oauth.scope.group.collections' },
|
||||
'atlas:read': { labelKey: 'oauth.scope.atlas:read.label', descriptionKey: 'oauth.scope.atlas:read.description', groupKey: 'oauth.scope.group.atlas' },
|
||||
'atlas:write': { labelKey: 'oauth.scope.atlas:write.label', descriptionKey: 'oauth.scope.atlas:write.description', groupKey: 'oauth.scope.group.atlas' },
|
||||
'packing:read': { labelKey: 'oauth.scope.packing:read.label', descriptionKey: 'oauth.scope.packing:read.description', groupKey: 'oauth.scope.group.packing' },
|
||||
|
||||
Reference in New Issue
Block a user