Переглянути джерело

✨feat: create a new route for releases

Naz 1 місяць тому
батько
коміт
3c4b64f0aa

+ 4 - 2
package.json

@@ -17,11 +17,12 @@
     "@sveltejs/vite-plugin-svelte": "^4.0.0",
     "@tailwindcss/typography": "^0.5.14",
     "autoprefixer": "^10.4.20",
-    "bits-ui": "^1.0.0-next.76",
+    "bits-ui": "^1.3.6",
     "clsx": "^2.1.1",
     "mdsvex": "^0.11.2",
     "svelte": "^5.0.0",
     "svelte-check": "^4.0.0",
+    "svelte-sonner": "^0.3.28",
     "tailwind-merge": "^2.6.0",
     "tailwind-variants": "^0.3.0",
     "tailwindcss": "^3.4.9",
@@ -32,6 +33,7 @@
   "dependencies": {
     "@fortawesome/fontawesome-free": "^6.7.2",
     "lucide-svelte": "^0.469.0",
-    "mode-watcher": "^0.5.0"
+    "mode-watcher": "^0.5.1",
+    "svelte-toast": "^1.0.0"
   }
 }

+ 16 - 0
src/lib/components/ui/card/card-content.svelte

@@ -0,0 +1,16 @@
+<script lang="ts">
+	import type { WithElementRef } from "bits-ui";
+	import type { HTMLAttributes } from "svelte/elements";
+	import { cn } from "$lib/utils.js";
+
+	let {
+		ref = $bindable(null),
+		class: className,
+		children,
+		...restProps
+	}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div bind:this={ref} class={cn("p-6", className)} {...restProps}>
+	{@render children?.()}
+</div>

+ 16 - 0
src/lib/components/ui/card/card-description.svelte

@@ -0,0 +1,16 @@
+<script lang="ts">
+	import type { WithElementRef } from "bits-ui";
+	import type { HTMLAttributes } from "svelte/elements";
+	import { cn } from "$lib/utils.js";
+
+	let {
+		ref = $bindable(null),
+		class: className,
+		children,
+		...restProps
+	}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
+</script>
+
+<p bind:this={ref} class={cn("text-muted-foreground text-sm", className)} {...restProps}>
+	{@render children?.()}
+</p>

+ 16 - 0
src/lib/components/ui/card/card-footer.svelte

@@ -0,0 +1,16 @@
+<script lang="ts">
+	import type { WithElementRef } from "bits-ui";
+	import type { HTMLAttributes } from "svelte/elements";
+	import { cn } from "$lib/utils.js";
+
+	let {
+		ref = $bindable(null),
+		class: className,
+		children,
+		...restProps
+	}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div bind:this={ref} class={cn("flex items-center p-6 pt-0", className)} {...restProps}>
+	{@render children?.()}
+</div>

+ 16 - 0
src/lib/components/ui/card/card-header.svelte

@@ -0,0 +1,16 @@
+<script lang="ts">
+	import type { WithElementRef } from "bits-ui";
+	import type { HTMLAttributes } from "svelte/elements";
+	import { cn } from "$lib/utils.js";
+
+	let {
+		ref = $bindable(null),
+		class: className,
+		children,
+		...restProps
+	}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div bind:this={ref} class={cn("flex flex-col space-y-1.5 p-6 pb-0", className)} {...restProps}>
+	{@render children?.()}
+</div>

+ 25 - 0
src/lib/components/ui/card/card-title.svelte

@@ -0,0 +1,25 @@
+<script lang="ts">
+	import type { WithElementRef } from "bits-ui";
+	import type { HTMLAttributes } from "svelte/elements";
+	import { cn } from "$lib/utils.js";
+
+	let {
+		ref = $bindable(null),
+		class: className,
+		level = 3,
+		children,
+		...restProps
+	}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
+		level?: 1 | 2 | 3 | 4 | 5 | 6;
+	} = $props();
+</script>
+
+<div
+	role="heading"
+	aria-level={level}
+	bind:this={ref}
+	class={cn("font-semibold leading-none tracking-tight", className)}
+	{...restProps}
+>
+	{@render children?.()}
+</div>

+ 20 - 0
src/lib/components/ui/card/card.svelte

@@ -0,0 +1,20 @@
+<script lang="ts">
+	import type { WithElementRef } from "bits-ui";
+	import type { HTMLAttributes } from "svelte/elements";
+	import { cn } from "$lib/utils.js";
+
+	let {
+		ref = $bindable(null),
+		class: className,
+		children,
+		...restProps
+	}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+	bind:this={ref}
+	class={cn("bg-card text-card-foreground rounded-xl border shadow", className)}
+	{...restProps}
+>
+	{@render children?.()}
+</div>

+ 22 - 0
src/lib/components/ui/card/index.ts

