enzostvs HF Staff commited on
Commit
9cd6ddb
·
verified ·
1 Parent(s): 3fcfc90

Upload 172 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. Dockerfile +31 -0
  3. README.md +6 -4
  4. assets/images/avatar_bot.png +0 -0
  5. assets/images/avatars/default-avatar-2.svg +79 -0
  6. assets/images/avatars/default-avatar.svg +85 -0
  7. assets/images/banner_footer.svg +118 -0
  8. assets/images/clyde/affraid.svg +1 -0
  9. assets/images/clyde/wave.svg +39 -0
  10. assets/images/color-picker.svg +9 -0
  11. assets/images/editor/transparent_bg.svg +11 -0
  12. assets/images/header/ananas.svg +32 -0
  13. assets/images/header/background.svg +31 -0
  14. assets/images/header/icons-slider/crown.svg +3 -0
  15. assets/images/header/icons-slider/hammer.svg +8 -0
  16. assets/images/header/icons-slider/leaf.svg +9 -0
  17. assets/images/header/icons-slider/partner.svg +9 -0
  18. assets/images/header/icons-slider/shape.svg +3 -0
  19. assets/images/header/icons-slider/shield.svg +5 -0
  20. assets/images/header/icons-slider/star.svg +9 -0
  21. assets/images/header/pizza.svg +31 -0
  22. assets/images/header/search.svg +9 -0
  23. assets/images/header/stars.svg +12 -0
  24. assets/images/premium-modal/modal-illustration.webp +0 -0
  25. components/clyde/clyde.tsx +296 -0
  26. components/clyde/message.tsx +54 -0
  27. components/collapse/index.tsx +47 -0
  28. components/color-picker/index.tsx +104 -0
  29. components/editor-badge/badge-editor.constants.ts +39 -0
  30. components/editor-badge/comps/badge.tsx +163 -0
  31. components/editor-badge/comps/download_button.tsx +60 -0
  32. components/editor-badge/comps/header/comps/icons-slider.tsx +152 -0
  33. components/editor-badge/comps/header/form/advanced.tsx +100 -0
  34. components/editor-badge/comps/header/form/main.tsx +267 -0
  35. components/editor-badge/comps/header/index.tsx +84 -0
  36. components/editor-badge/comps/preview.tsx +39 -0
  37. components/editor-badge/comps/select-shapes.tsx +74 -0
  38. components/editor-badge/comps/shine_effect.tsx +38 -0
  39. components/editor-badge/comps/tabs.tsx +53 -0
  40. components/editor-badge/comps/user_card.tsx +114 -0
  41. components/editor-badge/index.tsx +94 -0
  42. components/editor-icons/comps/header/comps/icons-slider.tsx +108 -0
  43. components/editor-icons/comps/header/index.tsx +84 -0
  44. components/editor-icons/comps/icons/icon-selected.tsx +603 -0
  45. components/editor-icons/comps/icons/index.tsx +107 -0
  46. components/editor-icons/comps/list/index.tsx +151 -0
  47. components/editor-icons/comps/list/list-item.tsx +86 -0
  48. components/editor-icons/comps/shapes/index.tsx +98 -0
  49. components/editor-icons/comps/shapes/shape-selected.tsx +307 -0
  50. components/editor-icons/comps/tabs.tsx +65 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ public/badge-editor.png filter=lfs diff=lfs merge=lfs -text
37
+ public/banner.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+
3
+ # Use an official Node.js runtime as the base image
4
+ FROM node:18
5
+
6
+ USER 1000
7
+
8
+ # Set the working directory in the container
9
+ WORKDIR /usr/src/app
10
+
11
+ # Copy package.json and package-lock.json to the container
12
+ COPY --chown=1000 package.json package-lock.json ./
13
+
14
+ # Install dependencies
15
+ RUN npm install
16
+
17
+ VOLUME /data
18
+
19
+ # Copy the rest of the application files to the container
20
+ COPY --chown=1000 . .
21
+ RUN chmod +x entrypoint.sh
22
+
23
+ # Build the Next.js application for production
24
+ # RUN npm run build
25
+
26
+ # Expose the application port (assuming your app runs on port 3000)
27
+ EXPOSE 3000
28
+
29
+ RUN npm run build
30
+
31
+ CMD ["npm", "run", "start"]
README.md CHANGED
@@ -1,11 +1,13 @@
1
  ---
2
  title: Discotools
3
- emoji: 🐢
4
- colorFrom: gray
5
- colorTo: yellow
6
  sdk: docker
7
  pinned: false
 
 
8
  license: mit
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Discotools
3
+ emoji:
4
+ colorFrom: blue
5
+ colorTo: pink
6
  sdk: docker
7
  pinned: false
8
+ app_port: 3000
9
+ short_description: Generate discord role icon and badge
10
  license: mit
11
  ---
12
 
