mirror of
https://github.com/lukevella/rallly.git
synced 2025-05-02 03:36:33 +02:00
Fixed remaning linting issues
This commit is contained in:
parent
a13df41bae
commit
b7765013ca
33 changed files with 140 additions and 1495 deletions
|
@ -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"
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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={
|
||||||
|
|
|
@ -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 ? (
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,24 +112,32 @@ 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 (
|
||||||
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"
|
<div
|
||||||
>
|
onClick={handleChange}
|
||||||
<VoteSelector
|
onKeyDown={(e) => {
|
||||||
value={field.value?.type}
|
if (e.key === "Enter") {
|
||||||
onChange={(vote) => {
|
handleChange();
|
||||||
field.onChange({ optionId, type: vote });
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
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"
|
||||||
</div>
|
>
|
||||||
)}
|
<VoteSelector
|
||||||
|
value={field.value?.type}
|
||||||
|
onChange={(vote) => {
|
||||||
|
field.onChange({ optionId, type: vote });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -17,18 +17,18 @@ 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>
|
</div>
|
||||||
<label className="flex items-center gap-x-2">
|
<div className="flex items-center gap-x-2">
|
||||||
<RadioGroupItem value="hours24" />
|
<RadioGroupItem id="hours24" value="hours24" />
|
||||||
<span>
|
<label htmlFor="hours24">
|
||||||
<Trans i18nKey="24h" />
|
<Trans i18nKey="24h" />
|
||||||
</span>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])) {
|
||||||
|
|
|
@ -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)}`);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -225,7 +225,7 @@ export const DayjsProvider: React.FunctionComponent<{
|
||||||
if (!localTime) {
|
if (!localTime) {
|
||||||
return dayjs(date).tz(preferredTimeZone);
|
return dayjs(date).tz(preferredTimeZone);
|
||||||
}
|
}
|
||||||
return dayjs(date).utc();
|
return dayjs(date).utc();
|
||||||
},
|
},
|
||||||
[preferredTimeZone],
|
[preferredTimeZone],
|
||||||
);
|
);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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": "*"
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -26,7 +26,7 @@ async function getSubscriptionsWithMissingMetadata(
|
||||||
)),
|
)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function normalizeSubscriptionMetadata() {
|
async function normalizeSubscriptionMetadata() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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}`);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
1420
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue