mirror of
https://github.com/Discreetly/frontend.git
synced 2026-01-08 20:38:04 -05:00
feat: Add loading component to Join form
- Added a loading component to the Join.svelte form to show a loading message when the invite code is being submitted. - Updated the addCode function to set the loading variable to true before making the API call and set it back to false when the API call is completed. - Added a msg variable to store any error messages returned by the API call and display them to the user. - Updated the submit button to show a loading message when the loading variable is true and disable the button when there is no invite code. - Added a new <aside> element to display the error message if there is one. - Added a style tag to set the overflow-wrap style property for the <aside> element. fix: Fix SelectRoom style issue - Updated the SelectRoom.svelte component to use the variant-ghost class for the <select> element and set the background color and text color based on whether the room is selected or not. fix: Update SelectServer component - Updated the SelectServer.svelte component to use the variant-ghost class for the <select> element and added a margin to the component. - Updated the add server button to use the variant-ghost class and added some margin to the button. feat: Add loading component - Added a new loading component to show a loading message and a progress indicator when the page is loading. refactor: Update prover.ts - Updated the logging in the genProof function in prover.ts to use console.info instead of console.debug to improve visibility of proof generation. fix: Update postAuth function - Added a new postAuth function to make a POST request with basic authentication to the API. - Updated the createInvite function in server.ts to use the postAuth function to create invite codes with basic authentication. feat: Add invite code functionality - Added new types for invite codes and
This commit is contained in:
18
src/app.html
18
src/app.html
@@ -11,22 +11,4 @@
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
<!-- Matomo -->
|
||||
<script>
|
||||
var _paq = (window._paq = window._paq || []);
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function () {
|
||||
var u = 'https://psedev.matomo.cloud/';
|
||||
_paq.push(['setTrackerUrl', u + 'matomo.php']);
|
||||
_paq.push(['setSiteId', '3']);
|
||||
var d = document,
|
||||
g = d.createElement('script'),
|
||||
s = d.getElementsByTagName('script')[0];
|
||||
g.async = true;
|
||||
g.src = '//cdn.matomo.cloud/psedev.matomo.cloud/matomo.js';
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Matomo Code -->
|
||||
</html>
|
||||
|
||||
@@ -4,23 +4,37 @@
|
||||
import { postInviteCode } from '$lib/services/server';
|
||||
import type { JoinResponseI } from '$lib/types';
|
||||
import { getCommitment, updateRooms } from '$lib/utils/';
|
||||
import Loading from './loading.svelte';
|
||||
import Button from './button.svelte';
|
||||
|
||||
let code = '';
|
||||
export let code = '';
|
||||
let acceptedRoomNames: string[] = [];
|
||||
let loading = false;
|
||||
let msg: string | undefined;
|
||||
|
||||
async function addCode(newCode: string) {
|
||||
const idc = getCommitment();
|
||||
const result = (await postInviteCode($selectedServer, {
|
||||
code: newCode.toLowerCase(),
|
||||
idc
|
||||
})) as JoinResponseI;
|
||||
console.log('INVITE CODE RESPONSE: ', result);
|
||||
if (result.status == 'valid' || result.status == 'already-added') {
|
||||
acceptedRoomNames = await updateRooms($selectedServer, result.roomIds);
|
||||
code = '';
|
||||
$configStore.signUpStatus.inviteAccepted = true;
|
||||
} else {
|
||||
alert('Invalid invite code');
|
||||
try {
|
||||
const idc = getCommitment();
|
||||
loading = true;
|
||||
const result = (await postInviteCode($selectedServer, {
|
||||
code: newCode.toLowerCase(),
|
||||
idc
|
||||
})) as JoinResponseI;
|
||||
console.debug('INVITE CODE RESPONSE: ', result);
|
||||
if (result.status == 'valid' || result.status == 'already-added') {
|
||||
console.debug('Updating new rooms');
|
||||
acceptedRoomNames = await updateRooms($selectedServer, result.roomIds);
|
||||
code = '';
|
||||
console.log(`Added to rooms: ${acceptedRoomNames}`);
|
||||
$configStore.signUpStatus.inviteAccepted = true;
|
||||
$configStore.signUpStatus.inviteCode = '';
|
||||
} else {
|
||||
alert('Invalid invite code');
|
||||
msg = 'Invalid invite code.';
|
||||
}
|
||||
loading = false;
|
||||
} catch (e: unknown) {
|
||||
msg = String(e.message);
|
||||
}
|
||||
}
|
||||
function inviteCodeKeyPress(event: KeyboardEvent) {
|
||||
@@ -77,27 +91,44 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid place-content-center">
|
||||
<div class="grid place-content-center mb-2 sm:mb-4">
|
||||
<label class="label">
|
||||
<span>Select or Add a Server</span>
|
||||
<SelectServer />
|
||||
</label>
|
||||
<label class="label mt-3">
|
||||
<span>Invite Code</span>
|
||||
<input
|
||||
class="input max-w-md"
|
||||
type="text"
|
||||
placeholder="Invite Code"
|
||||
bind:value={code}
|
||||
on:keydown={(event) => inviteCodeKeyPress(event)}
|
||||
/>
|
||||
<button
|
||||
class="btn variant-ghost-success"
|
||||
type="button"
|
||||
disabled={!code}
|
||||
on:click={() => addCode(code)}>Submit</button
|
||||
>
|
||||
<div class="m-2 sm:md-3">
|
||||
<input
|
||||
class="input max-w-md mb-2 variant-ghost"
|
||||
type="text"
|
||||
placeholder="Invite Code"
|
||||
bind:value={code}
|
||||
on:keydown={(event) => inviteCodeKeyPress(event)}
|
||||
/>
|
||||
{#if !loading}
|
||||
<button
|
||||
class="btn variant-ghost-success"
|
||||
type="button"
|
||||
disabled={!code}
|
||||
on:click={() => addCode(code)}>Submit</button
|
||||
>
|
||||
{:else}
|
||||
<p class="italic">Loading...</p>
|
||||
{/if}
|
||||
</div>
|
||||
</label>
|
||||
{#if msg}
|
||||
<aside class="p">
|
||||
<div>{msg}</div>
|
||||
<div>
|
||||
If you are having trouble and would like help, please message us on <a
|
||||
href="https://discord.gg/brJQ36KVxk"
|
||||
class="underline link">Discord</a
|
||||
>
|
||||
</div>
|
||||
</aside>
|
||||
{/if}
|
||||
{#if acceptedRoomNames.length > 0}
|
||||
<p class="text-center mt-2">You've been added to:</p>
|
||||
<div class="my-2">
|
||||
@@ -107,3 +138,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
aside {
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { selectedServer, selectedRoom, currentRoomsStore } from '$lib/stores';
|
||||
import { updateMessages } from '$lib/utils';
|
||||
import { drawerStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
function setRoom(roomId: string) {
|
||||
$selectedRoom[$selectedServer] = roomId;
|
||||
updateMessages($selectedServer, roomId);
|
||||
drawerStore.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <select class="select text-primary-500" bind:value={$selectedRoom[$selectedServer]}>
|
||||
{#each $currentRoomsStore as room}
|
||||
{#if room.roomId == $selectedRoom[$selectedServer]}
|
||||
<option value={room.roomId} title={room.roomId ? room.roomId.toString() : ''} selected
|
||||
>{room.name}</option
|
||||
>
|
||||
{:else}
|
||||
<option value={room.roomId}>{room.name}</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</select> -->
|
||||
|
||||
<dl class="m-2 md:my-3 md:mx-2">
|
||||
<dl class="m-2 sm:md-3 md:my-3 md:mx-2">
|
||||
{#each $currentRoomsStore as room}
|
||||
<div
|
||||
class="border my-1 md:my-2 py-1 px-2 md:py-3 md:px-4 rounded-token border-surface-400-500-token"
|
||||
class:bg-primary-active-token={room.roomId == $selectedRoom[$selectedServer]}
|
||||
class:bg-secondary-600-300-token={room.roomId == $selectedRoom[$selectedServer]}
|
||||
class:text-secondary-50-900-token={room.roomId == $selectedRoom[$selectedServer]}
|
||||
on:click={() => {
|
||||
setRoom(room.roomId.toString());
|
||||
}}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
valueAttr: { type: 'url', required: true },
|
||||
// Returns the updated response value
|
||||
response: (r: string) => {
|
||||
console.log('response:', r);
|
||||
console.debug('response:', r);
|
||||
if (getServerList().includes(r)) {
|
||||
console.warn('Server already exists');
|
||||
return;
|
||||
@@ -24,15 +24,15 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class=" flex flex-row">
|
||||
<select class="select text-primary-500" bind:value={$selectedServer}>
|
||||
<div class="m-2 sm:md-3 flex flex-row">
|
||||
<select class="select variant-ghost" bind:value={$selectedServer}>
|
||||
{#each Object.entries($serverStore) as [key, s]}
|
||||
<option value={key}>{s.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm variant-ghost-primary ms-2"
|
||||
class="btn btn-sm variant-ghost ms-2"
|
||||
on:click={() => {
|
||||
modalStore.trigger(addServerModal);
|
||||
}}>+</button
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
export let link: string;
|
||||
export let cls: string | undefined = 'btn-primary';
|
||||
export let text: string | undefined = '';
|
||||
export let size: string | undefined = 'lg';
|
||||
</script>
|
||||
|
||||
<a href={link} class="btn btn-{size} px-4 d-inline-flex align-items-center {cls}" type="button">
|
||||
<a href={link} class="btn variant-ghost px-4 d-inline-flex align-items-center {cls}" type="button">
|
||||
<slot>{text}</slot>
|
||||
</a>
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
import { ProgressRadial } from '@skeletonlabs/skeleton';
|
||||
</script>
|
||||
|
||||
<div class="grid place-content-center"><ProgressRadial /></div>
|
||||
<div class="grid place-content-center">
|
||||
<h5 class="h6">Loading...</h5>
|
||||
<ProgressRadial />
|
||||
</div>
|
||||
|
||||
@@ -131,7 +131,9 @@ async function genProof(
|
||||
x: messageHash,
|
||||
epoch: BigInt(epoch)
|
||||
};
|
||||
//console.debug('PROOFINPUTS:', proofInputs);
|
||||
console.info(
|
||||
`Generating proof: epoch ${epoch}, message ID ${messageId}, message hash {messageHash}`
|
||||
);
|
||||
return prover.generateProof(proofInputs).then((proof: RLNFullProof) => {
|
||||
console.log('Proof generated!');
|
||||
console.debug('PROOF:', proof);
|
||||
|
||||
@@ -70,3 +70,33 @@ export async function post(urlParts: string[] | string, data: object): Promise<o
|
||||
throw new Error(`Failed to post to ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description - makes a get request to the api
|
||||
* @param {string[] | string} urlParts - the url parts to be joined to form the url
|
||||
* @param {object} data - the data to be sent to the api
|
||||
* @returns {object} - the response from the api
|
||||
* @throws {Error} - if the request fails
|
||||
*/
|
||||
export async function postAuth(
|
||||
urlParts: string[] | string,
|
||||
data: object,
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<object> {
|
||||
const url = cleanURL(urlParts);
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Basic ' + btoa(username + ':' + password)
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
} else {
|
||||
throw new Error(`Failed to post to ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { MessageI, ServerI } from 'discreetly-interfaces';
|
||||
import type { RoomI } from '$lib/types';
|
||||
import type { Invites, RoomI } from '$lib/types';
|
||||
|
||||
import { get, post } from './api';
|
||||
import { get, post, postAuth } from './api';
|
||||
|
||||
export async function getIdentityRoomIds(server: string, idCommitment: string): Promise<string[]> {
|
||||
return get([server, `api/rooms/${idCommitment}`]) as Promise<string[]>;
|
||||
@@ -22,3 +22,14 @@ export async function postInviteCode(serverUrl: string, data: { code: string; id
|
||||
export async function getMessages(serverUrl: string, roomId: string) {
|
||||
return get([serverUrl, `api/room/${roomId}/messages`]) as Promise<MessageI[]>;
|
||||
}
|
||||
|
||||
export async function createInvite(
|
||||
serverUrl: string,
|
||||
numCodes: number,
|
||||
roomIds: string[],
|
||||
username: string,
|
||||
password: string
|
||||
) {
|
||||
const data = { numCodes, rooms: roomIds };
|
||||
return postAuth([serverUrl, `api/addcode`], data, username, password) as Promise<Invites>;
|
||||
}
|
||||
|
||||
@@ -27,13 +27,24 @@ export enum IdentityStoreE {
|
||||
export interface SignUpStatusI {
|
||||
inviteAccepted: boolean;
|
||||
identityBackedUp: boolean;
|
||||
inviteCode?: string;
|
||||
}
|
||||
|
||||
export interface ConfigurationI {
|
||||
signUpStatus: SignUpStatusI;
|
||||
identityStore: IdentityStoreE;
|
||||
apiUsername?: string;
|
||||
apiPassword?: string;
|
||||
}
|
||||
|
||||
export interface RoomI extends RI {
|
||||
server?: string;
|
||||
}
|
||||
|
||||
export interface InviteCode {
|
||||
claimcode: string;
|
||||
}
|
||||
export interface Invites {
|
||||
message?: string;
|
||||
codes: InviteCode[];
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ import type { IdentityStoreI } from '$lib/types';
|
||||
|
||||
export function createIdentity(regenerate = false) {
|
||||
if (!get(identityStore)._commitment || regenerate) {
|
||||
console.log('Creating identity');
|
||||
console.debug('Creating identity');
|
||||
const id = new Identity() as unknown as IdentityStoreI;
|
||||
identityStore.set(id);
|
||||
console.log('Identity Created! Congrats on your new journey');
|
||||
return 'created';
|
||||
} else {
|
||||
console.warn('Identity already exists');
|
||||
@@ -24,7 +25,7 @@ export function getCommitment() {
|
||||
}
|
||||
|
||||
export function getIdentityBackup() {
|
||||
return getIdentity().toString();
|
||||
return JSON.stringify(get(identityStore));
|
||||
}
|
||||
|
||||
export function doesIdentityExist(): boolean {
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function updateServer(
|
||||
url = Object.keys(get(serverStore))[0];
|
||||
selectedServer.set(url);
|
||||
}
|
||||
console.log('Updating server', url);
|
||||
console.debug('Updating server', url);
|
||||
const oldServerStore = get(store);
|
||||
getServerData(url).then((newServerData) => {
|
||||
const newServerStore = {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
>
|
||||
<Modal />
|
||||
<Toast position="t" />
|
||||
<Drawer position="top" padding="p-4">
|
||||
<Drawer position="top" padding="p-4" rounded="rounded-token">
|
||||
<SelectServer />
|
||||
<SelectRoom />
|
||||
</Drawer>
|
||||
|
||||
@@ -10,19 +10,17 @@
|
||||
<p class="card py-2 px-4 md:px-5 mb-3">
|
||||
If you have an <code class="code">invite code</code> head over to the <Button
|
||||
link="/signup"
|
||||
cls="variant-ghost-secondary m-2 sm:m-3"
|
||||
size="sm">Sign Up</Button
|
||||
cls="variant-ghost-primary m-2 sm:m-3">Sign Up</Button
|
||||
>
|
||||
</p>
|
||||
<p class="card py-2 px-4 md:px-5">
|
||||
Or if you want to request an invite code, join our <Button
|
||||
link="https://discord.gg/brJQ36KVxk"
|
||||
cls="variant-ghost-tertiary m-2 sm:m-3"
|
||||
size="sm">Discord</Button
|
||||
cls="variant-ghost-tertiary m-2 sm:m-3">Discord</Button
|
||||
>
|
||||
</p>
|
||||
{:else}
|
||||
<Button link="/chat" cls="variant-ghost-success" size="sm">Go Chat</Button>
|
||||
<Button link="/chat" cls="variant-ghost-success">Go Chat</Button>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -16,25 +16,29 @@
|
||||
gridColumns="grid-cols-3"
|
||||
slotDefault="place-self-center"
|
||||
slotTrail="place-content-end"
|
||||
background="bg-surface-50-800-token"
|
||||
background="bg-surface-50-900-token"
|
||||
padding="p-2 md:p-4"
|
||||
class="border-b border-surface-500/30"
|
||||
>
|
||||
<svelte:fragment slot="lead">
|
||||
<h1 class="h4 text-secondary-500">
|
||||
<h1 class="h4 text-primary-500">
|
||||
<a href="/"><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a>
|
||||
</h1>
|
||||
</svelte:fragment>
|
||||
<a href="/about" class="hidden sm:inline me-2">About</a>
|
||||
<a href="/about" class="btn btn-sm variant-ringed-secondary hidden sm:inline me-2">About</a>
|
||||
{#if identityExists}
|
||||
<a href="/chat" class="hidden sm:inline me-2">Chat</a>
|
||||
<a class="btn inline sm:hidden me-2" on:click={drawerOpen}>{roomName}</a>
|
||||
<a href="/chat" class="hidden btn btn-sm variant-ringed-secondary sm:inline me-2">Chat</a>
|
||||
<a
|
||||
class="btn btn-sm variant-ringed-secondary font-medium text-sm inline sm:hidden me-2"
|
||||
on:click={drawerOpen}>{roomName}</a
|
||||
>
|
||||
{:else}
|
||||
<a href="/signup">Sign Up</a>
|
||||
<a class="btn btn-sm variant-ringed-secondary" href="/signup">Sign Up</a>
|
||||
{/if}
|
||||
<svelte:fragment slot="trail">
|
||||
<div class="hidden sm:inline me-2 text-secondary-500">Alpha Version!</div>
|
||||
<div class="hidden sm:inline me-2 text-primary-500">Alpha Version!</div>
|
||||
{#if identityExists}
|
||||
<a href="/identity">⚙️</a>
|
||||
<a href="/identity">⚙</a>
|
||||
{/if}
|
||||
<LightSwitch />
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Sidebar from './Sidebar.svelte';
|
||||
</script>
|
||||
|
||||
<section id="chat-wrapper" class="bg-surface-100-800-token">
|
||||
<section id="chat-wrapper" class="bg-surface-50-900-token">
|
||||
<Sidebar />
|
||||
<ChatRoom />
|
||||
</section>
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
scrollChatToBottom();
|
||||
}
|
||||
socket.on('Members', (data: string) => {
|
||||
console.log(data);
|
||||
console.debug(data);
|
||||
});
|
||||
|
||||
socket.on('systemBroadcast', (data: string) => {
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<header
|
||||
class="hidden border-b border-surface-500/30 px-2 py-1 md:px-5 md:py-3 sm:flex flex-row justify-between place-items-center text-xs md:text-base"
|
||||
>
|
||||
<h2 id="roomTitle" class="h5 text-primary-500" class:connected title={roomId}>
|
||||
<h2 id="roomTitle" class="h5 text-secondary-800-100-token" class:connected title={roomId}>
|
||||
{roomName}
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -37,18 +37,24 @@
|
||||
{#if $currentRoomMessages}
|
||||
{#each $currentRoomMessages as bubble}
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="card p-2 md:p-4 space-y-1 md:space-y-2 bg-surface-200-700-token">
|
||||
<div
|
||||
class="card p-2 md:p-4 space-y-1 md:space-y-2 bg-surface-100-800-token border-1 border-secondary-500/30"
|
||||
>
|
||||
<header class="flex justify-between items-center text-xs md:text-sm">
|
||||
<small class="opacity-50 text-primary-500 mr-2 md:mr-4">{getTime(bubble)}</small>
|
||||
<small class="opacity-50 text-surface-700-200-token mr-2 md:mr-4"
|
||||
>{getTime(bubble)}</small
|
||||
>
|
||||
{#if bubble.epoch}
|
||||
<small class="hidden md:block opacity-50 text-primary-500"
|
||||
<small class="hidden md:block opacity-50 text-surface-700-200-token"
|
||||
>epoch: {bubble.epoch}</small
|
||||
>
|
||||
{:else}
|
||||
<small class="hidden md:block opacity-70 text-error-500">SYSTEM MESSAGE</small>
|
||||
<small class="hidden md:block opacity-70 text-error-500-400-token text-"
|
||||
>SYSTEM MESSAGE</small
|
||||
>
|
||||
{/if}
|
||||
</header>
|
||||
<p class="text-primary-500">{bubble.message}</p>
|
||||
<p class="text-surface-800-100-token">{bubble.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -105,9 +105,9 @@
|
||||
<textarea
|
||||
bind:value={messageText}
|
||||
maxlength="2000"
|
||||
class="p-1 md:p-2 text-primary-400 border"
|
||||
class:bg-surface-900={!canSendMessage}
|
||||
class:bg-surface-500={canSendMessage}
|
||||
class=" p-1 md:p-2 text-surface-900-50-token border"
|
||||
class:bg-surface-300-600-token={!canSendMessage}
|
||||
class:bg-surface-200-700-token={canSendMessage}
|
||||
name="prompt"
|
||||
id="prompt"
|
||||
placeholder={placeholderText()}
|
||||
@@ -118,8 +118,8 @@
|
||||
<button
|
||||
class:hidden={!canSendMessage}
|
||||
class={canSendMessage && messageText
|
||||
? 'text-xs md:text-base variant-ghost-primary'
|
||||
: 'text-xs md:text-base variant-ghost-surface'}
|
||||
? 'text-xs md:text-base variant-ghost-success'
|
||||
: 'text-xs md:text-base variant-ghost-secondary'}
|
||||
disabled={!canSendMessage}
|
||||
on:click={sendMessage}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import SelectRoom from '../../lib/components/SelectRoom.svelte';
|
||||
import SelectRoom from '$lib/components/SelectRoom.svelte';
|
||||
import SelectServer from '$lib/components/SelectServer.svelte';
|
||||
</script>
|
||||
|
||||
<div id="sidebar" class="hidden sm:grid grid-rows-[auto_1fr_auto] border-r border-surface-500/30">
|
||||
<!-- Header -->
|
||||
<header class="border-b border-surface-500/30 p-4">
|
||||
<header class="border-b border-surface-500/30 p-1">
|
||||
<SelectServer />
|
||||
</header>
|
||||
<!-- List -->
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<div class="mx-auto mt-10 max-w-[80ch]">
|
||||
<h2 class="h2 mb-8 text-center">Manage Your Identity</h2>
|
||||
<div class=" mx-2 md:mx-4">
|
||||
<div class="mx-auto mt-10 max-w-[80ch]">
|
||||
<h2 class="h2 mb-8 text-center">Manage Your Identity</h2>
|
||||
|
||||
<slot />
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
<script lang="ts">
|
||||
import JoinMore from './JoinMore.svelte';
|
||||
import { identityStore } from '$lib/stores';
|
||||
import DeleteIdentity from './DeleteIdentity.svelte';
|
||||
import BackupIdentity from './BackupIdentity.svelte';
|
||||
import RestoreIdentity from './RestoreIdentity.svelte';
|
||||
import Join from '$lib/components/Join.svelte';
|
||||
import { createIdentity } from '$lib/utils/';
|
||||
$: identityExists = !!$identityStore._commitment;
|
||||
</script>
|
||||
|
||||
{#if !identityExists}
|
||||
<div class="mb-8 text-center">
|
||||
<span class="badge variant-ghost-secondary text-sm px-4 py-2">Identity Not Found</span>
|
||||
<span class="text-base italic px-4 py-2 font-mono badge variant-outline-error"
|
||||
>Identity Not Found!</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-flow-rows gap-5 my-5 max-w-md mx-auto">
|
||||
{#if !identityExists}
|
||||
<button on:click={() => createIdentity()} class="btn variant-filled-success" type="button">
|
||||
<button
|
||||
on:click={() => createIdentity()}
|
||||
class="btn variant-ghost-primary font-medium"
|
||||
type="button"
|
||||
>
|
||||
Generate Identity
|
||||
</button>
|
||||
<RestoreIdentity />
|
||||
{:else}
|
||||
<BackupIdentity />
|
||||
<DeleteIdentity />
|
||||
<h4 class="h4 mt-4">Join More Rooms</h4>
|
||||
<Join />
|
||||
<JoinMore />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { getIdentityBackup } from '$lib/utils/';
|
||||
import Loading from '$lib/components/loading.svelte';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
let loading: boolean = false;
|
||||
let imageUrl: string | undefined = undefined;
|
||||
|
||||
function generateQR() {
|
||||
loading = true;
|
||||
const opts = {
|
||||
type: 'image/jpeg',
|
||||
errorCorrectionLevel: 'M',
|
||||
mode: 'Alphanumeric',
|
||||
color: {
|
||||
dark: '#202626',
|
||||
light: '#ffffff'
|
||||
}
|
||||
};
|
||||
|
||||
QRCode.toDataURL(getIdentityBackup(), opts).then((response) => {
|
||||
imageUrl = response;
|
||||
});
|
||||
setTimeout(() => {
|
||||
imageUrl = undefined;
|
||||
loading = false;
|
||||
}, 15000);
|
||||
let revealIdentity = false;
|
||||
let id = '';
|
||||
function reveal() {
|
||||
id = getIdentityBackup();
|
||||
if (revealIdentity == false) {
|
||||
revealIdentity = true;
|
||||
setTimeout(() => {
|
||||
revealIdentity = false;
|
||||
id = '';
|
||||
}, 60000);
|
||||
} else {
|
||||
revealIdentity = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<h4 class="h4">Backup Your Identity</h4>
|
||||
<a
|
||||
class="btn variant-ghost-success"
|
||||
href={'data:text/json;charset=utf-8,' + encodeURIComponent(getIdentityBackup())}
|
||||
download="identity.json">JSON</a
|
||||
>
|
||||
<a class="btn variant-ghost-success" on:click={generateQR} style="cursor:pointer">QR Code</a>
|
||||
<div class="card variant-ghost-secondary">
|
||||
<header class="card-header">
|
||||
<h4 class="h4">Backup Your Identity</h4>
|
||||
</header>
|
||||
<section class="px-4 pt-4 mb-5">
|
||||
<div class="m-2 sm:m-3 flex flex-col gap-4">
|
||||
<a
|
||||
class="btn variant-ghost-success"
|
||||
href={'data:text/json;charset=utf-8,' +
|
||||
encodeURIComponent(JSON.stringify(getIdentityBackup()))}
|
||||
download="identity.json">Download Identity Backup as JSON</a
|
||||
>
|
||||
{#if !revealIdentity}
|
||||
<div class="btn variant-ghost-success" on:click={reveal}>Show Identity</div>
|
||||
{:else}
|
||||
<div class="btn variant-ghost-success" on:click={reveal}>Hide Identity</div>
|
||||
<div id="reveleadIdentity" class="text-sm p-2 sm:p-4 bg-error-300-600-token max-w-sm">
|
||||
Identity:{id}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{#if loading && !imageUrl}
|
||||
<Loading />
|
||||
{:else}
|
||||
<img src={imageUrl} class="mx-auto" />
|
||||
{/if}
|
||||
<style>
|
||||
#reveleadIdentity {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,27 +23,33 @@
|
||||
isCheckboxChecked = checkbox.checked;
|
||||
isButtonDisabled = !isCheckboxChecked;
|
||||
});
|
||||
checkbox.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="card variant-soft-error">
|
||||
<div class="card variant-ghost-error">
|
||||
<header class="card-header">
|
||||
<h3 class="h4">Delete Your Identity & Reset Application</h3>
|
||||
</header>
|
||||
<section class="px-4 pt-4">
|
||||
<input type="checkbox" id="checkbox" class="mx-2 p-1" />
|
||||
<span class="ms-2 text-error-500"
|
||||
>I promise I backed up my identity, or I really want to destroy it.</span
|
||||
<span class="ms-2"
|
||||
>I promise I backed up my identity, or I really want to destroy it forever.</span
|
||||
>
|
||||
</section>
|
||||
<footer class="card-footer text-center">
|
||||
<button
|
||||
id="delete-identity"
|
||||
on:click={deleteIdentity}
|
||||
disabled={isButtonDisabled}
|
||||
class="btn btn-sm variant-ghost-error mt-8 text-sm font-bold text-warning-100"
|
||||
>Delete Identity</button
|
||||
>
|
||||
<footer class="card-footer text-center mb-2">
|
||||
{#if !isButtonDisabled}
|
||||
<button
|
||||
id="delete-identity"
|
||||
on:click={deleteIdentity}
|
||||
disabled={isButtonDisabled}
|
||||
class="btn btn-sm variant-ghost-error mt-8 text-sm font-bold">Delete Identity</button
|
||||
>
|
||||
{/if}
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
12
src/routes/identity/JoinMore.svelte
Normal file
12
src/routes/identity/JoinMore.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Join from '$lib/components/Join.svelte';
|
||||
</script>
|
||||
|
||||
<div class="card variant-ghost-tertiary">
|
||||
<header class="card-header">
|
||||
<h4 class="h4">Join More Rooms</h4>
|
||||
</header>
|
||||
<section class="py-2 mb-2">
|
||||
<Join />
|
||||
</section>
|
||||
</div>
|
||||
@@ -1,24 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { identityStore } from '$lib/stores';
|
||||
import { Identity } from '@semaphore-protocol/identity';
|
||||
import QrScanner from 'qr-scanner';
|
||||
import { FileDropzone } from '@skeletonlabs/skeleton';
|
||||
import { poseidon2 } from 'poseidon-lite/poseidon2';
|
||||
import { poseidon1 } from 'poseidon-lite/poseidon1';
|
||||
let files: FileList;
|
||||
let status = '';
|
||||
let hasCamera = false;
|
||||
|
||||
QrScanner.hasCamera().then((result) => {
|
||||
hasCamera = result;
|
||||
});
|
||||
|
||||
function restoreBackup(backup: any) {
|
||||
console.debug('Attempting restore of identity from backup file...');
|
||||
console.debug(backup);
|
||||
$identityStore = new Identity(JSON.stringify(backup));
|
||||
console.log($identityStore.identity);
|
||||
const id = JSON.parse(backup);
|
||||
if (!id._nullifier) {
|
||||
console.error("_nullifier doesn't exist in backup");
|
||||
}
|
||||
if (!id._trapdoor) {
|
||||
console.error("_trapdoor doesn't exist in backup");
|
||||
}
|
||||
if (!id._secret) {
|
||||
console.error("_secret doesn't exist in backup");
|
||||
}
|
||||
const checkSecret = poseidon2([id._nullifier, id._trapdoor]);
|
||||
if (checkSecret != id._secret) {
|
||||
console.error('Secret does not match secret from backup');
|
||||
}
|
||||
if (!id._commitment) {
|
||||
console.error("_commitment doesn't exist in backup");
|
||||
}
|
||||
const checkCommitment = poseidon1([id._secret]);
|
||||
if (checkCommitment != id._commitment) {
|
||||
console.error('Commitment does not match commitment backup');
|
||||
}
|
||||
$identityStore = id;
|
||||
console.log(
|
||||
'Identity restored from backup file with identity commitment:',
|
||||
$identityStore.identity.commitment
|
||||
$identityStore._commitment
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,47 +48,17 @@
|
||||
if (f.type == 'application/json' || f.type == 'text/plain') {
|
||||
f.text().then((text) => {
|
||||
unverifiedBackup = JSON.parse(text);
|
||||
console.debug(unverifiedBackup);
|
||||
restoreBackup(unverifiedBackup);
|
||||
});
|
||||
} else if (f.type == 'image/jpeg' || f.type == 'image/png') {
|
||||
QrScanner.scanImage(f)
|
||||
.then((result) => {
|
||||
unverifiedBackup = JSON.parse(result);
|
||||
console.debug(unverifiedBackup);
|
||||
restoreBackup(unverifiedBackup);
|
||||
})
|
||||
.catch((error) => {
|
||||
status = error || 'No QR code found.';
|
||||
console.log(error || 'No QR code found.');
|
||||
});
|
||||
} else {
|
||||
status = 'Invalid file type, must be JSON or QR code in jpeg or png.';
|
||||
status =
|
||||
'Invalid file type, must be a JSON object with the _nullifier, _trapdoor, _secret, and _commitment as stringified bigints';
|
||||
console.warn('Invalid file type');
|
||||
}
|
||||
}
|
||||
|
||||
function scanQR() {
|
||||
const videoDiv = document.getElementById('qr-camera');
|
||||
if (!videoDiv) return;
|
||||
const video = videoDiv.getElementsByTagName('video')[0];
|
||||
const qrScanner = new QrScanner(video, (result) => restoreBackup(JSON.parse(result.data)), {
|
||||
highlightCodeOutline: true
|
||||
});
|
||||
qrScanner.start();
|
||||
setTimeout(() => qrScanner.stop(), 30000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<h4 class="h4">Restore Your Identity</h4>
|
||||
<FileDropzone name="identity.json" bind:files on:change={onChangeHandler} />
|
||||
{#if hasCamera}
|
||||
<div id="qr-camera" class="flex flex-col justify-center">
|
||||
<button on:click={scanQR} class="btn variant-filled-success" type="button"
|
||||
>Scan a QR Code</button
|
||||
>
|
||||
<video />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="">{status}</div>
|
||||
|
||||
112
src/routes/invite/+page.svelte
Normal file
112
src/routes/invite/+page.svelte
Normal file
@@ -0,0 +1,112 @@
|
||||
<script lang="ts">
|
||||
import { createInvite } from '$lib/services/server';
|
||||
import { selectedServer, configStore, currentRoomsStore } from '$lib/stores';
|
||||
import { Accordion, AccordionItem } from '@skeletonlabs/skeleton';
|
||||
import qrcode from 'qrcode';
|
||||
let numCodes = 1;
|
||||
let roomIds: string[] = [];
|
||||
|
||||
function makeInviteQRCode(inviteCode: string) {
|
||||
const canvasContainer = document.getElementById('qr');
|
||||
const div = document.createElement('div');
|
||||
const canvas = document.createElement('canvas');
|
||||
div?.appendChild(canvas);
|
||||
const p = document.createElement('p');
|
||||
p.innerText = inviteCode;
|
||||
div?.appendChild(p);
|
||||
canvasContainer?.appendChild(div);
|
||||
const url = `${$selectedServer}/signup/${inviteCode}`;
|
||||
qrcode.toCanvas(canvas, url);
|
||||
}
|
||||
|
||||
async function newCodes(numCodes: number) {
|
||||
console.log(`Requesting ${numCodes} codes`);
|
||||
const canvasContainer = document.getElementById('qr');
|
||||
canvasContainer?.replaceChildren();
|
||||
const resp = await createInvite(
|
||||
$selectedServer,
|
||||
numCodes,
|
||||
roomIds,
|
||||
$configStore.apiUsername as string,
|
||||
$configStore.apiPassword as string
|
||||
);
|
||||
console.log(resp);
|
||||
const codes = resp.codes;
|
||||
codes.forEach((code) => {
|
||||
makeInviteQRCode(code.claimcode);
|
||||
});
|
||||
}
|
||||
|
||||
function updateRoomList(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const roomId = target.value;
|
||||
if (target.checked) {
|
||||
roomIds.push(roomId);
|
||||
} else {
|
||||
roomIds = roomIds.filter((id) => id !== roomId);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col place-content-center max-w-sm m-auto pt-5">
|
||||
<div id="qr" class="flex flex-col gap-12 place-content-center">
|
||||
<div>
|
||||
<canvas class="variant-ghost-success" />
|
||||
<p>no codes yet</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="btn my-12 variant-ghost-primary text-center items-center"
|
||||
on:click={() => {
|
||||
newCodes(numCodes);
|
||||
}}
|
||||
>
|
||||
New Codes
|
||||
</div>
|
||||
<Accordion>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">Number of Codes</svelte:fragment>
|
||||
<svelte:fragment slot="content"
|
||||
><label class="label">
|
||||
<span>Num Codes: {numCodes}</span>
|
||||
<input type="range" min="1" max="5" bind:value={numCodes} />
|
||||
</label></svelte:fragment
|
||||
>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">Rooms</svelte:fragment>
|
||||
<svelte:fragment slot="content"
|
||||
><div>
|
||||
{#each $currentRoomsStore as room}
|
||||
<label class="label">
|
||||
<input type="checkbox" value={room.roomId} on:change={updateRoomList} />
|
||||
<span title={String(room.roomId)}>{room.name}</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div></svelte:fragment
|
||||
>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">API</svelte:fragment>
|
||||
<svelte:fragment slot="content"
|
||||
><label class="label">
|
||||
<span>Api Username</span>
|
||||
<input type="text" class="input" bind:value={$configStore.apiUsername} />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Api Password</span>
|
||||
<input type="password" class="input" bind:value={$configStore.apiPassword} />
|
||||
</label></svelte:fragment
|
||||
>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#qr > div > canvas {
|
||||
margin: 0 auto;
|
||||
}
|
||||
#qr > div > p {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -11,12 +11,14 @@
|
||||
import { IdentityStoreE } from '$lib/types';
|
||||
</script>
|
||||
|
||||
<div class="h-100">
|
||||
<div class="h-100 mx-2 md:mx-4">
|
||||
<Stepper
|
||||
class="max-w-sm sm:max-w-md md:max-w-3xl mx-auto mt-16"
|
||||
on:complete={() => {
|
||||
goto('/chat');
|
||||
}}
|
||||
buttonNext="variant-filled-surface-50-900-token"
|
||||
buttonComplete="variant-filled-success"
|
||||
>
|
||||
<Step>
|
||||
<svelte:fragment slot="header"
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
<script lang="ts">
|
||||
console.info(
|
||||
'I see you are checking out the logs, let us know what you think on our discord: https://discord.gg/brJQ36KVxk'
|
||||
);
|
||||
</script>
|
||||
|
||||
<h5 class="text-center h4 my-4 md:my-7">
|
||||
This app is a little different from what you're used to.
|
||||
</h5>
|
||||
<div class="grid place-content-center md:text-lg mb-6 md:mb-10 px-2 md:px-0">
|
||||
<div id="info">
|
||||
<p>
|
||||
<b class="text-secondary-500">Discreetly</b> is an <b>anonymous</b> chat app
|
||||
<b class="text-primary-500">Discreetly</b> is an <b>anonymous</b> chat app
|
||||
</p>
|
||||
<p>Yes, you are <i>actually</i> anonymous, don't abuse it</p>
|
||||
<p>
|
||||
But you can still get <b class="text-secondary-500">banned</b> if you
|
||||
<b class="text-secondary-500">spam</b>, or if you get
|
||||
<b class="text-secondary-500">voted out</b>
|
||||
But you can still get <b class="text-primary-500">banned</b> if you
|
||||
<b class="text-primary-500">spam</b>, or if you get
|
||||
<b class="text-primary-500">voted out</b>
|
||||
<small class="text-surface-400 italic">coming soon</small>.
|
||||
</p>
|
||||
<p>
|
||||
There is
|
||||
<b class="text-secondary-500">no unban</b>
|
||||
<b class="text-primary-500">no unban</b>
|
||||
</p>
|
||||
<p>
|
||||
<b>Backup your identity</b>, there is
|
||||
<i class="text-secondary-500">no account recovery</i>
|
||||
<i class="text-primary-500">no account recovery</i>
|
||||
</p>
|
||||
<p class="mt-2 mb-4">
|
||||
If you want to understand how this works:
|
||||
<a href="/about" class="btn btn-sm variant-soft-tertiary ms-2 mt-2">Read More Here</a>
|
||||
<a href="/about" class="btn btn-sm variant-ghost-tertiary ms-2 mt-2">Read More Here</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,6 +41,6 @@
|
||||
#info p::before {
|
||||
content: '•';
|
||||
padding-right: 0.45em;
|
||||
color: rgb(var(--color-secondary-500));
|
||||
color: rgb(var(--color-primary-500));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
|
||||
<div class="grid place-content-center gap-5">
|
||||
{#if !identityExists}
|
||||
<button on:click={() => createIdentity()} class="btn variant-filled-success" type="button">
|
||||
<button on:click={() => createIdentity()} class="btn variant-ghost-primary" type="button">
|
||||
Generate Identity
|
||||
</button>
|
||||
<RestoreIdentity />
|
||||
{:else}
|
||||
<button on:click={() => createIdentity(true)} class="btn variant-ghost-warning" type="button">
|
||||
<p class="h4">Identity Created ✅</p>
|
||||
<button on:click={() => createIdentity(true)} class="btn variant-ghost-success" type="button">
|
||||
Re-Generate Identity
|
||||
</button>
|
||||
<RestoreIdentity />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Join from '$lib/components/Join.svelte';
|
||||
import { configStore } from '$lib/stores';
|
||||
</script>
|
||||
|
||||
<Join />
|
||||
<Join code={$configStore.signUpStatus.inviteCode} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { identityStore, configStore } from '$lib/stores';
|
||||
console.log('Yes, you should backup your identity, there is no recovery');
|
||||
</script>
|
||||
|
||||
<div class="grid place-content-center">
|
||||
|
||||
6
src/routes/signup/[invite]/+page.svelte
Normal file
6
src/routes/signup/[invite]/+page.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { configStore } from '$lib/stores';
|
||||
$configStore.signUpStatus.inviteCode = $page.params.invite;
|
||||
console.log(`Invited with code: ${$page.params.invite}`);
|
||||
</script>
|
||||
@@ -3,8 +3,8 @@
|
||||
/* =~= Theme Properties =~= */
|
||||
--theme-font-family-base: 'Space Grotesk', Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--theme-font-family-heading: 'Nippo', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--theme-font-color-base: var(--color-surface-800);
|
||||
--theme-font-color-dark: var(--color-primary-200);
|
||||
--theme-font-color-base: var(--color-surface-900);
|
||||
--theme-font-color-dark: var(--color-surface-50);
|
||||
--theme-rounded-base: 4px;
|
||||
--theme-rounded-container: 6px;
|
||||
--theme-border-base: 1px;
|
||||
@@ -17,28 +17,28 @@
|
||||
--on-error: 255 255 255;
|
||||
--on-surface: 255 255 255;
|
||||
/* =~= Theme Colors =~= */
|
||||
/* primary | #99b5b8 */
|
||||
--color-primary-50: 240 244 244; /* ⬅ #f0f4f4 */
|
||||
--color-primary-100: 235 240 241; /* ⬅ #ebf0f1 */
|
||||
--color-primary-200: 230 237 237; /* ⬅ #e6eded */
|
||||
--color-primary-300: 214 225 227; /* ⬅ #d6e1e3 */
|
||||
--color-primary-400: 184 203 205; /* ⬅ #b8cbcd */
|
||||
--color-primary-500: 153 181 184; /* ⬅ #99b5b8 */
|
||||
--color-primary-600: 138 163 166; /* ⬅ #8aa3a6 */
|
||||
--color-primary-700: 115 136 138; /* ⬅ #73888a */
|
||||
--color-primary-800: 92 109 110; /* ⬅ #5c6d6e */
|
||||
--color-primary-900: 75 89 90; /* ⬅ #4b595a */
|
||||
/* secondary | #fa5f5f */
|
||||
--color-secondary-50: 254 231 231; /* ⬅ #fee7e7 */
|
||||
--color-secondary-100: 254 223 223; /* ⬅ #fedfdf */
|
||||
--color-secondary-200: 254 215 215; /* ⬅ #fed7d7 */
|
||||
--color-secondary-300: 253 191 191; /* ⬅ #fdbfbf */
|
||||
--color-secondary-400: 252 143 143; /* ⬅ #fc8f8f */
|
||||
--color-secondary-500: 250 95 95; /* ⬅ #fa5f5f */
|
||||
--color-secondary-600: 225 86 86; /* ⬅ #e15656 */
|
||||
--color-secondary-700: 188 71 71; /* ⬅ #bc4747 */
|
||||
--color-secondary-800: 150 57 57; /* ⬅ #963939 */
|
||||
--color-secondary-900: 123 47 47; /* ⬅ #7b2f2f */
|
||||
/* primary | #fa5f5f */
|
||||
--color-primary-50: 254 231 231; /* ⬅ #fee7e7 */
|
||||
--color-primary-100: 254 223 223; /* ⬅ #fedfdf */
|
||||
--color-primary-200: 254 215 215; /* ⬅ #fed7d7 */
|
||||
--color-primary-300: 253 191 191; /* ⬅ #fdbfbf */
|
||||
--color-primary-400: 252 143 143; /* ⬅ #fc8f8f */
|
||||
--color-primary-500: 250 95 95; /* ⬅ #fa5f5f */
|
||||
--color-primary-600: 225 86 86; /* ⬅ #e15656 */
|
||||
--color-primary-700: 188 71 71; /* ⬅ #bc4747 */
|
||||
--color-primary-800: 150 57 57; /* ⬅ #963939 */
|
||||
--color-primary-900: 123 47 47; /* ⬅ #7b2f2f */
|
||||
/* secondary | #99b5b8 */
|
||||
--color-secondary-50: 240 244 244; /* ⬅ #f0f4f4 */
|
||||
--color-secondary-100: 235 240 241; /* ⬅ #ebf0f1 */
|
||||
--color-secondary-200: 230 237 237; /* ⬅ #e6eded */
|
||||
--color-secondary-300: 214 225 227; /* ⬅ #d6e1e3 */
|
||||
--color-secondary-400: 184 203 205; /* ⬅ #b8cbcd */
|
||||
--color-secondary-500: 153 181 184; /* ⬅ #99b5b8 */
|
||||
--color-secondary-600: 138 163 166; /* ⬅ #8aa3a6 */
|
||||
--color-secondary-700: 115 136 138; /* ⬅ #73888a */
|
||||
--color-secondary-800: 92 109 110; /* ⬅ #5c6d6e */
|
||||
--color-secondary-900: 75 89 90; /* ⬅ #4b595a */
|
||||
/* tertiary | #158993 */
|
||||
--color-tertiary-50: 220 237 239; /* ⬅ #dcedef */
|
||||
--color-tertiary-100: 208 231 233; /* ⬅ #d0e7e9 */
|
||||
@@ -61,17 +61,17 @@
|
||||
--color-success-700: 52 121 75; /* ⬅ #34794b */
|
||||
--color-success-800: 41 97 60; /* ⬅ #29613c */
|
||||
--color-success-900: 34 79 49; /* ⬅ #224f31 */
|
||||
/* warning | #ffb585 */
|
||||
--color-warning-50: 255 244 237; /* ⬅ #fff4ed */
|
||||
--color-warning-100: 255 240 231; /* ⬅ #fff0e7 */
|
||||
--color-warning-200: 255 237 225; /* ⬅ #ffede1 */
|
||||
--color-warning-300: 255 225 206; /* ⬅ #ffe1ce */
|
||||
--color-warning-400: 255 203 170; /* ⬅ #ffcbaa */
|
||||
--color-warning-500: 255 181 133; /* ⬅ #ffb585 */
|
||||
--color-warning-600: 230 163 120; /* ⬅ #e6a378 */
|
||||
--color-warning-700: 191 136 100; /* ⬅ #bf8864 */
|
||||
--color-warning-800: 153 109 80; /* ⬅ #996d50 */
|
||||
--color-warning-900: 125 89 65; /* ⬅ #7d5941 */
|
||||
/* warning | #d2b45c */
|
||||
--color-warning-50: 248 244 231; /* ⬅ #f8f4e7 */
|
||||
--color-warning-100: 246 240 222; /* ⬅ #f6f0de */
|
||||
--color-warning-200: 244 236 214; /* ⬅ #f4ecd6 */
|
||||
--color-warning-300: 237 225 190; /* ⬅ #ede1be */
|
||||
--color-warning-400: 224 203 141; /* ⬅ #e0cb8d */
|
||||
--color-warning-500: 210 180 92; /* ⬅ #d2b45c */
|
||||
--color-warning-600: 189 162 83; /* ⬅ #bda253 */
|
||||
--color-warning-700: 158 135 69; /* ⬅ #9e8745 */
|
||||
--color-warning-800: 126 108 55; /* ⬅ #7e6c37 */
|
||||
--color-warning-900: 103 88 45; /* ⬅ #67582d */
|
||||
/* error | #fa5f5f */
|
||||
--color-error-50: 254 231 231; /* ⬅ #fee7e7 */
|
||||
--color-error-100: 254 223 223; /* ⬅ #fedfdf */
|
||||
@@ -83,16 +83,16 @@
|
||||
--color-error-700: 188 71 71; /* ⬅ #bc4747 */
|
||||
--color-error-800: 150 57 57; /* ⬅ #963939 */
|
||||
--color-error-900: 123 47 47; /* ⬅ #7b2f2f */
|
||||
/* surface | #363f3f */
|
||||
--color-surface-50: 225 226 226; /* ⬅ #e1e2e2 */
|
||||
--color-surface-100: 215 217 217; /* ⬅ #d7d9d9 */
|
||||
--color-surface-200: 205 207 207; /* ⬅ #cdcfcf */
|
||||
--color-surface-300: 175 178 178; /* ⬅ #afb2b2 */
|
||||
--color-surface-400: 114 121 121; /* ⬅ #727979 */
|
||||
--color-surface-500: 54 63 63; /* ⬅ #363f3f */
|
||||
--color-surface-600: 49 57 57; /* ⬅ #313939 */
|
||||
--color-surface-700: 41 47 47; /* ⬅ #292f2f */
|
||||
--color-surface-800: 32 38 38; /* ⬅ #202626 */
|
||||
--color-surface-900: 26 31 31; /* ⬅ #1a1f1f */
|
||||
/* surface | #2c3434 */
|
||||
--color-surface-50: 223 225 225; /* ⬅ #dfe1e1 */
|
||||
--color-surface-100: 213 214 214; /* ⬅ #d5d6d6 */
|
||||
--color-surface-200: 202 204 204; /* ⬅ #cacccc */
|
||||
--color-surface-300: 171 174 174; /* ⬅ #abaeae */
|
||||
--color-surface-400: 107 113 113; /* ⬅ #6b7171 */
|
||||
--color-surface-500: 44 52 52; /* ⬅ #2c3434 */
|
||||
--color-surface-600: 40 47 47; /* ⬅ #282f2f */
|
||||
--color-surface-700: 33 39 39; /* ⬅ #212727 */
|
||||
--color-surface-800: 26 31 31; /* ⬅ #1a1f1f */
|
||||
--color-surface-900: 22 25 25; /* ⬅ #161919 */
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user