Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile fix #80

Merged
merged 12 commits into from
Mar 5, 2024
35 changes: 33 additions & 2 deletions app/components/confetti.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Index as ConfettiShower } from 'confetti-react'
import { ClientOnly } from 'remix-utils'
import { useState, useEffect } from 'react'

/**
* confetti is a unique random identifier which re-renders the component
*/
export function Confetti({ confetti }: { confetti?: string }) {
const { width, height } = useWindowSize()
return (
<ClientOnly>
{() => (
Expand All @@ -13,10 +15,39 @@ export function Confetti({ confetti }: { confetti?: string }) {
run={Boolean(confetti)}
recycle={false}
numberOfPieces={500}
width={window.innerWidth}
height={window.innerHeight}
width={width}
height={height}
/>
)}
</ClientOnly>
)
}

function useWindowSize() {
interface Size {
width: number | undefined
height: number | undefined
}
const [size, setSize] = useState<Size>({
width: undefined,
height: undefined,
})

useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
})
}

handleResize()
window.addEventListener('resize', handleResize)

return () => {
window.removeEventListener('resize', handleResize)
}
}, [])

return size
}
34 changes: 34 additions & 0 deletions app/components/forms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,22 @@ export function ErrorList({
)
}

export function Description({ text }: { text: string }) {
return <p className="text-sm text-muted-foreground">{text}</p>
}

