(
-
+
diff --git a/apps/user/app/auth/phone/reset-form.tsx b/apps/user/app/auth/phone/reset-form.tsx
index 7882420..6917d96 100644
--- a/apps/user/app/auth/phone/reset-form.tsx
+++ b/apps/user/app/auth/phone/reset-form.tsx
@@ -1,10 +1,10 @@
import useGlobalStore from '@/config/use-global';
import { zodResolver } from '@hookform/resolvers/zod';
-import { Icon } from '@iconify/react/dist/iconify.js';
import { Button } from '@workspace/ui/components/button';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@workspace/ui/components/form';
import { Input } from '@workspace/ui/components/input';
import { AreaCodeSelect } from '@workspace/ui/custom-components/area-code-select';
+import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl';
import { Dispatch, SetStateAction, useState } from 'react';
import { useForm } from 'react-hook-form';
@@ -97,12 +97,7 @@ export default function ResetForm({
render={({ field }) => (
-
+
@@ -118,7 +113,6 @@ export default function ResetForm({
('/v1/common/app/info', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
+
/** Get verification code POST /v1/common/send_code */
export async function sendEmailCode(body: API.SendCodeRequest, options?: { [key: string]: any }) {
return request
('/v1/common/send_code', {
diff --git a/apps/user/services/common/typings.d.ts b/apps/user/services/common/typings.d.ts
index f4ae1dc..29599d5 100644
--- a/apps/user/services/common/typings.d.ts
+++ b/apps/user/services/common/typings.d.ts
@@ -10,21 +10,59 @@ declare namespace API {
updated_at: number;
};
+ type AppConfig = {
+ name: string;
+ domains: string[];
+ describe: string;
+ startup_picture: string;
+ startup_picture_skip_time: number;
+ };
+
type Application = {
id: number;
- name: string;
- platform: string;
- subscribe_type: string;
icon: string;
- url: string;
+ name: string;
+ description: string;
+ subscribe_type: string;
+ };
+
+ type ApplicationPlatform = {
+ ios?: ApplicationVersion[];
+ mac?: ApplicationVersion[];
+ linux?: ApplicationVersion[];
+ android?: ApplicationVersion[];
+ windows?: ApplicationVersion[];
+ harmony?: ApplicationVersion[];
};
type ApplicationResponse = {
- windows: Application[];
- mac: Application[];
- linux: Application[];
- android: Application[];
- ios: Application[];
+ applications: ApplicationResponseInfo[];
+ };
+
+ type ApplicationResponseInfo = {
+ id: number;
+ name: string;
+ icon: string;
+ description: string;
+ subscription_protocol: string;
+ platform: ApplicationPlatform;
+ };
+
+ type ApplicationVersion = {
+ id: number;
+ url: string;
+ version: string;
+ description: string;
+ is_default: boolean;
+ };
+
+ type AppVersion = {
+ id: number;
+ os: string;
+ version: string;
+ download_url: string;
+ describe: string;
+ default_version: boolean;
};
type AuthConfig = {
@@ -113,6 +151,11 @@ declare namespace API {
created_at: number;
};
+ type GetAppInfoResponse = {
+ config: AppConfig;
+ versions: AppVersion[];
+ };
+
type GetGlobalConfigResponse = {
site: SiteConfig;
verify: VeifyConfig;
diff --git a/apps/user/services/user/typings.d.ts b/apps/user/services/user/typings.d.ts
index d084fec..dd5b479 100644
--- a/apps/user/services/user/typings.d.ts
+++ b/apps/user/services/user/typings.d.ts
@@ -10,21 +10,59 @@ declare namespace API {
updated_at: number;
};
+ type AppConfig = {
+ name: string;
+ domains: string[];
+ describe: string;
+ startup_picture: string;
+ startup_picture_skip_time: number;
+ };
+
type Application = {
id: number;
- name: string;
- platform: string;
- subscribe_type: string;
icon: string;
- url: string;
+ name: string;
+ description: string;
+ subscribe_type: string;
+ };
+
+ type ApplicationPlatform = {
+ ios?: ApplicationVersion[];
+ mac?: ApplicationVersion[];
+ linux?: ApplicationVersion[];
+ android?: ApplicationVersion[];
+ windows?: ApplicationVersion[];
+ harmony?: ApplicationVersion[];
};
type ApplicationResponse = {
- windows: Application[];
- mac: Application[];
- linux: Application[];
- android: Application[];
- ios: Application[];
+ applications: ApplicationResponseInfo[];
+ };
+
+ type ApplicationResponseInfo = {
+ id: number;
+ name: string;
+ icon: string;
+ description: string;
+ subscription_protocol: string;
+ platform: ApplicationPlatform;
+ };
+
+ type ApplicationVersion = {
+ id: number;
+ url: string;
+ version: string;
+ description: string;
+ is_default: boolean;
+ };
+
+ type AppVersion = {
+ id: number;
+ os: string;
+ version: string;
+ download_url: string;
+ describe: string;
+ default_version: boolean;
};
type AuthConfig = {
diff --git a/bun.lockb b/bun.lockb
index 1ef807b..059ed41 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 58cf795..eb51892 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -21,6 +21,10 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@hookform/resolvers": "^3.10.0",
+ "@iconify-json/flagpack": "^1.2.2",
+ "@iconify-json/mdi": "^1.2.2",
+ "@iconify-json/uil": "^1.2.3",
+ "@iconify/react": "^5.2.0",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
diff --git a/packages/ui/src/custom-components/area-code-select.tsx b/packages/ui/src/custom-components/area-code-select.tsx
index 4907cff..14b5bb1 100644
--- a/packages/ui/src/custom-components/area-code-select.tsx
+++ b/packages/ui/src/custom-components/area-code-select.tsx
@@ -1,6 +1,5 @@
'use client';
-import { Icon } from '@iconify/react';
import { Button } from '@workspace/ui/components/button';
import {
Command,
@@ -11,6 +10,7 @@ import {
CommandList,
} from '@workspace/ui/components/command';
import { Popover, PopoverContent, PopoverTrigger } from '@workspace/ui/components/popover';
+import { Icon } from '@workspace/ui/custom-components/icon';
import { cn } from '@workspace/ui/lib/utils';
import { countries, type ICountry } from '@workspace/ui/utils/countries';
import { BoxIcon, Check, ChevronsUpDown } from 'lucide-react';
diff --git a/packages/ui/src/custom-components/dynamic-Inputs.tsx b/packages/ui/src/custom-components/dynamic-Inputs.tsx
index dd705b2..522f31f 100644
--- a/packages/ui/src/custom-components/dynamic-Inputs.tsx
+++ b/packages/ui/src/custom-components/dynamic-Inputs.tsx
@@ -1,4 +1,6 @@
import { Button } from '@workspace/ui/components/button';
+import { Label } from '@workspace/ui/components/label.js';
+import { Switch } from '@workspace/ui/components/switch';
import { Combobox } from '@workspace/ui/custom-components/combobox';
import { EnhancedInput, EnhancedInputProps } from '@workspace/ui/custom-components/enhanced-input';
import { cn } from '@workspace/ui/lib/utils';
@@ -7,7 +9,7 @@ import { useEffect, useState } from 'react';
interface FieldConfig extends Omit {
name: string;
- type: 'text' | 'number' | 'select' | 'time';
+ type: 'text' | 'number' | 'select' | 'time' | 'boolean';
options?: { label: string; value: string }[];
internal?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -18,6 +20,7 @@ interface ObjectInputProps {
value: T;
onChange: (value: T) => void;
fields: FieldConfig[];
+ className?: string;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -25,6 +28,7 @@ export function ObjectInput>({
value,
onChange,
fields,
+ className,
}: ObjectInputProps) {
const [internalState, setInternalState] = useState(value);
@@ -32,7 +36,7 @@ export function ObjectInput>({
setInternalState(value);
}, [value]);
- const updateField = (key: keyof T, fieldValue: string | number) => {
+ const updateField = (key: keyof T, fieldValue: string | number | boolean) => {
let updatedInternalState = { ...internalState, [key]: fieldValue };
fields.forEach((field) => {
if (field.calculateValue && field.name === key) {
@@ -52,28 +56,44 @@ export function ObjectInput>({
onChange(filteredValue);
};
-
- return (
-
- {fields.map(({ name, type, options, className, ...fieldProps }) => (
-
- {type === 'select' && options ? (
+ const renderField = (field: FieldConfig) => {
+ switch (field.type) {
+ case 'select':
+ return (
+ field.options && (
- placeholder={fieldProps.placeholder}
- options={options}
- value={internalState[name]}
- onChange={(fieldValue) => {
- updateField(name, fieldValue);
- }}
+ placeholder={field.placeholder}
+ options={field.options}
+ value={internalState[field.name]}
+ onChange={(fieldValue) => updateField(field.name, fieldValue)}
/>
- ) : (
- updateField(name, fieldValue)}
- type={type}
- {...fieldProps}
+ )
+ );
+ case 'boolean':
+ return (
+
+ updateField(field.name, fieldValue)}
/>
- )}
+ {field.placeholder && }
+
+ );
+ default:
+ return (
+ updateField(field.name, fieldValue)}
+ {...field}
+ />
+ );
+ }
+ };
+ return (
+
+ {fields.map((field) => (
+
+ {renderField(field)}
))}
@@ -83,6 +103,8 @@ interface ArrayInputProps {
value?: T[];
onChange: (value: T[]) => void;
fields: FieldConfig[];
+ isReverse?: boolean;
+ className?: string;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -90,6 +112,8 @@ export function ArrayInput>({
value = [],
onChange,
fields,
+ isReverse = false,
+ className,
}: ArrayInputProps) {
const initializeDefaultItem = (): T =>
fields.reduce((acc, field) => {
@@ -117,7 +141,11 @@ export function ArrayInput>({
};
const createField = () => {
- setDisplayItems([...displayItems, initializeDefaultItem()]);
+ if (isReverse) {
+ setDisplayItems([initializeDefaultItem(), ...displayItems]);
+ } else {
+ setDisplayItems([...displayItems, initializeDefaultItem()]);
+ }
};
const deleteField = (index: number) => {
@@ -142,6 +170,7 @@ export function ArrayInput>({
value={item}
onChange={(updatedItem) => handleItemChange(index, updatedItem)}
fields={fields}
+ className={className}
/>
{displayItems.length > 1 && (
@@ -155,7 +184,7 @@ export function ArrayInput>({
)}
- {index === displayItems.length - 1 && (
+ {(isReverse ? index === 0 : index === displayItems.length - 1) && (
;
+}
diff --git a/packages/ui/src/custom-components/upload-image.tsx b/packages/ui/src/custom-components/upload-image.tsx
new file mode 100644
index 0000000..34b9ba1
--- /dev/null
+++ b/packages/ui/src/custom-components/upload-image.tsx
@@ -0,0 +1,99 @@
+import { Input } from '@workspace/ui/components/input';
+import { Label } from '@workspace/ui/components/label';
+import { cn } from '@workspace/ui/lib/utils';
+import { Upload } from 'lucide-react';
+import { useState } from 'react';
+
+type ReturnType = 'base64' | 'file';
+
+interface UploadImageProps {
+ onChange: (value: string | File) => void;
+ returnType?: ReturnType;
+ id?: string;
+ children?: React.ReactNode;
+ className?: string;
+}
+
+export const UploadImage = ({
+ onChange,
+ returnType = 'base64',
+ id = 'image-upload',
+ children,
+ className,
+}: UploadImageProps) => {
+ const [isDragging, setIsDragging] = useState(false);
+
+ const toBase64 = (file: File): Promise => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => resolve(reader.result as string);
+ reader.onerror = (error) => reject(error);
+ });
+ };
+
+ const handleImageUpload = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ try {
+ if (returnType === 'base64') {
+ const base64 = await toBase64(file);
+ onChange(base64);
+ } else {
+ onChange(file);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ const handleDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragging(true);
+ };
+
+ const handleDragLeave = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragging(false);
+ };
+
+ const handleDrop = async (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragging(false);
+
+ const file = e.dataTransfer.files?.[0];
+ if (!file) return;
+
+ try {
+ if (returnType === 'base64') {
+ const base64 = await toBase64(file);
+ onChange(base64);
+ } else {
+ onChange(file);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+};