Skip to content

Commit

Permalink
Add contact info form
Browse files Browse the repository at this point in the history
  • Loading branch information
mxsaad committed Feb 16, 2024
1 parent 64ad3cf commit 1b354d7
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/(root)/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default async function Profile({ params }: { params: { username: string }
<div className="flex flex-col items-center">
<Tabs defaultValue="view" className="w-full max-w-prose text-center">
{`%40${user?.username}` === params.username.toLowerCase() && ( // %40 = @ symbol in URL
<TabsList className="mt-6 mb-4">
<TabsList className="mt-10 mb-4">
<TabsTrigger value="view" className="flex gap-2 items-center">
<EyeOpenIcon /> View
</TabsTrigger>
Expand Down
87 changes: 87 additions & 0 deletions components/forms/contact-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"use client"

import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
FormDescription,
} from "@/components/ui/form"
import { PhoneInput } from "@/components/ui/phone-input"
import { isValidPhoneNumber } from "react-phone-number-input";

// Form Schema
const formSchema: z.Schema = z.object({
email: z.string({ required_error: "Email is required" }).email(),
phone: z.string({ required_error: "Phone number is required" })
.refine(isValidPhoneNumber, { message: "Invalid phone number" }),
})

export default function ContactForm() {
// Form Definition
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
phone: "",
},
})

// Submit
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="container flex flex-col gap-4">
<p className="text-muted-foreground">
We will never use this information to contact you. It is only provided to matches with your permission for them to contact you.
<br /><br />
Note: Sisters are expected to fear Allah (ﷻ) and only provide the contact information of their <i>wali</i> (guardian) with his permission.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="Enter email" {...field} />
</FormControl>
<FormMessage />
<FormDescription>
This is not your account email, it is only for matches to reach you at.
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Phone</FormLabel>
<FormControl>
<PhoneInput defaultCountry="US" {...field} />
</FormControl>
<FormMessage />
<FormDescription>
The phone number matches can reach you at.
</FormDescription>
</FormItem>
)}
/>
</div>
<Button type="submit" className="w-fit self-end">Save</Button>
</form>
</Form>
)
}
10 changes: 10 additions & 0 deletions components/shared/edit-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import OccupationForm from "../forms/occupation-form"
import FamilyForm from "../forms/family-form"
import GoalsForm from "../forms/goals-form"
import SpouseForm from "../forms/spouse-form"
import ContactForm from "../forms/contact-form"

export default function EditProfile() {
return (
Expand Down Expand Up @@ -78,6 +79,15 @@ export default function EditProfile() {
<SpouseForm />
</AccordionContent>
</AccordionItem>
{/* Contact Information */}
<AccordionItem value="contact">
<AccordionTrigger className="text-xl font-bold">
Contact Information
</AccordionTrigger>
<AccordionContent>
<ContactForm />
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
)
Expand Down
156 changes: 156 additions & 0 deletions components/ui/phone-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { CheckIcon, CaretSortIcon } from "@radix-ui/react-icons";

import * as React from "react";

import * as RPNInput from "react-phone-number-input";
import * as RPNInputSimple from "react-phone-number-input/input";

import flags from "react-phone-number-input/flags";

import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Input, InputProps } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";

import { cn } from "@/lib/utils";

export type PhoneInputValue = RPNInput.Value;

type PhoneInputSimpleProps = React.ComponentProps<
typeof RPNInputSimple.default
>;

const PhoneInputSimple = ({
className,
children,
...props
}: PhoneInputSimpleProps) => (
<RPNInputSimple.default
placeholder="Enter a phone number"
inputComponent={Input}
{...props}
/>
);
PhoneInputSimple.displayName = "PhoneInputSimple";

type PhoneInputProps = React.ComponentProps<typeof RPNInput.default>;

const PhoneInput = ({ className, children, ...props }: PhoneInputProps) => (
<RPNInput.default
className={cn("flex", className)}
placeholder={"Enter a phone number"}
flagComponent={FlagComponent}
countrySelectComponent={CountrySelect}
inputComponent={InputComponent}
{...props}
/>
);

PhoneInput.displayName = "PhoneInput";

const InputComponent = React.forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => (
<Input
className={cn("rounded-s-none rounded-e-lg", className)}
{...props}
ref={ref}
/>
),
);

type CountrySelectOption = { label: string; value: RPNInput.Country };

type CountrySelectProps = {
disabled?: boolean;
value: RPNInput.Country;
onChange: (value: RPNInput.Country) => void;
options: CountrySelectOption[];
};

const CountrySelect = ({
disabled,
value,
onChange,
options,
}: CountrySelectProps) => {
const handleSelect = React.useCallback(
(country: RPNInput.Country) => {
onChange(country);
},
[onChange],
);

return (
<Popover>
<PopoverTrigger asChild>
<Button
type="button"
variant={"outline"}
className={cn("rounded-e-none rounded-s-lg pl-3 pr-1 flex gap-1")}
disabled={disabled}>
<span className="flex items-center truncate">
<div className="bg-foreground/20 rounded-sm flex w-6 h-4">
{value && <FlagComponent country={value} countryName={value} />}
</div>
</span>
<CaretSortIcon className={`h-4 w-4 ${disabled ? "hidden" : ""}`} />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0">
<Command>
<CommandList>
<CommandInput placeholder="Search country..." />
<CommandEmpty>No country found.</CommandEmpty>
<CommandGroup>
{options
.filter((x) => x.value)
.map((option) => (
<CommandItem
className={"text-sm gap-2"}
key={option.value}
onSelect={() => handleSelect(option.value)}>
<FlagComponent
country={option.value}
countryName={option.label}
/>
<span>{option.label}</span>
<span className="text-foreground/50">
{`+${RPNInput.getCountryCallingCode(option.value)}`}
</span>
<CheckIcon
className={`ml-auto h-4 w-4 ${option.value === value ? "opacity-100" : "opacity-0"
}`}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};

const FlagComponent = ({ country, countryName }: RPNInput.FlagProps) => {
const Flag = flags[country];

return (
<span
className={"inline object-contain w-6 h-4 overflow-hidden rounded-sm"}>
{Flag && <Flag title={countryName} />}
</span>
);
};

export { PhoneInput, PhoneInputSimple };
55 changes: 55 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"react-day-picker": "^8.10.0",
"react-dom": "^18",
"react-hook-form": "^7.49.3",
"react-phone-number-input": "^3.3.9",
"sonner": "^1.4.0",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
Expand Down

0 comments on commit 1b354d7

Please sign in to comment.