export function Field({
labelProps,
inputProps,
errors,
className,
description,
}: {
labelProps: Omit<React.LabelHTMLAttributes<HTMLLabelElement>, 'className'>
inputProps: Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className'>
errors?: ListOfErrors
className?: string
description?: string
}) {
const fallbackId = useId()
const id = inputProps.id ?? fallbackId
Expand All @@ -60,13 +66,41 @@ export function Field({
aria-describedby={errorId}
{...inputProps}
/>
{description && <Description text={description} />}
<div className="min-h-[32px] px-4 pb-3 pt-1">
{errorId ? <ErrorList id={errorId} errors={errors} /> : null}
</div>
</div>
)
}

export function HeightField({
labelProps,
inputProps,
errors,
className,
}: {
labelProps: React.LabelHTMLAttributes<HTMLLabelElement>
inputProps: React.InputHTMLAttributes<HTMLInputElement>
errors?: ListOfErrors
className?: string
}) {
const fallbackId = useId()
const id = inputProps.id ?? fallbackId
const errorId = errors?.length ? `${id}-error` : undefined
return (
<div className={className}>
<Label htmlFor={id} {...labelProps} />
<Input
id={id}
aria-invalid={errorId ? true : undefined}
aria-describedby={errorId}
{...inputProps}
/>
</div>
)
}

export function DatePickerField({
labelProps,
errors,
Expand Down
18 changes: 11 additions & 7 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,15 @@ function App() {
const userIsAdmin = user?.roles.find(role => role.name === 'admin')

let nav = (
<Button asChild size="sm" variant="default">
<Button asChild variant="default">
<Link to="/login">Log In</Link>
</Button>
)
if (user) {
nav = (
<div className="flex grow items-center justify-between gap-1 sm:justify-end">
<div className="flex items-center justify-start gap-1">
<Button asChild className="px-4" size="sm" variant="default">
<Button asChild className="px-4" variant="default">
<Link to="/calendar" className="flex gap-2">
<Icon className="text-body-md" name="calendar" />
<span className="xsm:inline hidden">Calendar</span>
Expand Down Expand Up @@ -206,14 +206,18 @@ function App() {
<div className="font-light">Equestrian</div>
<div className="font-bold">Volunteer Scheduler</div>
</Link>
<div className="flex items-center justify-start gap-1">
<Link to="/tos" className="text-sm mr-2">Terms of Service</Link>
<Link to="/privacy" className="text-sm">Privacy Policy</Link>
<div className="flex items-center justify-start gap-1">
<Link to="/tos" className="mr-2 text-sm">
Terms of Service
</Link>
<Link to="/privacy" className="text-sm">
Privacy Policy
</Link>
</div>

<ThemeSwitch userPreference={data.requestInfo.session.theme} />
</div>

<div className="h-5" />
<Confetti confetti={data.flash?.confetti} />
<Toaster />
Expand Down Expand Up @@ -250,7 +254,7 @@ function UserDropdown() {
alt={user.name ?? user.username}
src={getUserImgSrc(user.imageId)}
/>
<span className="text-body-sm font-bold hidden sm:inline">
<span className="hidden text-body-sm font-bold sm:inline">
{user.name ?? user.username}
</span>
</Link>
Expand Down
1 change: 0 additions & 1 deletion app/routes/_auth+/forgot-password/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export async function action({ request }: DataFunctionArgs) {
const formData = await request.formData()
const submission = parse(formData, {
schema: forgotPasswordSchema,
acceptMultipleErrors: () => true,
})
if (submission.intent !== 'submit') {
return json({ status: 'idle', submission } as const)
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth+/forgot-password_.verify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async function validate(request: Request, body: FormData | URLSearchParams) {
return
}
}),
acceptMultipleErrors: () => true,

async: true,
})

Expand Down
16 changes: 12 additions & 4 deletions app/routes/_auth+/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export async function action({ request }: DataFunctionArgs) {
}
})
},
acceptMultipleErrors: () => true,

async: true,
})
if (submission.intent !== 'submit') {
Expand Down Expand Up @@ -169,7 +169,11 @@ export default function OnboardingPage() {
<Spacer size="xs" />
<Form method="POST" className="mx-auto w-full max-w-sm" {...form.props}>
<Field
labelProps={{ htmlFor: fields.username.id, children: 'Username (Cannot be an email address)' }}
labelProps={{
htmlFor: fields.username.id,
children: 'Username',
}}
description="Used for logging in (cannot be an email address)"
inputProps={{
...conform.input(fields.username),
autoComplete: 'username',
Expand All @@ -180,15 +184,19 @@ export default function OnboardingPage() {
errors={fields.username.errors}
/>
<Field
labelProps={{ htmlFor: fields.name.id, children: 'Name' }}
labelProps={{ htmlFor: fields.name.id, children: 'Full Name' }}
inputProps={{
...conform.input(fields.name),
autoComplete: 'name',
}}
errors={fields.name.errors}
/>
<Field
labelProps={{ htmlFor: fields.phone.id, children: 'Phone Number' }}
labelProps={{
htmlFor: fields.phone.id,
children: 'Phone Number',
}}
description="Must be 10 digits"
inputProps={{
...conform.input(fields.phone, { type: 'tel' }),
autoComplete: 'tel',
Expand Down
1 change: 0 additions & 1 deletion app/routes/_auth+/reset-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export async function action({ request }: DataFunctionArgs) {
const formData = await request.formData()
const submission = parse(formData, {
schema: resetPasswordSchema,
acceptMultipleErrors: () => true,
})
if (submission.intent !== 'submit') {
return json({ status: 'idle', submission } as const)
Expand Down
12 changes: 5 additions & 7 deletions app/routes/_auth+/signup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@ export const verificationType = 'onboarding'

const signupSchema = z.object({
email: emailSchema,
signupPassword: z
.string()
.min(1, {
message:
'Please fill this in with the password given to you by the volunteer coordinator.',
}),
signupPassword: z.string().min(1, {
message:
'Please fill this in with the password given to you by the volunteer coordinator.',
}),
})

export async function action({ request }: DataFunctionArgs) {
Expand Down Expand Up @@ -70,7 +68,7 @@ export async function action({ request }: DataFunctionArgs) {
}
})
},
acceptMultipleErrors: () => true,

async: true,
})
if (submission.intent !== 'submit') {
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth+/signup_.verify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async function validate(request: Request, body: URLSearchParams | FormData) {
return
}
}),
acceptMultipleErrors: () => true,

async: true,
})
if (submission.intent !== 'submit') {
Expand Down
93 changes: 62 additions & 31 deletions app/routes/_marketing+/privacy.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,87 @@
import {
type V2_MetaFunction
} from '@remix-run/node'
import { type V2_MetaFunction } from '@remix-run/node'

export const meta: V2_MetaFunction = () => {
return [
{
title: "Privacy Policy | TrotTrack.org",
{
title: 'Privacy Policy | TrotTrack.org',
},
{
property: "og:title",
content: "Privacy Policy | TrotTrack.org",
property: 'og:title',
content: 'Privacy Policy | TrotTrack.org',
},
{
name: "description",
content: "Read our Privacy Policy for TrotTrack.org, a nonprofit equestrian volunteer system that connects volunteers with equestrian organizations.",
name: 'description',
content:
'Read our Privacy Policy for TrotTrack.org, a nonprofit equestrian volunteer system that connects volunteers with equestrian organizations.',
},
];
};
]
}

export default function PrivacyRoute() {
return (
<div className="container flex min-h-full flex-col justify-center pb-32 pt-20">
<div className="mx-auto w-full max-w-lg">
<h1 className="text-2xl font-bold">Privacy Policy</h1>
</div>
<p className="text-lg">By using our website and services, you agree to comply with the following privacy policy:</p>
<div className="container prose flex min-h-full flex-col justify-center pb-32 pt-20 dark:prose-invert">
<h1>Privacy Policy</h1>
<p className="text-lg">
By using our website and services, you agree to comply with the
following privacy policy:
</p>
<section>
<h2 className="text-xl font-bold">1. Information Collection</h2>
<p>We collect certain information when you use our website and services. This may include personal information such as your name, email address, and other contact details. We also collect non-personal information such as your IP address and browsing behavior.</p>
<h2>1. Information Collection</h2>
<p>
We collect certain information when you use our website and services.
This may include personal information such as your name, email
address, and other contact details. We also collect non-personal
information such as your IP address and browsing behavior.
</p>
</section>
<section>
<h2 className="text-xl font-bold">2. Use of Information</h2>
<p>We use the information we collect to provide and improve our website and services. We may use your personal information to communicate with you, respond to your inquiries, and send you relevant updates and notifications.</p>
<h2>2. Use of Information</h2>
<p>
We use the information we collect to provide and improve our website
and services. We may use your personal information to communicate with
you, respond to your inquiries, and send you relevant updates and
notifications.
</p>
</section>
<section>
<h2 className="text-xl font-bold">3. Information Sharing</h2>
<p>We may share your information with third-party service providers who assist us in operating our website and services. We may also share your information when required by law or to protect our rights and interests.</p>
<h2>3. Information Sharing</h2>
<p>
We may share your information with third-party service providers who
assist us in operating our website and services. We may also share
your information when required by law or to protect our rights and
interests.
</p>
</section>
<section>
<h2 className="text-xl font-bold">4. Data Security</h2>
<p>We take reasonable measures to protect the security of your information. However, please note that no method of transmission over the internet or electronic storage is completely secure.</p>
<h2>4. Data Security</h2>
<p>
We take reasonable measures to protect the security of your
information. However, please note that no method of transmission over
the internet or electronic storage is completely secure.
</p>
</section>
<section>
<h2 className="text-xl font-bold">5. Cookies</h2>
<p>We use cookies to enhance your browsing experience and provide personalized content. You can choose to disable cookies in your browser settings, but please note that some features of our website may not function properly.</p>
<h2>5. Cookies</h2>
<p>
We use cookies to enhance your browsing experience and provide
personalized content. You can choose to disable cookies in your
browser settings, but please note that some features of our website
may not function properly.
</p>
</section>
<section>
<h2 className="text-xl font-bold">6. Changes to this Privacy Policy</h2>
<p>We may update this privacy policy from time to time. Any changes will be posted on this page, and the revised policy will be effective immediately upon posting.</p>
<p>By using our website and services, you agree to this privacy policy. If you do not agree with any part of this policy, please do not use our website or services.</p>
<h2>6. Changes to this Privacy Policy</h2>
<p>
We may update this privacy policy from time to time. Any changes will
be posted on this page, and the revised policy will be effective
immediately upon posting.
</p>
<p>
By using our website and services, you agree to this privacy policy.
If you do not agree with any part of this policy, please do not use
our website or services.
</p>
</section>
</div>
);
)
}

Loading
Loading