Fixed remaning linting issues

This commit is contained in:
Luke Vella 2024-10-13 17:24:24 +01:00
parent a13df41bae
commit b7765013ca
No known key found for this signature in database
GPG key ID: 469CAD687F0D784C
33 changed files with 140 additions and 1495 deletions

View file

@ -78,7 +78,10 @@ export const TimesShownIn = () => {
return ( return (
<ClockPreferences> <ClockPreferences>
<button className="inline-flex items-center gap-x-2.5 text-sm hover:underline"> <button
type="button"
className="inline-flex items-center gap-x-2.5 text-sm hover:underline"
>
<GlobeIcon className="size-4" /> <GlobeIcon className="size-4" />
<Trans <Trans
i18nKey="timeShownIn" i18nKey="timeShownIn"

View file

@ -1,23 +1,27 @@
/* eslint-disable */
// @ts-nocheck // @ts-nocheck
import { DateLocalizer } from "react-big-calendar"; import { DateLocalizer } from "react-big-calendar";
const weekRangeFormat = ({ start, end }, culture, local) => const weekRangeFormat = ({ start, end }, culture, local) =>
// biome-ignore lint/style/useTemplate: <explanation>
local.format(start, "MMMM DD", culture) + local.format(start, "MMMM DD", culture) +
" " + " - " +
local.format(end, local.eq(start, end, "month") ? "DD" : "MMMM DD", culture); local.format(end, local.eq(start, end, "month") ? "DD" : "MMMM DD", culture);
const dateRangeFormat = ({ start, end }, culture, local) => const dateRangeFormat = ({ start, end }, culture, local) =>
local.format(start, "L", culture) + " " + local.format(end, "L", culture); // biome-ignore lint/style/useTemplate: <explanation>
local.format(start, "L", culture) + " - " + local.format(end, "L", culture);
const timeRangeFormat = ({ start, end }, culture, local) => const timeRangeFormat = ({ start, end }, culture, local) =>
local.format(start, "LT", culture) + " " + local.format(end, "LT", culture); // biome-ignore lint/style/useTemplate: <explanation>
local.format(start, "LT", culture) + " - " + local.format(end, "LT", culture);
const timeRangeStartFormat = ({ start }, culture, local) => const timeRangeStartFormat = ({ start }, culture, local) =>
local.format(start, "LT", culture) + " "; // biome-ignore lint/style/useTemplate: <explanation>
local.format(start, "LT", culture) + " - ";
const timeRangeEndFormat = ({ end }, culture, local) => const timeRangeEndFormat = ({ end }, culture, local) =>
" " + local.format(end, "LT", culture); // biome-ignore lint/style/useTemplate: <explanation>
" - " + local.format(end, "LT", culture);
export const formats = { export const formats = {
dateFormat: "DD", dateFormat: "DD",
@ -62,6 +66,7 @@ export default function (dayjs) {
return [dtA, dtB, datePart]; return [dtA, dtB, datePart];
} }
// biome-ignore lint/style/useDefaultParameterLast: <explanation>
function startOf(date = null, unit) { function startOf(date = null, unit) {
const datePart = fixUnit(unit); const datePart = fixUnit(unit);
if (datePart) { if (datePart) {
@ -70,6 +75,7 @@ export default function (dayjs) {
return dayjs(date).toDate(); return dayjs(date).toDate();
} }
// biome-ignore lint/style/useDefaultParameterLast: <explanation>
function endOf(date = null, unit) { function endOf(date = null, unit) {
const datePart = fixUnit(unit); const datePart = fixUnit(unit);
if (datePart) { if (datePart) {

View file

@ -70,7 +70,7 @@ const PollOptionsForm = ({
[views, watchView], [views, watchView],
); );
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // biome-ignore lint/style/noNonNullAssertion: <explanation>
const watchOptions = watch("options", [])!; const watchOptions = watch("options", [])!;
const watchDuration = watch("duration"); const watchDuration = watch("duration");
const watchTimeZone = watch("timeZone"); const watchTimeZone = watch("timeZone");

View file

@ -51,14 +51,14 @@ const SettingTitle = ({
const Setting = ({ children }: React.PropsWithChildren) => { const Setting = ({ children }: React.PropsWithChildren) => {
return ( return (
<label <div
className={cn( className={cn(
"cursor-pointer bg-white hover:bg-gray-50 active:bg-gray-100", "cursor-pointer bg-white hover:bg-gray-50 active:bg-gray-100",
"flex select-none justify-between gap-x-4 gap-y-2.5 rounded-md border p-3 sm:flex-row ", "flex select-none justify-between gap-x-4 gap-y-2.5 rounded-md border p-3 sm:flex-row ",
)} )}
> >
{children} {children}
</label> </div>
); );
}; };

View file

@ -7,7 +7,7 @@ export type NewEventData = PollDetailsData &
PollOptionsData & PollOptionsData &
PollSettingsFormData; PollSettingsFormData;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // biome-ignore lint/suspicious/noExplicitAny: <explanation>
export interface PollFormProps<T extends Record<string, any>> { export interface PollFormProps<T extends Record<string, any>> {
onSubmit?: (data: T) => void; onSubmit?: (data: T) => void;
onChange?: (data: Partial<T>) => void; onChange?: (data: Partial<T>) => void;

View file

@ -80,9 +80,9 @@ export const InviteDialog = () => {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="min-w-0"> <div className="min-w-0">
<label className="mb-2"> <div className="mb-2">
<Trans i18nKey="inviteLink" defaults="Invite Link" /> <Trans i18nKey="inviteLink" defaults="Invite Link" />
</label> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<CopyInviteLinkButton /> <CopyInviteLinkButton />
<div className="shrink-0"> <div className="shrink-0">

View file

@ -77,7 +77,7 @@ const ModalProvider: React.FunctionComponent<ModalProviderProps> = ({
))} ))}
{modals.map((props, i) => ( {modals.map((props, i) => (
<Modal <Modal
key={i} key={`modal-${i}-${props.title}`}
visible={true} visible={true}
{...props} {...props}
content={ content={

View file

@ -161,7 +161,7 @@ export const NewParticipantForm = (props: NewParticipantModalProps) => {
) : null} ) : null}
</fieldset> </fieldset>
<fieldset> <fieldset>
<label className="mb-1 text-gray-500">{t("response")}</label> <div className="mb-1 text-sm text-gray-500">{t("response")}</div>
<VoteSummary votes={props.votes} /> <VoteSummary votes={props.votes} />
</fieldset> </fieldset>
{formState.errors.root?.message ? ( {formState.errors.root?.message ? (

View file

@ -303,7 +303,7 @@ const DesktopPoll: React.FunctionComponent = () => {
? visibleParticipants.map((participant, i) => { ? visibleParticipants.map((participant, i) => {
return ( return (
<ParticipantRow <ParticipantRow
key={i} key={participant.id}
participant={{ participant={{
id: participant.id, id: participant.id,
name: participant.name, name: participant.name,

View file

@ -19,7 +19,7 @@ import { YouAvatar } from "@/components/poll/you-avatar";
import { Trans } from "@/components/trans"; import { Trans } from "@/components/trans";
import { usePoll } from "../../poll-context"; import { usePoll } from "../../poll-context";
import { toggleVote, VoteSelector } from "../vote-selector"; import { VoteSelector, toggleVote } from "../vote-selector";
export interface ParticipantRowFormProps { export interface ParticipantRowFormProps {
name?: string; name?: string;
@ -112,13 +112,20 @@ const ParticipantRowForm = ({
<Controller <Controller
control={form.control} control={form.control}
name={`votes.${i}`} name={`votes.${i}`}
render={({ field }) => ( render={({ field }) => {
<div const handleChange = () => {
onClick={() => {
field.onChange({ field.onChange({
optionId, optionId,
type: toggleVote(field.value?.type), type: toggleVote(field.value?.type),
}); });
};
return (
<div
onClick={handleChange}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleChange();
}
}} }}
className="absolute inset-0 flex cursor-pointer items-center justify-center hover:bg-gray-100 active:bg-gray-200/50 active:ring-1 active:ring-inset active:ring-gray-200" className="absolute inset-0 flex cursor-pointer items-center justify-center hover:bg-gray-100 active:bg-gray-200/50 active:ring-1 active:ring-inset active:ring-gray-200"
> >
@ -129,7 +136,8 @@ const ParticipantRowForm = ({
}} }}
/> />
</div> </div>
)} );
}}
/> />
</td> </td>
); );

View file

@ -68,7 +68,7 @@ export const ParticipantRowView: React.FunctionComponent<{
{votes.map((vote, i) => { {votes.map((vote, i) => {
return ( return (
<td <td
key={i} key={`vote-${i}-${vote}`}
className={cn( className={cn(
"h-12 border-l border-t", "h-12 border-l border-t",
!vote || vote === "no" ? "bg-gray-100" : "bg-white", !vote || vote === "no" ? "bg-gray-100" : "bg-white",
@ -109,7 +109,7 @@ const ParticipantRow: React.FunctionComponent<ParticipantRowProps> = ({
const { user, ownsObject } = useUser(); const { user, ownsObject } = useUser();
const { getVote, optionIds } = usePoll(); const { getVote, optionIds } = usePoll();
const isYou = !!(user && ownsObject(participant) ); const isYou = !!(user && ownsObject(participant));
const { canEditParticipant } = usePermissions(); const { canEditParticipant } = usePermissions();
const canEdit = canEditParticipant(participant.id); const canEdit = canEditParticipant(participant.id);

View file

@ -51,19 +51,19 @@ const useScoreByOptionId = () => {
return React.useMemo(() => { return React.useMemo(() => {
const scoreByOptionId: Record<string, OptionScore> = {}; const scoreByOptionId: Record<string, OptionScore> = {};
options.forEach((option) => { for (const option of options) {
scoreByOptionId[option.id] = { scoreByOptionId[option.id] = {
yes: [], yes: [],
ifNeedBe: [], ifNeedBe: [],
no: [], no: [],
}; };
}); }
responses?.forEach((response) => { for (const response of responses) {
response.votes.forEach((vote) => { for (const vote of response.votes) {
scoreByOptionId[vote.optionId]?.[vote.type].push(response.id); scoreByOptionId[vote.optionId]?.[vote.type].push(response.id);
}); }
}); }
return scoreByOptionId; return scoreByOptionId;
}, [responses, options]); }, [responses, options]);

View file

@ -48,8 +48,8 @@ const PollOptionVoteSummary: React.FunctionComponent<{ optionId: string }> = ({
) : ( ) : (
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<div className="col-span-1 space-y-2.5"> <div className="col-span-1 space-y-2.5">
{participantsWhoVotedYes.map(({ name }, i) => ( {participantsWhoVotedYes.map(({ id, name }) => (
<div key={i} className="flex"> <div key={id} className="flex">
<div className="relative mr-2.5 flex size-5 items-center justify-center"> <div className="relative mr-2.5 flex size-5 items-center justify-center">
<OptimizedAvatarImage size="xs" name={name} /> <OptimizedAvatarImage size="xs" name={name} />
<VoteIcon <VoteIcon
@ -61,8 +61,8 @@ const PollOptionVoteSummary: React.FunctionComponent<{ optionId: string }> = ({
<div className="truncate text-sm">{name}</div> <div className="truncate text-sm">{name}</div>
</div> </div>
))} ))}
{participantsWhoVotedIfNeedBe.map(({ name }, i) => ( {participantsWhoVotedIfNeedBe.map(({ id, name }) => (
<div key={i} className="flex"> <div key={id} className="flex">
<div className="relative mr-2.5 flex size-5 items-center justify-center"> <div className="relative mr-2.5 flex size-5 items-center justify-center">
<OptimizedAvatarImage size="xs" name={name} /> <OptimizedAvatarImage size="xs" name={name} />
<VoteIcon <VoteIcon
@ -76,8 +76,8 @@ const PollOptionVoteSummary: React.FunctionComponent<{ optionId: string }> = ({
))} ))}
</div> </div>
<div className="col-span-1 space-y-2.5"> <div className="col-span-1 space-y-2.5">
{participantsWhoVotedNo.map(({ name }, i) => ( {participantsWhoVotedNo.map(({ id, name }) => (
<div key={i} className="flex"> <div key={id} className="flex">
<div className="relative mr-2.5 flex size-5 items-center justify-center"> <div className="relative mr-2.5 flex size-5 items-center justify-center">
<OptimizedAvatarImage size="xs" name={name} /> <OptimizedAvatarImage size="xs" name={name} />
<VoteIcon <VoteIcon
@ -122,6 +122,17 @@ const PollOption: React.FunctionComponent<PollOptionProps> = ({
onClick={() => { onClick={() => {
selectorRef.current?.click(); selectorRef.current?.click();
}} }}
onKeyUp={(e) => {
if (e.key === "Enter") {
selectorRef.current?.click();
}
}}
onKeyDown={(e) => {
if (e.key === " ") {
e.preventDefault();
selectorRef.current?.click();
}
}}
> >
<div className="flex h-7 items-center justify-between gap-x-4"> <div className="flex h-7 items-center justify-between gap-x-4">
<div className="shrink-0">{children}</div> <div className="shrink-0">{children}</div>

View file

@ -72,6 +72,14 @@ export function ScheduledEvent() {
const attendees = useAttendees(); const attendees = useAttendees();
const guestEmails: string[] = [];
for (const attendee of attendees) {
if (attendee.email) {
guestEmails.push(attendee.email);
}
}
if (!event) { if (!event) {
return null; return null;
} }
@ -116,9 +124,7 @@ export function ScheduledEvent() {
? { name: poll.user.name, email: poll.user.email } ? { name: poll.user.name, email: poll.user.email }
: undefined : undefined
} }
guests={attendees guests={guestEmails}
.filter((participant) => !!participant.email)
.map((participant) => participant.email!)}
/> />
</div> </div>
</div> </div>

View file

@ -106,7 +106,10 @@ const DateTimePreferencesForm = () => {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{dayjs.weekdays().map((day, index) => ( {dayjs.weekdays().map((day, index) => (
<SelectItem key={index} value={index.toString()}> <SelectItem
key={index.toString()}
value={index.toString()}
>
{day} {day}
</SelectItem> </SelectItem>
))} ))}

View file

@ -27,7 +27,7 @@ const Steps: React.FunctionComponent<StepsProps> = ({
{[...Array(total)].map((_, i) => { {[...Array(total)].map((_, i) => {
return ( return (
<span <span
key={i} key={i.toString()}
className={cn("h-2 w-2 rounded-full transition-all", { className={cn("h-2 w-2 rounded-full transition-all", {
"bg-primary-400": i <= current, "bg-primary-400": i <= current,
"bg-gray-300": i > current, "bg-gray-300": i > current,

View file

@ -20,6 +20,7 @@ export function DataTableColumnHeader<TData, TValue>({
return ( return (
<button <button
type="button"
className="flex w-full items-center gap-x-2.5" className="flex w-full items-center gap-x-2.5"
onClick={() => { onClick={() => {
column.toggleSorting(); column.toggleSorting();

View file

@ -17,19 +17,19 @@ const TimeFormatPicker = ({
return ( return (
<RadioGroup value={value} onValueChange={onChange} disabled={disabled}> <RadioGroup value={value} onValueChange={onChange} disabled={disabled}>
<div className="grid gap-y-1"> <div className="grid gap-y-1">
<label className="flex items-center gap-x-2"> <div className="flex items-center gap-x-2">
<RadioGroupItem value="hours12" /> <RadioGroupItem id="hours12" value="hours12" />
<span> <label htmlFor="hours12">
<Trans i18nKey="12h" /> <Trans i18nKey="12h" />
</span>
</label> </label>
<label className="flex items-center gap-x-2"> </div>
<RadioGroupItem value="hours24" /> <div className="flex items-center gap-x-2">
<span> <RadioGroupItem id="hours24" value="hours24" />
<label htmlFor="hours24">
<Trans i18nKey="24h" /> <Trans i18nKey="24h" />
</span>
</label> </label>
</div> </div>
</div>
</RadioGroup> </RadioGroup>
); );
}; };

View file

@ -1,6 +1,6 @@
import { Button } from "@rallly/ui/button"; import { Button } from "@rallly/ui/button";
import Link from "next/link";
import { Trans } from "next-i18next"; import { Trans } from "next-i18next";
import Link from "next/link";
import type React from "react"; import type React from "react";
import { usePostHog } from "@/utils/posthog"; import { usePostHog } from "@/utils/posthog";
@ -39,7 +39,7 @@ export const UpgradeButton = ({
); );
}; };
export const UpgradeLink = ({}) => { export const UpgradeLink = () => {
return ( return (
<Button variant="primary" asChild> <Button variant="primary" asChild>
<Link href="/settings/billing"> <Link href="/settings/billing">

View file

@ -14,7 +14,7 @@ export default function GlobalError({
}, [error]); }, [error]);
return ( return (
<html> <html lang="en">
<body> <body>
{/* `NextError` is the default Next.js error page component. Its type {/* `NextError` is the default Next.js error page component. Its type
definition requires a `statusCode` prop. However, since the App Router definition requires a `statusCode` prop. However, since the App Router

View file

@ -1,6 +1,6 @@
import crypto from "node:crypto";
// Original source: https://gist.github.com/dsumer/3594cda57e84a93a9019cddc71831882 // Original source: https://gist.github.com/dsumer/3594cda57e84a93a9019cddc71831882
import { prisma } from "@rallly/database"; import { prisma } from "@rallly/database";
import crypto from "node:crypto";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import * as Serialize from "php-serialize"; import * as Serialize from "php-serialize";
@ -55,7 +55,7 @@ export function validateWebhook(req: NextApiRequest) {
jsonObj = ksort(jsonObj); jsonObj = ksort(jsonObj);
for (const property in jsonObj) { for (const property in jsonObj) {
if ( if (
jsonObj.hasOwnProperty(property) && Object.hasOwn(jsonObj, property) &&
typeof jsonObj[property] !== "string" typeof jsonObj[property] !== "string"
) { ) {
if (Array.isArray(jsonObj[property])) { if (Array.isArray(jsonObj[property])) {

View file

@ -18,7 +18,7 @@ function joinPath(baseUrl: string, subpath = "") {
export function objectToQueryString(obj: Record<string, string>) { export function objectToQueryString(obj: Record<string, string>) {
const parts = []; const parts = [];
for (const key in obj) { for (const key in obj) {
if (obj.hasOwnProperty(key)) { if (Object.hasOwn(obj, key)) {
const value = obj[key]; const value = obj[key];
if (value !== undefined) { if (value !== undefined) {
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);

View file

@ -1,6 +1,6 @@
import dayjs from "dayjs";
import { supportedTimeZones } from "@/utils/supported-time-zones"; import { supportedTimeZones } from "@/utils/supported-time-zones";
import * as Sentry from "@sentry/nextjs";
import dayjs from "dayjs";
import type { import type {
DateTimeOption, DateTimeOption,
@ -67,11 +67,20 @@ export function normalizeTimeZone(timeZone: string) {
return dayjs().tz(tz, true).utcOffset() === timeZoneOffset; return dayjs().tz(tz, true).utcOffset() === timeZoneOffset;
}); });
} }
if (!tz) { if (!tz) {
tz = supportedTimeZones.find((tz) => { tz = supportedTimeZones.find((tz) => {
return getTimeZoneOffset(tz) === timeZoneOffset; return getTimeZoneOffset(tz) === timeZoneOffset;
})!; // We assume there has to be a timezone with the same offset });
if (!tz) {
Sentry.captureException(
new Error(`Failed to resolve timezone ${timeZone}`),
);
// TODO: This shouldn't happen since there is at least one timezone with the same offset
// that is supported by the application, but at least we have a fallback.
// We should prompt the user for their timezone if we can't resolve it automatically.
tz = "Europe/London";
}
} }
return tz; return tz;

View file

@ -3,7 +3,7 @@ export function getValueByPath<O extends Record<string, unknown>>(
path: string, path: string,
): unknown { ): unknown {
const pathArray = path.split("."); const pathArray = path.split(".");
// eslint-disable-next-line @typescript-eslint/no-explicit-any // biome-ignore lint/suspicious/noExplicitAny: <explanation>
let curr: any = obj; let curr: any = obj;
for (const part of pathArray) { for (const part of pathArray) {
if (curr[part] === undefined) { if (curr[part] === undefined) {

View file

@ -9,7 +9,7 @@
"useIgnoreFile": false "useIgnoreFile": false
}, },
"linter": { "linter": {
"ignore": ["node_modules", ".next", "dist"], "ignore": ["node_modules", ".next", "dist", "public/static/scripts"],
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true "recommended": true

View file

@ -13,6 +13,7 @@
}, },
"dependencies": { "dependencies": {
"@rallly/ui": "workspace:*", "@rallly/ui": "workspace:*",
"@rallly/database": "workspace:*",
"stripe": "^13.2.0", "stripe": "^13.2.0",
"@radix-ui/react-radio-group": "^1.2.0", "@radix-ui/react-radio-group": "^1.2.0",
"next": "*" "next": "*"

View file

@ -1,8 +1,8 @@
import { prisma } from "@rallly/database";
/** /**
* This script will go through all subscriptions and add the userId to the metadata. * This script will go through all subscriptions and add the userId to the metadata.
*/ */
import { stripe } from "../lib/stripe"; import { stripe } from "../lib/stripe";
import { prisma } from "@rallly/database";
async function getSubscriptionsWithMissingMetadata( async function getSubscriptionsWithMissingMetadata(
starting_after?: string, starting_after?: string,
@ -13,11 +13,11 @@ async function getSubscriptionsWithMissingMetadata(
limit: 100, limit: 100,
starting_after, starting_after,
}); });
subscriptions.data.forEach((subscription) => { for (const subscription of subscriptions.data) {
if (!subscription.metadata.userId) { if (!subscription.metadata.userId) {
res.push(subscription.id); res.push(subscription.id);
} }
}); }
if (subscriptions.has_more) { if (subscriptions.has_more) {
return [ return [
...res, ...res,

View file

@ -20,6 +20,7 @@ const prismaClientSingleton = () => {
export type ExtendedPrismaClient = ReturnType<typeof prismaClientSingleton>; export type ExtendedPrismaClient = ReturnType<typeof prismaClientSingleton>;
// biome-ignore lint/suspicious/noShadowRestrictedNames: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices
declare const globalThis: { declare const globalThis: {
prismaGlobal: ExtendedPrismaClient; prismaGlobal: ExtendedPrismaClient;
} & typeof global; } & typeof global;

View file

@ -15,6 +15,7 @@
"@rallly/tsconfig": "workspace:*", "@rallly/tsconfig": "workspace:*",
"@types/node": "^18.15.10", "@types/node": "^18.15.10",
"prisma": "^5.17.0", "prisma": "^5.17.0",
"dayjs": "^1.11.10",
"tsx": "^4.6.2" "tsx": "^4.6.2"
}, },
"dependencies": { "dependencies": {

View file

@ -160,9 +160,9 @@ async function main() {
await Promise.all( await Promise.all(
[freeUser, proUser, proUserLegacy].map(async (user) => { [freeUser, proUser, proUserLegacy].map(async (user) => {
Array.from({ length: 20 }).forEach(async () => { for (let i = 0; i < 20; i++) {
await createPollForUser(user.id); await createPollForUser(user.id);
}); }
console.info(`✓ Added ${user.email}`); console.info(`✓ Added ${user.email}`);
}), }),
); );

View file

@ -1,6 +1,7 @@
import type React from "react"; import type React from "react";
type ComponentPropsAs< type ComponentPropsAs<
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
C extends React.ElementType<any>, C extends React.ElementType<any>,
T extends React.ComponentPropsWithoutRef<C>["as"], T extends React.ComponentPropsWithoutRef<C>["as"],
> = Omit< > = Omit<

1420
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff