feat: Frontend for Account Manager
This commit is contained in:
		
							parent
							
								
									09dce72dfc
								
							
						
					
					
						commit
						79552dd57c
					
				| @ -24,7 +24,7 @@ | |||||||
|     "@tailwindcss/typography": "^0.5.16", |     "@tailwindcss/typography": "^0.5.16", | ||||||
|     "@types/markdown-it": "^14.1.2", |     "@types/markdown-it": "^14.1.2", | ||||||
|     "autoprefixer": "^10.4.20", |     "autoprefixer": "^10.4.20", | ||||||
|     "bits-ui": "^0.22.0", |     "bits-ui": "0.22.0", | ||||||
|     "clsx": "^2.1.1", |     "clsx": "^2.1.1", | ||||||
|     "eslint": "^9.18.0", |     "eslint": "^9.18.0", | ||||||
|     "eslint-config-prettier": "^10.0.1", |     "eslint-config-prettier": "^10.0.1", | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="max-h-screen p-2" style={`max-width: calc(100vw - 1px - ${sidebarWidth}px)`}> |     <div class="relative max-h-screen p-2" style={`max-width: calc(100vw - 1px - ${sidebarWidth}px)`}> | ||||||
|       {@render children()} |       {@render children()} | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | |||||||
							
								
								
									
										34
									
								
								src/lib/components/ui/select/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/lib/components/ui/select/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | import { Select as SelectPrimitive } from "bits-ui"; | ||||||
|  | 
 | ||||||
|  | import Label from "./select-label.svelte"; | ||||||
|  | import Item from "./select-item.svelte"; | ||||||
|  | import Content from "./select-content.svelte"; | ||||||
|  | import Trigger from "./select-trigger.svelte"; | ||||||
|  | import Separator from "./select-separator.svelte"; | ||||||
|  | 
 | ||||||
|  | const Root = SelectPrimitive.Root; | ||||||
|  | const Group = SelectPrimitive.Group; | ||||||
|  | const Input = SelectPrimitive.Input; | ||||||
|  | const Value = SelectPrimitive.Value; | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  | 	Root, | ||||||
|  | 	Item, | ||||||
|  | 	Group, | ||||||
|  | 	Input, | ||||||
|  | 	Label, | ||||||
|  | 	Value, | ||||||
|  | 	Content, | ||||||
|  | 	Trigger, | ||||||
|  | 	Separator, | ||||||
|  | 	//
 | ||||||
|  | 	Root as Select, | ||||||
|  | 	Item as SelectItem, | ||||||
|  | 	Group as SelectGroup, | ||||||
|  | 	Input as SelectInput, | ||||||
|  | 	Label as SelectLabel, | ||||||
|  | 	Value as SelectValue, | ||||||
|  | 	Content as SelectContent, | ||||||
|  | 	Trigger as SelectTrigger, | ||||||
|  | 	Separator as SelectSeparator, | ||||||
|  | }; | ||||||
							
								
								
									
										36
									
								
								src/lib/components/ui/select/select-content.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/lib/components/ui/select/select-content.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { Select as SelectPrimitive } from "bits-ui"; | ||||||
|  | 	import { scale } from "svelte/transition"; | ||||||
|  | 	import { cn, flyAndScale } from "$lib/utils.js"; | ||||||
|  | 
 | ||||||
|  | 	type $$Props = SelectPrimitive.ContentProps; | ||||||
|  | 
 | ||||||
|  | 	let className: $$Props["class"] = undefined; | ||||||
|  | 	export let sideOffset: $$Props["sideOffset"] = 4; | ||||||
|  | 	export let inTransition: $$Props["inTransition"] = flyAndScale; | ||||||
|  | 	export let inTransitionConfig: $$Props["inTransitionConfig"] = undefined; | ||||||
|  | 	export let outTransition: $$Props["outTransition"] = scale; | ||||||
|  | 	export let outTransitionConfig: $$Props["outTransitionConfig"] = { | ||||||
|  | 		start: 0.95, | ||||||
|  | 		opacity: 0, | ||||||
|  | 		duration: 50, | ||||||
|  | 	}; | ||||||
|  | 	export { className as class }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <SelectPrimitive.Content | ||||||
|  | 	{inTransition} | ||||||
|  | 	{inTransitionConfig} | ||||||
|  | 	{outTransition} | ||||||
|  | 	{outTransitionConfig} | ||||||
|  | 	{sideOffset} | ||||||
|  | 	class={cn( | ||||||
|  | 		"bg-popover text-popover-foreground relative z-50 min-w-[8rem] overflow-hidden rounded-md border shadow-md focus:outline-none", | ||||||
|  | 		className | ||||||
|  | 	)} | ||||||
|  | 	{...$$restProps} | ||||||
|  | > | ||||||
|  | 	<div class="w-full p-1"> | ||||||
|  | 		<slot /> | ||||||
|  | 	</div> | ||||||
|  | </SelectPrimitive.Content> | ||||||
							
								
								
									
										37
									
								
								src/lib/components/ui/select/select-item.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/lib/components/ui/select/select-item.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { Select as SelectPrimitive } from "bits-ui"; | ||||||
