Shop about page

This commit is contained in:
Thastertyn committed 2025-03-18 15:26:16 +01:00
1 parent 260bef98cd
commit 09ddb05efe
13 files changed
+594 -39

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ export default function Dashboard() {
<>
{/* ===== Top Heading ===== */}
<Header>
<TopNav links={topNav} />
<Search />
<div className="ml-auto flex items-center space-x-4">
<Search />
<ThemeSwitch />
+2 -2
View File
@@ -62,10 +62,10 @@ export default function Products() {
<Main fixed>
<div>
<h1 className="text-2xl font-bold tracking-tight">
App Integrations
Products
</h1>
<p className="text-muted-foreground">
Here&apos;s a list of your apps for the integration!
Here&apos;s a list of your products!
</p>
</div>
<div className="my-4 flex items-end justify-between sm:my-0 sm:items-center">
@@ -0,0 +1,10 @@
import ContentSection from "../components/content-section";
import PasswordChangeForm from "./security-form";
export default function SettingsSecurity() {
return (
<ContentSection title="Security" desc="You can change your password here">
<PasswordChangeForm />
</ContentSection>
);
}
@@ -0,0 +1,116 @@
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from "@/components/ui/form";
import { PasswordInput } from "@/components/password-input";
import { toast } from "@/hooks/useToast";
const passwordChangeSchema = z
.object({
oldPassword: z.string().min(1, {
message: "Please enter your existing password"
}),
newPassword: z
.string()
.min(1, { message: "Please enter your new password" })
.min(8, { message: "Password must be at least 8 characters long" })
.max(128, { message: "Password must be at most 128 characters long" })
.refine((password) => /[A-Z]/.test(password), {
message: "Password must contain at least one uppercase letter"
})
.refine((password) => /[a-z]/.test(password), {
message: "Password must contain at least one lowercase letter"
})
.refine((password) => /\d/.test(password), {
message: "Password must contain at least one number"
})
.refine((password) => /[@$!%*?&]/.test(password), {
message:
"Password must contain at least one special character (@, $, !, %, *, ?, &)"
}),
confirmNewPassword: z.string().min(1, {
message: "Please confirm your new password"
})
})
.refine((data) => data.newPassword === data.confirmNewPassword, {
message: "Passwords don't match.",
path: ["confirmNewPassword"]
});
type PasswordChangeFormValues = z.infer<typeof passwordChangeSchema>;
export default function PasswordChangeForm() {
const form = useForm<PasswordChangeFormValues>({
resolver: zodResolver(passwordChangeSchema),
defaultValues: {
oldPassword: "",
newPassword: "",
confirmNewPassword: ""
}
});
function onSubmit(data: PasswordChangeFormValues) {
toast({
title: "Password Changed (MOCK)",
description: "Your password has been updated."
});
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="oldPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Existing Password</FormLabel>
<FormControl>
<PasswordInput
placeholder="Enter current password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel>New Password</FormLabel>
<FormControl>
<PasswordInput placeholder="Enter new password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirmNewPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Confirm New Password</FormLabel>
<FormControl>
<PasswordInput placeholder="Confirm new password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Change Password</Button>
</form>
</Form>
);
}
@@ -0,0 +1,241 @@
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { PhoneInput } from "@/components/ui/phone-number-input";
import { Input } from "@/components/ui/input";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
const shopAboutSchema = z.object({
name: z.string().min(1, "Name is required"),
description: z.string().min(1, "Description is required"),
currency: z.string().min(1, "Currency is required"),
contact_email: z.string().email("Invalid email"),
contact_phone_number: z.string().min(1, "Phone number is required"),
address: z.object({
street: z.string(),
city: z.string(),
state: z.string().optional(),
postal_code: z.string(),
country: z.string()
}),
status: z.enum(["active", "inactive", "suspended"])
});
type ShopAboutFormValues = z.infer<typeof shopAboutSchema>;
export function ShopAboutForm() {
const defaultValues: ShopAboutFormValues = {
name: "My Shop",
description: "This is a sample shop description.",
currency: "USD",
contact_email: "user@example.com",
contact_phone_number: "+266018975510",
address: {
street: "123 Main St",
city: "Metropolis",
state: "",
postal_code: "12345",
country: "USA"
},
status: "inactive"
};
const form = useForm<ShopAboutFormValues>({
resolver: zodResolver(shopAboutSchema),
defaultValues,
mode: "onChange"
});
function onSubmit(data: ShopAboutFormValues) {
console.log("Submitted shop about data:", data);
}
return (
<ScrollArea
orientation="horizontal"
type="always"
className="hidden w-full min-w-40 bg-background px-1 md:block">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-xl">Basic Details</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Shop Name</FormLabel>
<FormControl>
<Input placeholder="Enter shop name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="currency"
render={({ field }) => (
<FormItem>
<FormLabel>Currency</FormLabel>
<FormControl>
<Input placeholder="e.g., USD" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea placeholder="Describe your shop" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-xl">Contact Information</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<FormField
control={form.control}
name="contact_email"
render={({ field }) => (
<FormItem>
<FormLabel>Contact Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="user@example.com"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="contact_phone_number"
render={({ field }) => (
<FormItem>
<FormLabel>Phone Number</FormLabel>
<FormControl>
<PhoneInput {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-xl">Address</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<FormField
control={form.control}
name="address.street"
render={({ field }) => (
<FormItem>
<FormLabel>Street Address</FormLabel>
<FormControl>
<Input placeholder="123 Shop Street" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="address.city"
render={({ field }) => (
<FormItem>
<FormLabel>City</FormLabel>
<FormControl>
<Input placeholder="City" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address.state"
render={({ field }) => (
<FormItem>
<FormLabel>State/Province</FormLabel>
<FormControl>
<Input placeholder="State" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address.postal_code"
render={({ field }) => (
<FormItem>
<FormLabel>Postal Code</FormLabel>
<FormControl>
<Input placeholder="Postal Code" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address.country"
render={({ field }) => (
<FormItem>
<FormLabel>Country</FormLabel>
<FormControl>
<Input placeholder="Country" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
<div className="flex justify-end gap-4">
<Button variant="outline">Cancel</Button>
<Button type="submit">Save Changes</Button>
</div>
</form>
</Form>
</ScrollArea>
);
}
@@ -0,0 +1,86 @@
import { useState } from "react";
import { useLocation, useNavigate } from "@tanstack/react-router";
import {
Select,
SelectContent,
SelectTrigger,
SelectValue
} from "@/components/ui/select";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { IconBuildingStore, IconClock, IconLink } from "@tabler/icons-react";
export default function ShopSidebar({ ...props }) {
const { pathname } = useLocation();
const navigate = useNavigate();
const [val, setVal] = useState(pathname ?? "/settings");
const handleSelect = (e: string) => {
setVal(e);
navigate({ to: e });
};
return (
<>
<div className="p-1 md:hidden">
<Select value={val} onValueChange={handleSelect}>
<SelectTrigger className="h-12 sm:w-48">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent></SelectContent>
</Select>
</div>
<Card className="sticky h-fit top-2">
<CardHeader>
<CardTitle className="font-heading text-2xl">Shop Preview</CardTitle>
<CardDescription className="text-muted-foreground">
Update your shop details and preferences
</CardDescription>
</CardHeader>
<CardContent>
<div className="relative mb-4 aspect-square w-full overflow-hidden rounded-lg">
<img
alt="Shop preview"
src="/images/placeholder.png"
width={400}
height={400}
className="object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-background/90 to-background/0 p-4">
<Badge variant="secondary" className="mb-2">
Status: Inactive
</Badge>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center gap-2">
<IconBuildingStore className="h-4 w-4 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
Complete your profile to activate your shop
</p>
</div>
<div className="flex items-center gap-2">
<IconClock className="h-4 w-4 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
Set business hours
</p>
</div>
<div className="flex items-center gap-2">
<IconLink className="h-4 w-4 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
Add social media links
</p>
</div>
</div>
</CardContent>
</Card>
</>
);
}
+48
View File
@@ -0,0 +1,48 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle
} from "@/components/ui/card";
import { Header } from "@/components/layout/header";
import { Main } from "@/components/layout/main";
import { TopNav } from "@/components/layout/top-nav";
import { ProfileDropdown } from "@/components/profile-dropdown";
import { ThemeSwitch } from "@/components/theme-switch";
import { ShopAboutForm } from "./components/shop-about-form";
import { Search } from "@/components/search";
import ShopSidebar from "./components/shop-sidebar";
import { Separator } from "@/components/ui/separator";
export default function AboutPage() {
return (
<>
{/* ===== Top Heading ===== */}
<Header>
<Search />
<div className="ml-auto flex items-center space-x-4">
<ThemeSwitch />
<ProfileDropdown />
</div>
</Header>
{/* ===== Main ===== */}
<Main>
<div className="my-4">
<h1 className="text-2xl font-bold tracking-tight">About Your Shop</h1>
<p className="text-muted-foreground">
Here&apos;s an overview info about your shop
</p>
</div>
<Separator />
<div className="my-4 flex flex-row gap-4">
<ShopSidebar />
<ShopAboutForm />
</div>
</Main>
</>
);
}