Skip to content

Commit 0144bbb

Browse files
implemented suggestions
1 parent 2d7680b commit 0144bbb

File tree

3 files changed

+189
-138
lines changed

3 files changed

+189
-138
lines changed

src/pretalx/frontend/schedule-editor/eslint.config.js

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,66 @@
11
import vue from 'eslint-plugin-vue'
22
import vuePug from 'eslint-plugin-vue-pug'
33
import js from '@eslint/js'
4+
import tseslint from '@typescript-eslint/eslint-plugin'
5+
import tsParser from '@typescript-eslint/parser'
46

57
export default [
68
// Base JavaScript recommended rules
79
js.configs.recommended,
810

11+
// TypeScript recommended rules for .ts files
12+
{
13+
files: ['**/*.ts', '**/*.tsx'],
14+
languageOptions: {
15+
parser: tsParser,
16+
parserOptions: {
17+
ecmaVersion: 2022,
18+
sourceType: 'module',
19+
project: './tsconfig.json'
20+
}
21+
},
22+
plugins: {
23+
'@typescript-eslint': tseslint
24+
},
25+
rules: {
26+
...tseslint.configs.recommended.rules,
27+
// TypeScript-specific rules
28+
'@typescript-eslint/no-unused-vars': 'warn',
29+
'@typescript-eslint/no-explicit-any': 'warn',
30+
'@typescript-eslint/explicit-function-return-type': 'off',
31+
'@typescript-eslint/explicit-module-boundary-types': 'off',
32+
'@typescript-eslint/no-non-null-assertion': 'warn',
33+
'@typescript-eslint/no-empty-object-type': 'warn'
34+
}
35+
},
36+
937
// Vue 3 recommended configuration
1038
...vue.configs['flat/recommended'],
1139

40+
// Vue + TypeScript configuration (separate from pure TS to avoid project issues)
41+
{
42+
files: ['**/*.vue'],
43+
languageOptions: {
44+
parser: vue.parser,
45+
parserOptions: {
46+
ecmaVersion: 2022,
47+
sourceType: 'module',
48+
parser: {
49+
ts: tsParser
50+
}
51+
// Removed project reference for Vue files to avoid parsing issues
52+
}
53+
},
54+
plugins: {
55+
'@typescript-eslint': tseslint
56+
},
57+
rules: {
58+
// Apply some TypeScript rules to Vue files as well
59+
'@typescript-eslint/no-unused-vars': 'warn',
60+
'@typescript-eslint/no-explicit-any': 'warn'
61+
}
62+
},
63+
1264
// Vue Pug configuration
1365
{
1466
plugins: {
@@ -20,6 +72,7 @@ export default [
2072
},
2173

2274
{
75+
files: ['**/*.js', '**/*.ts', '**/*.vue'],
2376
languageOptions: {
2477
ecmaVersion: 2022,
2578
sourceType: 'module',
@@ -37,13 +90,16 @@ export default [
3790
// Formatting and style rules
3891
'arrow-parens': 'off',
3992
'generator-star-spacing': 'off',
40-
'indent': ['error', 'tab', { SwitchCase: 1 }],
41-
'no-tabs': 'off',
93+
'indent': 'off', // Disable indentation rule due to mixed codebase
94+
'no-tabs': 'off', // Allow both tabs and spaces
4295
'comma-dangle': 'off',
4396
'curly': 'off',
4497
'no-return-assign': 'off',
4598
'object-curly-spacing': 'off',
4699

100+
// Override JS no-unused-vars for TypeScript files (handled by @typescript-eslint/no-unused-vars)
101+
'no-unused-vars': 'off',
102+
47103
// Vue-specific rules
48104
'vue/require-default-prop': 'off',
49105
'vue/multi-word-component-names': 'off',
@@ -57,9 +113,6 @@ export default [
57113
'vue/no-undef-properties': 'off',
58114
'vue/require-explicit-emits': 'off',
59115
'vue/no-mutating-props': 'error',
60-
61-
// JavaScript rules
62-
'no-unused-vars': 'warn',
63116
}
64117
}
65118
]
Lines changed: 37 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,27 @@
1-
import { z } from 'zod';
1+
import {
2+
ScheduleSchema,
3+
AvailabilitySchema,
4+
WarningsSchema,
5+
TalkSchema,
6+
Talk,
7+
Schedule,
8+
Availability,
9+
Warnings
10+
} from './schemas';
211

312
const basePath = process.env.BASE_PATH || '/talk';
413

5-
const SpeakerSchema = z.object({
6-
code: z.string(),
7-
name: z.string(),
8-
});
9-
10-
// Title can be a string or a language object like {en: "Title"}
11-
const toTitleRecord = (val: unknown): Record<string, string> => {
12-
if (val !== null && typeof val === 'object') {
13-
return val as Record<string, string>;
14-
}
15-
if (typeof val === 'string') {
16-
return { en: val };
14+
const calculateDuration = (start?: string, end?: string): number | undefined => {
15+
if (!start || !end) return undefined;
16+
try {
17+
const startTime = new Date(start).getTime();
18+
const endTime = new Date(end).getTime();
19+
return (endTime - startTime) / (1000 * 60);
20+
} catch {
21+
return undefined;
1722
}
18-
return { en: '' };
1923
};
2024

21-
// Room schema
22-
const RoomSchema = z.object({
23-
id: z.number(),
24-
name: z.record(z.string(), z.string()).default({}),
25-
description: z.record(z.string(), z.string()).default({})
26-
});
27-
28-
const TrackSchema = z.object({
29-
id: z.number(),
30-
name: z.record(z.string(), z.string()).default({})
31-
});
32-
33-
// Talk schema
34-
const TalkSchema = z.object({
35-
id: z.number(),
36-
// Some talks have code, some don't
37-
code: z.string().optional(),
38-
title: z.union([
39-
z.string(),
40-
z.record(z.string(), z.string())
41-
]).transform(toTitleRecord),
42-
abstract: z.string().optional(),
43-
speakers: z.array(z.string()).optional().default([]),
44-
// Room and track come as numbers in the API
45-
room: z.union([
46-
z.number(),
47-
z.string().transform(val => parseInt(val, 10) || null)
48-
]).nullable().optional(),
49-
track: z.union([
50-
z.number(),
51-
z.string().transform(val => parseInt(val, 10) || null)
52-
]).nullable().optional(),
53-
54-
start: z.string().nullable().optional(),
55-
end: z.string().nullable().optional(),
56-
state: z.string().optional(),
57-
updated: z.string().optional(),
58-
uncreated: z.boolean().optional(),
59-
availabilities: z.array(z.unknown()).optional().default([]),
60-
// Duration field - it appears some talks don't have this field
61-
duration: z.number().optional()
62-
});
63-
64-
// Main schedule schema
65-
const ScheduleSchema = z.object({
66-
version: z.nullable(z.string().nullable()),
67-
event_start: z.string(),
68-
event_end: z.string(),
69-
timezone: z.string(),
70-
locales: z.array(z.string()).default([]),
71-
rooms: z.array(RoomSchema).default([]),
72-
tracks: z.array(TrackSchema).default([]),
73-
speakers: z.array(SpeakerSchema).default([]),
74-
talks: z.array(TalkSchema).default([]),
75-
now: z.string().optional(),
76-
warnings: z.record(z.string(), z.any()).optional().default({})
77-
});
78-
79-
const AvailabilitySchema = z.object({
80-
rooms: z.record(z.string(), z.array(z.object({
81-
start: z.string(),
82-
end: z.string()
83-
}))).optional(),
84-
talks: z.record(z.string(), z.array(z.object({
85-
start: z.string(),
86-
end: z.string()
87-
}))).optional(),
88-
});
89-
90-
const WarningSchema = z.object({
91-
message: z.string(),
92-
});
93-
94-
const WarningsSchema = z.record(z.string(), z.array(WarningSchema)).optional();
9525
interface TalkPayload {
9626
id?: number;
9727
code?: string;
@@ -103,30 +33,15 @@ interface TalkPayload {
10333
duration?: number;
10434
}
10535

106-
// Helper to calculate duration in minutes from start and end time strings
107-
const calculateDuration = (start?: string, end?: string): number | undefined => {
108-
if (!start || !end) return undefined;
109-
110-
try {
111-
const startTime = new Date(start).getTime();
112-
const endTime = new Date(end).getTime();
113-
return (endTime - startTime) / (1000 * 60); // Duration in minutes
114-
} catch (e) {
115-
return undefined;
116-
}
117-
};
118-
11936
const api = {
12037
eventSlug: basePath ? window.location.pathname.split("/")[4] : window.location.pathname.split("/")[3],
12138

12239
async http<T>(verb: string, url: string, body: unknown): Promise<T> {
12340
const headers: Record<string, string> = {};
124-
if (body) {
125-
headers['Content-Type'] = 'application/json';
126-
}
41+
if (body) headers['Content-Type'] = 'application/json';
12742

12843
const options: RequestInit = {
129-
method: verb || 'GET',
44+
method: verb,
13045
headers,
13146
body: body ? JSON.stringify(body) : undefined,
13247
credentials: 'include',
@@ -147,68 +62,54 @@ const api = {
14762
return json as T;
14863
},
14964

150-
async fetchTalks(options?: { since?: string; warnings?: boolean }): Promise<z.infer<typeof ScheduleSchema>> {
65+
async fetchTalks(options?: { since?: string; warnings?: boolean }): Promise<Schedule> {
15166
let url = `${basePath}/orga/event/${this.eventSlug}/schedule/api/talks/`;
152-
153-
// Build query parameters
154-
const params = new URLSearchParams();
155-
if (window.location.search) {
156-
new URLSearchParams(window.location.search).forEach((value, key) => {
157-
params.append(key, value);
158-
});
159-
}
67+
const params = new URLSearchParams(window.location.search);
16068
if (options?.since) params.append('since', options.since);
16169
if (options?.warnings) params.append('warnings', 'true');
162-
16370
url += `?${params.toString()}`;
16471

165-
const data = await this.http('GET', url, null);
72+
const data = await this.http<unknown>('GET', url, null);
16673
return ScheduleSchema.parse(data);
16774
},
16875

169-
async fetchAvailabilities(): Promise<z.infer<typeof AvailabilitySchema>> {
76+
async fetchAvailabilities(): Promise<Availability> {
17077
const url = `${basePath}/orga/event/${this.eventSlug}/schedule/api/availabilities/`;
171-
const data = await this.http('GET', url, null);
78+
const data = await this.http<unknown>('GET', url, null);
17279
return AvailabilitySchema.parse(data);
17380
},
17481

175-
async fetchWarnings(): Promise<z.infer<typeof WarningsSchema>> {
82+
async fetchWarnings(): Promise<Warnings> {
17683
const url = `${basePath}/orga/event/${this.eventSlug}/schedule/api/warnings/`;
177-
const data = await this.http('GET', url, null);
84+
const data = await this.http<unknown>('GET', url, null);
17885
return WarningsSchema.parse(data);
17986
},
18087

18188
async saveTalk(
18289
talk: TalkPayload,
18390
{ action = 'PATCH' }: { action?: string } = {}
184-
): Promise<z.infer<typeof TalkSchema> | void> {
91+
): Promise<Talk | void> {
18592
const url = new URL(window.location.href);
18693
url.pathname = `${url.pathname}api/talks/${talk.id ? `${talk.id}/` : ''}`;
18794
url.search = window.location.search;
18895

18996
let payload: unknown = undefined;
19097
if (action !== 'DELETE') {
191-
const roomId = talk.room && typeof talk.room === 'object'
192-
? talk.room.id
193-
: talk.room;
194-
195-
// Calculate duration if not provided but we have start and end times
98+
const roomId = typeof talk.room === 'object' ? talk.room.id : talk.room;
19699
const duration = talk.duration ?? calculateDuration(talk.start, talk.end);
197100

198101
payload = {
199102
room: roomId,
200103
start: talk.start,
201104
end: talk.end,
202-
duration: duration,
105+
duration,
203106
title: talk.title,
204107
description: talk.description,
205108
};
206109
}
207110

208-
// Make request
209111
const response = await this.http<unknown>(action, url.toString(), payload);
210112

211-
// Validate and return response
212113
if (action !== 'DELETE') {
213114
return TalkSchema.parse(response);
214115
}
@@ -218,10 +119,13 @@ const api = {
218119
await this.saveTalk({ id: talk.id }, { action: 'DELETE' });
219120
},
220121

221-
async createTalk(talk: Omit<TalkPayload, 'id'>): Promise<z.infer<typeof TalkSchema>> {
222-
const response = await this.saveTalk(talk, { action: 'POST' }) as z.infer<typeof TalkSchema>;
223-
return TalkSchema.parse(response);
122+
async createTalk(talk: Omit<TalkPayload, 'id'>): Promise<Talk> {
123+
const response = await this.saveTalk(talk, { action: 'POST' });
124+
if (!response) {
125+
throw new Error('Failed to create talk: No response from server');
126+
}
127+
return response;
224128
}
225129
};
226130

227-
export default api
131+
export default api

0 commit comments

Comments
 (0)