|  | 	import Check from "svelte-radix/Check.svelte"; | ||||||
|  | 	import { cn } from "$lib/utils.js"; | ||||||
|  | 
 | ||||||
|  | 	type $$Props = SelectPrimitive.ItemProps; | ||||||
|  | 	type $$Events = Required<SelectPrimitive.ItemEvents>; | ||||||
|  | 
 | ||||||
|  | 	let className: $$Props["class"] = undefined; | ||||||
|  | 	export let value: $$Props["value"]; | ||||||
|  | 	export let label: $$Props["label"] = undefined; | ||||||
|  | 	export let disabled: $$Props["disabled"] = undefined; | ||||||
|  | 	export { className as class }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <SelectPrimitive.Item | ||||||
|  | 	{value} | ||||||
|  | 	{disabled} | ||||||
|  | 	{label} | ||||||
|  | 	class={cn( | ||||||
|  | 		"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", | ||||||
|  | 		className | ||||||
|  | 	)} | ||||||
|  | 	{...$$restProps} | ||||||
|  | 	on:click | ||||||
|  | 	on:pointermove | ||||||
|  | 	on:focusin | ||||||
|  | > | ||||||
|  | 	<span class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> | ||||||
|  | 		<SelectPrimitive.ItemIndicator> | ||||||
|  | 			<Check class="h-4 w-4" /> | ||||||
|  | 		</SelectPrimitive.ItemIndicator> | ||||||
|  | 	</span> | ||||||
|  | 	<slot> | ||||||
|  | 		{label || value} | ||||||
|  | 	</slot> | ||||||
|  | </SelectPrimitive.Item> | ||||||
							
								
								
									
										13
									
								
								src/lib/components/ui/select/select-label.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/lib/components/ui/select/select-label.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { Select as SelectPrimitive } from "bits-ui"; | ||||||
|  | 	import { cn } from "$lib/utils.js"; | ||||||
|  | 
 | ||||||
|  | 	type $$Props = SelectPrimitive.LabelProps; | ||||||
|  | 
 | ||||||
|  | 	let className: $$Props["class"] = undefined; | ||||||
|  | 	export { className as class }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <SelectPrimitive.Label class={cn("px-2 py-1.5 text-sm font-semibold", className)} {...$$restProps}> | ||||||
|  | 	<slot /> | ||||||
|  | </SelectPrimitive.Label> | ||||||
							
								
								
									
										11
									
								
								src/lib/components/ui/select/select-separator.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/lib/components/ui/select/select-separator.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { Select as SelectPrimitive } from "bits-ui"; | ||||||
|  | 	import { cn } from "$lib/utils.js"; | ||||||
|  | 
 | ||||||
|  | 	type $$Props = SelectPrimitive.SeparatorProps; | ||||||
|  | 
 | ||||||
|  | 	let className: $$Props["class"] = undefined; | ||||||
|  | 	export { className as class }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <SelectPrimitive.Separator class={cn("bg-muted -mx-1 my-1 h-px", className)} {...$$restProps} /> | ||||||
							
								
								
									
										24
									
								
								src/lib/components/ui/select/select-trigger.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/lib/components/ui/select/select-trigger.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	import { Select as SelectPrimitive } from "bits-ui"; | ||||||
|  | 	import CaretSort from "svelte-radix/CaretSort.svelte"; | ||||||
|  | 	import { cn } from "$lib/utils.js"; | ||||||
|  | 
 | ||||||
|  | 	type $$Props = SelectPrimitive.TriggerProps; | ||||||
|  | 	type $$Events = SelectPrimitive.TriggerEvents; | ||||||
|  | 
 | ||||||
|  | 	let className: $$Props["class"] = undefined; | ||||||
|  | 	export { className as class }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <SelectPrimitive.Trigger | ||||||
|  | 	class={cn( | ||||||
|  | 		"border-input ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring aria-[invalid]:border-destructive data-[placeholder]:[&>span]:text-muted-foreground flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", | ||||||
|  | 		className | ||||||
|  | 	)} | ||||||
|  | 	{...$$restProps} | ||||||
|  | > | ||||||
|  | 	<slot /> | ||||||
|  | 	<div> | ||||||
|  | 		<CaretSort class="h-4 w-4 opacity-50" /> | ||||||
|  | 	</div> | ||||||
|  | </SelectPrimitive.Trigger> | ||||||
							
								
								
									
										12
									
								
								src/routes/(main)/account/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/routes/(main)/account/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | import { auth } from '$lib/server/db/auth'; | ||||||
