@skalfa/skalfa-app 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +43 -43
- package/.github/workflows/publish.yml +39 -0
- package/CONTRIBUTING.md +45 -0
- package/LICENSE +21 -0
- package/README.md +91 -28
- package/app/auth/edit/page.tsx +65 -65
- package/app/auth/login/page.tsx +63 -63
- package/app/auth/me/page.tsx +58 -58
- package/app/auth/register/page.tsx +69 -69
- package/app/auth/verify/page.tsx +53 -53
- package/app/dashboard/user/page.tsx +76 -76
- package/app/layout.tsx +37 -37
- package/app/manifest.ts +25 -0
- package/app/page.tsx +13 -13
- package/barrels.json +5 -5
- package/blueprints/starter.blueprint.json +102 -102
- package/bun.lock +916 -0
- package/components/base.components/chip/Chip.component.tsx +39 -39
- package/components/base.components/document/DocumentViewer.component.tsx +163 -163
- package/components/base.components/document/ExportExcel.component.tsx +340 -340
- package/components/base.components/document/ImportExcel.component.tsx +315 -315
- package/components/base.components/document/PrintTable.component.tsx +204 -204
- package/components/base.components/document/RenderPDF.component.tsx +415 -415
- package/components/base.components/input/Checkbox.component.tsx +109 -109
- package/components/base.components/input/Input.component.tsx +332 -332
- package/components/base.components/input/InputCheckbox.component.tsx +174 -174
- package/components/base.components/input/InputCurrency.component.tsx +163 -163
- package/components/base.components/input/InputDate.component.tsx +352 -352
- package/components/base.components/input/InputDatetime.component.tsx +260 -260
- package/components/base.components/input/InputDocument.component.tsx +351 -351
- package/components/base.components/input/InputImage.component.tsx +533 -533
- package/components/base.components/input/InputMap.component.tsx +317 -317
- package/components/base.components/input/InputNumber.component.tsx +192 -192
- package/components/base.components/input/InputOtp.component.tsx +169 -169
- package/components/base.components/input/InputPassword.component.tsx +236 -236
- package/components/base.components/input/InputRadio.component.tsx +175 -175
- package/components/base.components/input/InputTime.component.tsx +275 -275
- package/components/base.components/input/InputValues.component.tsx +68 -68
- package/components/base.components/input/Radio.component.tsx +102 -102
- package/components/base.components/input/Select.component.tsx +541 -541
- package/components/base.components/modal/BottomSheet.component.tsx +245 -245
- package/components/base.components/supervision/FormSupervision.component.tsx +433 -433
- package/components/base.components/supervision/TableSupervision.component.tsx +697 -697
- package/components/base.components/table/ControlBar.component.tsx +497 -497
- package/components/base.components/table/FilterComponent.tsx +518 -518
- package/components/base.components/table/Table.component.tsx +469 -469
- package/components/base.components/typography/TypographyArticle.component.tsx +26 -26
- package/components/base.components/typography/TypographyColumn.component.tsx +20 -20
- package/components/base.components/typography/TypographyContent.component.tsx +20 -20
- package/components/base.components/typography/TypographyTips.component.tsx +20 -20
- package/components/base.components/wrap/Draggable.component.tsx +303 -303
- package/components/base.components/wrap/IDBProvider.tsx +12 -12
- package/components/base.components/wrap/Image.component.tsx +9 -9
- package/components/base.components/wrap/ShortcutProvider.tsx +57 -57
- package/components/base.components/wrap/Swipe.component.tsx +93 -93
- package/components/index.ts +2 -2
- package/contexts/AppProvider.tsx +11 -11
- package/contexts/Auth.context.tsx +64 -64
- package/contexts/Toggle.context.tsx +44 -44
- package/next.config.ts +15 -1
- package/package.json +14 -13
- package/public/204.svg +19 -19
- package/public/500.svg +39 -39
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/images/logo-fill.png +0 -0
- package/public/images/logo-full-fill.png +0 -0
- package/public/images/logo-full.png +0 -0
- package/public/images/logo.png +0 -0
- package/schema/idb/app.schema.ts +8 -8
- package/src-tauri/Cargo.toml +14 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +11 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/src/main.rs +7 -0
- package/src-tauri/tauri.conf.json +36 -0
- package/styles/globals.css +231 -231
- package/styles/tailwind.safelist +68 -68
- package/utils/commands/barrels.ts +27 -27
- package/utils/commands/light.ts +21 -21
- package/utils/commands/logger.ts +42 -42
- package/utils/commands/stubs/table-blueprint.stub +12 -12
- package/utils/commands/use-pdf.ts +29 -29
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useEffect } from "react"
|
|
4
|
-
import { shortcut } from "@utils"
|
|
5
|
-
import { useToggleContext } from "@contexts"
|
|
6
|
-
import { ModalComponent } from "@components";
|
|
7
|
-
|
|
8
|
-
export function ShortcutProvider() {
|
|
9
|
-
const { setToggle, toggle } = useToggleContext();
|
|
10
|
-
const shortcuts = shortcut.list()
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
shortcut.init()
|
|
14
|
-
}, [])
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
shortcut.register("ctrl+/", () => {
|
|
18
|
-
setToggle("MODAL_SHORTCUT_HELP")
|
|
19
|
-
}, "List Shortcut")
|
|
20
|
-
}, [])
|
|
21
|
-
|
|
22
|
-
function formatShortcutKey(key: string) {
|
|
23
|
-
return key
|
|
24
|
-
.split("+")
|
|
25
|
-
.map(k => {
|
|
26
|
-
if (k === "ctrl") return "Ctrl"
|
|
27
|
-
if (k === "shift") return "Shift"
|
|
28
|
-
if (k === "alt") return "Alt"
|
|
29
|
-
if (k === "arrowup") return "↑"
|
|
30
|
-
if (k === "arrowdown") return "↓"
|
|
31
|
-
if (k === " ") return "SPACE"
|
|
32
|
-
return k.toUpperCase()
|
|
33
|
-
})
|
|
34
|
-
.join(" + ")
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<>
|
|
39
|
-
<ModalComponent
|
|
40
|
-
show={!!toggle["MODAL_SHORTCUT_HELP"]}
|
|
41
|
-
onClose={() => setToggle("MODAL_SHORTCUT_HELP")}
|
|
42
|
-
title="Shortcut"
|
|
43
|
-
>
|
|
44
|
-
<div className="p-4 grid grid-cols-2 gap-4">
|
|
45
|
-
{shortcuts.map(({ key, description }) => (
|
|
46
|
-
<>
|
|
47
|
-
<kbd className="px-2 py-1 border rounded bg-gray-100 w-max">
|
|
48
|
-
{formatShortcutKey(key)}
|
|
49
|
-
</kbd>
|
|
50
|
-
<span>: {description}</span>
|
|
51
|
-
</>
|
|
52
|
-
))}
|
|
53
|
-
</div>
|
|
54
|
-
</ModalComponent>
|
|
55
|
-
</>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react"
|
|
4
|
+
import { shortcut } from "@utils"
|
|
5
|
+
import { useToggleContext } from "@contexts"
|
|
6
|
+
import { ModalComponent } from "@components";
|
|
7
|
+
|
|
8
|
+
export function ShortcutProvider() {
|
|
9
|
+
const { setToggle, toggle } = useToggleContext();
|
|
10
|
+
const shortcuts = shortcut.list()
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
shortcut.init()
|
|
14
|
+
}, [])
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
shortcut.register("ctrl+/", () => {
|
|
18
|
+
setToggle("MODAL_SHORTCUT_HELP")
|
|
19
|
+
}, "List Shortcut")
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
function formatShortcutKey(key: string) {
|
|
23
|
+
return key
|
|
24
|
+
.split("+")
|
|
25
|
+
.map(k => {
|
|
26
|
+
if (k === "ctrl") return "Ctrl"
|
|
27
|
+
if (k === "shift") return "Shift"
|
|
28
|
+
if (k === "alt") return "Alt"
|
|
29
|
+
if (k === "arrowup") return "↑"
|
|
30
|
+
if (k === "arrowdown") return "↓"
|
|
31
|
+
if (k === " ") return "SPACE"
|
|
32
|
+
return k.toUpperCase()
|
|
33
|
+
})
|
|
34
|
+
.join(" + ")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<ModalComponent
|
|
40
|
+
show={!!toggle["MODAL_SHORTCUT_HELP"]}
|
|
41
|
+
onClose={() => setToggle("MODAL_SHORTCUT_HELP")}
|
|
42
|
+
title="Shortcut"
|
|
43
|
+
>
|
|
44
|
+
<div className="p-4 grid grid-cols-2 gap-4">
|
|
45
|
+
{shortcuts.map(({ key, description }) => (
|
|
46
|
+
<>
|
|
47
|
+
<kbd className="px-2 py-1 border rounded bg-gray-100 w-max">
|
|
48
|
+
{formatShortcutKey(key)}
|
|
49
|
+
</kbd>
|
|
50
|
+
<span>: {description}</span>
|
|
51
|
+
</>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
</ModalComponent>
|
|
55
|
+
</>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { ReactNode, useRef, useState } from 'react'
|
|
4
|
-
import { cn } from '@/utils';
|
|
5
|
-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type SwipeActionType = {
|
|
9
|
-
label ?: string,
|
|
10
|
-
icon ?: any,
|
|
11
|
-
onAction ?: () => void;
|
|
12
|
-
className ?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type SwipeProps = {
|
|
16
|
-
leftActionControl ?: SwipeActionType;
|
|
17
|
-
rightActionControl ?: SwipeActionType;
|
|
18
|
-
children ?: ReactNode;
|
|
19
|
-
className ?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function SwipeComponent({
|
|
23
|
-
leftActionControl,
|
|
24
|
-
rightActionControl,
|
|
25
|
-
children,
|
|
26
|
-
className,
|
|
27
|
-
} : SwipeProps) {
|
|
28
|
-
|
|
29
|
-
const startX = useRef(0);
|
|
30
|
-
const [offset, setOffset] = useState(0);
|
|
31
|
-
const [dragging, setDragging] = useState(false);
|
|
32
|
-
|
|
33
|
-
function onTouchStart(e: React.TouchEvent) {
|
|
34
|
-
setDragging(true);
|
|
35
|
-
startX.current = e.touches[0].clientX;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function onTouchMove(e: React.TouchEvent) {
|
|
39
|
-
if (!dragging) return;
|
|
40
|
-
|
|
41
|
-
const currentX = e.touches[0].clientX;
|
|
42
|
-
const delta = currentX - startX.current;
|
|
43
|
-
|
|
44
|
-
let allowed = delta;
|
|
45
|
-
|
|
46
|
-
// Jika tidak ada aksi kiri → larang geser ke kanan
|
|
47
|
-
if (!leftActionControl && delta > 0) allowed = 0;
|
|
48
|
-
|
|
49
|
-
// Jika tidak ada aksi kanan → larang geser ke kiri
|
|
50
|
-
if (!rightActionControl && delta < 0) allowed = 0;
|
|
51
|
-
|
|
52
|
-
// Batasi maksimal ±100px
|
|
53
|
-
allowed = Math.max(Math.min(allowed, 100), -100);
|
|
54
|
-
|
|
55
|
-
setOffset(allowed);
|
|
56
|
-
e.preventDefault();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function onTouchEnd() {
|
|
60
|
-
if (!dragging) return;
|
|
61
|
-
setDragging(false);
|
|
62
|
-
|
|
63
|
-
if (offset > 60 && leftActionControl) leftActionControl.onAction?.();
|
|
64
|
-
if (offset < -60 && rightActionControl) rightActionControl.onAction?.();
|
|
65
|
-
|
|
66
|
-
setOffset(0);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<div className="relative w-full overflow-hidden select-none">
|
|
71
|
-
<div className={cn("absolute h-full left-0 w-1/2 flex items-center px-5 gap-2 bg-light-warning text-warning", leftActionControl?.className)}>
|
|
72
|
-
<FontAwesomeIcon icon={leftActionControl?.icon} /> {leftActionControl?.label}
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
<div className={cn("absolute h-full right-0 w-1/2 flex justify-end items-center px-5 gap-2 bg-light-danger text-danger", rightActionControl?.className)}>
|
|
76
|
-
<FontAwesomeIcon icon={rightActionControl?.icon} /> {rightActionControl?.label}
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<div
|
|
80
|
-
className={cn("relative z-10 bg-background", className)}
|
|
81
|
-
style={{
|
|
82
|
-
transform: `translateX(${offset}px)`,
|
|
83
|
-
transition: dragging ? "none" : "transform 0.2s ease",
|
|
84
|
-
}}
|
|
85
|
-
onTouchStart={onTouchStart}
|
|
86
|
-
onTouchMove={onTouchMove}
|
|
87
|
-
onTouchEnd={onTouchEnd}
|
|
88
|
-
>
|
|
89
|
-
{children}
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useRef, useState } from 'react'
|
|
4
|
+
import { cn } from '@/utils';
|
|
5
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export type SwipeActionType = {
|
|
9
|
+
label ?: string,
|
|
10
|
+
icon ?: any,
|
|
11
|
+
onAction ?: () => void;
|
|
12
|
+
className ?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type SwipeProps = {
|
|
16
|
+
leftActionControl ?: SwipeActionType;
|
|
17
|
+
rightActionControl ?: SwipeActionType;
|
|
18
|
+
children ?: ReactNode;
|
|
19
|
+
className ?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function SwipeComponent({
|
|
23
|
+
leftActionControl,
|
|
24
|
+
rightActionControl,
|
|
25
|
+
children,
|
|
26
|
+
className,
|
|
27
|
+
} : SwipeProps) {
|
|
28
|
+
|
|
29
|
+
const startX = useRef(0);
|
|
30
|
+
const [offset, setOffset] = useState(0);
|
|
31
|
+
const [dragging, setDragging] = useState(false);
|
|
32
|
+
|
|
33
|
+
function onTouchStart(e: React.TouchEvent) {
|
|
34
|
+
setDragging(true);
|
|
35
|
+
startX.current = e.touches[0].clientX;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function onTouchMove(e: React.TouchEvent) {
|
|
39
|
+
if (!dragging) return;
|
|
40
|
+
|
|
41
|
+
const currentX = e.touches[0].clientX;
|
|
42
|
+
const delta = currentX - startX.current;
|
|
43
|
+
|
|
44
|
+
let allowed = delta;
|
|
45
|
+
|
|
46
|
+
// Jika tidak ada aksi kiri → larang geser ke kanan
|
|
47
|
+
if (!leftActionControl && delta > 0) allowed = 0;
|
|
48
|
+
|
|
49
|
+
// Jika tidak ada aksi kanan → larang geser ke kiri
|
|
50
|
+
if (!rightActionControl && delta < 0) allowed = 0;
|
|
51
|
+
|
|
52
|
+
// Batasi maksimal ±100px
|
|
53
|
+
allowed = Math.max(Math.min(allowed, 100), -100);
|
|
54
|
+
|
|
55
|
+
setOffset(allowed);
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onTouchEnd() {
|
|
60
|
+
if (!dragging) return;
|
|
61
|
+
setDragging(false);
|
|
62
|
+
|
|
63
|
+
if (offset > 60 && leftActionControl) leftActionControl.onAction?.();
|
|
64
|
+
if (offset < -60 && rightActionControl) rightActionControl.onAction?.();
|
|
65
|
+
|
|
66
|
+
setOffset(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="relative w-full overflow-hidden select-none">
|
|
71
|
+
<div className={cn("absolute h-full left-0 w-1/2 flex items-center px-5 gap-2 bg-light-warning text-warning", leftActionControl?.className)}>
|
|
72
|
+
<FontAwesomeIcon icon={leftActionControl?.icon} /> {leftActionControl?.label}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className={cn("absolute h-full right-0 w-1/2 flex justify-end items-center px-5 gap-2 bg-light-danger text-danger", rightActionControl?.className)}>
|
|
76
|
+
<FontAwesomeIcon icon={rightActionControl?.icon} /> {rightActionControl?.label}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div
|
|
80
|
+
className={cn("relative z-10 bg-background", className)}
|
|
81
|
+
style={{
|
|
82
|
+
transform: `translateX(${offset}px)`,
|
|
83
|
+
transition: dragging ? "none" : "transform 0.2s ease",
|
|
84
|
+
}}
|
|
85
|
+
onTouchStart={onTouchStart}
|
|
86
|
+
onTouchMove={onTouchMove}
|
|
87
|
+
onTouchEnd={onTouchEnd}
|
|
88
|
+
>
|
|
89
|
+
{children}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}
|
package/components/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './base.components'
|
|
2
|
-
export * from './construct.components'
|
|
1
|
+
export * from './base.components'
|
|
2
|
+
export * from './construct.components'
|
|
3
3
|
export * from './structure.components'
|
package/contexts/AppProvider.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ReactNode } from 'react'
|
|
2
|
-
import { AuthContextProvider } from './Auth.context'
|
|
3
|
-
import { ToggleContextProvider } from './Toggle.context'
|
|
4
|
-
|
|
5
|
-
const registerProviders = [
|
|
6
|
-
AuthContextProvider,
|
|
7
|
-
ToggleContextProvider,
|
|
8
|
-
]
|
|
9
|
-
|
|
10
|
-
export function ContextAppProvider({ children }: { children: ReactNode }) {
|
|
11
|
-
return registerProviders.reduceRight((acc, Provider) => <Provider>{acc}</Provider>, children)
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import { AuthContextProvider } from './Auth.context'
|
|
3
|
+
import { ToggleContextProvider } from './Toggle.context'
|
|
4
|
+
|
|
5
|
+
const registerProviders = [
|
|
6
|
+
AuthContextProvider,
|
|
7
|
+
ToggleContextProvider,
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
export function ContextAppProvider({ children }: { children: ReactNode }) {
|
|
11
|
+
return registerProviders.reduceRight((acc, Provider) => <Provider>{acc}</Provider>, children)
|
|
12
12
|
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { api, auth } from "@utils";
|
|
4
|
-
import { createContext, FC, ReactNode, useContext, useEffect, useState } from "react";
|
|
5
|
-
|
|
6
|
-
interface AuthContextInterface {
|
|
7
|
-
registerToken : string | null;
|
|
8
|
-
setRegisterToken : (token: string | null) => void;
|
|
9
|
-
accessToken : string | null;
|
|
10
|
-
setAccessToken : (token: string | null) => void;
|
|
11
|
-
user : Record<string, any> | null;
|
|
12
|
-
setUser : (user: Record<string, any> | null) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const AuthContext = createContext<AuthContextInterface | undefined>(undefined);
|
|
16
|
-
|
|
17
|
-
export const AuthContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
|
18
|
-
const [accessToken, setAccessToken] = useState<string | null>(null);
|
|
19
|
-
const [registerToken, setRegisterToken] = useState<string | null>(null);
|
|
20
|
-
const [user, setUser] = useState<Record<string, any> | null>(null);
|
|
21
|
-
|
|
22
|
-
const set_access_token = (token: string | null) => {
|
|
23
|
-
setAccessToken(token);
|
|
24
|
-
|
|
25
|
-
if(token) {
|
|
26
|
-
auth.setAccessToken(token)
|
|
27
|
-
} else {
|
|
28
|
-
auth.deleteAccessToken()
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const fetchUser = async () => {
|
|
33
|
-
const fetch = await api({path: "me"});
|
|
34
|
-
|
|
35
|
-
setUser(fetch?.data?.data)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const token = auth.getAccessToken() || null;
|
|
40
|
-
|
|
41
|
-
setAccessToken(token)
|
|
42
|
-
|
|
43
|
-
fetchUser()
|
|
44
|
-
}, []);
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<AuthContext.Provider value={{
|
|
48
|
-
registerToken,
|
|
49
|
-
setRegisterToken,
|
|
50
|
-
accessToken,
|
|
51
|
-
setAccessToken: (token) => set_access_token(token),
|
|
52
|
-
user,
|
|
53
|
-
setUser,
|
|
54
|
-
}}>{children}</AuthContext.Provider>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const useAuthContext = (): AuthContextInterface => {
|
|
59
|
-
const context = useContext(AuthContext);
|
|
60
|
-
if (!context) {
|
|
61
|
-
throw new Error("useAuthContext must be used within a AuthContext.Provider");
|
|
62
|
-
}
|
|
63
|
-
return context;
|
|
64
|
-
};
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { api, auth } from "@utils";
|
|
4
|
+
import { createContext, FC, ReactNode, useContext, useEffect, useState } from "react";
|
|
5
|
+
|
|
6
|
+
interface AuthContextInterface {
|
|
7
|
+
registerToken : string | null;
|
|
8
|
+
setRegisterToken : (token: string | null) => void;
|
|
9
|
+
accessToken : string | null;
|
|
10
|
+
setAccessToken : (token: string | null) => void;
|
|
11
|
+
user : Record<string, any> | null;
|
|
12
|
+
setUser : (user: Record<string, any> | null) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const AuthContext = createContext<AuthContextInterface | undefined>(undefined);
|
|
16
|
+
|
|
17
|
+
export const AuthContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
|
18
|
+
const [accessToken, setAccessToken] = useState<string | null>(null);
|
|
19
|
+
const [registerToken, setRegisterToken] = useState<string | null>(null);
|
|
20
|
+
const [user, setUser] = useState<Record<string, any> | null>(null);
|
|
21
|
+
|
|
22
|
+
const set_access_token = (token: string | null) => {
|
|
23
|
+
setAccessToken(token);
|
|
24
|
+
|
|
25
|
+
if(token) {
|
|
26
|
+
auth.setAccessToken(token)
|
|
27
|
+
} else {
|
|
28
|
+
auth.deleteAccessToken()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const fetchUser = async () => {
|
|
33
|
+
const fetch = await api({path: "me"});
|
|
34
|
+
|
|
35
|
+
setUser(fetch?.data?.data)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const token = auth.getAccessToken() || null;
|
|
40
|
+
|
|
41
|
+
setAccessToken(token)
|
|
42
|
+
|
|
43
|
+
fetchUser()
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<AuthContext.Provider value={{
|
|
48
|
+
registerToken,
|
|
49
|
+
setRegisterToken,
|
|
50
|
+
accessToken,
|
|
51
|
+
setAccessToken: (token) => set_access_token(token),
|
|
52
|
+
user,
|
|
53
|
+
setUser,
|
|
54
|
+
}}>{children}</AuthContext.Provider>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const useAuthContext = (): AuthContextInterface => {
|
|
59
|
+
const context = useContext(AuthContext);
|
|
60
|
+
if (!context) {
|
|
61
|
+
throw new Error("useAuthContext must be used within a AuthContext.Provider");
|
|
62
|
+
}
|
|
63
|
+
return context;
|
|
64
|
+
};
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, useState, useCallback } from "react";
|
|
4
|
-
|
|
5
|
-
interface ToggleContextInterface {
|
|
6
|
-
toggle: Record<string, ToggleValue>;
|
|
7
|
-
setToggle: (key: string, value?: ToggleValue) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const ToggleContext = createContext<ToggleContextInterface | null>(null);
|
|
11
|
-
|
|
12
|
-
type ToggleValue = string | number | boolean | object;
|
|
13
|
-
|
|
14
|
-
const toggleState: Record<string, ToggleValue> = {};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export const ToggleContextProvider = ({ children }: { children: React.ReactNode }) => {
|
|
18
|
-
const [, forceRender] = useState(0);
|
|
19
|
-
|
|
20
|
-
const setToggle = useCallback((key: string, value?: ToggleValue) => {
|
|
21
|
-
const next = value !== undefined ? value : !toggleState[key];
|
|
22
|
-
if (toggleState[key] === next) return;
|
|
23
|
-
|
|
24
|
-
toggleState[key] = next;
|
|
25
|
-
forceRender(v => v + 1);
|
|
26
|
-
}, []);
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<ToggleContext.Provider
|
|
30
|
-
value={{
|
|
31
|
-
toggle: toggleState,
|
|
32
|
-
setToggle
|
|
33
|
-
}}
|
|
34
|
-
>
|
|
35
|
-
{children}
|
|
36
|
-
</ToggleContext.Provider>
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const useToggleContext = () => {
|
|
41
|
-
const ctx = useContext(ToggleContext);
|
|
42
|
-
if (!ctx) throw new Error("useToggleContext must be used inside provider");
|
|
43
|
-
return ctx;
|
|
44
|
-
};
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState, useCallback } from "react";
|
|
4
|
+
|
|
5
|
+
interface ToggleContextInterface {
|
|
6
|
+
toggle: Record<string, ToggleValue>;
|
|
7
|
+
setToggle: (key: string, value?: ToggleValue) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ToggleContext = createContext<ToggleContextInterface | null>(null);
|
|
11
|
+
|
|
12
|
+
type ToggleValue = string | number | boolean | object;
|
|
13
|
+
|
|
14
|
+
const toggleState: Record<string, ToggleValue> = {};
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export const ToggleContextProvider = ({ children }: { children: React.ReactNode }) => {
|
|
18
|
+
const [, forceRender] = useState(0);
|
|
19
|
+
|
|
20
|
+
const setToggle = useCallback((key: string, value?: ToggleValue) => {
|
|
21
|
+
const next = value !== undefined ? value : !toggleState[key];
|
|
22
|
+
if (toggleState[key] === next) return;
|
|
23
|
+
|
|
24
|
+
toggleState[key] = next;
|
|
25
|
+
forceRender(v => v + 1);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ToggleContext.Provider
|
|
30
|
+
value={{
|
|
31
|
+
toggle: toggleState,
|
|
32
|
+
setToggle
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</ToggleContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const useToggleContext = () => {
|
|
41
|
+
const ctx = useContext(ToggleContext);
|
|
42
|
+
if (!ctx) throw new Error("useToggleContext must be used inside provider");
|
|
43
|
+
return ctx;
|
|
44
|
+
};
|
package/next.config.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const isTauri = process.env.IS_TAURI === "true";
|
|
4
|
+
|
|
5
|
+
let nextConfig: NextConfig = {
|
|
4
6
|
reactStrictMode: true,
|
|
5
7
|
images: {
|
|
6
8
|
remotePatterns: [
|
|
@@ -14,4 +16,16 @@ const nextConfig: NextConfig = {
|
|
|
14
16
|
},
|
|
15
17
|
};
|
|
16
18
|
|
|
19
|
+
if (isTauri) {
|
|
20
|
+
nextConfig = {
|
|
21
|
+
...nextConfig,
|
|
22
|
+
output: "export",
|
|
23
|
+
images: {
|
|
24
|
+
...nextConfig.images,
|
|
25
|
+
unoptimized: true,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
export default nextConfig;
|
|
31
|
+
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skalfa/skalfa-app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"dev"
|
|
6
|
-
"build"
|
|
7
|
-
"start"
|
|
8
|
-
"test"
|
|
9
|
-
"lint"
|
|
10
|
-
"light"
|
|
11
|
-
"barrels"
|
|
5
|
+
"dev": "concurrently --raw \"bun run barrels\" \"next dev\"",
|
|
6
|
+
"build": "next build",
|
|
7
|
+
"start": "next start",
|
|
8
|
+
"test": "bun tsc --noEmit",
|
|
9
|
+
"lint": "npx eslint app/* components/* utils/* contexts/* schema/*",
|
|
10
|
+
"light": "bun utils/commands/light.ts",
|
|
11
|
+
"barrels": "bun run utils/commands/barrels.ts"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
|
@@ -30,14 +30,15 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@eslint/eslintrc": "^3",
|
|
32
32
|
"@types/node": "^20",
|
|
33
|
-
"@types/react": "^19",
|
|
34
|
-
"@types/react-dom": "^19",
|
|
33
|
+
"@types/react": "^19.0.0",
|
|
34
|
+
"@types/react-dom": "^19.0.0",
|
|
35
35
|
"barrelsby": "^2.8.1",
|
|
36
36
|
"commander": "^14.0.2",
|
|
37
37
|
"concurrently": "^9.2.1",
|
|
38
38
|
"eslint": "^9.19.0",
|
|
39
39
|
"eslint-config-next": "^15.1.6",
|
|
40
40
|
"eslint-config-prettier": "^10.0.1",
|
|
41
|
-
"typescript": "^
|
|
42
|
-
}
|
|
43
|
-
|
|
41
|
+
"typescript": "^6.0.3"
|
|
42
|
+
},
|
|
43
|
+
"description": "Modern frontend starter template built with Next.js, pre-configured with PWA, Tauri, and modular extensions."
|
|
44
|
+
}
|