From 4db42044ce78ff0b27543b6b90d1ea3455776423 Mon Sep 17 00:00:00 2001 From: Felix Schulze Date: Fri, 18 Oct 2024 13:12:19 +0200 Subject: [PATCH] styles, form, backend partial --- app/globals.css | 71 ++++++++-------- app/layout.tsx | 2 +- app/page.tsx | 36 +------- app/sign-up/page.tsx | 17 ++++ app/sign-up/sign-up-form.tsx | 124 +++++++++++++++++++++++++++ components/ui/calendar.tsx | 95 +++++++++++++++++++++ components/ui/input.tsx | 25 ++++++ components/ui/popover.tsx | 31 +++++++ components/ui/select.tsx | 160 +++++++++++++++++++++++++++++++++++ lib/actions.ts | 19 +++++ lib/listmonk.ts | 21 +++++ 11 files changed, 530 insertions(+), 71 deletions(-) create mode 100644 app/sign-up/page.tsx create mode 100644 app/sign-up/sign-up-form.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/select.tsx create mode 100644 lib/actions.ts create mode 100644 lib/listmonk.ts diff --git a/app/globals.css b/app/globals.css index cdc9ba3..54a53d1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -25,51 +25,52 @@ @layer base { :root { --background: 0 0% 100%; - --foreground: 240 10% 3.9%; + --foreground: 20 14.3% 4.1%; --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; + --card-foreground: 20 14.3% 4.1%; --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; + --popover-foreground: 20 14.3% 4.1%; + --primary: 24.6 95% 53.1%; + --primary-foreground: 60 9.1% 97.8%; + --secondary: 60 4.8% 95.9%; + --secondary-foreground: 24 9.8% 10%; + --muted: 60 4.8% 95.9%; + --muted-foreground: 25 5.3% 44.7%; + --accent: 60 4.8% 95.9%; + --accent-foreground: 24 9.8% 10%; --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 10% 3.9%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 20 5.9% 90%; + --input: 20 5.9% 90%; + --ring: 24.6 95% 53.1%; + --radius: 1rem; --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; - --radius: 0.5rem; } + .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; + --background: 20 14.3% 4.1%; + --foreground: 60 9.1% 97.8%; + --card: 20 14.3% 4.1%; + --card-foreground: 60 9.1% 97.8%; + --popover: 20 14.3% 4.1%; + --popover-foreground: 60 9.1% 97.8%; + --primary: 20.5 90.2% 48.2%; + --primary-foreground: 60 9.1% 97.8%; + --secondary: 12 6.5% 15.1%; + --secondary-foreground: 60 9.1% 97.8%; + --muted: 12 6.5% 15.1%; + --muted-foreground: 24 5.4% 63.9%; + --accent: 12 6.5% 15.1%; + --accent-foreground: 60 9.1% 97.8%; + --destructive: 0 72.2% 50.6%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 12 6.5% 15.1%; + --input: 12 6.5% 15.1%; + --ring: 20.5 90.2% 48.2%; --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; --chart-3: 30 80% 55%; diff --git a/app/layout.tsx b/app/layout.tsx index c32c8d6..da66ad2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -20,7 +20,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + -
-

Subscribe

- -

- -

-

- -

- -

- - -
- Produktnyheter för Schulze Network -

- -

- -

-
- - ); + return "helloworld"; } diff --git a/app/sign-up/page.tsx b/app/sign-up/page.tsx new file mode 100644 index 0000000..d4cb99c --- /dev/null +++ b/app/sign-up/page.tsx @@ -0,0 +1,17 @@ +import Image from "next/image"; +import SignUp from "./sign-up-form"; + +export default function Page() { + return ( +
+ Bangers and Mash GBG + +
+ ); +} diff --git a/app/sign-up/sign-up-form.tsx b/app/sign-up/sign-up-form.tsx new file mode 100644 index 0000000..9932f37 --- /dev/null +++ b/app/sign-up/sign-up-form.tsx @@ -0,0 +1,124 @@ +"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 { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { format } from "date-fns"; +import { CalendarIcon } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Calendar } from "@/components/ui/calendar"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { signupFormSubmit } from "@/lib/actions"; + +export const signupFormSchema = z.object({ + name: z.string().min(2, { message: "Name is required" }).max(50, { message: "Name is too long" }), + email: z.string().email({ message: "Email is invalid" }), + dob: z.date({ required_error: "Birthday is required" }), +}); + +export const youngestDate = new Date(new Date().setFullYear(new Date().getFullYear() - 21)); +export const oldestDate = new Date(new Date().setFullYear(new Date().getFullYear() - 100)); + +export default function SignUp() { + const form = useForm>({ + resolver: zodResolver(signupFormSchema), + defaultValues: { + name: "", + email: "", + dob: youngestDate, + }, + }); + function onSubmit(values: z.infer) { + signupFormSubmit(values); + } + return ( +
+ + ( + + Email + + + + + We will contact you here with information about events. + + + + )} + /> + ( + + Name + + + + Please enter your full name. + + + )} + /> + ( + + Date of birth + + + + + + + + date > youngestDate} + defaultMonth={field.value} + fromDate={oldestDate} + toDate={youngestDate} + captionLayout="dropdown" + /> + + + You must be over 21 to sign up. + + + )} + /> + + + + ); +} diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx new file mode 100644 index 0000000..b473cc2 --- /dev/null +++ b/components/ui/calendar.tsx @@ -0,0 +1,95 @@ +"use client"; + +import * as React from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { DayPicker } from "react-day-picker"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +export type CalendarProps = React.ComponentProps; + +function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { + return ( + , + IconRight: ({ ...props }) => , + Dropdown: ({ children, value, onChange, ...props }) => { + const options = React.Children.toArray(children) as React.ReactElement< + React.HTMLProps + >[]; + const selected = options.find((child) => child.props.value === value); + const handleChange = (value: string) => { + const changeEvent = { + target: { value }, + } as React.ChangeEvent; + onChange?.(changeEvent); + }; + return ( + + ); + }, + }} + {...props} + /> + ); +} +Calendar.displayName = "Calendar"; + +export { Calendar }; diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..a921025 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx new file mode 100644 index 0000000..a0ec48b --- /dev/null +++ b/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/components/ui/select.tsx b/components/ui/select.tsx new file mode 100644 index 0000000..cbe5a36 --- /dev/null +++ b/components/ui/select.tsx @@ -0,0 +1,160 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/lib/actions.ts b/lib/actions.ts new file mode 100644 index 0000000..41c989f --- /dev/null +++ b/lib/actions.ts @@ -0,0 +1,19 @@ +"use server"; + +import { z } from "zod"; +import { oldestDate, signupFormSchema, youngestDate } from "@/app/sign-up/sign-up-form"; +import listmonk from "./listmonk"; + +export async function signupFormSubmit(data: z.infer) { + if (data.dob > youngestDate || data.dob < oldestDate) { + return { error: "Invalid date of birth" }; + } + const listmonkData = { + email: data.email, + name: data.name, + attribs: { + dob: data.dob.toISOString(), + }, + }; + console.log(listmonkData); +} diff --git a/lib/listmonk.ts b/lib/listmonk.ts new file mode 100644 index 0000000..434d52b --- /dev/null +++ b/lib/listmonk.ts @@ -0,0 +1,21 @@ +import "server-only"; + +async function makeApiCall(url: string, options?: RequestInit) { + try { + const response = await fetch(url, options); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Error making API call:", error); + throw error; + } +} + +async function listmonk() { + const listmonkUrl = process.env.LISTMONK_URL ?? ""; + makeApiCall(listmonkUrl); +} + +export default listmonk;