@@ -0,0 +1,22 @@
+import Root from "./card.svelte";
+import Content from "./card-content.svelte";
+import Description from "./card-description.svelte";
+import Footer from "./card-footer.svelte";
+import Header from "./card-header.svelte";
+import Title from "./card-title.svelte";
+
+export {
+	Root,
+	Content,
+	Description,
+	Footer,
+	Header,
+	Title,
+	//
+	Root as Card,
+	Content as CardContent,
+	Description as CardDescription,
+	Footer as CardFooter,
+	Header as CardHeader,
+	Title as CardTitle,
+};

+ 1 - 0
src/lib/components/ui/sonner/index.ts

@@ -0,0 +1 @@
+export { default as Toaster } from "./sonner.svelte";

+ 20 - 0
src/lib/components/ui/sonner/sonner.svelte

@@ -0,0 +1,20 @@
+<script lang="ts">
+	import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
+	import { mode } from "mode-watcher";
+
+	let restProps: SonnerProps = $props();
+</script>
+
+<Sonner
+	theme={$mode}
+	class="toaster group"
+	toastOptions={{
+		classes: {
+			toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
+			description: "group-[.toast]:text-muted-foreground",
+			actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
+			cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
+		},
+	}}
+	{...restProps}
+/>

+ 7 - 0
src/lib/components/ui/switch/index.ts

@@ -0,0 +1,7 @@
+import Root from "./switch.svelte";
+
+export {
+	Root,
+	//
+	Root as Switch,
+};

+ 27 - 0
src/lib/components/ui/switch/switch.svelte

@@ -0,0 +1,27 @@
+<script lang="ts">
+	import { Switch as SwitchPrimitive, type WithoutChildrenOrChild } from "bits-ui";
+	import { cn } from "$lib/utils.js";
+
+	let {
+		ref = $bindable(null),
+		checked = $bindable(false),
+		class: className,
+		...restProps
+	}: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
+</script>
+
+<SwitchPrimitive.Root
+	bind:ref
+	bind:checked
+	class={cn(
+		"focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+		className
+	)}
+	{...restProps}
+>
+	<SwitchPrimitive.Thumb
+		class={cn(
+			"bg-background pointer-events-none block size-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
+		)}
+	/>
+</SwitchPrimitive.Root>

+ 7 - 2
src/lib/share/Header.svelte

@@ -7,13 +7,18 @@
   import Button from "$lib/components/ui/button/button.svelte";
 </script>
 
-<header class="fixed top-0 flex justify-between items-center p-3 w-full bg-background border-b">
+<header class="fixed top-0 flex justify-between items-center p-3 w-full bg-background border-b mb-32">
   <div class="flex items-center gap-4">
     <button on:click={() => goto("/#")}>
-      <h1 class="text-xl">
+      <h1 class="text-xl font-bold">
         <i class="fa-solid fa-flask"></i> RhazesEMR
       </h1>
     </button>
+    <button on:click={() => goto("/download")}>
+      <h1 class="text-lg">
+        Download
+      </h1>
+    </button>
     <button on:click={() => window.location.href = "https://docs.rhazesemr.info"}>
       <h1 class="text-lg">
         Docs

+ 7 - 2
src/routes/+layout.svelte

@@ -9,7 +9,11 @@
   import Header from "$lib/share/Header.svelte";
   import Footer from "$lib/share/Footer.svelte";
 