13
+ ## This is an old project.
assets/images/avatar_bot.png ADDED
assets/images/avatars/default-avatar-2.svg ADDED
assets/images/avatars/default-avatar.svg ADDED
assets/images/banner_footer.svg ADDED
assets/images/clyde/affraid.svg ADDED
assets/images/clyde/wave.svg ADDED
assets/images/color-picker.svg ADDED
assets/images/editor/transparent_bg.svg ADDED
assets/images/header/ananas.svg ADDED
assets/images/header/background.svg ADDED
assets/images/header/icons-slider/crown.svg ADDED
assets/images/header/icons-slider/hammer.svg ADDED
assets/images/header/icons-slider/leaf.svg ADDED
assets/images/header/icons-slider/partner.svg ADDED
assets/images/header/icons-slider/shape.svg ADDED
assets/images/header/icons-slider/shield.svg ADDED
assets/images/header/icons-slider/star.svg ADDED
assets/images/header/pizza.svg ADDED
assets/images/header/search.svg ADDED
assets/images/header/stars.svg ADDED
assets/images/premium-modal/modal-illustration.webp ADDED
components/clyde/clyde.tsx ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+
3
+ export const Clyde = () => {
4
+ const [isHovered, setIsHovered] = useState(false);
5
+ return (
6
+ <svg
7
+ width="105px"
8
+ height="105px"
9
+ viewBox="0 0 262 389"
10
+ fill="none"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ onMouseEnter={() => setIsHovered(true)}
13
+ onMouseLeave={() => setIsHovered(false)}
14
+ className="cursor-pointer"
15
+ >
16
+ <g
17
+ id="Page-1"
18
+ stroke="none"
19
+ strokeWidth="1"
20
+ fill="none"
21
+ fillRule="evenodd"
22
+ >
23
+ <g
24
+ id="Group"
25
+ transform="translate(0.444809, 0.613233)"
26
+ fillRule="nonzero"
27
+ >
28
+ <path
29
+ d="M255.4368,258.333967 C239.872,234.577167 204.2368,222.186767 204.2368,222.186767 L184.2688,231.505167 C184.2688,231.505167 206.8992,308.919567 213.1968,314.858767 C219.4944,320.797967 248.6272,324.791567 248.6272,324.791567 C248.6272,324.791567 271.0016,282.090767 255.4368,258.333967 Z"
30
+ id="Path"
31
+ fill="#E8EDEC"
32
+ ></path>
33
+ <path
34
+ d="M204.2368,222.186767 L184.2688,231.505167 C184.2688,231.505167 186.4704,238.980367 189.6448,249.425167 C201.9328,244.765967 212.992,238.570767 222.976,230.788367 C212.48,225.053967 204.288,222.237967 204.2368,222.186767 L204.2368,222.186767 Z"
35
+ id="Path"
36
+ fill="#BEC9CE"
37
+ ></path>
38
+ <path
39
+ d="M220.5696,288.746767 C216.4736,286.903567 213.4528,283.114767 213.4528,283.114767 L214.272,305.796367 C214.272,305.796367 227.6352,320.695567 226.2016,315.268367 C224.768,309.841167 227.7888,291.869967 227.7376,291.050767 C227.6352,290.231567 223.8976,290.231567 220.5696,288.746767 Z"
40
+ id="Path"
41
+ fill="#D0DBE2"
42
+ ></path>
43
+ <path
44
+ d="M253.5424,321.565967 C252.8768,318.596367 248.2688,319.057167 237.4656,316.445967 C226.6624,313.885967 214.7328,308.253967 214.7328,306.973967 L213.4016,360.682767 C218.4704,366.314767 226.8672,370.717967 236.5952,369.181967 C247.2448,367.441167 253.9008,358.071567 253.5936,352.541967 C253.2864,347.012367 248.32,347.729167 248.32,347.729167 C248.9856,343.172367 245.9648,340.151567 246.2208,339.844367 C246.4256,339.485967 254.2592,324.535567 253.5424,321.565967 Z"
45
+ id="Path"
46
+ fill="#FFB818"
47
+ ></path>
48
+ <path
49
+ d="M226.7136,313.117967 C220.0064,310.609167 214.784,307.844367 214.784,306.973967 L213.4016,360.682767 C222.6176,370.922767 238.3872,372.868367 248.2688,363.242767 C238.336,368.823567 227.9424,363.908367 227.6864,350.903567 C227.4816,341.482767 232.6528,331.857167 226.7136,313.117967 Z"
50
+ id="Path"
51
+ fill="#FF5D12"
52
+ ></path>
53
+ <path
54
+ d="M204.2368,222.186767 L184.2688,231.505167 C184.2688,231.505167 185.3952,235.447567 187.2896,241.591567 L213.9136,286.186767 C220.0576,252.497167 210.0736,229.866767 204.2368,222.186767 L204.2368,222.186767 Z"
55
+ id="Path"
56
+ fill="#FFB818"
57
+ ></path>
58
+ <path
59
+ d="M191.744,248.605967 C198.8608,245.789967 205.6192,242.359567 211.968,238.468367 C209.6128,230.788367 206.592,225.309967 204.2368,222.237967 L184.2688,231.556367 C184.2688,231.556367 185.3952,235.498767 187.2896,241.642767 L191.5392,248.759567 C191.5904,248.657167 191.6928,248.605967 191.744,248.605967 L191.744,248.605967 Z"
60
+ id="Path"
61
+ fill="#FF5D12"
62
+ ></path>
63
+ <path
64
+ d="M53.6064,213.124367 C27.8016,213.124367 6.8608,234.065167 6.8608,259.869967 L6.8608,324.893967 C6.8608,350.698767 27.8016,371.639567 53.6064,371.639567 L67.6352,371.639567 C93.44,371.639567 114.3808,350.698767 114.3808,324.893967 L114.3808,259.869967 C114.3808,234.065167 93.44,213.124367 67.6352,213.124367 L53.6064,213.124367 Z"
65
+ id="Path"
66
+ fill="#D0DBE2"
67
+ ></path>
68
+ <path
69
+ d="M73.3184,213.482767 C47.5136,213.482767 26.5728,234.423567 26.5728,260.228367 L26.5728,325.201167 C26.5728,351.005967 47.5136,371.946767 73.3184,371.946767 L87.3472,371.946767 C113.152,371.946767 134.0928,351.005967 134.0928,325.201167 L134.0928,260.228367 C134.0928,234.423567 113.152,213.482767 87.3472,213.482767 L73.3184,213.482767 Z"
70
+ id="Path"
71
+ fill="#BEC9CE"
72
+ ></path>
73
+ <ellipse
74
+ id="Oval"
75
+ fill="#FFB818"
76
+ cx="18.2272"
77
+ cy="278.865167"
78
+ rx="7.936"
79
+ ry="9.3184"
80
+ ></ellipse>
81
+ <path
82
+ d="M10.2912,278.865167 C10.2912,283.985167 13.824,288.183567 18.2272,288.183567 C21.76,288.183567 24.7296,285.469967 25.7536,281.681167 C24.4224,282.858767 22.7328,283.575567 20.9408,283.575567 C16.5888,283.575567 13.0048,279.428367 13.0048,274.257167 C13.0048,273.284367 13.1584,272.311567 13.3632,271.441167 C11.52,273.181967 10.2912,275.895567 10.2912,278.865167 Z"
83
+ id="Path"
84
+ fill="#FF5D12"
85
+ ></path>
86
+ <path
87
+ d="M16.5376,297.245967 C14.5408,297.245967 12.9024,298.884367 12.9024,300.881167 L12.9024,325.047567 C12.9024,327.044367 14.5408,328.682767 16.5376,328.682767 L19.9168,328.682767 C21.9136,328.682767 23.552,327.044367 23.552,325.047567 L23.552,300.881167 C23.552,298.884367 21.9136,297.245967 19.9168,297.245967 L16.5376,297.245967 Z"
88
+ id="Path"
89
+ fill="#FDCC00"
90
+ ></path>
91
+ <path
92
+ d="M16.5376,297.245967 C14.5408,297.245967 12.9024,298.884367 12.9024,300.881167 L12.9024,325.047567 C12.9024,327.044367 14.5408,328.682767 16.5376,328.682767 L19.9168,328.682767 C21.9136,328.682767 23.552,327.044367 23.552,325.047567 L23.552,323.357967 L20.6848,323.357967 C18.688,323.357967 17.0496,321.719567 17.0496,319.722767 L17.0496,297.245967 L16.5376,297.245967 Z"
93
+ id="Path"
94
+ fill="#FCA800"
95
+ ></path>
96
+ <path
97
+ d="M68.352,362.986767 C84.1216,395.601167 205.2096,396.625167 216.1664,361.501967 C236.7488,295.658767 191.232,230.429967 187.136,230.481167 L82.432,227.613967 C82.432,227.613967 48.4864,321.975567 68.352,362.986767 Z"
98
+ id="Path"
99
+ fill="#E8EDEC"
100
+ ></path>
101
+ <path
102
+ d="M28.0064,255.569167 C21.504,269.239567 19.0464,287.773967 22.3744,304.004367 L66.9184,297.911567 C67.9424,282.397967 84.1216,232.529167 84.1216,232.529167 L60.7232,215.684367 C60.7232,215.684367 43.6736,222.545167 28.0064,255.569167 L28.0064,255.569167 Z"
103
+ id="Path"
104
+ fill="#E8EDEC"
105
+ ></path>
106
+ <path
107
+ d="M45.2608,228.381967 C55.1936,236.778767 66.56,243.537167 79.104,248.452367 C81.92,239.185167 84.0704,232.477967 84.0704,232.477967 L60.672,215.633167 C60.672,215.684367 54.3232,218.141967 45.2608,228.381967 Z"
108
+ id="Path"
109
+ fill="#BEC9CE"
110
+ ></path>
111
+ <path
112
+ d="M76.1856,247.274767 C94.0032,254.801167 114.3808,258.692367 136.7552,258.692367 C156.416,258.692367 174.9504,255.313167 191.7952,248.554767 C194.1504,247.633167 196.4032,246.609167 198.7072,245.585167 C193.1264,236.061967 188.3648,230.481167 187.1872,230.481167 L82.432,227.613967 C82.432,227.613967 79.6672,235.293967 76.1856,247.274767 Z"
113
+ id="Path"
114
+ fill="#BEC9CE"
115
+ ></path>
116
+ <path
117
+ d="M42.6496,231.556367 C47.4624,237.137167 62.1056,256.337167 66.3552,286.135567 C66.3552,286.135567 72.704,243.076367 67.1232,220.241167 L60.7232,215.633167 C60.7232,215.684367 52.6848,218.909967 42.6496,231.556367 L42.6496,231.556367 Z"
118
+ id="Path"
119
+ fill="#FFB818"
120
+ ></path>
121
+ <path
122
+ d="M45.2608,228.381967 C52.5824,234.577167 60.6208,239.850767 69.376,244.151567 C69.3248,235.498767 68.7104,226.999567 67.072,220.241167 L60.672,215.633167 C60.672,215.684367 54.1696,218.193167 45.2608,228.381967 L45.2608,228.381967 Z"
123
+ id="Path"
124
+ fill="#FF5D12"
125
+ ></path>
126
+ <path
127
+ d="M133.5808,302.980367 L135.8848,325.661967 C136.704,333.495567 143.6672,339.229967 151.552,338.410767 L193.536,334.109967 C201.3696,333.290767 207.104,326.327567 206.2848,318.442767 L203.9808,295.761167 C203.1616,287.927567 196.1984,282.193167 188.3136,283.012367 L146.3296,287.313167 C138.496,288.132367 132.7616,295.146767 133.5808,302.980367 Z"
128
+ id="Path"
129
+ fill="#BEC9CE"
130
+ ></path>
131
+ <path
132
+ d="M141.5168,298.628367 L143.872,321.770767 C144.4864,327.658767 149.76,331.959567 155.648,331.396367 L197.888,327.095567 C203.776,326.481167 208.0768,321.207567 207.5136,315.319567 L205.1584,292.177167 C204.544,286.289167 199.2704,281.988367 193.3824,282.551567 L151.1424,286.852367 C145.2032,287.466767 140.9024,292.740367 141.5168,298.628367 Z"
133
+ id="Path"
134
+ fill="#D0DBE2"
135
+ ></path>
136
+ <ellipse
137
+ id="Oval"
138
+ fill="#FFB818"
139
+ transform="translate(160.568126, 307.574545) rotate(-5.730000) translate(-160.568126, -307.574545) "
140
+ cx="160.568126"
141
+ cy="307.574545"
142
+ rx="7.936"
143
+ ry="9.3184"
144
+ ></ellipse>
145
+ <path
146
+ d="M152.4736,308.407567 C152.9856,313.527567 156.928,317.316367 161.28,316.855567 C164.8128,316.497167 167.4752,313.476367 168.1408,309.636367 C166.912,310.967567 165.376,311.837967 163.5328,312.042767 C159.1808,312.503567 155.2384,308.714767 154.7264,303.594767 C154.624,302.621967 154.6752,301.649167 154.8288,300.727567 C153.088,302.673167 152.1664,305.437967 152.4736,308.407567 Z"
147
+ id="Path"
148
+ fill="#FF5D12"
149
+ ></path>
150
+ <path
151
+ d="M176.5888,303.543567 L193.1776,301.853967 C195.1744,301.649167 196.608,299.857167 196.4032,297.911567 L196.3008,297.041167 C196.096,295.044367 194.304,293.610767 192.3584,293.815567 L175.7696,295.505167 C173.7728,295.709967 172.3392,297.501967 172.544,299.447567 L172.6464,300.317967 C172.8512,302.314767 174.592,303.748367 176.5888,303.543567 Z"
152
+ id="Path"
153
+ fill="#A0CF30"
154
+ ></path>
155
+ <path
156
+ d="M178.0736,318.033167 L194.6624,316.343567 C196.6592,316.138767 198.0928,314.346767 197.888,312.401167 L197.7856,311.530767 C197.5808,309.533967 195.7888,308.100367 193.8432,308.305167 L177.2544,309.994767 C175.2576,310.199567 173.824,311.991567 174.0288,313.937167 L174.1312,314.807567 C174.2848,316.804367 176.0768,318.237967 178.0736,318.033167 Z"
157
+ id="Path"
158
+ fill="#FDCC00"
159
+ ></path>
160
+ <path
161
+ d="M175.4624,303.645967 L194.304,301.700367 C195.6864,301.546767 196.6592,300.164367 196.5056,298.525967 L196.4544,298.218767 L177.6128,300.164367 C176.2304,300.317967 175.0016,299.089167 174.848,297.501967 L174.6432,295.607567 C173.2608,295.761167 172.288,297.143567 172.4416,298.781967 L172.6464,300.983567 C172.8512,302.621967 174.08,303.799567 175.4624,303.645967 L175.4624,303.645967 Z"
162
+ id="Path"
163
+ fill="#89A520"
164
+ ></path>
165
+ <path
166
+ d="M176.9472,318.186767 L195.7888,316.241167 C197.1712,316.087567 198.144,314.705167 197.9904,313.066767 L197.9392,312.759567 L179.0976,314.705167 C177.7152,314.858767 176.4864,313.629967 176.3328,312.042767 L176.128,310.148367 C174.7456,310.301967 173.7728,311.684367 173.9264,313.322767 L174.1312,315.524367 C174.336,317.111567 175.5648,318.289167 176.9472,318.186767 L176.9472,318.186767 Z"
167
+ id="Path"
168
+ fill="#FCA800"
169
+ ></path>
170
+ <ellipse
171
+ id="Oval"
172
+ fill="#E8EDEC"
173
+ cx="136.7552"
174
+ cy="128.797967"
175
+ rx="122.368"
176
+ ry="117.8112"
177
+ ></ellipse>
178
+ <path
179
+ d="M147.2,11.3963673 C75.264,5.55956735 14.336,60.1387673 14.336,128.797967 C14.336,202.935567 67.8912,251.524367 146.8416,246.301967 C73.8304,241.745167 35.2768,190.903567 35.2768,128.797967 C35.2768,67.1019673 84.48,16.5163673 147.2,11.3963673 Z"
180
+ id="Path"
181
+ fill="#D0DBE2"
182
+ ></path>
183
+ <path
184
+ d="M95.8464,9.29716735 C88.9856,13.4955673 86.1696,21.4827673 86.1696,21.4827673 C86.1696,21.4827673 128.9216,22.1995673 141.9264,66.2827673 L201.8304,66.2827673 C201.8304,66.2827673 206.1312,31.6715673 174.2848,10.9355673 C142.7968,-9.49323265 104.5504,3.97236735 95.8464,9.29716735 L95.8464,9.29716735 Z"
185
+ id="Path"
186
+ fill="#FFB818"
187
+ ></path>
188
+ <path
189
+ d="M96.1024,9.14356735 L95.7952,9.29716735 C88.9344,13.4955673 86.1184,21.4827673 86.1184,21.4827673 C86.1184,21.4827673 128.8704,22.1995673 141.8752,66.2827673 L152.5248,66.2827673 C152.1152,64.7467673 151.6544,63.1595673 151.1424,61.4699673 C138.8032,20.7147673 114.0224,10.6795673 96.1024,9.14356735 L96.1024,9.14356735 Z"
190
+ id="Path"
191
+ fill="#FF5D12"
192
+ ></path>
193
+ <path
194
+ d="M57.6512,112.874767 C46.8992,144.772367 63.2832,224.849167 154.7776,224.849167 C261.0688,224.849167 259.9424,133.303567 252.4672,107.652367 C240.64,66.8459673 213.7088,57.0155673 174.1824,54.3019673 C112.2816,50.0523673 72.2944,69.4059673 57.6512,112.874767 Z"
195
+ id="Path"
196
+ fill="#000000"
197
+ ></path>
198
+ <path
199
+ d="M168.448,238.980367 C182.5792,237.444367 208.384,223.159567 189.2352,222.391567 C185.5488,222.237967 192.9216,213.226767 177.3056,213.124367 C159.8464,213.021967 161.792,226.897167 142.7968,229.303567 C123.7504,231.761167 144.0768,241.693967 168.448,238.980367 L168.448,238.980367 Z"
200
+ id="Path"
201
+ fill="#E8EDEC"
202
+ ></path>
203
+ <ellipse
204
+ id="Oval"
205
+ fill="#FFB818"
206
+ cx="25.9584"
207
+ cy="141.393167"
208
+ rx="24.832"
209
+ ry="42.2912"
210
+ ></ellipse>
211
+ <ellipse
212
+ id="Oval"
213
+ fill="#FF5D12"
214
+ cx="18.944"
215
+ cy="141.393167"
216
+ rx="18.944"
217
+ ry="35.584"
218
+ ></ellipse>
219
+ <g
220
+ transform="translate(64.249724, 83.345216)"
221
+ fill="#474747"
222
+ id="Shape"
223
+ >
224
+ <path d="M172.959876,7.97435136 C173.369476,3.98075136 170.451076,0.396751356 166.457476,0.0383513555 C162.463876,-0.371248644 158.879876,2.54715136 158.521476,6.54075136 C158.111876,10.5343514 161.030276,14.1183514 165.023876,14.4767514 C169.017476,14.8863514 172.550276,11.9679514 172.959876,7.97435136 Z M160.774276,109.247951 C160.108676,110.118351 161.337476,111.193551 162.105476,110.425551 C181.715076,91.4815514 191.187076,49.1903514 178.796676,20.2111514 C177.567876,17.2927514 174.188676,15.9615514 171.270276,17.1903514 C168.300676,18.4703514 166.918276,21.9519514 168.403076,24.9727514 C177.670276,43.9167514 177.311876,69.1071514 170.860676,89.1775514 C166.662276,102.182351 160.671876,109.401551 160.774276,109.247951 L160.774276,109.247951 Z M0.00627565551,56.8191514 C0.723075656,64.9087514 2.66867566,72.9471514 5.63827566,80.6271514 C10.8094757,93.9391514 17.8238757,102.643151 17.6190757,102.387151 C18.9502757,104.127951 21.4078757,104.486351 23.1486757,103.206351 C24.8894757,101.875151 25.2478757,99.4175514 23.9678757,97.6767514 C23.3534757,96.8575514 16.6462757,90.2527514 10.6046757,78.4767514 C4.46067566,66.4447514 2.87347566,56.1535514 2.92467566,56.4607514 C2.56627566,54.5663514 -0.147324344,54.9759514 0.00627565551,56.8191514 Z M29.3950757,104.639951 C26.7326757,105.049551 24.8382757,107.507151 25.2478757,110.220751 C25.6574757,112.883151 28.1150757,114.777551 30.8286757,114.367951 C33.4910757,113.958351 35.3854757,111.500751 34.9758757,108.787151 C34.5662757,106.073551 32.1086757,104.230351 29.3950757,104.639951 Z"></path>
225
+ </g>
226
+ <path
227
+ d="M214.681871,100.902169 C201.516737,89.3061686 180.601537,91.0902023 168.283842,105.029978 C155.118363,93.4335095 134.743599,95.4683743 123.258102,109.089316 C111.772605,122.710258 113.958018,141.612003 126.909166,152.193685 L172.379643,189.540493 L215.003181,145.28004 C227.534789,132.355011 227.464387,112.462126 214.681871,100.902169 Z"
228
+ id="Path"
229
+ fill={isHovered ? "#FF5D12" : "#57F287"}
230
+ ></path>
231
+ <path
232
+ d="M22.3744,304.004367 C26.0608,321.821967 36.8128,336.823567 57.088,339.383567 C95.1296,344.196367 110.2848,306.973967 110.2848,306.973967 L76.6464,279.018767 C66.048,288.337167 48.3328,294.532367 48.3328,294.532367 L22.3744,304.004367 Z"
233
+ id="Path"
234
+ fill="#E8EDEC"
235
+ ></path>
236
+ <g transform="translate(34.406400, 299.498767)" fill="#D0DBE2">
237
+ <path
238
+ d="M75.8272,7.424 C75.776,7.424 75.776,7.424 75.8272,7.424 C75.776,7.3728 75.7248,7.3728 75.7248,7.3216 C75.1616,6.912 72.96,5.3248 65.5872,0 C65.536,0 42.0352,49.1008 0,28.8256 C5.5808,34.6112 13.1072,38.656 22.7328,39.8848 C60.7232,44.6976 75.8784,7.4752 75.8272,7.424 C75.8784,7.4752 75.8272,7.424 75.8272,7.424 L75.8272,7.424 Z"
239
+ id="Shape"
240
+ ></path>
241
+ <path
242
+ d="M75.7248,7.3216 C75.7248,7.3216 75.776,7.3216 75.776,7.3728 C75.776,7.424 75.7248,7.3728 75.7248,7.3216 C75.6736,7.3216 75.6736,7.3216 75.7248,7.3216 Z"
243
+ id="Path"
244
+ ></path>
245
+ <path
246
+ d="M75.776,7.3728 L75.7248,7.3216 C75.7248,7.3728 75.7248,7.3728 75.776,7.3728 Z"
247
+ id="Path"
248
+ ></path>
249
+ </g>
250
+ <path
251
+ d="M73.728,279.530767 C66.4576,285.418767 57.7024,289.361167 49.0496,292.842767 C46.7456,293.764367 44.4928,294.634767 42.1888,295.505167 C41.5744,295.709967 41.1648,296.324367 41.3696,296.989967 C41.5232,297.553167 42.24,298.065167 42.8544,297.809167 C51.7632,294.481167 60.672,290.948367 68.7616,285.981967 C71.1168,284.548367 73.3184,282.961167 75.4688,281.271567 C75.9808,280.861967 75.8784,279.991567 75.4688,279.581967 C74.9568,279.018767 74.24,279.121167 73.728,279.530767 L73.728,279.530767 Z"
252
+ id="Path"
253
+ fill="#BEC9CE"
254
+ ></path>
255
+ <path
256
+ d="M158.1056,243.690767 C160.1536,238.519567 157.7472,229.917967 146.2272,234.065167 C134.7072,238.212367 114.688,245.380367 111.7696,244.919567 C108.8,244.407567 110.7456,236.113167 102.3488,231.505167 C96.3584,228.228367 87.9104,229.969167 86.528,235.805967 C85.1456,241.642767 94.464,256.797967 87.9104,265.757967 C84.9408,269.802767 74.2912,277.789967 73.2672,279.172367 C72.2432,280.554767 76.3904,293.866767 86.3232,300.983567 C96.3072,308.100367 110.08,311.991567 112.64,310.711567 C115.2,309.431567 122.7776,292.740367 124.0064,292.074767 C125.2352,291.409167 134.144,290.538767 140.8512,280.042767 C146.6368,271.031567 140.1856,257.156367 140.3392,257.156367 C140.544,257.156367 156.0576,248.861967 158.1056,243.690767 L158.1056,243.690767 Z"
257
+ id="Path"
258
+ fill="#FFB818"
259
+ ></path>
260
+ <path
261
+ d="M248.32,347.729167 C248.32,347.729167 246.784,353.463567 242.7392,356.791567 C242.7392,356.791567 249.344,354.487567 251.4944,348.394767 C250.0608,347.473167 248.32,347.729167 248.32,347.729167 L248.32,347.729167 Z"
262
+ id="Path"
263
+ fill="#FF5D12"
264
+ ></path>
265
+ <path
266
+ d="M110.4384,364.317967 C85.9136,367.338767 70.3488,358.020367 62.8736,351.773967 C59.5968,349.009167 54.8352,348.855567 51.456,351.466767 C46.7968,354.999567 46.5408,362.013967 51.0464,365.751567 C60.0064,373.226767 76.544,383.261967 101.0688,383.261967 C123.904,383.261967 145.0496,375.479567 158.4128,356.945167 C166.144,346.193167 168.0896,336.209167 168.2944,335.133967 C163.6352,332.164367 153.6512,332.522767 148.48,336.311567 C148.48,336.260367 144.384,360.170767 110.4384,364.317967 L110.4384,364.317967 Z"
267
+ id="Path"
268
+ fill="#82D1FF"
269
+ ></path>
270
+ <path
271
+ d="M47.872,357.610767 C47.5136,360.580367 48.5376,363.652367 51.0464,365.751567 C60.0064,373.226767 76.544,383.261967 101.0688,383.261967 C123.904,383.261967 145.0496,375.479567 158.4128,356.945167 C166.144,346.193167 168.0896,336.209167 168.2944,335.133967 C167.424,334.570767 166.3488,334.109967 165.1712,333.802767 C157.1328,358.173967 134.656,376.657167 99.3792,376.657167 C72.2432,376.605967 51.712,362.679567 47.872,357.610767 L47.872,357.610767 Z"
272
+ id="Path"
273
+ fill="#51B1FC"
274
+ ></path>
275
+ <path
276
+ d="M76.4928,357.969167 C76.4928,357.969167 76.4416,358.020367 76.4928,357.969167 C76.544,357.764367 76.544,357.764367 76.4928,357.969167 Z"
277
+ id="Path"
278
+ fill="#51B1FC"
279
+ ></path>
280
+ <g
281
+ transform="translate(52.633600, 341.073167)"
282
+ fill="#51B1FC"
283
+ id="Shape"
284
+ >
285
+ <path d="M0,25.9584 C1.1776,26.88 2.5088,27.8528 3.9424,28.8256 C8.192,24.4224 11.9808,19.5584 15.104,14.2848 C13.6192,13.312 12.3392,12.3392 11.1616,11.4688 C8.2432,16.384 5.4272,19.8656 6.1952,18.8928 C4.3008,21.248 2.2528,23.6544 0,25.9584 Z M18.5856,36.608 C22.2208,31.5904 25.0368,26.0608 27.5456,20.3776 C25.9584,19.8144 24.4736,19.2512 23.0912,18.6368 C18.8928,28.2112 15.0016,33.536 14.1312,34.6624 C15.5136,35.3792 17.0496,35.9936 18.5856,36.608 L18.5856,36.608 Z M36.5056,41.3184 C38.2976,35.4304 39.5264,29.3888 40.192,23.296 C38.5536,23.0912 36.9664,22.8352 35.4304,22.528 C34.816,28.6208 33.5872,34.6112 31.744,40.4992 C33.28,40.8576 34.8672,41.1136 36.5056,41.3184 Z M53.3504,42.0352 C53.76,35.8912 53.8112,29.7472 53.4016,23.6544 C51.7632,23.7568 50.1248,23.808 48.5888,23.808 C48.9984,29.9008 48.9472,36.0448 48.5376,42.1888 C50.1248,42.1376 51.7632,42.1376 53.3504,42.0352 Z M65.6384,40.5504 C67.2256,40.2432 68.8128,39.8848 70.3488,39.4752 C69.632,33.3824 68.5056,27.392 67.072,21.4528 C65.5872,21.8624 64,22.2208 62.3616,22.528 C64.6656,31.744 65.4848,39.0656 65.28,37.6832 C65.4336,38.6048 65.536,39.5264 65.6384,40.5504 Z M81.5616,35.5328 C83.0464,34.8672 84.48,34.1504 85.9136,33.3824 C84.0704,27.648 81.7152,22.016 78.7968,16.6912 C77.4656,17.4592 76.032,18.176 74.496,18.8416 C77.3632,24.1152 79.7184,29.5936 81.5616,35.5328 Z M96.3584,26.1632 C97.5872,25.088 98.7648,23.9616 99.9424,22.784 C96.6656,17.5616 91.9552,12.3392 88.1664,9.2672 C87.1424,10.3936 85.9648,11.52 84.6336,12.6464 C87.6032,14.8992 91.4944,19.2512 93.3376,21.7088 C92.672,20.8384 94.5152,23.0912 96.3584,26.1632 L96.3584,26.1632 Z M110.7456,7.68 C105.6256,4.4032 100.0448,1.8432 94.2592,0 C93.696,1.28 92.928,2.7648 91.9552,4.352 C94.2592,5.0688 96.4608,5.8368 98.56,6.7072 C97.4336,6.2464 102.4,8.1408 108.4416,11.9296 C109.2608,10.4448 110.0288,9.0112 110.7456,7.68 L110.7456,7.68 Z"></path>
286
+ </g>
287
+ <path
288
+ d="M158.1056,243.690767 C158.9248,241.642767 159.0272,239.082767 158.1568,236.983567 C151.1424,247.786767 136.0896,251.575567 134.144,255.978767 C132.096,260.535567 140.9024,279.991567 115.9168,288.183567 C108.3904,290.641167 110.4896,288.797967 96.6144,306.717967 C103.9872,309.994767 110.8992,311.530767 112.6912,310.660367 C115.2512,309.380367 122.8288,292.689167 124.0576,292.023567 C125.2864,291.357967 134.1952,290.487567 140.9024,279.991567 C146.688,270.980367 140.2368,257.105167 140.3904,257.105167 C140.544,257.156367 156.0576,248.861967 158.1056,243.690767 Z"
289
+ id="Path"
290
+ fill="#FF5D12"
291
+ ></path>
292
+ </g>
293
+ </g>
294
+ </svg>
295
+ );
296
+ };
components/clyde/message.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { useState } from "react";
3
+ import { useMount, useUpdateEffect } from "react-use";
4
+
5
+ import { Clyde } from "./clyde";
6
+
7
+ export const ClydeMessage = ({
8
+ message,
9
+ auto = false,
10
+ onClick,
11
+ }: {
12
+ message: string;
13
+ auto?: boolean;
14
+ onClick: any;
15
+ }) => {
16
+ const [show, setShow] = useState(false);
17
+
18
+ useMount(() => {
19
+ if (auto) {
20
+ setTimeout(() => {
21
+ setShow(true);
22
+ }, 2000);
23
+ }
24
+ });
25
+
26
+ useUpdateEffect(() => {
27
+ if (auto && show) {
28
+ setTimeout(() => {
29
+ setShow(false);
30
+ }, 5000);
31
+ }
32
+ }, [show]);
33
+
34
+ return (
35
+ <div
36
+ className="left-0 lg:left-14 max-w-max group absolute top-0 transform -translate-y-[calc(100%-10px)] -z-1"
37
+ onClick={onClick}
38
+ >
39
+ <Clyde />
40
+ <div
41
+ className={classNames(
42
+ "transition-all duration-200 absolute top-2 left-0 bg-white shadow-xl text-sm text-dark-200 font-medium rounded-lg px-3.5 py-1.5 whitespace-nowrap transform group-hover:translate-x-[90px] opacity-0 group-hover:opacity-100",
43
+ {
44
+ "translate-x-[90px] opacity-100": show,
45
+ }
46
+ )}
47
+ >
48
+ {message}
49
+ <div className="w-[0px] h-[0px] border-t-[7px] border-t-transparent border-b-[7px] border-b-transparent border-r-[7px] border-r-white absolute top-[calc(50%-7px)] -left-[6px]" />
50
+ </div>
51
+ {/* Hello<p>lol</p> */}
52
+ </div>
53
+ );
54
+ };
components/collapse/index.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ChevronDownIcon } from "@heroicons/react/solid";
2
+ import classNames from "classnames";
3
+ import { useState } from "react";
4
+ import { useIntl } from "react-intl";
5
+
6
+ export const Collapse = ({
7
+ children,
8
+ title,
9
+ className,
10
+ open: defaultOpen = false,
11
+ onOpenClassName,
12
+ parentClassName,
13
+ }: {
14
+ children: React.ReactNode;
15
+ title: string | React.ReactNode;
16
+ className?: string;
17
+ open?: boolean;
18
+ onOpenClassName?: string;
19
+ parentClassName?: string;
20
+ }) => {
21
+ const [open, setOpen] = useState(defaultOpen);
22
+ const intl = useIntl();
23
+
24
+ return (
25
+ <div className={`${parentClassName} w-full`}>
26
+ <div
27
+ className={`${className} flex items-center justify-between cursor-pointer transition-all duration-200 ${
28
+ open && onOpenClassName
29
+ }`}
30
+ onClick={() => setOpen(!open)}
31
+ >
32
+ {typeof title === "string" ? intl.formatMessage({ id: title }) : title}
33
+ <div>
34
+ <ChevronDownIcon
35
+ className={classNames(
36
+ "w-5 text-white opacity-50 transition-all duration-200",
37
+ {
38
+ "rotate-180": open,
39
+ }
40
+ )}
41
+ />
42
+ </div>
43
+ </div>
44
+ {open && children}
45
+ </div>
46
+ );
47
+ };
components/color-picker/index.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef } from "react";
2
+ import { useClickAway } from "react-use";
3
+ import Picker, { useColorPicker } from "react-best-gradient-color-picker";
4
+ import classNames from "classnames";
5
+
6
+ import ColorPickerIcon from "@/assets/images/color-picker.svg";
7
+ import { IconType } from "@/types/editor";
8
+ import Image from "next/image";
9
+
10
+ export const ColorPicker = ({
11
+ value,
12
+ data,
13
+ gradients = true,
14
+ full = false,
15
+ onChange,
16
+ className,
17
+ }: {
18
+ value?: string;
19
+ data?: any;
20
+ gradients?: boolean;
21
+ full?: boolean;
22
+ onChange: (c: any, datas: any) => void;
23
+ className?: string;
24
+ }) => {
25
+ const [open, setOpen] = useState(false);
26
+ const ref = useRef(null);
27
+
28
+ const [color, setColor] = useState(value ?? "#fff");
29
+ const { getGradientObject } = useColorPicker(color, setColor);
30
+
31
+ useClickAway(ref, () => setOpen(false));
32
+
33
+ const renderBackground = (data: any) => {
34
+ if (data?.gradient?.enabled) {
35
+ if (data?.gradient?.type === "linearGradient") {
36
+ return {
37
+ background: `linear-gradient(${data?.gradient?.colours
38
+ ?.map((e: any) => e.value)
39
+ .join(", ")})`,
40
+ };
41
+ } else {
42
+ return {
43
+ background: `radial-gradient(${data?.gradient?.colours
44
+ ?.map((e: any) => e.value)
45
+ .join(", ")})`,
46
+ };
47
+ }
48
+ }
49
+ return { backgroundColor: data?.colour ?? data?.color ?? "#fff" };
50
+ };
51
+
52
+ return (
53
+ <div className="relative ml-[2px]">
54
+ <div
55
+ ref={ref}
56
+ className={classNames(
57
+ "bg-dark-500 z-[100] md:z-10 lg:rounded-md shadow-lg p-3 border border-dark-600 fixed md:absolute w-full md:w-max md:h-min bottom-0 md:-top-full left-0 transform md:translate-x-7",
58
+ {
59
+ "pointer-events-none opacity-0": !open,
60
+ }
61
+ )}
62
+ >
63
+ {open && (
64
+ <Picker
65
+ value={color}
66
+ hidePresets={true}
67
+ hideAdvancedSliders={true}
68
+ hideColorGuide={true}
69
+ hideInputType={true}
70
+ onChange={(c: string) => {
71
+ setColor(c);
72
+ const datas = getGradientObject();
73
+ onChange(c, datas);
74
+ }}
75
+ className={`${
76
+ data?.gradient?.enabled
77
+ ? "color__picker_discotools--gradient"
78
+ : "color__picker_discotools"
79
+ } ${gradients ? "" : "color__picker without-gradients"}`}
80
+ />
81
+ )}
82
+ </div>
83
+ <div
84
+ className={classNames(
85
+ `group flex items-center transition-all duration-200 hover:ring-opacity-100 ring-opacity-70 justify-center border-[1px] borderk-dark-600 ring-[1px] ring-white h-8 rounded cursor-pointer ${className}`,
86
+ {
87
+ "w-20": !full,
88
+ "w-full": full,
89
+ }
90
+ )}
91
+ style={data ? renderBackground(data) : { backgroundColor: color }}
92
+ onClick={() => setOpen(true)}
93
+ >
94
+ <Image
95
+ src={ColorPickerIcon}
96
+ width={12}
97
+ height={12}
98
+ alt="ColorPicker"
99
+ className="opacity-80"
100
+ />
101
+ </div>
102
+ </div>
103
+ );
104
+ };
components/editor-badge/badge-editor.constants.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { BadgeType } from "types/badge";
2
+
3
+ export const DEFAULT_VALUE: BadgeType = {
4
+ colour: "#5865F1",
5
+ gradient: {
6
+ enabled: false,
7
+ colours: [],
8
+ angle: 0,
9
+ type: "linear-gradient",
10
+ },
11
+ text: {
12
+ value: "DISCOTOOLS.XYZ",
13
+ colour: "#ffffff",
14
+ gradient: {
15
+ enabled: false,
16
+ colours: [],
17
+ angle: 0,
18
+ type: "linear-gradient",
19
+ },
20
+ },
21
+ icon: {
22
+ component: "King",
23
+ position: "left",
24
+ enabled: true,
25
+ colour: "#ffffff",
26
+ },
27
+ shinyEffect: false,
28
+ type: "circle",
29
+ radius: 40,
30
+ fontFamily: "Montserrat",
31
+ fontWeight: "700",
32
+ letterSpacing: 0,
33
+ border: {
34
+ width: 0,
35
+ colour: "#ffffff",
36
+ },
37
+ };
38
+
39
+ export const RATIONAL_BADGE_WIDTH = 32;
components/editor-badge/comps/badge.tsx ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { forwardRef, useEffect, useRef, useState } from "react";
2
+
3
+ import { BadgeType } from "@/types/badge";
4
+ import { FONT_FAMILY } from "@/components/font-family/font-family.constants";
5
+
6
+ import { RATIONAL_BADGE_WIDTH } from "../badge-editor.constants";
7
+ import { Icons } from "@/components/svg/icons";
8
+ import { IconItem } from "@/types/editor";
9
+ import { BADGE_COMPONENTS } from "@/components/svg/badges";
10
+ import classNames from "classnames";
11
+ import { ShineEffect } from "./shine_effect";
12
+
13
+ export const Badge = forwardRef(
14
+ (
15
+ {
16
+ badge,
17
+ size = "medium",
18
+ }: {
19
+ badge: BadgeType;
20
+ size?: "medium" | "small";
21
+ },
22
+ ref
23
+ ) => {
24
+ const textRef = useRef<any>(null);
25
+ const svgRef = useRef<any>(null);
26
+ const badgeContentRef = useRef<any>(null);
27
+ const [viewBox, setViewBox] = useState<string>("0 0 24 24");
28
+
29
+ useEffect(() => {
30
+ if (!textRef.current) return;
31
+ if (!badgeContentRef.current) return;
32
+
33
+ const textWidth = textRef.current.getBoundingClientRect().width;
34
+
35
+ let newBadgeWidth = RATIONAL_BADGE_WIDTH;
36
+ if (textWidth > RATIONAL_BADGE_WIDTH) {
37
+ newBadgeWidth = Math.ceil(textWidth / 32) * 32;
38
+ }
39
+ if (
40
+ (findShape?.autoResize || badge.type === "circle") &&
41
+ newBadgeWidth - textWidth < (badge.type === "circle" ? 6 : 3)
42
+ )
43
+ newBadgeWidth += 32;
44
+ badgeContentRef.current.style.width = `${newBadgeWidth}px`;
45
+ }, [
46
+ badge.text,
47
+ badge.fontFamily,
48
+ badge.letterSpacing,
49
+ badge.fontWeight,
50
+ badge.type,
51
+ badge?.icon?.component,
52
+ badge?.icon?.enabled,
53
+ badge?.icon?.position,
54
+ ]);
55
+
56
+ const fontFamily = FONT_FAMILY?.find((f) => f.label === badge?.fontFamily);
57
+
58
+ const defaultStyle = {
59
+ letterSpacing: badge?.letterSpacing,
60
+ fontWeight: badge?.fontWeight,
61
+ };
62
+
63
+ const findIcon = Icons?.find(
64
+ (i: IconItem) => badge?.icon?.component === i.name
65
+ );
66
+ const findShape = BADGE_COMPONENTS?.find(
67
+ (b: any) => b.name === badge?.type
68
+ );
69
+
70
+ useEffect(() => {
71
+ return setViewBox(
72
+ svgRef?.current?.getAttribute("viewBox") ?? "0 0 200 200"
73
+ );
74
+ }, [badge?.icon]);
75
+
76
+ const IconComponent = findIcon?.component as any;
77
+ const ShapeComponent = findShape?.component as any;
78
+
79
+ return (
80
+ <div
81
+ id={size === "medium" ? "discotools-selected-badge" : ""}
82
+ ref={ref as any}
83
+ className="max-w-max flex items-center h-[32px] justify-center relative"
84
+ >
85
+ {findShape?.component && (
86
+ <ShapeComponent.left color={badge?.stringColor ?? badge?.colour} />
87
+ )}
88
+ {badge?.shinyEffect && (
89
+ <>
90
+ <ShineEffect className="absolute left-[6px] -bottom-[0px] z-1" />
91
+ <ShineEffect className="absolute right-[6px] -top-[0px] z-1" />
92
+ </>
93
+ )}
94
+ <div
95
+ className="whitespace-nowrap text-white font-bold flex text-[18px] h-[28px] items-center justify-center text-center transition-all duration-200 relative"
96
+ ref={badgeContentRef}
97
+ style={{
98
+ background: badge?.stringColor ?? badge?.colour,
99
+ borderRadius: badge.type !== "circle" ? 0 : badge?.radius ?? 0,
100
+ }}
101
+ >
102
+ <div
103
+ ref={textRef as any}
104
+ className="flex items-center justify-center gap-2"
105
+ >
106
+ {badge?.icon?.enabled &&
107
+ badge?.icon?.position?.includes("left") &&
108
+ findIcon?.component &&
109
+ badge?.icon?.component && (
110
+ <svg
111
+ width={16}
112
+ height={16}
113
+ fill={badge?.icon?.colour}
114
+ viewBox={viewBox}
115
+ style={{ minWidth: 16, minHeight: 16 }}
116
+ >
117
+ <IconComponent ref={svgRef as any} />
118
+ </svg>
119
+ )}
120
+
121
+ {badge?.text?.value && (
122
+ <p
123
+ className={`!bg-clip-text text-transparent ${fontFamily?.value}`}
124
+ style={
125
+ badge?.text?.gradient?.enabled
126
+ ? {
127
+ backgroundImage:
128
+ badge?.text?.stringColor ?? badge?.text?.colour,
129
+ ...defaultStyle,
130
+ }
131
+ : {
132
+ backgroundColor:
133
+ badge?.text?.stringColor ?? badge?.text?.colour,
134
+ ...defaultStyle,
135
+ }
136
+ }
137
+ >
138
+ {badge?.text?.value}
139
+ </p>
140
+ )}
141
+ {badge?.icon?.enabled &&
142
+ badge?.icon?.position?.includes("right") &&
143
+ findIcon?.component &&
144
+ badge?.icon?.component && (
145
+ <svg
146
+ width={16}
147
+ height={16}
148
+ fill={badge?.icon?.colour}
149
+ viewBox={viewBox}
150
+ style={{ minWidth: 16, minHeight: 16 }}
151
+ >
152
+ <IconComponent ref={svgRef as any} />
153
+ </svg>
154
+ )}
155
+ </div>
156
+ </div>
157
+ {findShape?.component && (
158
+ <ShapeComponent.right color={badge?.stringColor ?? badge?.colour} />
159
+ )}
160
+ </div>
161
+ );
162
+ }
163
+ );
components/editor-badge/comps/download_button.tsx ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useState } from "react";
2
+ import { ChevronDownIcon } from "@heroicons/react/solid";
3
+ import classNames from "classnames";
4
+ import { useClickAway } from "react-use";
5
+
6
+ export const DownloadButton = ({
7
+ onSave,
8
+ }: {
9
+ onSave: (e?: boolean) => void;
10
+ }) => {
11
+ const [dropdown, setDropdown] = useState(false);
12
+ const ref = useRef(null);
13
+
14
+ useClickAway(ref, () => {
15
+ setDropdown(false);
16
+ });
17
+
18
+ return (
19
+ <div
20
+ ref={ref}
21
+ className="flex justify-start mt-5 bg-darkGreen rounded-md relative"
22
+ >
23
+ <button
24
+ onClick={() => onSave()}
25
+ className="bg-darkGreen w-full text-white font-semibold p-2 rounded-l-md text-sm font-title relative overflow-hidden group"
26
+ >
27
+ <div className="absolute top-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
28
+ <div className="absolute bottom-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
29
+ <span className="relative z-1">Download as .zip</span>
30
+ </button>
31
+ <div
32
+ className="bg-dark-600 bg-opacity-10 px-1.5 border-l border-dark-600 border-opacity-20 flex items-center hover:bg-opacity-20 cursor-pointer"
33
+ onClick={(e) => {
34
+ e.preventDefault();
35
+ e.stopPropagation();
36
+ setDropdown(!dropdown);
37
+ }}
38
+ >
39
+ <ChevronDownIcon className="w-5 h-5 text-white" />
40
+ </div>
41
+ <div
42
+ className={classNames(
43
+ "bg-darkGreen w-full rounded-md absolute bottom-0 left-0 translate-y-[calc(100%+8px)] transition-all duration-200",
44
+ {
45
+ "pointer-events-none opacity-0 !-translate-y-1/2": !dropdown,
46
+ }
47
+ )}
48
+ >
49
+ <button
50
+ onClick={() => onSave(true)}
51
+ className="bg-darkGreen w-full text-white font-semibold p-2 rounded-md text-sm font-title relative overflow-hidden group"
52
+ >
53
+ <div className="absolute top-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
54
+ <div className="absolute bottom-0 left-0 h-0 bg-blue transition-all duration-300 w-full group-hover:h-1/2" />
55
+ <span className="relative z-1">Download as Image</span>
56
+ </button>
57
+ </div>
58
+ </div>
59
+ );
60
+ };
components/editor-badge/comps/header/comps/icons-slider.tsx ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useState } from "react";
2
+ import { useInterval } from "react-use";
3
+ import { motion } from "framer-motion";
4
+ import Image from "next/image";
5
+
6
+ import { sleep } from "utils";
7
+ import HammerIcon from "assets/images/header/icons-slider/hammer.svg";
8
+ import ShieldIcon from "assets/images/header/icons-slider/shield.svg";
9
+ import CrownIcon from "assets/images/header/icons-slider/crown.svg";
10
+ import PartnerIcon from "assets/images/header/icons-slider/partner.svg";
11
+ import StarIcon from "assets/images/header/icons-slider/star.svg";
12
+ import LeafIcon from "assets/images/header/icons-slider/leaf.svg";
13
+
14
+ const ICONS = [
15
+ HammerIcon,
16
+ ShieldIcon,
17
+ CrownIcon,
18
+ PartnerIcon,
19
+ StarIcon,
20
+ LeafIcon,
21
+ ];
22
+
23
+ const icon = {
24
+ hidden: {
25
+ opacity: 0,
26
+ pathLength: 0,
27
+ fill: "rgba(255, 255, 255, 0)",
28
+ },
29
+ visible: {
30
+ opacity: 1,
31
+ pathLength: 1,
32
+ fill: "rgba(255, 255, 255, 1)",
33
+ },
34
+ };
35
+
36
+ export const IconsSlider = () => {
37
+ const [selected, setSelected] = useState(0);
38
+ const ref = useRef<HTMLDivElement>(null);
39
+
40
+ useInterval(async () => {
41
+ if (ref?.current) {
42
+ ref.current?.classList?.add("opacity-0", "-translate-x-full", "scale-50");
43
+ await sleep(300);
44
+ ref.current?.classList?.remove("-translate-x-full");
45
+ ref.current?.classList?.add("translate-x-full");
46
+
47
+ setSelected((prev) => (prev + 1) % ICONS.length);
48
+ // if (this.selected >= this.avatars.length - 1) {
49
+ // this.selected = 0;
50
+ // } else {
51
+ // this.selected += 1;
52
+ // }
53
+ await sleep(300);
54
+ ref.current?.classList?.remove(
55
+ "translate-x-full",
56
+ "scale-50",
57
+ "opacity-0"
58
+ );
59
+ }
60
+ }, 2000);
61
+
62
+ return (
63
+ <div className="w-48 h-32 mx-auto relative z-1 flex items-center justify-center mt-7">
64
+ {/* <Image
65
+ src={ShapeIcon}
66
+ width={32}
67
+ height={32}
68
+ alt="Animation shape"
69
+ className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility"
70
+ /> */}
71
+
72
+ <motion.svg
73
+ viewBox="0 0 224 70"
74
+ xmlns="http://www.w3.org/2000/svg"
75
+ className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility stroke-white stroke-2"
76
+ >
77
+ {/* <motion.path
78
+ d="M154.208 0H39.792l23.074 35-23.074 35h114.416l-23.074-35 23.074-35z"
79
+ variants={icon}
80
+ initial="hidden"
81
+ animate="visible"
82
+ transition={{
83
+ default: { duration: 2, ease: "easeInOut" },
84
+ fill: { duration: 2, ease: [1, 0, 0.8, 1] },
85
+ }}
86
+ /> */}
87
+ <motion.path
88
+ variants={icon}
89
+ initial="hidden"
90
+ animate="visible"
91
+ transition={{
92
+ default: { duration: 2, ease: "easeInOut" },
93
+ fill: { duration: 2, ease: [1, 0, 0.8, 1] },
94
+ }}
95
+ d="M183.573 0H39.793l23.073 35-23.074 35h143.781L160.5 35l23.073-35z"
96
+ fill="#fff"
97
+ />
98
+ <motion.path
99
+ variants={icon}
100
+ initial="hidden"
101
+ animate="visible"
102
+ transition={{
103
+ default: { duration: 2, ease: "easeInOut" },
104
+ fill: { duration: 2, ease: [1, 0, 0.8, 1] },
105
+ }}
106
+ d="M28.79 0H9.26l5.05 7.609 4.883 7.608 4.21 6.425L32.324 35l-8.923 13.358-2.525 4.058-6.566 9.975L9.26 70h19.53l23.065-35L28.79 0z"
107
+ fill="#fff"
108
+ />
109
+ <motion.path
110
+ variants={icon}
111
+ initial="hidden"
112
+ animate="visible"
113
+ transition={{
114
+ default: { duration: 2, ease: "easeInOut" },
115
+ fill: { duration: 2, ease: [1, 0, 0.8, 1] },
116
+ }}
117
+ d="M10.775 21.643L0 35l10.775 13.358L21.55 35 10.775 21.643zM194.765 0h19.525l-5.049 7.609-4.881 7.608-4.208 6.425L191.231 35l8.921 13.358 2.524 4.058 6.565 9.975L214.29 70h-19.525l-23.059-35 23.059-35z"
118
+ fill="#fff"
119
+ />
120
+ <motion.path
121
+ variants={icon}
122
+ initial="hidden"
123
+ animate="visible"
124
+ transition={{
125
+ default: { duration: 2, ease: "easeInOut" },
126
+ fill: { duration: 2, ease: [1, 0, 0.8, 1] },
127
+ }}
128
+ d="M212.545 21.7L223.366 35l-10.821 13.3L201.724 35l10.821-13.3z"
129
+ fill="#fff"
130
+ />
131
+ </motion.svg>
132
+ <div
133
+ ref={ref}
134
+ className="transform transition duration-300 w-[45px] h-[45px] filter drop-shadow-xl origin-center"
135
+ >
136
+ <Image
137
+ src={ICONS[selected]}
138
+ width={32}
139
+ height={32}
140
+ alt="Animation icon, change every 3 seconds"
141
+ className="w-full h-full"
142
+ draggable="false"
143
+ />
144
+ </div>
145
+ <p></p>
146
+ </div>
147
+ // <div class="w-64 h-64 mx-auto relative z-1 flex items-center justify-center">
148
+ // <img src="@/assets/images/shadow_gradient.svg" alt="Linear gradient behind avatar" width="86" height="86" draggable="false" class="h-full absolute left-0 top-0 w-full -z-1 backface-visibility">
149
+ // <Avatar />
150
+ // </div>
151
+ );
152
+ };
components/editor-badge/comps/header/form/advanced.tsx ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { FormattedMessage, useIntl } from "react-intl";
3
+
4
+ import { FontFamilySelector } from "@/components/font-family";
5
+ import { FontWeight } from "@/components/font-weight";
6
+
7
+ import { Switch } from "@/components/switch";
8
+ import { BadgeType } from "@/types/badge";
9
+ import { PremiumOverlay } from "@/components/premium/overlay";
10
+
11
+ import { Label } from "@/components/label";
12
+ import { Input } from "@/components/input";
13
+
14
+ export const AdvancedForm = ({
15
+ badge,
16
+ setBadge,
17
+ }: {
18
+ badge: BadgeType;
19
+ setBadge: (b: BadgeType) => void;
20
+ }) => {
21
+ const intl = useIntl();
22
+
23
+ return (
24
+ <div className="grid grid-cols-1 gap-4 px-6 pb-6 pt-5">
25
+ {badge?.type === "circle" && (
26
+ <div>
27
+ <Label className="mb-2">Rounded</Label>
28
+ <Input
29
+ value={badge?.radius}
30
+ type="number"
31
+ placeholder="Badge radius"
32
+ className="bg-dark-600 rounded p-3 text-sm text-white placeholder-dark-200"
33
+ onChange={(newRadius) => {
34
+ let radius: number = Number(newRadius);
35
+ if (radius > 100) radius = 100;
36
+ setBadge({ ...badge, radius: Number(radius) });
37
+ }}
38
+ />
39
+ </div>
40
+ )}
41
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 relative">
42
+ <div className="lg:col-span-2">
43
+ <Label>Font</Label>
44
+ </div>
45
+ <div>
46
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
47
+ <FormattedMessage id="Font Family" />
48
+ </p>
49
+ <FontFamilySelector
50
+ value={badge?.fontFamily}
51
+ onSelect={(fontFamily) => setBadge({ ...badge, fontFamily })}
52
+ />
53
+ </div>
54
+ <div>
55
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
56
+ <FormattedMessage id="Font Weight" />
57
+ </p>
58
+ <FontWeight
59
+ value={badge?.fontWeight}
60
+ onSelect={(fontWeight) => setBadge({ ...badge, fontWeight })}
61
+ />
62
+ </div>
63
+ <div className="lg:col-span-2">
64
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
65
+ <FormattedMessage id="Letter Spacing" />
66
+ </p>
67
+ <Input
68
+ value={badge?.letterSpacing}
69
+ type="number"
70
+ placeholder="Letter spacing"
71
+ className="bg-dark-600 rounded p-3 text-sm text-white placeholder-dark-200"
72
+ onChange={(newLetterSpacing) => {
73
+ let letterSpacing: number = Number(newLetterSpacing);
74
+ if (letterSpacing > 100) letterSpacing = 100;
75
+ setBadge({
76
+ ...badge,
77
+ letterSpacing: Number(letterSpacing),
78
+ });
79
+ }}
80
+ />
81
+ </div>
82
+ <PremiumOverlay className="!bg-dark-500 !bg-opacity-80" />
83
+ </div>
84
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
85
+ <div className="lg:col-span-2 flex items-center justify-start gap-3">
86
+ <Label>Shiny Effect</Label>
87
+ <Switch
88
+ value={badge?.shinyEffect}
89
+ onChange={(enabled: boolean) =>
90
+ setBadge({
91
+ ...badge,
92
+ shinyEffect: enabled,
93
+ })
94
+ }
95
+ />
96
+ </div>
97
+ </div>
98
+ </div>
99
+ );
100
+ };
components/editor-badge/comps/header/form/main.tsx ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { FormattedMessage, useIntl } from "react-intl";
3
+
4
+ import { FontFamilySelector } from "@/components/font-family";
5
+ import { FontWeight } from "@/components/font-weight";
6
+
7
+ import { Switch } from "@/components/switch";
8
+ import { BadgeType } from "@/types/badge";
9
+ import { ColorPicker } from "components/color-picker";
10
+ import { Label } from "@/components/label";
11
+ import { Input } from "@/components/input";
12
+ import classNames from "classnames";
13
+ import { SelectShapes } from "../../select-shapes";
14
+ import { IconPicker } from "@/components/icon-picker";
15
+
16
+ export const MainForm = ({
17
+ badge,
18
+ setBadge,
19
+ }: {
20
+ badge: BadgeType;
21
+ setBadge: (b: BadgeType) => void;
22
+ }) => {
23
+ const intl = useIntl();
24
+
25
+ return (
26
+ <div className="grid grid-cols-1 gap-4 px-6 pb-6 pt-5">
27
+ <SelectShapes badge={badge} onChange={setBadge} />
28
+ <div>
29
+ <Label className="mb-2">Text</Label>
30
+ <Input
31
+ value={badge?.text?.value}
32
+ placeholder="Badge text"
33
+ className="bg-dark-600 rounded px-3 py-3 text-sm text-white placeholder-dark-200 w-full"
34
+ onChange={(value) => {
35
+ setBadge({
36
+ ...badge,
37
+ text: { ...badge.text, value: value as string },
38
+ });
39
+ }}
40
+ />
41
+ </div>
42
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
43
+ <div className="lg:col-span-2">
44
+ <Label>Colors</Label>
45
+ </div>
46
+ <div>
47
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
48
+ <FormattedMessage id="Background" />
49
+ </p>
50
+ <ColorPicker
51
+ data={badge}
52
+ full
53
+ value={badge?.stringColor ?? badge?.colour}
54
+ gradients={badge?.type === "circle"}
55
+ onChange={(c: any, datas) => {
56
+ let newBadge = { ...badge, stringColor: c };
57
+ if (c.includes("gradient")) {
58
+ const { colors, degrees } = datas;
59
+ const gradientType = c?.startsWith("linear")
60
+ ? "linearGradient"
61
+ : "radialGradient";
62
+ const anglePI =
63
+ Number(degrees ? degrees?.replace("deg", "") : 90) *
64
+ (Math.PI / 180);
65
+ const angleCoords = {
66
+ x1: Math.round(50 + Math.sin(anglePI) * 50) + "%",
67
+ y1: Math.round(50 + Math.cos(anglePI) * 50) + "%",
68
+ x2: Math.round(50 + Math.sin(anglePI + Math.PI) * 50) + "%",
69
+ y2: Math.round(50 + Math.cos(anglePI + Math.PI) * 50) + "%",
70
+ };
71
+
72
+ newBadge["gradient"] = {
73
+ ...newBadge.gradient,
74
+ enabled: true,
75
+ colours: colors,
76
+ angle: angleCoords,
77
+ type: gradientType,
78
+ };
79
+ } else {
80
+ newBadge = {
81
+ ...newBadge,
82
+ colour: c,
83
+ gradient: {
84
+ ...newBadge?.gradient,
85
+ enabled: false,
86
+ },
87
+ };
88
+ }
89
+ setBadge(newBadge);
90
+ }}
91
+ />
92
+ </div>
93
+ <div>
94
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
95
+ <FormattedMessage id="Text" />
96
+ </p>
97
+ <ColorPicker
98
+ data={badge?.text}
99
+ value={badge?.text?.stringColor ?? badge?.text?.colour}
100
+ full
101
+ onChange={(c: any, datas) => {
102
+ let newBadge = {
103
+ ...badge,
104
+ text: { ...badge.text, stringColor: c },
105
+ };
106
+ if (c.includes("gradient")) {
107
+ const { colors, degrees } = datas;
108
+ const gradientType = c?.startsWith("linear")
109
+ ? "linearGradient"
110
+ : "radialGradient";
111
+ const anglePI =
112
+ Number(degrees ? degrees?.replace("deg", "") : 90) *
113
+ (Math.PI / 180);
114
+ const angleCoords = {
115
+ x1: Math.round(50 + Math.sin(anglePI) * 50) + "%",
116
+ y1: Math.round(50 + Math.cos(anglePI) * 50) + "%",
117
+ x2: Math.round(50 + Math.sin(anglePI + Math.PI) * 50) + "%",
118
+ y2: Math.round(50 + Math.cos(anglePI + Math.PI) * 50) + "%",
119
+ };
120
+ newBadge["text"]["gradient"] = {
121
+ ...newBadge?.text?.gradient,
122
+ enabled: true,
123
+ colours: colors,
124
+ angle: angleCoords,
125
+ type: gradientType,
126
+ };
127
+ } else {
128
+ newBadge = {
129
+ ...newBadge,
130
+ text: {
131
+ ...newBadge?.text,
132
+ colour: c,
133
+ gradient: {
134
+ ...newBadge?.text?.gradient,
135
+ enabled: false,
136
+ },
137
+ },
138
+ };
139
+ }
140
+ setBadge(newBadge);
141
+ }}
142
+ />
143
+ </div>
144
+ </div>
145
+ <div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
146
+ <div className="lg:col-span-4 flex items-center justify-start gap-3">
147
+ <Label>Icon</Label>
148
+ <Switch
149
+ value={badge?.icon?.enabled}
150
+ onChange={(enabled: boolean) =>
151
+ setBadge({
152
+ ...badge,
153
+ icon: {
154
+ ...badge.icon,
155
+ position: badge?.icon?.position ?? "left",
156
+ enabled,
157
+ },
158
+ })
159
+ }
160
+ />
161
+ </div>
162
+ <div>
163
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
164
+ <FormattedMessage id="Color" />
165
+ </p>
166
+ <ColorPicker
167
+ data={badge?.icon}
168
+ full
169
+ className="h-[38px]"
170
+ value={badge?.icon?.stringColor ?? badge?.icon?.colour}
171
+ gradients={false}
172
+ onChange={(c: any, datas) => {
173
+ setBadge({
174
+ ...badge,
175
+ icon: {
176
+ ...badge?.icon,
177
+ position: badge?.icon?.position ?? "left",
178
+ colour: c,
179
+ stringColor: c,
180
+ },
181
+ });
182
+ }}
183
+ />
184
+ </div>
185
+ <div className="flex items-start justify-start gap-4 lg:col-span-3">
186
+ <div>
187
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
188
+ <FormattedMessage id="Icon" />
189
+ </p>
190
+ <IconPicker
191
+ icon={badge?.icon?.component}
192
+ onSelect={(component) => {
193
+ setBadge({
194
+ ...badge,
195
+ icon: {
196
+ ...badge?.icon,
197
+ position: badge?.icon?.position ?? "left",
198
+ enabled: true,
199
+ component,
200
+ },
201
+ });
202
+ }}
203
+ />
204
+ </div>
205
+ <div className="w-full">
206
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
207
+ <FormattedMessage id="Position" />
208
+ </p>
209
+ <div className="flex items-center justify-between">
210
+ <div
211
+ className={classNames(
212
+ "w-full h-[40px] text-sm p-2 text-dark-100 rounded-l-lg border-l border-t border-b border-dark-300 text-center font-semibold cursor-pointer",
213
+ {
214
+ "bg-green !text-white bg-opacity-30 border-green":
215
+ badge?.icon?.position === "left",
216
+ }
217
+ )}
218
+ onClick={() =>
219
+ setBadge({
220
+ ...badge,
221
+ icon: { ...badge?.icon, position: "left" },
222
+ })
223
+ }
224
+ >
225
+ Left
226
+ </div>
227
+ <div
228
+ className={classNames(
229
+ "w-full h-[40px] text-sm p-2 text-dark-100 border-t border-b border-dark-300 text-center font-semibold cursor-pointer",
230
+ {
231
+ "bg-green !text-white bg-opacity-30 border-green":
232
+ badge?.icon?.position === "right",
233
+ }
234
+ )}
235
+ onClick={() =>
236
+ setBadge({
237
+ ...badge,
238
+ icon: { ...badge?.icon, position: "right" },
239
+ })
240
+ }
241
+ >
242
+ Right
243
+ </div>
244
+ <div
245
+ className={classNames(
246
+ "w-full h-[40px] text-sm p-2 text-dark-100 rounded-r-lg border-t border-r border-b border-dark-300 text-center font-semibold cursor-pointer",
247
+ {
248
+ "bg-green !text-white bg-opacity-30 border-green":
249
+ badge?.icon?.position === "left-right",
250
+ }
251
+ )}
252
+ onClick={() =>
253
+ setBadge({
254
+ ...badge,
255
+ icon: { ...badge?.icon, position: "left-right" },
256
+ })
257
+ }
258
+ >
259
+ Left & Right
260
+ </div>
261
+ </div>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>
266
+ );
267
+ };
components/editor-badge/comps/header/index.tsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+
3
+ import { Tag } from "components/tag";
4
+ import { IconsSlider } from "./comps/icons-slider";
5
+
6
+ // import { Editor } from "components/editor";
7
+ // import { ArrayOfIcons } from "components/svg/icons";
8
+
9
+ // import { Navigation } from "components/navigation";
10
+
11
+ import Background from "assets/images/header/background.svg";
12
+ import Stars from "assets/images/header/stars.svg";
13
+ import Ananas from "assets/images/header/ananas.svg";
14
+ import Pizza from "assets/images/header/pizza.svg";
15
+ import { FormattedMessage, useIntl } from "react-intl";
16
+
17
+ export const Header = ({ children }: any) => {
18
+ const intl = useIntl();
19
+ return (
20
+ <header className="bg-white relative z-20">
21
+ <div className="z-1 px-6 pt-28">
22
+ <div className="container mx-auto relative">
23
+ <div className="max-w-[1051px] w-full mx-auto text-center">
24
+ <Tag
25
+ label={`${intl.formatMessage({ id: "badge.new" })} 🔥`}
26
+ className="mb-5"
27
+ >
28
+ NEW FEATURE AVAILABLE
29
+ </Tag>
30
+ <IconsSlider />
31
+ <div className="max-w-max relative mx-auto md:px-12 pt-3 z-1 mt-2">
32
+ <h1 className="text-white font-title font-black text-4xl md:text-[70px] !leading-none text-center">
33
+ <FormattedMessage
34
+ id="badgeEditor.header.title"
35
+ values={{ span: () => <br /> }}
36
+ />{" "}
37
+
38
+ </h1>
39
+ <Image
40
+ width={32}
41
+ height={32}
42
+ src={Stars}
43
+ className="absolute top-0 left-0 w-full pointer-events-none"
44
+ alt="Stars on title"
45
+ />
46
+ </div>
47
+ <h2 className="text-white font-medium text-center mt-5 text-lg md:text-[26px] tracking-wide">
48
+ <FormattedMessage id="badgeEditor.header.subtitle" />
49
+ </h2>
50
+ <p className="text-white text-opacity-70 text-sm mt-2">
51
+ <FormattedMessage id="footer.legal2" />
52
+ </p>
53
+ </div>
54
+ {children}
55
+ </div>
56
+ </div>
57
+ <div className="absolute top-0 left-0 h-full max-h-[875px] bg-blue -z-1 w-full">
58
+ <Image
59
+ width={32}
60
+ height={32}
61
+ alt="Pizza with sauce"
62
+ src={Pizza}
63
+ className="w-[90px] lg:w-[120px] opacity-60 lg:opacity-100 absolute select-none left-10 lg:left-20 top-32 lg:top-1/3 pointer-events-none block"
64
+ />
65
+ <Image
66
+ width={32}
67
+ height={32}
68
+ alt="Ananas party!"
69
+ src={Ananas}
70
+ className="w-[150px] absolute select-none bottom-20 right-16 pointer-events-none hidden lg:block"
71
+ />
72
+ <Image
73
+ width={32}
74
+ height={32}
75
+ alt="Background header cloud"
76
+ src={Background}
77
+ className="w-full absolute select-none bottom-0 left-0 pointer-events-none -z-1 opacity-50"
78
+ />
79
+ </div>
80
+ {/* <div className="bg-blue h-full w-full absolute top-0 left-0 -z-1">
81
+ </div> */}
82
+ </header>
83
+ );
84
+ };
components/editor-badge/comps/preview.tsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMount, useUpdateEffect } from "react-use";
2
+ import { domToPng } from "modern-screenshot";
3
+
4
+ import { BadgeType } from "@/types/badge";
5
+ import { useState } from "react";
6
+
7
+ export const Preview = ({ badge }: { badge: BadgeType }) => {
8
+ const [badgeUrl, setBadgeUrl] = useState("");
9
+
10
+ useUpdateEffect(() => {
11
+ setTimeout(() => {
12
+ domToPng(
13
+ document.getElementById("discotools-selected-badge") as HTMLElement,
14
+ {
15
+ scale: 2,
16
+ }
17
+ ).then(async (dataUrl) => {
18
+ setBadgeUrl(dataUrl);
19
+ });
20
+ }, 190);
21
+ }, [badge]);
22
+
23
+ useMount(() => {
24
+ setTimeout(() => {
25
+ domToPng(
26
+ document.getElementById("discotools-selected-badge") as HTMLElement,
27
+ {
28
+ scale: 2,
29
+ }
30
+ ).then(async (dataUrl) => {
31
+ setBadgeUrl(dataUrl);
32
+ });
33
+ }, 100);
34
+ });
35
+
36
+ return (
37
+ <div>{badgeUrl && <img src={badgeUrl} className="h-[19.25px]" />}</div>
38
+ );
39
+ };
components/editor-badge/comps/select-shapes.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { faCheck } from "@fortawesome/free-solid-svg-icons";
4
+
5
+ import { Label } from "@/components/label";
6
+ import { BADGE_COMPONENTS } from "@/components/svg/badges";
7
+ import { BadgeType } from "@/types/badge";
8
+ import { useUser } from "@/utils/auth";
9
+ import { PremiumContext } from "@/components/premium/premium";
10
+ import { useContext } from "react";
11
+ import { Premium } from "@/components/premium";
12
+
13
+ export const SelectShapes = ({
14
+ badge,
15
+ onChange,
16
+ }: {
17
+ badge: BadgeType;
18
+ onChange: (b: BadgeType) => void;
19
+ }) => {
20
+ const { user } = useUser();
21
+ const { setOpen } = useContext(PremiumContext);
22
+ return (
23
+ <div>
24
+ <Label className="mb-2">Shapes</Label>
25
+ <div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
26
+ {BADGE_COMPONENTS.map((component, i) => {
27
+ const Component = component.mini as any;
28
+ return (
29
+ <div
30
+ key={i}
31
+ className={classNames(
32
+ "flex items-center justify-start gap-2 cursor-pointer",
33
+ {
34
+ "opacity-50 relative": component.isPremium && !user,
35
+ }
36
+ )}
37
+ onClick={
38
+ component.isPremium && !user
39
+ ? () => setOpen(true)
40
+ : () =>
41
+ onChange({
42
+ ...badge,
43
+ type:
44
+ component?.name === badge?.type
45
+ ? "circle"
46
+ : component.name,
47
+ })
48
+ }
49
+ >
50
+ <div
51
+ className={classNames(
52
+ "w-5 h-5 min-w-[1.25rem] border-2 rounded flex items-center justify-center",
53
+ {
54
+ "bg-green bg-opacity-60 border-green":
55
+ badge?.type === component.name,
56
+ "border-dark-200": badge?.type !== component.name,
57
+ }
58
+ )}
59
+ >
60
+ {badge?.type === component.name && (
61
+ <FontAwesomeIcon
62
+ icon={faCheck}
63
+ className="w-3 text-white text-opacity-100"
64
+ />
65
+ )}
66
+ </div>
67
+ <Component />
68
+ </div>
69
+ );
70
+ })}
71
+ </div>
72
+ </div>
73
+ );
74
+ };
components/editor-badge/comps/shine_effect.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const ShineEffect = ({
2
+ size = 8,
3
+ className,
4
+ }: {
5
+ size?: number;
6
+ className?: string;
7
+ }) => {
8
+ return (
9
+ <svg
10
+ width={size}
11
+ height={size}
12
+ viewBox="0 0 5 5"
13
+ version="1.1"
14
+ className={className}
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ >
17
+ <g
18
+ id="Page-1"
19
+ stroke="none"
20
+ stroke-width="1"
21
+ fill="none"
22
+ fill-rule="evenodd"
23
+ >
24
+ <g
25
+ id="Group"
26
+ transform="translate(-102.000000, 0.000000)"
27
+ fill="#FFFFFF"
28
+ fill-rule="nonzero"
29
+ >
30
+ <polygon
31
+ id="Shape"
32
+ points="104.5 0 105.384 1.616 107 2.5 105.384 3.384 104.5 5 103.616 3.384 102 2.5 103.616 1.616"
33
+ ></polygon>
34
+ </g>
35
+ </g>
36
+ </svg>
37
+ );
38
+ };
components/editor-badge/comps/tabs.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import classNames from "classnames";
3
+
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import { faShapes, faAnglesRight } from "@fortawesome/free-solid-svg-icons";
6
+ import { FormattedMessage } from "react-intl";
7
+
8
+ const TABS_ELEMENTS = [
9
+ {
10
+ icon: faShapes,
11
+ name: "badgeEditor.tabs.main",
12
+ },
13
+ {
14
+ icon: faAnglesRight,
15
+ name: "badgeEditor.tabs.advanced",
16
+ },
17
+ ];
18
+
19
+ export const EditorTabs = ({
20
+ current,
21
+ onChange,
22
+ }: {
23
+ current: number;
24
+ onChange: (e: number) => void;
25
+ }) => {
26
+ return (
27
+ // p-2 lg:p-4
28
+ <div className="bg-dark-600 rounded-t-2xl">
29
+ <ul className="w-full relative z-1 grid grid-cols-2">
30
+ {TABS_ELEMENTS.map((tab, index) => (
31
+ <li
32
+ key={index}
33
+ className={classNames(
34
+ "hover:bg-blue hover:bg-opacity-50 flex items-center gap-5 lg:gap-4 cursor-pointer text-white text-left text-xs lg:text-sm font-semibold tracking-wide p-4 lg:p-5 justify-center uppercase",
35
+ {
36
+ "bg-dark-500 hover:!bg-dark-500 hover:!bg-opacity-100":
37
+ current === index,
38
+ "first-step rounded-t-2xl rounded-r-none": index === 0,
39
+ "second-step rounded-t-2xl rounded-l-none": index === 1,
40
+ }
41
+ )}
42
+ onClick={() => onChange(index)}
43
+ >
44
+ <FontAwesomeIcon icon={tab.icon} className="w-4 min-w-[1rem]" />
45
+ <p>
46
+ <FormattedMessage id={tab.name} />
47
+ </p>
48
+ </li>
49
+ ))}
50
+ </ul>
51
+ </div>
52
+ );
53
+ };
components/editor-badge/comps/user_card.tsx ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from "react";
2
+
3
+ import { BadgeType } from "@/types/badge";
4
+ import { useUser } from "@/utils/auth";
5
+ import DefaultAvatar from "assets/images/avatars/default-avatar.svg";
6
+ import { Moderator } from "@/components/svg/icons/discord";
7
+ import { Boost2 } from "@/components/svg/icons/discord";
8
+ import { Preview } from "./preview";
9
+ import { DownloadButton } from "./download_button";
10
+
11
+ export const UserCard = ({
12
+ badge,
13
+ onSave,
14
+ }: {
15
+ badge: BadgeType;
16
+ onSave: (e?: boolean) => void;
17
+ }) => {
18
+ const { user, loading } = useUser();
19
+
20
+ const convertNumberToStringColor = (color: number) => {
21
+ if (!color) return "#121212";
22
+ return "#" + color?.toString(16);
23
+ };
24
+
25
+ const bannerStyle = useMemo(() => {
26
+ let style: any = { backgroundColor: "#121212", height: 60 };
27
+
28
+ if (user?.id) {
29
+ if (user?.banner) {
30
+ style.backgroundImage = `url(https://cdn.discordapp.com/banners/${user.id}/${user.banner}.gif?size=1024)`;
31
+ style.height = 120;
32
+ style.borderLeft = "5px solid #232429";
33
+ style.borderTop = "5px solid #232429";
34
+ style.borderRight = "5px solid #232429";
35
+ } else {
36
+ // style.backgroundColor = convertNumberToStringColor(user?.accent_color);
37
+ style.backgroundColor = user?.accent_color;
38
+ }
39
+ }
40
+
41
+ return style;
42
+ }, [user]);
43
+
44
+ return (
45
+ <div className="bg-dark-500 rounded-lg w-full overflow-hidden">
46
+ <header
47
+ className={`w-full rounded-t-lg bg-cover bg-center bg-no-repeat p-6 relative ${
48
+ loading && "animate-pulse"
49
+ }`}
50
+ style={bannerStyle}
51
+ >
52
+ <figure
53
+ style={{
54
+ backgroundImage: `url(${
55
+ user?.id
56
+ ? user.avatar
57
+ ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`
58
+ : `https://cdn.discordapp.com/embed/avatars/${
59
+ user?.id % 5
60
+ }.png`
61
+ : DefaultAvatar.src
62
+ })`,
63
+ }}
64
+ className="w-[80px] h-[80px] rounded-full ring-[6px] ring-dark-500 absolute bottom-0 left-5 translate-y-1/2 bg-cover bg-center"
65
+ >
66
+ <div className="bg-[#22A55A] w-[16px] h-[16px] rounded-full absolute bottom-1 right-1 ring-[6px] ring-dark-500"></div>
67
+ </figure>
68
+ </header>
69
+ <main className="px-5 pb-5 pt-4 flex flex-col gap-4 items-end">
70
+ <div className="rounded-md bg-[#111214] p-1.5 flex items-center justify-end gap-2">
71
+ <svg
72
+ width={16}
73
+ height={16}
74
+ viewBox="0 0 160 189"
75
+ fill="#FFAC32"
76
+ style={{ minWidth: 16, minHeight: 16 }}
77
+ >
78
+ <Moderator />
79
+ </svg>
80
+ <svg
81
+ width={16}
82
+ height={16}
83
+ viewBox="0 0 170 144"
84
+ fill="#FF73FA"
85
+ style={{ minWidth: 16, minHeight: 16 }}
86
+ >
87
+ <Boost2 />
88
+ </svg>
89
+ </div>
90
+ <div className="bg-[#111214] rounded-lg px-3 pt-3 pb-4 w-full">
91
+ <p className="text-white font-bold text-xl">
92
+ {user ? user?.username : "Captain Astro"}
93
+ </p>
94
+ <p className="text-white text-sm">
95
+ {user ? `${user?.username}` : "captainastro"}
96
+ </p>
97
+ <div className="w-full h-[1px] bg-[#2E2F35] my-3" />
98
+ <p className="text-xs uppercase font-bold text-white mb-2">
99
+ About me
100
+ </p>
101
+ <Preview badge={badge} />
102
+ {/* <p className="text-xs uppercase font-bold text-white mt-3 mb-2">
103
+ Member since
104
+ </p> */}
105
+ <DownloadButton onSave={onSave} />
106
+ <p className="text-xs text-dark-200 mt-2">
107
+ Image will be split into multiple parts. You will upload them as
108
+ server emojis on Discord to use them on your discord Profile.
109
+ </p>
110
+ </div>
111
+ </main>
112
+ </div>
113
+ );
114
+ };
components/editor-badge/index.tsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef } from "react";
2
+ import { domToPng } from "modern-screenshot";
3
+ import ImageToPieces from "image-to-pieces";
4
+ import JSZip from "jszip";
5
+ import download from "downloadjs";
6
+
7
+ import {
8
+ DEFAULT_VALUE,
9
+ RATIONAL_BADGE_WIDTH,
10
+ } from "components/editor-badge/badge-editor.constants";
11
+ import BackgroundTransparent from "assets/images/editor/transparent_bg.svg";
12
+ import { API } from "@/utils/api";
13
+ import { useUser } from "@/utils/auth";
14
+
15
+ import { MainForm } from "./comps/header/form/main";
16
+ import { AdvancedForm } from "./comps/header/form/advanced";
17
+ import { Badge } from "./comps/badge";
18
+ import { EditorTabs } from "./comps/tabs";
19
+ import { UserCard } from "./comps/user_card";
20
+
21
+ export default function Editor() {
22
+ const { user } = useUser();
23
+ const [badge, setBadge] = useState({
24
+ ...DEFAULT_VALUE,
25
+ });
26
+
27
+ const badgeRef = useRef<any>(null);
28
+ const [current, setCurrent] = useState(0);
29
+
30
+ const handleSave = (hasImage = false) => {
31
+ domToPng(
32
+ document.getElementById("discotools-selected-badge") as HTMLElement,
33
+ {
34
+ scale: 2,
35
+ }
36
+ ).then(async (dataUrl) => {
37
+ if (hasImage) {
38
+ download(dataUrl, "badge.png", "image/png");
39
+ // API.downloadBadge();
40
+ return;
41
+ }
42
+
43
+ if (!badgeRef?.current) return;
44
+ const badgeWidth = badgeRef.current.getBoundingClientRect().width;
45
+ const image = new ImageToPieces(
46
+ null,
47
+ badgeWidth / RATIONAL_BADGE_WIDTH,
48
+ 1
49
+ );
50
+ await image.loadImageByUrl(dataUrl);
51
+ const tiles = image
52
+ .getTiles()
53
+ ?.map((tile: any) => tile.data?.replace("data:image/png;base64,", ""));
54
+ const zip_folder = new JSZip();
55
+ tiles.forEach((tile: any, i: number) =>
56
+ zip_folder.file(`${i}.png`, tile, { base64: true })
57
+ );
58
+
59
+ zip_folder.generateAsync({ type: "blob" }).then(function (content) {
60
+ download(content, "badges.zip");
61
+ // API.downloadBadge();
62
+ });
63
+ });
64
+ };
65
+
66
+ const renderTabs = () => {
67
+ switch (current) {
68
+ case 0:
69
+ return <MainForm badge={badge} setBadge={setBadge} />;
70
+ case 1:
71
+ return <AdvancedForm badge={badge} setBadge={setBadge} />;
72
+ }
73
+ };
74
+
75
+ return (
76
+ <div className="flex-col lg:flex-row flex max-w-5xl mx-auto gap-5 lg:gap-10 mt-10">
77
+ <div className="flex-1 bg-dark-500 shadow-lg rounded-2xl">
78
+ <EditorTabs current={current} onChange={setCurrent} />
79
+ {renderTabs()}
80
+ </div>
81
+ <div className="flex items-start flex-col gap-5 lg:gap-10 lg:max-w-[375px] w-full">
82
+ <header
83
+ className="relative p-5 flex items-center justify-center bg-repeat h-[100px] w-full z-10 transition-all duration-200 first-step rounded-2xl"
84
+ style={{
85
+ backgroundImage: `url(${BackgroundTransparent.src})`,
86
+ }}
87
+ >
88
+ <Badge ref={badgeRef} badge={badge} />
89
+ </header>
90
+ <UserCard badge={badge} onSave={handleSave} />
91
+ </div>
92
+ </div>
93
+ );
94
+ }
components/editor-icons/comps/header/comps/icons-slider.tsx ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useState } from "react";
2
+ import { useInterval } from "react-use";
3
+ import { motion } from "framer-motion";
4
+ import Image from "next/image";
5
+
6
+ import { sleep } from "utils";
7
+ import HammerIcon from "assets/images/header/icons-slider/hammer.svg";
8
+ import ShieldIcon from "assets/images/header/icons-slider/shield.svg";
9
+ import CrownIcon from "assets/images/header/icons-slider/crown.svg";
10
+ import PartnerIcon from "assets/images/header/icons-slider/partner.svg";
11
+ import StarIcon from "assets/images/header/icons-slider/star.svg";
12
+ import LeafIcon from "assets/images/header/icons-slider/leaf.svg";
13
+
14
+ const ICONS = [
15
+ HammerIcon,
16
+ ShieldIcon,
17
+ CrownIcon,
18
+ PartnerIcon,
19
+ StarIcon,
20
+ LeafIcon,
21
+ ];
22
+
23
+ const icon = {
24
+ hidden: {
25
+ opacity: 0,
26
+ pathLength: 0,
27
+ fill: "rgba(255, 255, 255, 0)",
28
+ },
29
+ visible: {
30
+ opacity: 1,
31
+ pathLength: 1,
32
+ fill: "rgba(255, 255, 255, 1)",
33
+ },
34
+ };
35
+
36
+ export const IconsSlider = () => {
37
+ const [selected, setSelected] = useState(0);
38
+ const ref = useRef<HTMLDivElement>(null);
39
+
40
+ useInterval(async () => {
41
+ if (ref?.current) {
42
+ ref.current?.classList?.add("opacity-0", "-translate-x-full", "scale-50");
43
+ await sleep(300);
44
+ ref.current?.classList?.remove("-translate-x-full");
45
+ ref.current?.classList?.add("translate-x-full");
46
+
47
+ setSelected((prev) => (prev + 1) % ICONS.length);
48
+ // if (this.selected >= this.avatars.length - 1) {
49
+ // this.selected = 0;
50
+ // } else {
51
+ // this.selected += 1;
52
+ // }
53
+ await sleep(300);
54
+ ref.current?.classList?.remove(
55
+ "translate-x-full",
56
+ "scale-50",
57
+ "opacity-0"
58
+ );
59
+ }
60
+ }, 2000);
61
+
62
+ return (
63
+ <div className="w-32 h-32 mx-auto relative z-1 flex items-center justify-center mt-7">
64
+ {/* <Image
65
+ src={ShapeIcon}
66
+ width={32}
67
+ height={32}
68
+ alt="Animation shape"
69
+ className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility"
70
+ /> */}
71
+
72
+ <motion.svg
73
+ viewBox="-1 0 130 124"
74
+ xmlns="http://www.w3.org/2000/svg"
75
+ className="h-full absolute left-0 top-0 w-full -z-1 backface-visibility stroke-white stroke-2"
76
+ >
77
+ <motion.path
78
+ d="M115.634914,44.3288323 C113.80147,38.3166291 119.301802,28.5951133 115.634914,23.6490789 C111.968027,18.7030445 100.924726,20.8775941 95.9360531,17.2533448 C90.9900187,13.6717337 89.4550426,2.15943794 83.6988819,0.581823534 C77.9427212,-1.0384291 70.3531168,6.97757468 63.9573827,6.97757468 C57.5616486,6.97757468 50.1852353,-1.89119365 44.2158834,0.368631971 C38.2038508,2.62845844 37.0099804,13.7143719 31.9786704,17.295983 C26.9473588,20.9202324 15.6482285,19.0015121 12.1518948,23.6917171 C8.65555928,28.3819221 14.070615,38.7003731 12.1518948,44.3714705 C10.2331745,50.0423548 0,54.7747716 0,61.042591 C0,67.3530486 10.4890031,71.744786 12.3224468,77.7141379 C14.1558906,83.6834897 8.65555928,93.4476437 12.3224468,98.3936781 C16.0319726,103.339712 27.0326352,101.165163 32.0213078,104.789412 C36.9673422,108.371023 38.5875957,119.883771 44.3437981,121.461385 C50.0999588,123.039 57.6895633,115.065651 64.0852973,115.065651 C70.4810314,115.065651 77.8574448,123.465382 83.8267966,121.461385 C89.8387866,119.457389 91.2032099,108.328385 96.1492442,104.789412 C101.095279,101.165163 112.309132,103.083883 115.848105,98.3936781 C119.34444,93.7034731 113.929385,83.3850221 115.848105,77.7141379 C117.766825,72.0432536 128,67.3104104 128,61.042591 C127.914724,54.7321334 117.468358,50.2981841 115.634914,44.3288323 Z"
79
+ variants={icon}
80
+ initial="hidden"
81
+ animate="visible"
82
+ transition={{
83
+ default: { duration: 2, ease: "easeInOut" },
84
+ fill: { duration: 2, ease: [1, 0, 0.8, 1] },
85
+ }}
86
+ />
87
+ </motion.svg>
88
+ <div
89
+ ref={ref}
90
+ className="transform transition duration-300 w-[68px] h-[60px] filter drop-shadow-xl origin-center"
91
+ >
92
+ <Image
93
+ src={ICONS[selected]}
94
+ width={32}
95
+ height={32}
96
+ alt="Animation icon, change every 3 seconds"
97
+ className="w-full h-full"
98
+ draggable="false"
99
+ />
100
+ </div>
101
+ <p></p>
102
+ </div>
103
+ // <div class="w-64 h-64 mx-auto relative z-1 flex items-center justify-center">
104
+ // <img src="@/assets/images/shadow_gradient.svg" alt="Linear gradient behind avatar" width="86" height="86" draggable="false" class="h-full absolute left-0 top-0 w-full -z-1 backface-visibility">
105
+ // <Avatar />
106
+ // </div>
107
+ );
108
+ };
components/editor-icons/comps/header/index.tsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+
3
+ import { Tag } from "components/tag";
4
+ import { IconsSlider } from "./comps/icons-slider";
5
+
6
+ // import { Editor } from "components/editor";
7
+ // import { ArrayOfIcons } from "components/svg/icons";
8
+
9
+ // import { Navigation } from "components/navigation";
10
+
11
+ import Background from "assets/images/header/background.svg";
12
+ import Stars from "assets/images/header/stars.svg";
13
+ import Ananas from "assets/images/header/ananas.svg";
14
+ import Pizza from "assets/images/header/pizza.svg";
15
+ import { FormattedMessage, useIntl } from "react-intl";
16
+
17
+ export const Header = ({ children }: any) => {
18
+ const intl = useIntl();
19
+ return (
20
+ <header className="bg-white relative z-20">
21
+ <div className="z-1 px-6 pt-28">
22
+ <div className="container mx-auto relative">
23
+ <div className="max-w-[1051px] w-full mx-auto text-center">
24
+ <Tag
25
+ label={`${intl.formatMessage({ id: "badge.new" })} 🔥`}
26
+ className="mb-5"
27
+ >
28
+ NEW VERSION RELEASED!
29
+ </Tag>
30
+ <IconsSlider />
31
+ <div className="max-w-max relative mx-auto md:px-12 pt-3 z-1 mt-4">
32
+ <h1 className="text-white font-title font-black text-4xl md:text-[70px] !leading-none text-center">
33
+ <FormattedMessage
34
+ id="iconsEditor.header.title"
35
+ values={{ span: () => <br /> }}
36
+ />{" "}
37
+
38
+ </h1>
39
+ <Image
40
+ width={32}
41
+ height={32}
42
+ src={Stars}
43
+ className="absolute top-0 left-0 w-full pointer-events-none"
44
+ alt="Stars on title"
45
+ />
46
+ </div>
47
+ <h2 className="text-white font-medium text-center mt-5 text-lg md:text-[26px] tracking-wide">
48
+ <FormattedMessage id="iconsEditor.header.subtitle" />
49
+ </h2>
50
+ <p className="text-white text-opacity-70 text-sm mt-2">
51
+ <FormattedMessage id="footer.legal2" />
52
+ </p>
53
+ </div>
54
+ {children}
55
+ </div>
56
+ </div>
57
+ <div className="absolute top-0 left-0 h-full max-h-[875px] bg-blue -z-1 w-full">
58
+ <Image
59
+ width={32}
60
+ height={32}
61
+ alt="Pizza with sauce"
62
+ src={Pizza}
63
+ className="w-[90px] lg:w-[120px] opacity-60 lg:opacity-100 absolute select-none left-10 lg:left-20 top-32 lg:top-1/3 pointer-events-none block"
64
+ />
65
+ <Image
66
+ width={32}
67
+ height={32}
68
+ alt="Ananas party!"
69
+ src={Ananas}
70
+ className="w-[150px] absolute select-none bottom-20 right-16 pointer-events-none hidden lg:block"
71
+ />
72
+ <Image
73
+ width={32}
74
+ height={32}
75
+ alt="Background header cloud"
76
+ src={Background}
77
+ className="w-full absolute select-none bottom-0 left-0 pointer-events-none -z-1 opacity-50"
78
+ />
79
+ </div>
80
+ {/* <div className="bg-blue h-full w-full absolute top-0 left-0 -z-1">
81
+ </div> */}
82
+ </header>
83
+ );
84
+ };
components/editor-icons/comps/icons/icon-selected.tsx ADDED
@@ -0,0 +1,603 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import {
3
+ faChevronDown,
4
+ faChevronRight,
5
+ faTrash,
6
+ faCaretDown,
7
+ faCaretUp,
8
+ } from "@fortawesome/free-solid-svg-icons";
9
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
10
+
11
+ import { ListItem } from "components/editor-icons/comps/list/list-item";
12
+ import {
13
+ Icons as ICONS,
14
+ IconCustomIcon,
15
+ IconCustomText,
16
+ } from "components/svg/icons";
17
+ import { IconItem, IconType } from "@/types/editor";
18
+ import { ColorPicker } from "components/color-picker";
19
+ import { Range } from "components/range";
20
+ import { Input } from "components/input";
21
+ import { Switch } from "@/components/switch";
22
+ import { PremiumOverlay } from "@/components/premium/overlay";
23
+ import { FormattedMessage, useIntl } from "react-intl";
24
+ import { Label } from "@/components/label";
25
+
26
+ export const IconSelected = ({
27
+ index,
28
+ totalIcons,
29
+ icon,
30
+ current,
31
+ setCurrent,
32
+ onDelete,
33
+ onChange,
34
+ onChangeOrder,
35
+ }: {
36
+ index: number;
37
+ totalIcons: number;
38
+ icon: IconType;
39
+ current?: number | null;
40
+ setCurrent: (index: number | null) => void;
41
+ onDelete: (index: number) => void;
42
+ onChange: (idnex: number, icon: IconType) => void;
43
+ onChangeOrder: (index: number, value: number) => void;
44
+ }) => {
45
+ const findIcon: any = icon?.custom_text?.enabled
46
+ ? IconCustomText
47
+ : icon?.image
48
+ ? IconCustomIcon
49
+ : ICONS?.find((i: IconItem) => icon.component === i.name);
50
+
51
+ const handleChange = (index: number, icon: any) => {
52
+ onChange(index, icon);
53
+ };
54
+
55
+ const intl = useIntl();
56
+
57
+ return (
58
+ <div className="flex items-center justify-start gap-3 w-full">
59
+ <div
60
+ className={classNames(
61
+ "bg-dark-600 w-full rounded-lg pl-4 py-4 pr-6 group cursor-pointer transition-all border-2 border-dark-600 duration-200 hover:border-blue",
62
+ {
63
+ "!border-blue": current === index,
64
+ }
65
+ )}
66
+ >
67
+ <div
68
+ className="tracking-wider flex items-center justify-between gap-4"
69
+ onClick={() => setCurrent(current === index ? null : index)}
70
+ >
71
+ <div className="flex items-center justify-start gap-4">
72
+ <div className="w-10">
73
+ {icon?.image ? (
74
+ <div className="flex items-center justify-center p-2 cursor-pointer rounded-lg relative group border border-dark-200 border-solid">
75
+ <img
76
+ src={icon?.image}
77
+ className="w-full h-full object-contain"
78
+ />
79
+ </div>
80
+ ) : (
81
+ <ListItem
82
+ icon={findIcon}
83
+ fill={findIcon?.defaultColor ?? "#fff"}
84
+ tooltip={false}
85
+ hoverable={false}
86
+ onSelect={() => {}}
87
+ />
88
+ )}
89
+ </div>
90
+ <div>
91
+ <p className="text-white font-semibold">
92
+ <FormattedMessage id={findIcon?.name} />
93
+ </p>
94
+ <p className="text-xs text-dark-200">
95
+ {findIcon?.tags?.join(", ")}
96
+ </p>
97
+ </div>
98
+ </div>
99
+ <div className="flex items-center justify-end gap-4">
100
+ <div
101
+ className="bg-danger bg-opacity-60 rounded-lg text-white hover:bg-opacity-100 cursor-pointer w-8 h-8 flex items-center justify-center"
102
+ onClick={(e: any) => {
103
+ e.preventDefault();
104
+ e.stopPropagation();
105
+ onDelete(index);
106
+ }}
107
+ >
108
+ <FontAwesomeIcon icon={faTrash} className="w-4" />
109
+ </div>
110
+ <div className="flex items-center justify-end">
111
+ <FontAwesomeIcon
112
+ icon={current === index ? faChevronDown : faChevronRight}
113
+ className="text-dark-100 transition-all duration-200 w-3"
114
+ />
115
+ </div>
116
+ </div>
117
+ </div>
118
+ {current === index && (
119
+ <div className="border-t border-dark-400 pt-3 mt-4 grid grid-cols-2 gap-4">
120
+ <div className="col-span-2">
121
+ <Label className="mb-1">
122
+ <FormattedMessage id="iconsEditor.editor.customisation.positions" />
123
+ </Label>
124
+ <div className="grid grid-cols-2 gap-x-6 gap-y-3">
125
+ <div className="gap-4 w-full col-span-2 grid grid-cols-3">
126
+ <div className="flex items-center gap-3 col-span-2">
127
+ <p className="font-semibold uppercase text-dark-200 text-xs">
128
+ X
129
+ </p>
130
+ <Range
131
+ value={icon?.position?.x ?? 0}
132
+ max={
133
+ icon?.custom_text?.enabled
134
+ ? 1000
135
+ : icon?.position?.xPath ?? undefined
136
+ }
137
+ onChange={(value) => {
138
+ const newIcon = {
139
+ ...icon,
140
+ position: { ...icon.position, x: Number(value) },
141
+ };
142
+ handleChange(index, newIcon);
143
+ }}
144
+ />
145
+ </div>
146
+ <Input
147
+ value={icon?.position?.x}
148
+ type="number"
149
+ placeholder={intl.formatMessage({
150
+ id: "iconsEditor.editor.customisation.positions.x",
151
+ })}
152
+ className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
153
+ onChange={(newX) => {
154
+ let x: number = Number(newX);
155
+ if (x > 100) x = 100;
156
+ const newIcon = {
157
+ ...icon,
158
+ position: { ...icon.position, x: x },
159
+ };
160
+ handleChange(index, newIcon);
161
+ }}
162
+ />
163
+ </div>
164
+ <div className="gap-4 w-full col-span-2 grid grid-cols-3">
165
+ <div className="flex items-center gap-3 col-span-2">
166
+ <p className="font-semibold uppercase text-dark-200 text-xs">
167
+ Y
168
+ </p>
169
+ <Range
170
+ value={icon?.position?.y ?? 0}
171
+ max={
172
+ icon?.custom_text?.enabled
173
+ ? 1000
174
+ : icon?.position?.yPath ?? undefined
175
+ }
176
+ onChange={(value) => {
177
+ const newIcon = {
178
+ ...icon,
179
+ position: { ...icon.position, y: Number(value) },
180
+ };
181
+ handleChange(index, newIcon);
182
+ }}
183
+ />
184
+ </div>
185
+ <Input
186
+ value={icon?.position?.y}
187
+ type="number"
188
+ placeholder={intl.formatMessage({
189
+ id: "iconsEditor.editor.customisation.positions.y",
190
+ })}
191
+ className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
192
+ onChange={(newY) => {
193
+ let y = Number(newY);
194
+ if (y > 100) y = 100;
195
+ const newIcon = {
196
+ ...icon,
197
+ position: { ...icon.position, y: y },
198
+ };
199
+ handleChange(index, newIcon);
200
+ }}
201
+ />
202
+ </div>
203
+ <div className="gap-4 w-full col-span-2 grid grid-cols-3">
204
+ <div className="flex items-center gap-3 col-span-2">
205
+ <p className="font-semibold uppercase text-dark-200 text-xs">
206
+ Z
207
+ </p>
208
+ <Range
209
+ value={icon?.position?.angle ?? 0}
210
+ max={360}
211
+ onChange={(value) => {
212
+ const newIcon = {
213
+ ...icon,
214
+ position: { ...icon.position, angle: Number(value) },
215
+ };
216
+ handleChange(index, newIcon);
217
+ }}
218
+ />
219
+ </div>
220
+ <Input
221
+ value={icon?.position?.angle}
222
+ placeholder={intl.formatMessage({
223
+ id: "iconsEditor.editor.customisation.angle",
224
+ })}
225
+ type="number"
226
+ className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
227
+ onChange={(newAngle) => {
228
+ let angle = Number(newAngle);
229
+ if (angle > 360) angle = 360;
230
+ const newIcon = {
231
+ ...icon,
232
+ position: { ...icon.position, angle: angle },
233
+ };
234
+ handleChange(index, newIcon);
235
+ }}
236
+ />
237
+ </div>
238
+ {!icon?.custom_text?.enabled && (
239
+ <div className="gap-4 w-full col-span-2 grid grid-cols-3">
240
+ <div className="flex items-center gap-3 col-span-2">
241
+ <p className="font-semibold uppercase text-dark-200 text-xs">
242
+ <FormattedMessage id="iconsEditor.editor.customisation.scale" />
243
+ </p>
244
+ <Range
245
+ value={icon?.position?.scale ?? 1}
246
+ min={0}
247
+ max={2}
248
+ step={0.1}
249
+ onChange={(value) => {
250
+ const newIcon = {
251
+ ...icon,
252
+ position: {
253
+ ...icon.position,
254
+ scale: Number(value),
255
+ },
256
+ };
257
+ handleChange(index, newIcon);
258
+ }}
259
+ />
260
+ </div>
261
+ <Input
262
+ value={
263
+ icon?.position?.scale
264
+ ? icon?.position?.scale * 100
265
+ : undefined
266
+ }
267
+ type="number"
268
+ placeholder={intl.formatMessage({
269
+ id: "iconsEditor.editor.customisation.scale",
270
+ })}
271
+ className="bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200"
272
+ onChange={(newScale) => {
273
+ let scale = Number(newScale);
274
+ if (scale > 100) scale = 100;
275
+ const newIcon = {
276
+ ...icon,
277
+ position: {
278
+ ...icon.position,
279
+ scale: Math.trunc(scale) / 100,
280
+ },
281
+ };
282
+ handleChange(index, newIcon);
283
+ }}
284
+ />
285
+ </div>
286
+ )}
287
+ </div>
288
+ </div>
289
+ {!icon?.image && (
290
+ <div className="col-span-2">
291
+ <Label className="!mb-2.5">
292
+ <FormattedMessage
293
+ id="iconsEditor.editor.customisation.gradientColor"
294
+ values={{
295
+ span: (t) => (
296
+ <span className="text-xs opacity-40">{t}</span>
297
+ ),
298
+ }}
299
+ />
300
+ </Label>
301
+ <ColorPicker
302
+ data={icon}
303
+ value={icon?.stringColor ?? icon?.colour}
304
+ onChange={(c: any, datas) => {
305
+ let newIcon = { ...icon, stringColor: c };
306
+ if (c.includes("gradient")) {
307
+ const { colors, degrees } = datas;
308
+ const gradientType = c?.startsWith("linear")
309
+ ? "linearGradient"
310
+ : "radialGradient";
311
+
312
+ const angle = c?.startsWith("linear")
313
+ ? c?.replace("linear-gradient(", "")?.split("deg")?.[0]
314
+ : 90;
315
+
316
+ newIcon["gradient"] = {
317
+ ...newIcon.gradient,
318
+ enabled: true,
319
+ colours: colors,
320
+ angle,
321
+ type: gradientType,
322
+ };
323
+ } else {
324
+ newIcon = {
325
+ ...newIcon,
326
+ colour: c,
327
+ gradient: {
328
+ ...icon?.gradient,
329
+ enabled: false,
330
+ },
331
+ };
332
+ }
333
+ handleChange(index, newIcon);
334
+ }}
335
+ />
336
+ </div>
337
+ )}
338
+ {icon?.custom_text?.enabled && (
339
+ <>
340
+ <div className="col-span-2">
341
+ <Label className="!mb-2.5">
342
+ <FormattedMessage id="iconsEditor.editor.customisation.customText" />
343
+ </Label>
344
+ <div className="grid grid-cols-2 gap-5">
345
+ <div>
346
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
347
+ <FormattedMessage id="iconsEditor.editor.customisation.customText.text" />
348
+ </p>
349
+ <input
350
+ type="text"
351
+ className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
352
+ placeholder="10"
353
+ value={icon?.custom_text?.text}
354
+ onChange={({ target }) => {
355
+ const newIcon = {
356
+ ...icon,
357
+ custom_text: {
358
+ ...icon.custom_text,
359
+ text: target.value,
360
+ },
361
+ };
362
+ handleChange(index, newIcon);
363
+ }}
364
+ />
365
+ </div>
366
+ <div>
367
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
368
+ <FormattedMessage id="iconsEditor.editor.customisation.customText.fontSize" />
369
+ </p>
370
+ <input
371
+ type="number"
372
+ className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
373
+ placeholder="10"
374
+ value={icon?.custom_text?.size}
375
+ onChange={({ target }) => {
376
+ const newIcon = {
377
+ ...icon,
378
+ custom_text: {
379
+ ...icon.custom_text,
380
+ size: Number(target.value),
381
+ },
382
+ };
383
+ handleChange(index, newIcon);
384
+ }}
385
+ />
386
+ </div>
387
+ </div>
388
+ </div>
389
+ </>
390
+ )}
391
+ <div className="relative col-span-2 grid grid-cols-2 gap-4">
392
+ {!icon?.image && (
393
+ <div>
394
+ <Label className="!mb-2.5">
395
+ <FormattedMessage id="iconsEditor.editor.customisation.border" />
396
+ </Label>
397
+ <div className="flex items-start justify-start gap-5">
398
+ <div className="w-full">
399
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
400
+ <FormattedMessage id="iconsEditor.editor.customisation.width" />
401
+ </p>
402
+ <input
403
+ type="number"
404
+ className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
405
+ placeholder={
406
+ intl.formatMessage({
407
+ id: "iconsEditor.editor.customisation.width",
408
+ }) + "...px"
409
+ }
410
+ min={0}
411
+ value={icon?.border?.width}
412
+ onChange={({ target }) => {
413
+ const newIcon = {
414
+ ...icon,
415
+ border: {
416
+ ...icon.border,
417
+ width: target?.value
418
+ ? Number(target.value)
419
+ : undefined,
420
+ },
421
+ };
422
+ handleChange(index, newIcon);
423
+ }}
424
+ />
425
+ </div>
426
+ <div>
427
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
428
+ <FormattedMessage id="iconsEditor.editor.customisation.color" />
429
+ </p>
430
+ <ColorPicker
431
+ value={icon?.border?.colour}
432
+ gradients={false}
433
+ onChange={(c: any) => {
434
+ const newIcon = {
435
+ ...icon,
436
+ border: {
437
+ ...icon.border,
438
+ colour: c,
439
+ },
440
+ };
441
+ handleChange(index, newIcon);
442
+ }}
443
+ />
444
+ </div>
445
+ </div>
446
+ </div>
447
+ )}
448
+ <div className="col-span-2">
449
+ <div className="flex items-center justify-start gap-3 mb-2.5 ">
450
+ <Label className="!mb-0">
451
+ <FormattedMessage id="iconsEditor.editor.customisation.shadow" />
452
+ </Label>
453
+ <Switch
454
+ value={icon?.shadow?.enabled}
455
+ onChange={(enabled: boolean) => {
456
+ const newIcon = {
457
+ ...icon,
458
+ shadow: {
459
+ ...icon.shadow,
460
+ enabled,
461
+ },
462
+ };
463
+ handleChange(index, newIcon);
464
+ }}
465
+ />
466
+ </div>
467
+ {icon?.shadow?.enabled && (
468
+ <div className="gap-5 grid grid-cols-2">
469
+ <div className="w-full">
470
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
471
+ X
472
+ </p>
473
+ <input
474
+ type="number"
475
+ className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
476
+ placeholder={intl.formatMessage({
477
+ id: "iconsEditor.editor.customisation.shadow.x.placeholder",
478
+ })}
479
+ min={0}
480
+ value={icon?.shadow?.position?.x}
481
+ onChange={({ target }) => {
482
+ const newIcon = {
483
+ ...icon,
484
+ shadow: {
485
+ ...icon.shadow,
486
+ position: {
487
+ ...icon?.shadow?.position,
488
+ x: target?.value
489
+ ? Number(target.value)
490
+ : undefined,
491
+ },
492
+ },
493
+ };
494
+ handleChange(index, newIcon);
495
+ }}
496
+ />
497
+ </div>
498
+ <div className="w-full">
499
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
500
+ Y
501
+ </p>
502
+ <input
503
+ type="number"
504
+ className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
505
+ placeholder={intl.formatMessage({
506
+ id: "iconsEditor.editor.customisation.shadow.y.placeholder",
507
+ })}
508
+ min={0}
509
+ value={icon?.shadow?.position?.y}
510
+ onChange={({ target }) => {
511
+ const newIcon = {
512
+ ...icon,
513
+ shadow: {
514
+ ...icon.shadow,
515
+ position: {
516
+ ...icon?.shadow?.position,
517
+ y: target?.value
518
+ ? Number(target.value)
519
+ : undefined,
520
+ },
521
+ },
522
+ };
523
+ handleChange(index, newIcon);
524
+ }}
525
+ />
526
+ </div>
527
+ <div className="w-full">
528
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
529
+ <FormattedMessage id="iconsEditor.editor.customisation.shadow.blur" />
530
+ </p>
531
+ <input
532
+ type="number"
533
+ className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
534
+ placeholder={intl.formatMessage({
535
+ id: "iconsEditor.editor.customisation.shadow.blur.placeholder",
536
+ })}
537
+ min={0}
538
+ value={icon?.shadow?.position?.blur}
539
+ onChange={({ target }) => {
540
+ const newIcon = {
541
+ ...icon,
542
+ shadow: {
543
+ ...icon.shadow,
544
+ position: {
545
+ ...icon?.shadow?.position,
546
+ blur: target?.value
547
+ ? Number(target.value)
548
+ : undefined,
549
+ },
550
+ },
551
+ };
552
+ handleChange(index, newIcon);
553
+ }}
554
+ />
555
+ </div>
556
+ <div>
557
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
558
+ <FormattedMessage id="iconsEditor.editor.customisation.color" />
559
+ </p>
560
+ <ColorPicker
561
+ value={icon?.shadow?.colour}
562
+ gradients={false}
563
+ onChange={(c: any) => {
564
+ const newIcon = {
565
+ ...icon,
566
+ shadow: {
567
+ ...icon.shadow,
568
+ colour: c,
569
+ },
570
+ };
571
+ handleChange(index, newIcon);
572
+ }}
573
+ />
574
+ </div>
575
+ </div>
576
+ )}
577
+ </div>
578
+ <PremiumOverlay />
579
+ </div>
580
+ </div>
581
+ )}
582
+ </div>
583
+ {totalIcons > 1 && (
584
+ <div className="flex flex-col gap-0.5">
585
+ {index !== 0 && (
586
+ <FontAwesomeIcon
587
+ icon={faCaretUp}
588
+ className="text-dark-200 cursor-pointer hover:text-white w-3"
589
+ onClick={() => onChangeOrder(index, index - 1)}
590
+ />
591
+ )}
592
+ {index !== totalIcons - 1 && (
593
+ <FontAwesomeIcon
594
+ icon={faCaretDown}
595
+ className="text-dark-200 cursor-pointer hover:text-white w-3"
596
+ onClick={() => onChangeOrder(index, index + 1)}
597
+ />
598
+ )}
599
+ </div>
600
+ )}
601
+ </div>
602
+ );
603
+ };
components/editor-icons/comps/icons/index.tsx ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { PlusIcon } from "@heroicons/react/solid";
3
+
4
+ import { EditorType, IconType } from "@/types/editor";
5
+ import { Empty } from "@/components/empty";
6
+
7
+ import { IconSelected } from "./icon-selected";
8
+ import { FormattedMessage, useIntl } from "react-intl";
9
+
10
+ export const Icons = ({
11
+ editor,
12
+ onChange,
13
+ onStep,
14
+ }: {
15
+ editor: EditorType;
16
+ onChange: (e: EditorType) => void;
17
+ onStep: (step: number, multiple?: boolean) => void;
18
+ }) => {
19
+ const [opened, setOpened] = useState<number | null>(0);
20
+ const intl = useIntl();
21
+
22
+ const handleChange = (index: number, icon: any) => {
23
+ const newIcons = [...editor.icons];
24
+ newIcons[index] = icon;
25
+ onChange({
26
+ ...editor,
27
+ icons: newIcons,
28
+ });
29
+ };
30
+
31
+ const handleDeleteIcon = (index: number) => {
32
+ const newIcons = [...editor.icons];
33
+ newIcons.splice(index, 1);
34
+ onChange({
35
+ ...editor,
36
+ icons: newIcons,
37
+ });
38
+ };
39
+
40
+ const handleChangeOrder = (index: number, value: number) => {
41
+ const newIcons = [...editor.icons];
42
+ const [removed] = newIcons.splice(index, 1);
43
+ newIcons.splice(value, 0, removed);
44
+ onChange({
45
+ ...editor,
46
+ icons: newIcons,
47
+ });
48
+ };
49
+
50
+ return (
51
+ <div className="grid grid-cols-1 gap-4">
52
+ <p className="font-bold tracking-wider text-lg text-white">
53
+ <FormattedMessage id="iconsEditor.editor.icons.title" />
54
+ </p>
55
+ {editor?.icons?.length > 0 ? (
56
+ <>
57
+ <div
58
+ className="border-2 border-dark-300 border-dashed w-full rounded-lg pl-4 py-4 pr-6 group cursor-pointer transition-all duration-200 hover:border-blue group flex items-center justify-start"
59
+ onClick={() => onStep(0, true)}
60
+ >
61
+ <div className="flex items-center justify-start gap-4">
62
+ <div className="w-10 h-10 flex items-center justify-center bg-dark-200 bg-opacity-20 rounded-lg group-hover:bg-blue transition-all duration-200">
63
+ <PlusIcon className="text-white w-6" />
64
+ </div>
65
+ <div>
66
+ <p className="text-white font-semibold">
67
+ <FormattedMessage id="iconsEditor.editor.icons.add.title" />
68
+ </p>
69
+ <p className="text-xs text-dark-200">
70
+ <FormattedMessage id="iconsEditor.editor.icons.add.subtitle" />
71
+ </p>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ {editor?.icons?.map((icon: IconType, k) => {
76
+ return (
77
+ <IconSelected
78
+ key={k}
79
+ totalIcons={editor?.icons?.length}
80
+ index={k}
81
+ icon={icon}
82
+ current={opened}
83
+ setCurrent={setOpened}
84
+ onDelete={handleDeleteIcon}
85
+ onChange={handleChange}
86
+ onChangeOrder={handleChangeOrder}
87
+ />
88
+ );
89
+ })}
90
+ </>
91
+ ) : (
92
+ <Empty
93
+ title={intl.formatMessage({
94
+ id: "iconsEditor.editor.icons.empty.title",
95
+ })}
96
+ description={intl.formatMessage({
97
+ id: "iconsEditor.editor.icons.empty.subtitle",
98
+ })}
99
+ button={intl.formatMessage({
100
+ id: "iconsEditor.editor.icons.empty.cta",
101
+ })}
102
+ action={() => onStep(0)}
103
+ />
104
+ )}
105
+ </div>
106
+ );
107
+ };
components/editor-icons/comps/list/index.tsx ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ArrowRightIcon, ArrowLeftIcon } from "@heroicons/react/solid";
2
+ import { useContext, useRef } from "react";
3
+
4
+ import { useResults } from "components/search/hooks/useSearch";
5
+ import { ListItem } from "components/editor-icons/comps/list/list-item";
6
+ import { IconItem } from "types/editor";
7
+ import { Empty } from "components/empty";
8
+ import { login, useUser } from "utils/auth";
9
+ import { PremiumContext } from "components/premium/premium";
10
+ import { Premium } from "@/components/premium";
11
+ import classNames from "classnames";
12
+ import { toBase64 } from "@/components/upload";
13
+ import { FormattedMessage } from "react-intl";
14
+
15
+ export const ListIcons = ({
16
+ onSelect,
17
+ onCustomText,
18
+ onCustomUpload,
19
+ }: {
20
+ onSelect: (e: IconItem) => void;
21
+ onCustomText: () => void;
22
+ onCustomUpload: (s: string) => void;
23
+ }) => {
24
+ const { user } = useUser();
25
+ const { setOpen } = useContext(PremiumContext);
26
+ const inputRef = useRef<HTMLInputElement>(null);
27
+
28
+ const { results, page, maxPage, totalItems, setPage } = useResults();
29
+
30
+ const handleCustomUpload = (e: any) => {
31
+ const file = e.target.files[0];
32
+ if (file) {
33
+ toBase64(file).then((res: any) => {
34
+ onCustomUpload(res);
35
+ });
36
+ }
37
+ };
38
+
39
+ return (
40
+ <div>
41
+ <div className="flex flex-col lg:flex-row gap-y-3 lg:gap-y-0 items-start justify-between">
42
+ <p className="font-bold tracking-wider text-base text-white">
43
+ <FormattedMessage
44
+ id="iconsEditor.editor.listIcons.totalIcons"
45
+ values={{ total: () => totalItems }}
46
+ />
47
+ </p>
48
+ <input
49
+ ref={inputRef}
50
+ type="file"
51
+ className="hidden"
52
+ onChange={handleCustomUpload}
53
+ />
54
+ <div className="flex w-full lg:w-auto items-center justify-end gap-3">
55
+ <button
56
+ className={classNames(
57
+ "w-full lg:w-auto px-3 py-2 hover:bg-opacity-70 text-xs font-semibold text-white rounded-md flex items-center justify-center lg:justify-start gap-2",
58
+ {
59
+ "bg-dark-300": !user?.id,
60
+ "bg-darkGreen": user?.id,
61
+ }
62
+ )}
63
+ onClick={() => inputRef.current?.click()}
64
+ >
65
+ {!user && <Premium size="16" tooltip={true} />}
66
+ <FormattedMessage id="iconsEditor.editor.listIcons.uploadCustomIcon" />
67
+ </button>
68
+ <button
69
+ className={classNames(
70
+ "w-full lg:w-auto px-3 py-2 hover:bg-opacity-70 text-xs font-semibold text-white rounded-md flex items-center justify-center lg:justify-start gap-2",
71
+ {
72
+ "bg-dark-300": !user?.id,
73
+ "bg-yellow": user?.id,
74
+ }
75
+ )}
76
+ onClick={onCustomText}
77
+ >
78
+ {!user && <Premium size="16" tooltip={true} />}
79
+ <FormattedMessage id="iconsEditor.editor.listIcons.useCustomText" />
80
+ </button>
81
+ </div>
82
+ </div>
83
+ <div className="flex jlg:items-start flex-wrap justify-start mt-3 gap-2 lg:gap-3">
84
+ {results?.map((result: any, key: number) => (
85
+ <ListItem
86
+ key={key}
87
+ icon={result}
88
+ fill={result?.defaultColor}
89
+ onSelect={(icon) => {
90
+ // if (icon?.isPremium && !user?.id) return setOpen(true);
91
+ onSelect(icon);
92
+ }}
93
+ />
94
+ ))}
95
+ {results?.length === 0 && (
96
+ <Empty
97
+ title="Clyde is disappointed..."
98
+ description="...there is no icon that matches your search :("
99
+ action={() => {
100
+ if (!user?.id) return login();
101
+ // send to api a request;
102
+ console.log("display Modal and call API");
103
+ }}
104
+ button="Ask for a new Icon"
105
+ />
106
+ )}
107
+ </div>
108
+ {results?.length > 0 && (
109
+ <div className="flex items-center justify-between gap-4 mt-10">
110
+ <button
111
+ disabled={page <= 1}
112
+ className="w-full lg:w-auto group border-2 border-darkGreen bg-darkGreen text-white font-medium tracking-wide px-3.5 py-2 text-sm flex items-center justify-center gap-2 rounded-lg hover:bg-opacity-90 hover:border-opacity-0 disabled:bg-dark-100 disabled:bg-opacity-20 disabled:border-dark-100 disabled:border-opacity-0 disabled:text-dark-200 disabled:cursor-not-allowed"
113
+ onClick={() => setPage(page - 1)}
114
+ >
115
+ <ArrowLeftIcon className="w-4 text-white group-disabled:text-dark-200" />
116
+ <FormattedMessage
117
+ id="iconsEditor.editor.listIcons.pagination.previous"
118
+ values={{
119
+ span: (t) => <span className="hidden lg:inline">{t}</span>,
120
+ }}
121
+ />
122
+ </button>
123
+ <div className="hidden items-center justify-center gap-2 lg:flex">
124
+ <p className="text-dark-100 font-medium text-sm">
125
+ <FormattedMessage
126
+ id="iconsEditor.editor.listIcons.pagination.total"
127
+ values={{
128
+ page: () => page,
129
+ maxPage: () => maxPage,
130
+ }}
131
+ />
132
+ </p>
133
+ </div>
134
+ <button
135
+ disabled={page === maxPage}
136
+ className="w-full lg:w-auto group border-2 border-darkGreen bg-darkGreen text-white font-medium tracking-wide px-3.5 py-2 text-sm flex items-center justify-center gap-2 rounded-lg hover:bg-opacity-90 hover:border-opacity-0 disabled:bg-dark-100 disabled:bg-opacity-20 disabled:border-dark-100 disabled:border-opacity-0 disabled:text-dark-200 disabled:cursor-not-allowed"
137
+ onClick={page >= maxPage ? () => {} : () => setPage(page + 1)}
138
+ >
139
+ <FormattedMessage
140
+ id="iconsEditor.editor.listIcons.pagination.next"
141
+ values={{
142
+ span: (t) => <span className="hidden lg:inline">{t}</span>,
143
+ }}
144
+ />
145
+ <ArrowRightIcon className="w-4 text-white group-disabled:text-dark-200" />
146
+ </button>
147
+ </div>
148
+ )}
149
+ </div>
150
+ );
151
+ };
components/editor-icons/comps/list/list-item.tsx ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import React, { useEffect, useRef } from "react";
3
+
4
+ export const ListItem = ({
5
+ icon,
6
+ tooltip = true,
7
+ hoverable = true,
8
+ fill = "#b0b0b0",
9
+ onSelect,
10
+ }: {
11
+ icon: any;
12
+ tooltip?: boolean;
13
+ hoverable?: boolean;
14
+ fill?: string;
15
+ onSelect: (e: any) => void;
16
+ }) => {
17
+ const ref = useRef<any>(null);
18
+ const svgRef = useRef<SVGSVGElement>(null);
19
+ const [viewBox, setViewBox] = React.useState<string>("0 0 24 24");
20
+ const renderComponent = () => {
21
+ const Component = icon?.component as any;
22
+ return <Component ref={ref} />;
23
+ };
24
+
25
+ useEffect(() => {
26
+ return setViewBox(ref?.current?.getAttribute("viewBox") ?? "0 0 200 200");
27
+ });
28
+
29
+ return (
30
+ <div
31
+ className={classNames(
32
+ "flex items-center justify-center p-2 cursor-pointer rounded-lg relative group border border-dark-200 border-solid",
33
+ {
34
+ "hover:border-yellow": hoverable && icon?.isPremium,
35
+ "hover:border-dark-100": hoverable && !icon?.isPremium,
36
+ }
37
+ )}
38
+ onClick={() => onSelect(icon)}
39
+ >
40
+ {/* {icon?.isPremium && (
41
+ <div className="w-4 h-4 bg-yellow rounded-full absolute -right-1.5 -top-1 border-[2px] border-dark-500" />
42
+ )} */}
43
+ <svg
44
+ width={24}
45
+ height={24}
46
+ fill={fill}
47
+ ref={svgRef}
48
+ viewBox={viewBox}
49
+ xmlns="http://www.w3.org/2000/svg"
50
+ className="h-auto"
51
+ >
52
+ {renderComponent()}
53
+ </svg>
54
+ {hoverable && (
55
+ <div
56
+ className={classNames(
57
+ "group-hover:opacity-100 bg-opacity-20 opacity-0 rounded-lg transition-all duration-200 absolute h-full w-full left-0 top-0 pointer-events-none transform translate-y-full group-hover:translate-y-0",
58
+ {
59
+ "bg-yellow": icon?.isPremium,
60
+ "bg-dark-100": !icon?.isPremium,
61
+ }
62
+ )}
63
+ />
64
+ )}
65
+ {/* {tooltip && (
66
+ <>
67
+ <div
68
+ className={classNames(
69
+ "group-hover:opacity-100 bg-opacity-20 opacity-0 rounded-lg transition-all duration-200 absolute h-full w-full left-0 top-0 pointer-events-none transform translate-y-full group-hover:translate-y-0",
70
+ {
71
+ "bg-yellow": icon?.isPremium,
72
+ "bg-dark-100": !icon?.isPremium,
73
+ }
74
+ )}
75
+ />
76
+ <div className="opacity-0 transform z-10 pt-1 translate-y-full pointer-events-none group-hover:opacity-100 absolute bottom-0 flex items-center justify-center flex-col">
77
+ <div className="w-[0px] h-[0px] border-l-[5px] border-l-transparent border-r-[5px] border-r-transparent border-b-[5px] border-b-dark-600 border-opacity-80" />
78
+ <div className="bg-dark-600 bg-opacity-80 text-xs text-white rounded py-0.5 px-1.5 whitespace-nowrap">
79
+ {icon?.tags?.join(", ")}
80
+ </div>
81
+ </div>
82
+ </>
83
+ )} */}
84
+ </div>
85
+ );
86
+ };
components/editor-icons/comps/shapes/index.tsx ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from "react";
2
+
3
+ import { EditorType } from "types/editor";
4
+ import { SHAPES } from "components/svg/shapes";
5
+ import classNames from "classnames";
6
+ import { Empty } from "@/components/empty";
7
+ import { useUser } from "@/utils/auth";
8
+
9
+ import { ShapeSelected } from "./shape-selected";
10
+ import { PremiumContext } from "@/components/premium/premium";
11
+ import { FormattedMessage, useIntl } from "react-intl";
12
+
13
+ export const Shapes = ({
14
+ editor,
15
+ onChange,
16
+ }: {
17
+ editor: EditorType;
18
+ onChange: (s: any) => void;
19
+ }) => {
20
+ const { user } = useUser();
21
+ const intl = useIntl();
22
+
23
+ const { setOpen } = useContext(PremiumContext);
24
+
25
+ return (
26
+ <>
27
+ <p className="font-bold tracking-wider text-lg text-white mb-3">
28
+ <FormattedMessage id="iconsEditor.editor.shape.title" />
29
+ </p>
30
+ <div className="flex items-start flex-wrap justify-start mt-3 gap-3">
31
+ {SHAPES.map((shape, k) => (
32
+ <div
33
+ key={k}
34
+ className={classNames(
35
+ "relative flex items-center justify-center p-2 cursor-pointer rounded-lg group border border-dark-200 border-solid",
36
+ {
37
+ "bg-blue border-blue": editor.shape?.component === shape?.name,
38
+ // "hover:border-yellow": shape?.isPremium,
39
+ }
40
+ )}
41
+ onClick={() => {
42
+ // if (!user && shape?.isPremium) {
43
+ // return setOpen(true);
44
+ // }
45
+ onChange({ ...editor.shape, component: shape?.name });
46
+ }}
47
+ >
48
+ {/* {shape?.isPremium && (
49
+ <div className="w-4 h-4 bg-yellow rounded-full absolute -right-1.5 -top-1 border-[2px] border-dark-500" />
50
+ )} */}
51
+ <_ShapeComponent
52
+ component={shape.component}
53
+ width={28}
54
+ height={28}
55
+ shape={shape}
56
+ />
57
+ <div
58
+ className={classNames(
59
+ "group-hover:opacity-100 bg-opacity-20 opacity-0 rounded-lg transition-all duration-200 absolute h-full w-full left-0 top-0 pointer-events-none transform translate-y-full group-hover:translate-y-0",
60
+ {
61
+ // "bg-yellow": shape?.isPremium,
62
+ "bg-dark-100": !shape?.isPremium,
63
+ }
64
+ )}
65
+ />
66
+ </div>
67
+ ))}
68
+ </div>
69
+ {editor.shape?.component ? (
70
+ <ShapeSelected
71
+ shape={editor?.shape}
72
+ handleChange={(shape: any) => onChange({ ...editor.shape, ...shape })}
73
+ onDelete={() => onChange({ ...editor.shape, component: null })}
74
+ />
75
+ ) : (
76
+ <Empty
77
+ title={intl.formatMessage({
78
+ id: "iconsEditor.editor.shape.empty.title",
79
+ })}
80
+ description={intl.formatMessage({
81
+ id: "iconsEditor.editor.shape.empty.subtitle",
82
+ })}
83
+ className="mt-12"
84
+ // button={<div onClick={() => onStep(0)}>Add my first Icon</div>}
85
+ />
86
+ )}
87
+ </>
88
+ );
89
+ };
90
+
91
+ const _ShapeComponent = ({ component, shape, onClick, ...props }: any) => {
92
+ const ShapeComponent = component as any;
93
+ const newShape = {
94
+ ...shape,
95
+ component: `${shape.name}-small`,
96
+ };
97
+ return <ShapeComponent shape={newShape} {...props} />;
98
+ };
components/editor-icons/comps/shapes/shape-selected.tsx ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { useState } from "react";
3
+ import {
4
+ faChevronDown,
5
+ faChevronRight,
6
+ faTrash,
7
+ faCaretDown,
8
+ faCaretUp,
9
+ } from "@fortawesome/free-solid-svg-icons";
10
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
11
+
12
+ import { ColorPicker } from "components/color-picker";
13
+ import { Range } from "components/range";
14
+ import { UploadImage } from "@/components/upload";
15
+ import { Switch } from "@/components/switch";
16
+ import { login, useUser } from "@/utils/auth";
17
+ import { Premium } from "@/components/premium";
18
+ import { PremiumOverlay } from "@/components/premium/overlay";
19
+ import { FormattedMessage, useIntl } from "react-intl";
20
+ import { Label } from "@/components/label";
21
+
22
+ export const ShapeSelected = ({
23
+ shape,
24
+ handleChange,
25
+ onDelete,
26
+ }: {
27
+ shape: any;
28
+ handleChange: (s: any) => void;
29
+ onDelete: () => void;
30
+ }) => {
31
+ const { user } = useUser();
32
+ const intl = useIntl();
33
+ const [open, setOpen] = useState(true);
34
+
35
+ return (
36
+ <div
37
+ className={classNames(
38
+ "mt-5 bg-dark-600 w-full rounded-lg pl-4 py-4 pr-6 cursor-pointer transition-all border-2 border-dark-600 duration-200 hover:border-blue",
39
+ {
40
+ "border-blue": open,
41
+ }
42
+ )}
43
+ >
44
+ <div
45
+ className="tracking-wider flex items-center justify-between gap-4"
46
+ onClick={() => setOpen(!open)}
47
+ >
48
+ <p className="text-white font-semibold">
49
+ <FormattedMessage id="iconsEditor.editor.shape.update.title" />
50
+ </p>
51
+ <div className="flex items-center justify-end gap-4">
52
+ <div
53
+ className="bg-danger bg-opacity-60 rounded-lg text-white hover:bg-opacity-100 cursor-pointer w-8 h-8 flex items-center justify-center"
54
+ onClick={(e: any) => {
55
+ e.preventDefault();
56
+ e.stopPropagation();
57
+ onDelete();
58
+ }}
59
+ >
60
+ <FontAwesomeIcon icon={faTrash} className="w-4" />
61
+ </div>
62
+ <div className="w-4 flex items-center justify-end">
63
+ <FontAwesomeIcon
64
+ icon={open ? faChevronDown : faChevronRight}
65
+ className="text-dark-100 transition-all duration-200"
66
+ />
67
+ </div>
68
+ </div>
69
+ </div>
70
+ {open && (
71
+ <div className="border-t border-dark-400 pt-3 mt-4 grid grid-cols-2 gap-4">
72
+ <div className="w-full col-span-2">
73
+ <Label className="!mb-2">
74
+ <FormattedMessage id="iconsEditor.editor.customisation.rotation" />
75
+ </Label>
76
+ <div className="flex items-center gap-3 w-full">
77
+ <p className="font-semibold uppercase text-dark-200 text-xs">Z</p>
78
+ <Range
79
+ value={shape?.position?.angle ?? 0}
80
+ max={360}
81
+ onChange={(value) => {
82
+ const newShape = {
83
+ ...shape,
84
+ position: { ...shape.position, angle: Number(value) },
85
+ };
86
+ handleChange(newShape);
87
+ }}
88
+ />
89
+ <input
90
+ type="number"
91
+ className="w-full bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
92
+ placeholder={intl.formatMessage({
93
+ id: "iconsEditor.editor.customisation.angle",
94
+ })}
95
+ value={shape?.position?.angle}
96
+ onChange={({ target }) => {
97
+ const newShape = {
98
+ ...shape,
99
+ position: {
100
+ ...shape.position,
101
+ angle: target.value
102
+ ? Number(target.value) > 360
103
+ ? 360
104
+ : Number(target.value)
105
+ : undefined,
106
+ },
107
+ };
108
+ handleChange(newShape);
109
+ }}
110
+ />
111
+ </div>
112
+ </div>
113
+ {shape?.component === "Square" && (
114
+ <div className="w-full col-span-2">
115
+ <Label className="!mb-2">
116
+ <FormattedMessage id="iconsEditor.editor.customisation.radius" />
117
+ </Label>
118
+ <div className="flex items-center gap-3 w-full">
119
+ <p className="font-semibold uppercase text-dark-200 text-xs">
120
+ Z
121
+ </p>
122
+ <Range
123
+ value={shape?.radius ?? 0}
124
+ max={120}
125
+ onChange={(value) => {
126
+ const newShape = {
127
+ ...shape,
128
+ radius: Number(value),
129
+ };
130
+ handleChange(newShape);
131
+ }}
132
+ />
133
+ <input
134
+ type="number"
135
+ className="w-full bg-dark-500 rounded px-2 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
136
+ placeholder={intl.formatMessage({
137
+ id: "iconsEditor.editor.customisation.angle",
138
+ })}
139
+ value={shape?.radius}
140
+ onChange={({ target }) => {
141
+ const newShape = {
142
+ ...shape,
143
+ radius: Number(target.value),
144
+ };
145
+ handleChange(newShape);
146
+ }}
147
+ />
148
+ </div>
149
+ </div>
150
+ )}
151
+ <div className="col-span-2">
152
+ <div className="flex items-center justify-start gap-3 mb-2.5 ">
153
+ <Label className="">
154
+ <FormattedMessage
155
+ id="iconsEditor.editor.customisation.gradientColor"
156
+ values={{
157
+ span: (t) => (
158
+ <span className="text-xs opacity-40">{t}</span>
159
+ ),
160
+ }}
161
+ />
162
+ </Label>
163
+ <Switch
164
+ value={!shape?.transparency}
165
+ onChange={(transparency: boolean) => {
166
+ const newShape = {
167
+ ...shape,
168
+ transparency: !transparency,
169
+ };
170
+ handleChange(newShape);
171
+ }}
172
+ />
173
+ </div>
174
+ <ColorPicker
175
+ value={shape?.stringColor ?? shape?.colour}
176
+ data={shape}
177
+ onChange={(c: any, datas) => {
178
+ let newShape = { ...shape, stringColor: c };
179
+ if (c.includes("gradient")) {
180
+ const { colors, degrees } = datas;
181
+ const gradientType = c?.startsWith("linear")
182
+ ? "linearGradient"
183
+ : "radialGradient";
184
+
185
+ const angle = c?.startsWith("linear")
186
+ ? c?.replace("linear-gradient(", "")?.split("deg")?.[0]
187
+ : 90;
188
+
189
+ newShape["gradient"] = {
190
+ ...newShape.gradient,
191
+ enabled: true,
192
+ colours: colors,
193
+ angle,
194
+ type: gradientType,
195
+ };
196
+ } else {
197
+ newShape = {
198
+ ...newShape,
199
+ colour: c,
200
+ gradient: {
201
+ ...shape?.gradient,
202
+ enabled: false,
203
+ },
204
+ };
205
+ }
206
+
207
+ handleChange(newShape);
208
+ }}
209
+ />
210
+ </div>
211
+ <div className="col-span-2 grid grid-cols-2 gap-4 relative">
212
+ <div className="col-span-2">
213
+ <div className="flex items-center justify-start gap-3 mb-2.5 ">
214
+ <Label className="">
215
+ <FormattedMessage id="iconsEditor.editor.customisation.backgroundImage" />
216
+ </Label>
217
+ <Switch
218
+ value={shape?.image?.enabled}
219
+ onChange={(enabled: boolean) => {
220
+ const newShape = {
221
+ ...shape,
222
+ image: {
223
+ ...shape.image,
224
+ enabled,
225
+ },
226
+ };
227
+ handleChange(newShape);
228
+ }}
229
+ />
230
+ </div>
231
+ {shape?.image?.enabled && (
232
+ <UploadImage
233
+ values={shape?.image?.urls}
234
+ onChange={(urls: string) => {
235
+ const newShape = {
236
+ ...shape,
237
+ image: {
238
+ ...shape.image,
239
+ urls,
240
+ },
241
+ };
242
+ handleChange(newShape);
243
+ }}
244
+ />
245
+ )}
246
+ </div>
247
+ <div>
248
+ <Label className="!mb-2.5">
249
+ <FormattedMessage id="iconsEditor.editor.customisation.border" />
250
+ </Label>
251
+ <div className="flex items-start justify-start gap-5">
252
+ <div className="w-full">
253
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
254
+ <FormattedMessage id="iconsEditor.editor.customisation.width" />
255
+ </p>
256
+ <input
257
+ type="number"
258
+ className="bg-dark-500 w-full rounded px-4 py-2 text-sm text-white placeholder-dark-200 outline-none border-none"
259
+ placeholder={
260
+ intl.formatMessage({
261
+ id: "iconsEditor.editor.customisation.width",
262
+ }) + "...px"
263
+ }
264
+ min={0}
265
+ value={shape?.border?.width}
266
+ onChange={({ target }) => {
267
+ const newShape = {
268
+ ...shape,
269
+ border: {
270
+ ...shape.border,
271
+ width: target?.value
272
+ ? Number(target.value)
273
+ : undefined,
274
+ },
275
+ };
276
+ handleChange(newShape);
277
+ }}
278
+ />
279
+ </div>
280
+ <div>
281
+ <p className="font-semibold uppercase text-dark-200 text-xs mb-2">
282
+ <FormattedMessage id="iconsEditor.editor.customisation.color" />
283
+ </p>
284
+ <ColorPicker
285
+ value={shape?.border?.colour}
286
+ gradients={false}
287
+ onChange={(c: any) => {
288
+ const newShape = {
289
+ ...shape,
290
+ border: {
291
+ ...shape.border,
292
+ colour: c,
293
+ },
294
+ };
295
+ handleChange(newShape);
296
+ }}
297
+ />
298
+ </div>
299
+ </div>
300
+ </div>
301
+ <PremiumOverlay />
302
+ </div>
303
+ </div>
304
+ )}
305
+ </div>
306
+ );
307
+ };
components/editor-icons/comps/tabs.tsx ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import classNames from "classnames";
3
+
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import {
6
+ faSearch,
7
+ faArrowsUpDownLeftRight,
8
+ faPalette,
9
+ faShapes,
10
+ faIcons,
11
+ } from "@fortawesome/free-solid-svg-icons";
12
+ import { FormattedMessage } from "react-intl";
13
+
14
+ const TABS_ELEMENTS = [
15
+ {
16
+ icon: faSearch,
17
+ name: "iconsEditor.editor.tabs.listIcons",
18
+ },
19
+ {
20
+ icon: faShapes,
21
+ name: "iconsEditor.editor.tabs.shape",
22
+ },
23
+ {
24
+ icon: faIcons,
25
+ name: "iconsEditor.editor.tabs.icons",
26
+ },
27
+ ];
28
+
29
+ export const EditorTabs = ({
30
+ current,
31
+ onChange,
32
+ }: {
33
+ current: number;
34
+ onChange: (e: number) => void;
35
+ }) => {
36
+ return (
37
+ // p-2 lg:p-4
38
+ <div className="bg-dark-600 rounded-t-2xl lg:rounded-r-none">
39
+ <ul className="w-full relative z-1 grid grid-cols-3">
40
+ {TABS_ELEMENTS.map((tab, index) => (
41
+ <li
42
+ key={index}
43
+ className={classNames(
44
+ "hover:bg-blue hover:bg-opacity-50 flex items-center gap-5 lg:gap-4 cursor-pointer text-white text-left text-xs lg:text-sm font-semibold tracking-wide p-4 lg:p-5 justify-center uppercase",
45
+ {
46
+ "bg-dark-500 hover:!bg-dark-500 hover:!bg-opacity-100":
47
+ current === index,
48
+ "second-step rounded-t-2xl rounded-r-none": index === 0,
49
+ "third-step": index === 1,
50
+ "fourth-step rounded-t-2xl lg:rounded-t-none rounded-l-none":
51
+ index === 2,
52
+ }
53
+ )}
54
+ onClick={() => onChange(index)}
55
+ >
56
+ <FontAwesomeIcon icon={tab.icon} className="w-4 min-w-[1rem]" />
57
+ <p>
58
+ <FormattedMessage id={tab.name} />
59
+ </p>
60
+ </li>
61
+ ))}
62
+ </ul>
63
+ </div>
64
+ );
65
+ };