Shop about page
This commit is contained in:
1 parent
260bef98cd
commit
09ddb05efe
13 files changed
+594
-39
No files matched your search
@@ -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 />
|
||||
|
||||
@@ -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's a list of your apps for the integration!
|
||||
Here'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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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's an overview info about your shop
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="my-4 flex flex-row gap-4">
|
||||
<ShopSidebar />
|
||||
|
||||
<ShopAboutForm />
|
||||
</div>
|
||||
</Main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user