-  let loading = true;
+  import { Toaster } from "$lib/components/ui/sonner";
+
+  let loading = $state(true);
+
+  let { children } = $props();
 
   onMount(() => {
     setTimeout(() => (loading = false), 1000);
@@ -17,6 +21,7 @@
 </script>
 
 <ModeWatcher />
+<Toaster />
 <main class="flex flex-col min-h-screen">
   {#if loading}
     <div
@@ -24,7 +29,7 @@
     ></div>
   {:else}
     <Header />
-    <slot></slot>
+    {@render children?.()}
     <Footer />
   {/if}
 </main>

+ 29 - 1
src/routes/+page.svelte

@@ -1,4 +1,32 @@
+<script>
+  import { goto } from "$app/navigation";
+
+  import Button from "$lib/components/ui/button/button.svelte";
+  import { Github, Download, Book } from "lucide-svelte";
+</script>
+
 <main class="flex flex-grow flex-col items-center mt-32">
   <img class="rounded-md" src="./favicon.png" alt="Logo" width="128" />
-  <p class="animate-pulse text-sm text-muted-foreground italic m-3">Coming soon...</p>
+  <p class="m-3 text-xl">Rhazes EMR</p>
+  <p class="m-3">Modern and User-Friendly Electronic Medical Record</p>
+
+  <div>
+    <Button class="text-lg p-5" onclick={() => goto("/download")}
+      ><Download />Download</Button
+    >
+    <Button
+      class="text-lg p-5"
+      variant="secondary"
+      href="https://github.rhazesemr.info"
+    >
+      <Github /> Source Code</Button
+    >
+    <Button
+      class="text-lg p-5"
+      variant="secondary"
+      href="https://docs.rhazesemr.info"
+    >
+      <Book /> Documentation</Button
+    >
+  </div>
 </main>

+ 146 - 0
src/routes/download/+page.svelte

@@ -0,0 +1,146 @@
+<script>
+  import { Button } from "$lib/components/ui/button";
+  import * as Card from "$lib/components/ui/card";
+
+  import { toast } from "svelte-sonner";
+  import { Download, Github } from "lucide-svelte";
+</script>
+
+<main class="flex flex-grow flex-col items-center mt-28">
+  <div class="m-3 text-4xl flex items-center animate-bounce">
+    <Download size={52} />
+    <p class="px-2">Download</p>
+    <Download size={52} />
+  </div>
+
+  <p class="text-lg">Download the latest version of RhazesEMR</p>
+
+  <div class="flex flex-wrap justify-center mt-10">
+    <Card.Root class="w-[380px] m-2">
+      <Card.Header>
+        <Card.Title>Windows Installer</Card.Title>
+        <Card.Description>RhazesEMR Desktop Client for Windows</Card.Description
+        >
+      </Card.Header>
+      <Card.Content class="grid gap-4">
+        <div class="flex justify-center pb-4">
+          <i class="fa-brands fa-windows text-9xl"></i>
+        </div>
+
+        <div class="flex flex-grow justify-center">
+          <Button class="w-1/2 mr-1">
+            <Download /> EXE
+          </Button>
+          <Button class="w-1/2">
+            <Download /> MSI
+          </Button>
+        </div>
+      </Card.Content>
+      <Card.Footer>
+        <Button class="w-full" variant="secondary"
+          ><Github /> Github Release</Button
+        >
+      </Card.Footer>
+    </Card.Root>
+
+    <Card.Root class="w-[380px] m-2">
+      <Card.Header>
+        <Card.Title>Android Installer</Card.Title>
+        <Card.Description>RhazesEMR Mobile Client for Android</Card.Description>
+      </Card.Header>
+      <Card.Content class="grid gap-4">
+        <div class="flex justify-center pt-4">
+          <i class="fa-brands fa-android text-9xl"></i>
+        </div>
+        <Button class="w-full">
+          <Download /> APK
+        </Button>
+      </Card.Content>
+      <Card.Footer>
+        <Button class="w-full" variant="secondary"
+          ><Github /> Github Release</Button
+        >
+      </Card.Footer>
+    </Card.Root>
+
+    <Card.Root class="w-[380px] m-2">
+      <Card.Header>
+        <Card.Title>Linux Installer</Card.Title>
+        <Card.Description
+          >RhazesEMR Desktop Client for GNU/Linux</Card.Description
+        >
+      </Card.Header>
+      <Card.Content class="grid gap-4">
+        <div class="flex justify-center pt-2 pb-2">
+          <i class="fa-brands fa-linux text-9xl"></i>
+        </div>
+        <div class="flex flex-grow justify-center">
+          <Button class="w-1/3 mr-1">
+            <Download /> DEB
+          </Button>
+          <Button class="w-1/3 mr-1">
+            <Download /> RPM
+          </Button>
+          <Button class="w-1/3">
+            <Download /> AppImage
+          </Button>
+        </div>
+      </Card.Content>
+      <Card.Footer>
+        <Button class="w-full" variant="secondary"
+          ><Github /> Github Release</Button
+        >
+      </Card.Footer>
+    </Card.Root>
+
+    <Card.Root class="w-[380px] m-2">
+      <Card.Header>
+        <Card.Title>MacOS Installer</Card.Title>
+        <Card.Description>RhazesEMR Desktop Client for MacOS</Card.Description>
+      </Card.Header>
+      <Card.Content class="grid gap-4">
+        <div class="flex justify-center pt-2 pb-2">
+          <i class="fa-brands fa-apple text-9xl"></i>
+        </div>
+        <button
+          onclick={() =>
+            toast.warning("Not available yet", {
+              description: "this release is still in need for further testing",
+            })}
+        >
+          <Button class="w-full" disabled>...</Button>
+        </button>
+      </Card.Content>
+      <Card.Footer>
+        <Button class="w-full" variant="secondary"
+          ><Github /> Github Release</Button
+        >
+      </Card.Footer>
+    </Card.Root>
+
+    <Card.Root class="w-[380px] m-2">
+      <Card.Header>
+        <Card.Title>iOS Installer</Card.Title>
+        <Card.Description>RhazesEMR Mobile Client for iOS</Card.Description>
+      </Card.Header>
+      <Card.Content class="grid gap-4">
+        <div class="flex justify-center pt-2 pb-2">
+          <i class="fa-brands fa-apple text-9xl"></i>
+        </div>
+        <button
+          onclick={() =>
+            toast.warning("Not available yet", {
+              description: "this release is still in need for further testing",
+            })}
+        >
+          <Button class="w-full" disabled>...</Button>
+        </button>
+      </Card.Content>
+      <Card.Footer>
+        <Button class="w-full" variant="secondary"
+          ><Github /> Github Release</Button
+        >
+      </Card.Footer>
+    </Card.Root>
+  </div>
+</main>