diff --git a/app/(auth)/forgot-password/page.tsx b/app/(auth)/forgot-password/page.tsx index ed0471f6..97ba826d 100644 --- a/app/(auth)/forgot-password/page.tsx +++ b/app/(auth)/forgot-password/page.tsx @@ -1,5 +1,6 @@ 'use client'; import { useForm } from 'react-hook-form'; +import { useState } from 'react'; import { createClient } from '@/app/lib/supabase/browser-client'; type ForgotPasswordFormValues = { @@ -8,7 +9,8 @@ type ForgotPasswordFormValues = { export default function ForgotPasswordPage() { const supabase = createClient(); - + const [errorMessage, setErrorMessage] = useState(''); + const [successMessage, setSuccessMessage] = useState(''); const { register, handleSubmit, @@ -19,14 +21,19 @@ export default function ForgotPasswordPage() { }); const onSubmit = async (values: ForgotPasswordFormValues) => { + setErrorMessage(''); + setSuccessMessage(''); + const { error } = await supabase.auth.resetPasswordForEmail(values.email); if (error) { - alert(error.message); + setErrorMessage('Failed to send reset password email: ' + error.message); return; } - alert('Instructions to reset your password have been sent to your email.'); + setSuccessMessage( + 'Instructions to reset your password have been sent to your email.', + ); }; return ( @@ -53,6 +60,9 @@ export default function ForgotPasswordPage() { + + {errorMessage &&
{errorMessage}
} + {successMessage &&{successMessage}
} ); diff --git a/app/(auth)/reset-password/page.tsx b/app/(auth)/reset-password/page.tsx index c7e7ddfe..98bd3374 100644 --- a/app/(auth)/reset-password/page.tsx +++ b/app/(auth)/reset-password/page.tsx @@ -3,6 +3,7 @@ import { useForm, useWatch } from 'react-hook-form'; import { createClient } from '@/app/lib/supabase/browser-client'; import { useRouter } from 'next/navigation'; +import { useState } from 'react'; type ResetPasswordFormValues = { password: string; @@ -12,6 +13,8 @@ type ResetPasswordFormValues = { export default function ResetPasswordPage() { const supabase = createClient(); const router = useRouter(); + const [errorMessage, setErrorMessage] = useState(''); + const [successMessage, setSuccessMessage] = useState(''); const { register, @@ -29,16 +32,19 @@ export default function ResetPasswordPage() { }); const onSubmit = async (values: ResetPasswordFormValues) => { + setErrorMessage(''); + setSuccessMessage(''); + const { error } = await supabase.auth.updateUser({ password: values.password, }); if (error) { - alert(error.message); + setErrorMessage(error.message); return; } - alert('Your password has been updated.'); + setSuccessMessage('Your password has been updated.'); router.push('/home'); }; @@ -82,6 +88,9 @@ export default function ResetPasswordPage() {{errorMessage}
} + {successMessage &&{successMessage}
} + diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx index e09aa9dd..3c8a4283 100644 --- a/app/(auth)/sign-in/page.tsx +++ b/app/(auth)/sign-in/page.tsx @@ -2,6 +2,7 @@ import { createClient } from '@/app/lib/supabase/browser-client'; import { useForm } from 'react-hook-form'; +import { useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; @@ -14,20 +15,25 @@ export default function SignInPage() { const { register, handleSubmit, - formState: { errors }, + formState: { errors, isSubmitting }, } = useFormPassword is required.
)}{errorMessage}
} + {successMessage &&{successMessage}
} ); } diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx index 55d69f0b..87d18dd2 100644 --- a/app/(auth)/sign-up/page.tsx +++ b/app/(auth)/sign-up/page.tsx @@ -4,7 +4,6 @@ import { useForm, useWatch, SubmitHandler } from 'react-hook-form'; import { createClient } from '@/app/lib/supabase/browser-client'; import { useState } from 'react'; import Link from 'next/link'; -import { useRouter } from 'next/navigation'; type Inputs = { firstName: string; @@ -22,14 +21,17 @@ export default function SignUpPage() { formState: { errors }, } = useForm{errorMessage}
} + {successMessage &&{successMessage}
} ); } diff --git a/app/(main)/components/DeleteTicketButton.tsx b/app/(main)/components/DeleteTicketButton.tsx index dbd352c0..d0ce3fad 100644 --- a/app/(main)/components/DeleteTicketButton.tsx +++ b/app/(main)/components/DeleteTicketButton.tsx @@ -2,26 +2,39 @@ import { deleteTicket } from '@/app/actions/ticket'; import { usePathname, useRouter } from 'next/navigation'; +import { useState, useTransition } from 'react'; export default function DeleteTicketButton({ ticketId }: { ticketId: string }) { const pathname = usePathname(); const router = useRouter(); + const [errorMessage, setErrorMessage] = useState(''); + const [successMessage, setSuccessMessage] = useState(''); + const [isPending, startTransition] = useTransition(); async function handleDelete() { - const result = await deleteTicket(ticketId); + setErrorMessage(''); + setSuccessMessage(''); + startTransition(async () => { + const result = await deleteTicket(ticketId); - if (!result.success) { - console.error('Failed to delete ticket:', result.error); - return; - } + if (!result.success) { + setErrorMessage('Failed to delete ticket.' + result.error); + return; + } - const parentPath = pathname.split('/').slice(0, -1).join('/') || '/'; - router.push(parentPath); + setSuccessMessage('Ticket deleted successfully!'); + const parentPath = pathname.split('/').slice(0, -1).join('/') || '/'; + router.push(parentPath); + }); } return ( - + <> + {errorMessage &&{errorMessage}
} + {errorMessage && ( ++ {errorMessage} +
+ )} {hasChanged && ( <> - - + + > )} diff --git a/app/(main)/components/OutOfStockTicketItemCard.tsx b/app/(main)/components/OutOfStockTicketItemCard.tsx index 330a77e3..eebb25b7 100644 --- a/app/(main)/components/OutOfStockTicketItemCard.tsx +++ b/app/(main)/components/OutOfStockTicketItemCard.tsx @@ -1,4 +1,5 @@ 'use client'; + import { updateTicketItemDescription } from '@/app/actions/ticket'; import { useState } from 'react'; @@ -9,33 +10,73 @@ interface OutOfStockTicketItemCardProps { export default function OutOfStockTicketItemCard({ ticketItemId, - freeTextDescription, + freeTextDescription: initialDescription, }: OutOfStockTicketItemCardProps) { - const [description, setDescription] = useState(freeTextDescription); + const [description, setDescription] = useState(initialDescription); const [isChanged, setIsChanged] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const [isSaving, setIsSaving] = useState(false); const handleChange = (e: React.ChangeEvent{errorMessage}
}+ {errorMessage} +
+ )} + > ); } diff --git a/app/(main)/components/TicketStatusDropdown.tsx b/app/(main)/components/TicketStatusDropdown.tsx index d8fed11f..5009872f 100644 --- a/app/(main)/components/TicketStatusDropdown.tsx +++ b/app/(main)/components/TicketStatusDropdown.tsx @@ -18,6 +18,7 @@ export default function TicketStatusDropdown({ const [originalStatus, setOriginalStatus] = useState{errorMessage}
} + {successMessage &&{successMessage}
} + {formState.isDirty && ( <> -{errorMessage}
} + {successMessage &&{successMessage}
} +{errorMessage}
} + {successMessage &&{successMessage}
} + + {bothFilled && ( +{errorMessage}
} + {successMessage &&{successMessage}
} +{errorMessage}
} + {successMessage &&{successMessage}
} +{errorMessage}
} + {successMessage &&{successMessage}
} + {/* search results */} {searchData.map((u) => ({errorMessage}
} + {successMessage &&{successMessage}
} + > ); } diff --git a/app/(main)/test/components/DonationsTestComponent.tsx b/app/(main)/test/components/DonationsTestComponent.tsx index e180fb13..84dfdc5b 100644 --- a/app/(main)/test/components/DonationsTestComponent.tsx +++ b/app/(main)/test/components/DonationsTestComponent.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; export default function DonationsTestComponent() { const [idToDelete, setIdToDelete] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); const handleCreate = async () => { try { @@ -38,8 +39,9 @@ export default function DonationsTestComponent() { }; const handleDelete = async () => { + setErrorMessage(''); if (!idToDelete) { - alert('Please enter a valid UUID to delete'); + setErrorMessage('Please enter a valid UUID to delete'); return; } @@ -58,6 +60,9 @@ export default function DonationsTestComponent() {{errorMessage}
+ )} ); } diff --git a/supabase/config.toml b/supabase/config.toml index 50ea9efb..f22e2af2 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -1,412 +1,412 @@ -# For detailed configuration reference documentation, visit: -# https://supabase.com/docs/guides/local-development/cli/config -# A string used to distinguish different Supabase projects on the same host. Defaults to the -# working directory name when running `supabase init`. -project_id = "path-app" - -[api] -enabled = true -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. `public` and `graphql_public` schemas are included by default. -schemas = ["public", "graphql_public"] -# Extra schemas to add to the search_path of every request. -extra_search_path = ["public", "extensions"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[api.tls] -# Enable HTTPS endpoints locally using a self-signed certificate. -enabled = false -# Paths to self-signed certificate pair. -# cert_path = "../certs/my-cert.pem" -# key_path = "../certs/my-key.pem" - -[db] -# Port to use for the local database URL. -port = 54322 -# Port used by db diff command to initialize the shadow database. -shadow_port = 54320 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 17 - -[db.pooler] -enabled = false -# Port to use for the local connection pooler. -port = 54329 -# Specifies when a server connection can be reused by other clients. -# Configure one of the supported pooler modes: `transaction`, `session`. -pool_mode = "transaction" -# How many server connections to allow per user/database pair. -default_pool_size = 20 -# Maximum number of client connections allowed. -max_client_conn = 100 - -# [db.vault] -# secret_key = "env(SECRET_VALUE)" - -[db.migrations] -# If disabled, migrations will be skipped during a db push or reset. -enabled = true -# Specifies an ordered list of schema files that describe your database. -# Supports glob patterns relative to supabase directory: "./schemas/*.sql" -schema_paths = [ - "./schemas/roles.sql", - "./schemas/functions/can_assign_role.sql", - "./schemas/users.sql", - "./schemas/user_roles.sql", - "./schemas/functions/custom_access_token_hook.sql", - "./schemas/functions/handle_new_user.sql", - "./schemas/functions/handle_user_email_update.sql", - "./schemas/functions/handle_user_role_update.sql", - "./schemas/categories.sql", - "./schemas/subcategories.sql", - "./schemas/inventory_items.sql", - "./schemas/stores.sql", - "./schemas/functions/can_manage_store.sql", - "./schemas/functions/can_manage_store_public.sql", - "./schemas/store_admins.sql", - "./schemas/functions/handle_new_store_admin.sql", - "./schemas/store_items.sql", - "./schemas/tickets.sql", - "./schemas/ticket_items.sql", - "./schemas/donations.sql", - "./schemas/functions/handle_ticket_fulfillment.sql", - "./schemas/*.sql", - "./schemas/functions/*.sql" -] - -[db.seed] -# If enabled, seeds the database after migrations during a db reset. -enabled = true -# Specifies an ordered list of seed files to load during db reset. -# Supports glob patterns relative to supabase directory: "./seeds/*.sql" -sql_paths = [ - "./seeds/roles.sql", - "./seeds/auth.sql", - "./seeds/user_roles.sql", - "./seeds/categories.sql", - "./seeds/subcategories.sql", - "./seeds/inventory_items.sql", - "./seeds/stores.sql", - "./seeds/store_admins.sql", - "./seeds/store_items.sql", - "./seeds/tickets.sql", - "./seeds/ticket_items.sql", - "./seeds/donations.sql", - "./seeds/*.sql" -] - -[db.network_restrictions] -# Enable management of network restrictions. -enabled = false -# List of IPv4 CIDR blocks allowed to connect to the database. -# Defaults to allow all IPv4 connections. Set empty array to block all IPs. -allowed_cidrs = ["0.0.0.0/0"] -# List of IPv6 CIDR blocks allowed to connect to the database. -# Defaults to allow all IPv6 connections. Set empty array to block all IPs. -allowed_cidrs_v6 = ["::/0"] - -[realtime] -enabled = true -# Bind realtime via either IPv4 or IPv6. (default: IPv4) -# ip_version = "IPv6" -# The maximum length in bytes of HTTP request headers. (default: 4096) -# max_header_length = 4096 - -[studio] -enabled = true -# Port to use for Supabase Studio. -port = 54323 -# External URL of the API server that frontend connects to. -api_url = "http://127.0.0.1" -# OpenAI API Key to use for Supabase AI in the Supabase Studio. -openai_api_key = "env(OPENAI_API_KEY)" - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -enabled = true -# Port to use for the email testing server web interface. -port = 54324 -# Uncomment to expose additional ports for testing user applications that send emails. -# smtp_port = 54325 -# pop3_port = 54326 -# admin_email = "admin@email.com" -# sender_name = "Admin" - -[storage] -enabled = true -# The maximum file size allowed (e.g. "5MB", "500KB"). -file_size_limit = "50MiB" - -# Image transformation API is available to Supabase Pro plan. -# [storage.image_transformation] -# enabled = true - -# Uncomment to configure local storage buckets -# [storage.buckets.images] -# public = false -# file_size_limit = "50MiB" -# allowed_mime_types = ["image/png", "image/jpeg"] -# objects_path = "./images" - -[storage.buckets.profile_photos] -public = true -file_size_limit = "200KB" -allowed_mime_types = ["image/png", "image/jpeg"] - -[auth] -enabled = true -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://localhost:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://localhost:3000"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). -jwt_expiry = 900 -# JWT issuer URL. If not set, defaults to the local API URL (http://127.0.0.1: