Tauri (local working) (#128)
* chore(nix): add `cargo-tauri` * chore(rust): add rustfmt config * feat: add tauri app * fix(config): tauri and web config nextjs * fix: default value * fix: force graph * fix: undefined me * fix: undefined me in page detail * fix: remove title from search component * fix: package version * chore: next config * feat: random btn for go to auth * fix the config * feat: tauri --------- Co-authored-by: Alice Carroll <git@alice-carroll.pet> Co-authored-by: Aslam H <iupin5212@gmail.com>
4
.gitignore
vendored
@@ -14,4 +14,6 @@ private
|
||||
past-*
|
||||
output
|
||||
dist
|
||||
target
|
||||
|
||||
# rust
|
||||
/target/
|
||||
22
.rustfmt.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
#:schema https://json.schemastore.org/rustfmt.json
|
||||
edition = "2021"
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_bodies = true
|
||||
format_macro_matchers = true
|
||||
format_strings = true
|
||||
group_imports = "StdExternalCrate"
|
||||
hex_literal_case = "Upper"
|
||||
imports_granularity = "Crate"
|
||||
imports_layout = "HorizontalVertical"
|
||||
match_arm_blocks = true
|
||||
match_block_trailing_comma = true
|
||||
reorder_impl_items = true
|
||||
unstable_features = true
|
||||
use_field_init_shorthand = true
|
||||
use_small_heuristics = "Max"
|
||||
use_try_shorthand = true
|
||||
wrap_comments = true
|
||||
|
||||
hard_tabs = true
|
||||
max_width = 120
|
||||
newline_style = "Unix"
|
||||
6
.taplo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[formatting]
|
||||
column_width = 120
|
||||
crlf = false
|
||||
indent_string = "\t"
|
||||
reorder_keys = true
|
||||
trailing_newline = true
|
||||
4365
Cargo.lock
generated
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[workspace]
|
||||
members = ["app"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Alice Carroll <learn-anything@alice-carroll.pet>"]
|
||||
edition = "2021"
|
||||
license-file = "license"
|
||||
repository = "https://github.com/learn-anything/learn-anything.xyz"
|
||||
rust-version = "1.80"
|
||||
version = "0.1.0"
|
||||
3
app/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/gen/schemas
|
||||
4365
app/Cargo.lock
generated
Normal file
20
app/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license-file.workspace = true
|
||||
name = "learn-anything"
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib", "lib"]
|
||||
name = "app_lib"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-rc.6", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "2.0.0-rc.6", features = [] }
|
||||
3
app/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
11
app/capabilities/default.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
BIN
app/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
app/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
app/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
app/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
app/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
app/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
app/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
app/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/icons/icon.icns
Normal file
BIN
app/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
app/icons/icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
4
app/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default().run(tauri::generate_context!()).expect("error while running tauri application");
|
||||
}
|
||||
6
app/src/main.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
||||
31
app/tauri.conf.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"productName": "learn-anything",
|
||||
"version": "0.1.0",
|
||||
"identifier": "xyz.learn-anything",
|
||||
"build": {
|
||||
"frontendDist": "../out",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeDevCommand": "bun dev",
|
||||
"beforeBuildCommand": "bun build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Learn Anything",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"url": "http://localhost:3000"
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"]
|
||||
}
|
||||
}
|
||||
8
flake.lock
generated
@@ -20,16 +20,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1724395761,
|
||||
"narHash": "sha256-zRkDV/nbrnp3Y8oCADf5ETl1sDrdmAW6/bBVJ8EbIdQ=",
|
||||
"lastModified": 1725041072,
|
||||
"narHash": "sha256-VQz/S5SYgmERo8orFuBJSP/2dE08RMXu4YaVaO9jowo=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ae815cee91b417be55d43781eb4b73ae1ecc396c",
|
||||
"rev": "d8c78f272ca287089e989dbb3c43594b0929d73d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"ref": "staging",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
21
flake.nix
@@ -1,6 +1,7 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
# TODO: switch to nixpkgs-unstable when 69f3b4defe91949a97ca751ada931525d391e0fc is there
|
||||
nixpkgs.url = "github:nixos/nixpkgs/staging";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
};
|
||||
outputs =
|
||||
@@ -8,13 +9,21 @@
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
systems = nixpkgs.lib.platforms.all;
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
{ pkgs, lib, ... }:
|
||||
let
|
||||
cargo-tauri = pkgs.callPackage ./nix/cargo-tauri.nix { };
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.bun
|
||||
pkgs.nodejs
|
||||
];
|
||||
packages =
|
||||
[
|
||||
pkgs.bun
|
||||
pkgs.nodejs
|
||||
cargo-tauri
|
||||
]
|
||||
++ lib.optionals pkgs.stdenv.isDarwin (
|
||||
[ pkgs.libiconv ] ++ (with pkgs.darwin.apple_sdk.frameworks; [ WebKit ])
|
||||
);
|
||||
};
|
||||
# TODO: Package LA using Nix
|
||||
};
|
||||
|
||||
67
nix/cargo-tauri.nix
Normal file
@@ -0,0 +1,67 @@
|
||||
# TODO: use upstream cargo-tauri when it is updated to v2 in nixpkgs
|
||||
# copied from nixpkgs to change the version and hashes
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
rustPlatform,
|
||||
fetchFromGitHub,
|
||||
openssl,
|
||||
pkg-config,
|
||||
glibc,
|
||||
libsoup,
|
||||
cairo,
|
||||
gtk3,
|
||||
webkitgtk,
|
||||
darwin,
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (darwin.apple_sdk.frameworks) CoreServices Security SystemConfiguration;
|
||||
in
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "tauri";
|
||||
version = "2.0.0-rc.7";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "tauri-apps";
|
||||
repo = pname;
|
||||
rev = "tauri-cli-v${version}";
|
||||
hash = "sha256-y8Y9En1r1HU9sZcYHFhB+botVQBZfzqoDrlgp98ltrY=";
|
||||
};
|
||||
|
||||
# Manually specify the sourceRoot since this crate depends on other crates in the workspace. Relevant info at
|
||||
# https://discourse.nixos.org/t/difficulty-using-buildrustpackage-with-a-src-containing-multiple-cargo-workspaces/10202
|
||||
sourceRoot = "${src.name}/tooling/cli";
|
||||
|
||||
cargoHash = "sha256-u1JcLuP9KFCIb7Wkk3u/hoV/bBarEvFfqpGXVyXSvpo=";
|
||||
|
||||
buildInputs =
|
||||
[ openssl ]
|
||||
++ lib.optionals stdenv.isLinux [
|
||||
glibc
|
||||
libsoup
|
||||
cairo
|
||||
gtk3
|
||||
webkitgtk
|
||||
]
|
||||
++ lib.optionals stdenv.isDarwin [
|
||||
CoreServices
|
||||
Security
|
||||
SystemConfiguration
|
||||
];
|
||||
nativeBuildInputs = [ pkg-config ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Build smaller, faster, and more secure desktop applications with a web frontend";
|
||||
mainProgram = "cargo-tauri";
|
||||
homepage = "https://tauri.app/";
|
||||
license = with licenses; [
|
||||
asl20 # or
|
||||
mit
|
||||
];
|
||||
maintainers = with maintainers; [
|
||||
dit7ya
|
||||
happysalada
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -5,13 +5,15 @@
|
||||
"web": "cd web && bun dev",
|
||||
"web:build": "bun run --filter '*' build",
|
||||
"cli": "bun run --watch cli/run.ts",
|
||||
"seed": "bun --watch cli/seed.ts"
|
||||
"seed": "bun --watch cli/seed.ts",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"workspaces": [
|
||||
"web"
|
||||
],
|
||||
"dependencies": {
|
||||
"jazz-nodejs": "0.7.35-unique.2",
|
||||
"@tauri-apps/cli": "^2.0.0-rc.6",
|
||||
"jazz-nodejs": "^0.7.34",
|
||||
"react-icons": "^5.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
5
web/app/(pages)/tauri/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import TauriRoute from "@/components/routes/tauri/TauriRoute"
|
||||
|
||||
export default function TauriPage() {
|
||||
return <TauriRoute />
|
||||
}
|
||||
@@ -3,7 +3,13 @@
|
||||
*/
|
||||
import { NextRequest } from "next/server"
|
||||
import axios from "axios"
|
||||
import { DEFAULT_VALUES, GET } from "./route"
|
||||
import { GET } from "./route"
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
TITLE: "",
|
||||
DESCRIPTION: "",
|
||||
FAVICON: null
|
||||
}
|
||||
|
||||
jest.mock("axios")
|
||||
const mockedAxios = axios as jest.Mocked<typeof axios>
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Metadata {
|
||||
url: string
|
||||
}
|
||||
|
||||
export const DEFAULT_VALUES = {
|
||||
const DEFAULT_VALUES = {
|
||||
TITLE: "",
|
||||
DESCRIPTION: "",
|
||||
FAVICON: null
|
||||
|
||||
@@ -90,6 +90,9 @@ export const ProfileSection: React.FC = () => {
|
||||
<DropdownMenuItem>
|
||||
<MenuItem icon="LogOut" text="Log out" onClick={signOut} onClose={closeMenu} />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<MenuItem icon="CircleUser" text="Tauri" href="/tauri" onClose={closeMenu} />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,13 @@ import type * as force_graph from "./force-graph-client"
|
||||
import { useCoState } from "@/lib/providers/jazz-provider"
|
||||
import { PublicGlobalGroup } from "@/lib/schema/master/public-group"
|
||||
import { ID } from "jazz-tools"
|
||||
import dynamic from "next/dynamic"
|
||||
import { Button } from "../ui/button"
|
||||
import Link from "next/link"
|
||||
|
||||
let graph_data_promise = import("./graph-data.json").then(a => a.default)
|
||||
let ForceGraphClient = react.lazy(() => import("./force-graph-client-lazy"))
|
||||
// let ForceGraphClient = react.lazy(() => import("./force-graph-client-lazy"))
|
||||
const ForceGraphClient = dynamic(() => import("./force-graph-client-lazy"), { ssr: false })
|
||||
|
||||
export function PublicHomeRoute() {
|
||||
let raw_graph_data = react.use(graph_data_promise)
|
||||
@@ -66,6 +70,7 @@ export function PublicHomeRoute() {
|
||||
/>
|
||||
<div className="absolute left-0 top-0 z-20 p-4">
|
||||
<h2 className="text-xl font-bold text-black dark:text-white">Learn Anything</h2>
|
||||
<Link href={"/1password"}>Random Topic</Link>
|
||||
</div>
|
||||
<div className="absolute left-1/2 top-1/2 z-10 w-[60%] -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<div className="flex flex-col items-center justify-center gap-6">
|
||||
|
||||
@@ -204,20 +204,20 @@ const drawGraph = (canvas: fg.canvas.CanvasState, color_map: ColorMap): void =>
|
||||
ctx.textAlign = "center"
|
||||
ctx.textBaseline = "middle"
|
||||
|
||||
const max_size = Math.max(canvas.ctx.canvas.width, canvas.ctx.canvas.height)
|
||||
|
||||
for (const node of graph.nodes) {
|
||||
const { x, y } = node.position
|
||||
const opacity = 0.6 + ((node.mass - 1) / 50) * 4
|
||||
|
||||
ctx.font = `${
|
||||
canvas.max_size / 200 + (((node.mass - 1) / 5) * (canvas.max_size / 100)) / canvas.scale
|
||||
}px sans-serif`
|
||||
ctx.font = `${max_size / 200 + (((node.mass - 1) / 5) * (max_size / 100)) / canvas.scale}px sans-serif`
|
||||
|
||||
ctx.fillStyle =
|
||||
node.anchor || canvas.hovered_node === node
|
||||
? `rgba(129, 140, 248, ${opacity})`
|
||||
: `hsl(${color_map[node.key as string]} / ${opacity})`
|
||||
|
||||
ctx.fillText(node.label, (x / graph.grid.size) * canvas.max_size, (y / graph.grid.size) * canvas.max_size)
|
||||
ctx.fillText(node.label, (x / graph.grid.size) * max_size, (y / graph.grid.size) * max_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ export const createForceGraph = (props: ForceGraphProps): react.JSX.Element => {
|
||||
let [nodes, edges] = generateNodesFromRawData(props.raw_nodes)
|
||||
|
||||
let color_map = generateColorMap(nodes)
|
||||
|
||||
let bump_end = anim.bump(0)
|
||||
let graph = fg.graph.makeGraph(graph_options, nodes.slice(), edges.slice())
|
||||
|
||||
/*
|
||||
@@ -272,7 +272,7 @@ export const createForceGraph = (props: ForceGraphProps): react.JSX.Element => {
|
||||
let window_size = ws.useWindowSize()
|
||||
|
||||
let alpha = 0 // 0 - 1
|
||||
let bump_end = anim.bump(0)
|
||||
|
||||
let frame_iter_limit = anim.frameIterationsLimit()
|
||||
|
||||
let loop = anim.animationLoop(time => {
|
||||
|
||||
@@ -135,7 +135,7 @@ export const LinkForm: React.FC<LinkFormProps> = ({
|
||||
}
|
||||
|
||||
const onSubmit = (values: LinkFormValues) => {
|
||||
if (isFetching) return
|
||||
if (isFetching || !me) return
|
||||
|
||||
try {
|
||||
const personalLinks = me.root?.personalLinks?.toJSON() || []
|
||||
|
||||
@@ -82,7 +82,7 @@ export const DetailPageForm = ({ page }: { page: PersonalPage }) => {
|
||||
// if (newTitle === page.title) return
|
||||
|
||||
console.log("Updating page title")
|
||||
const personalPages = me.root?.personalPages?.toJSON() || []
|
||||
const personalPages = me?.root?.personalPages?.toJSON() || []
|
||||
const slug = generateUniqueSlug(personalPages, page.slug || "")
|
||||
|
||||
const trimmedTitle = editor.getText().trim()
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Input } from "@/components/ui/input"
|
||||
|
||||
export const SearchHeader = () => {
|
||||
return (
|
||||
<ContentHeader title="Search">
|
||||
<ContentHeader>
|
||||
<Input placeholder="Search something..." />
|
||||
</ContentHeader>
|
||||
)
|
||||
|
||||
11
web/components/routes/tauri/TauriRoute.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import { useAccount } from "@/lib/providers/jazz-provider"
|
||||
|
||||
export default function TauriRoute() {
|
||||
const { me } = useAccount()
|
||||
|
||||
console.log({ pages: me?.root?.personalPages?.toJSON() })
|
||||
|
||||
return <div>{JSON.stringify(me?.root?.personalPages)}</div>
|
||||
}
|
||||
@@ -5,7 +5,6 @@ const isProd = process.env.NODE_ENV === "production"
|
||||
const internalHost = process.env.TAURI_DEV_HOST || "localhost"
|
||||
const isIgnoreBuild = process.env.IGNORE_BUILD_ERRORS === "true"
|
||||
|
||||
console.log(isIgnoreBuild)
|
||||
const ignoreBuild = {
|
||||
typescript: {
|
||||
ignoreBuildErrors: true
|
||||
@@ -27,7 +26,7 @@ const commonConfig = {
|
||||
|
||||
const tauriConfig = {
|
||||
...commonConfig,
|
||||
output: "export",
|
||||
// output: "export",
|
||||
images: {
|
||||
...commonConfig.images,
|
||||
unoptimized: true
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/nextjs": "^5.3.7",
|
||||
"@clerk/nextjs": "^5.4.0",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
@@ -60,13 +60,13 @@
|
||||
"@tiptap/pm": "^2.6.6",
|
||||
"@tiptap/react": "^2.6.6",
|
||||
"@tiptap/suggestion": "^2.6.6",
|
||||
"axios": "^1.7.5",
|
||||
"axios": "^1.7.7",
|
||||
"cheerio": "1.0.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"framer-motion": "^11.3.31",
|
||||
"framer-motion": "^11.4.0",
|
||||
"jazz-react": "0.7.35-unique.2",
|
||||
"jazz-react-auth-clerk": "0.7.33-new-auth.1",
|
||||
"jazz-tools": "0.7.35-unique.2",
|
||||
@@ -75,9 +75,9 @@
|
||||
"lucide-react": "^0.429.0",
|
||||
"next": "14.2.5",
|
||||
"next-themes": "^0.3.0",
|
||||
"nuqs": "^1.17.8",
|
||||
"nuqs": "^1.19.0",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^9.0.8",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.0",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
@@ -95,14 +95,14 @@
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.5.1",
|
||||
"@types/node": "^22.5.2",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "14.2.5",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"postcss": "^8.4.41",
|
||||
"postcss": "^8.4.44",
|
||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"ts-jest": "^29.2.5",
|
||||
|
||||