|  | import { redirect } from '@sveltejs/kit'; | ||||||
|  | 
 | ||||||
|  | export async function load({ request }): Promise<void> { | ||||||
|  |   const session = await auth.api.getSession({ | ||||||
|  |     headers: request.headers, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   if (!session) { | ||||||
|  |     redirect(307, '/signup'); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								src/routes/(main)/account/+page.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/routes/(main)/account/+page.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  |   import { Input } from '$lib/components/ui/input/index'; | ||||||
|  |   import { Label } from '$lib/components/ui/label/index'; | ||||||
|  |   import { Button } from '$lib/components/ui/button/index'; | ||||||
|  |   import * as Dialog from '$lib/components/ui/dialog'; | ||||||
|  | 
 | ||||||
|  |   let open: boolean = $state(false); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <main class="abs-center w-2/3"> | ||||||
|  |   <div class="relative grid w-full grid-cols-1 gap-3 md:grid-cols-2"> | ||||||
|  |     <!-- Update Password --> | ||||||
|  |     <form class="grid w-full items-start gap-3"> | ||||||
|  |       <fieldset class="grid w-full gap-3 rounded-lg border p-4"> | ||||||
|  |         <legend class="-ml-1 px-1 text-sm font-medium"> Update Password </legend> | ||||||
|  |         <div class="grid gap-3"> | ||||||
|  |           <Label for="temperature">Current Password</Label> | ||||||
|  |           <Input id="current" type="password" placeholder="Current Password" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="grid gap-3"> | ||||||
|  |           <Label for="temperature">New Password</Label> | ||||||
|  |           <Input id="new" type="password" placeholder="New Password" /> | ||||||
|  |         </div> | ||||||
|  |         <Button type="submit">Update Password</Button> | ||||||
|  |       </fieldset> | ||||||
|  |     </form> | ||||||
|  |     <!-- Change Username --> | ||||||
|  |     <form class="grid w-full items-start gap-3"> | ||||||
|  |       <fieldset class="grid w-full gap-3 rounded-lg border p-4"> | ||||||
|  |         <legend class="-ml-1 px-1 text-sm font-medium"> Update Username </legend> | ||||||
|  |         <div class="grid gap-3"> | ||||||
|  |           <Label for="temperature">New Username</Label> | ||||||
|  |           <Input id="newUsername" type="password" placeholder="New Username" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="grid gap-3"> | ||||||
|  |           <Label for="temperature">Password</Label> | ||||||
|  |           <Input id="password" type="password" placeholder="Password" /> | ||||||
|  |         </div> | ||||||
|  |         <Button type="submit">Update Username</Button> | ||||||
|  |       </fieldset> | ||||||
|  |     </form> | ||||||
|  |     <!-- Upload Profile Photo --> | ||||||
|  |     <form class="grid w-full items-start gap-3"> | ||||||
|  |       <fieldset class="grid w-full gap-3 rounded-lg border p-4"> | ||||||
|  |         <legend class="-ml-1 px-1 text-sm font-medium"> Upload Profile Image </legend> | ||||||
|  |         <Input type="file" accept="image/jpeg, image/png" /> | ||||||
|  |         <Button type="submit">Update Profile Photo</Button> | ||||||
|  |       </fieldset> | ||||||
|  |     </form> | ||||||
|  |     <!-- Account Actions --> | ||||||
|  |     <div class="grid w-full items-start gap-3"> | ||||||
|  |       <fieldset class="grid w-full gap-3 rounded-lg border p-4"> | ||||||
|  |         <legend class="-ml-1 px-1 text-sm font-medium"> Account Actions </legend> | ||||||
|  |         <form> | ||||||
|  |           <Button type="submit" class="w-full">Sign Out</Button> | ||||||
|  |         </form> | ||||||
|  |         <Button variant="destructive" class="w-full" onclick={() => (open = !open)}>Delete Account</Button> | ||||||
|  |       </fieldset> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </main> | ||||||
|  | 
 | ||||||
|  | <Dialog.Root bind:open> | ||||||
|  |   <Dialog.Content> | ||||||
|  |     <Dialog.Header> | ||||||
|  |       <Dialog.Title>Are you sure absolutely sure?</Dialog.Title> | ||||||
|  |       <Dialog.Description> | ||||||
|  |         This action cannot be undone. This will permanently delete your account and remove your data from our database. | ||||||
|  |         <div class="mt-2 flex gap-2"> | ||||||
|  |           <Button class="w-1/2" onclick={() => (open = !open)}>I changed my mind!</Button> | ||||||
|  |           <Button variant="destructive" class="w-1/2" type="submit">Delete Account</Button> | ||||||
|  |         </div> | ||||||
|  |       </Dialog.Description> | ||||||
|  |     </Dialog.Header> | ||||||
|  |   </Dialog.Content> | ||||||
|  | </Dialog.Root> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user